Pwnablekr_Toddlers Bottle_leg

링크에 가면 C언어 코드와 디스어셈블된 코드가 있다. C언어 코드는 인라인 어셈블리 코드가 섞여 있다.  문제에 대한 답은 ssh로 접속하여 입력한다. 

C언어 코드는 다음과 같다. 인라인 어셈블리는 arm 어셈블리이다.

#include <stdio.h>
#include <fcntl.h>
int key1(){
    asm("mov r3, pc\n");
}
int key2(){
    asm(
    "push    {r6}\n"
    "add    r6, pc, $1\n"
    "bx    r6\n"
    ".code   16\n"
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
    "push    {r3}\n"
    "pop    {pc}\n"
    ".code    32\n"
    "pop    {r6}\n"
    );
}
int key3(){
    asm("mov r3, lr\n");
}
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d", &key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}

주 알고리즘은 다음과 같다

  • 입력값과 key1()+key2()+key3() 값이 같으면 통과

디스어셈블된 코드는 다음과 같다.

(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c <+0>: push {r4, r11, lr}
   0x00008d40 <+4>: add r11, sp, #8
   0x00008d44 <+8>: sub sp, sp, #12
   0x00008d48 <+12>: mov r3, #0
   0x00008d4c <+16>: str r3, [r11, #-16]
   0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
   0x00008d54 <+24>: bl 0xfb6c <printf>
   0x00008d58 <+28>: sub r3, r11, #16
   0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
   0x00008d60 <+36>: mov r1, r3
   0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>: bl 0x8cd4 <key1>
   0x00008d6c <+48>: mov r4, r0
   0x00008d70 <+52>: bl 0x8cf0 <key2>
   0x00008d74 <+56>: mov r3, r0
   0x00008d78 <+60>: add r4, r4, r3
   0x00008d7c <+64>: bl 0x8d20 <key3>
   0x00008d80 <+68>: mov r3, r0
   0x00008d84 <+72>: add r2, r4, r3
   0x00008d88 <+76>: ldr r3, [r11, #-16]
   0x00008d8c <+80>: cmp r2, r3
   0x00008d90 <+84>: bne 0x8da8 <main+108>
   0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
   0x00008d98 <+92>: bl 0x1050c <puts>
   0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
   0x00008da0 <+100>: bl 0xf89c <system>
   0x00008da4 <+104>: b 0x8db0 <main+116>
   0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
   0x00008dac <+112>: bl 0x1050c <puts>
   0x00008db0 <+116>: mov r3, #0
   0x00008db4 <+120>: mov r0, r3
   0x00008db8 <+124>: sub sp, r11, #8
   0x00008dbc <+128>: pop {r4, r11, pc}
   0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
   0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
   0x00008dc8 <+140>:   ; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:   ; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>: push {r11}  ; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>: add r11, sp, #0
   0x00008cdc <+8>: mov r3, pc
   0x00008ce0 <+12>: mov r0, r3
   0x00008ce4 <+16>: sub sp, r11, #0
   0x00008ce8 <+20>: pop {r11}  ; (ldr r11, [sp], #4)
   0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>: push {r11}  ; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>: add r11, sp, #0
   0x00008cf8 <+8>: push {r6}  ; (str r6, [sp, #-4]!)
   0x00008cfc <+12>: add r6, pc, #1
   0x00008d00 <+16>: bx r6
   0x00008d04 <+20>: mov r3, pc
   0x00008d06 <+22>: adds r3, #4
   0x00008d08 <+24>: push {r3}
   0x00008d0a <+26>: pop {pc}
   0x00008d0c <+28>: pop {r6}  ; (ldr r6, [sp], #4)
   0x00008d10 <+32>: mov r0, r3
   0x00008d14 <+36>: sub sp, r11, #0
   0x00008d18 <+40>: pop {r11}  ; (ldr r11, [sp], #4)
   0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>: push {r11}  ; (str r11, [sp, #-4]!)
   0x00008d24 <+4>: add r11, sp, #0
   0x00008d28 <+8>: mov r3, lr
   0x00008d2c <+12>: mov r0, r3
   0x00008d30 <+16>: sub sp, r11, #0
   0x00008d34 <+20>: pop {r11}  ; (ldr r11, [sp], #4)
   0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)

arm 어셈블리어 코드에서 주목할 특징은 다음과 같다.

  • PC 값 (=R15 값): 다다음 실행할 코드의 주소를 가리키고 있음
    • intel x86의 EIP가 다음 실행할 주소를 가지고 있는 것에 반해, arm의 R15는 다다음에 실행할 주소를 가지고 있다. 이는 특유의 파이프라인때문에 생긴 특징이다. 참고
  • 함수 반환값을 r0에 넣음
    • intel x86에서 함수 반환값을 eax에 넣는 것처럼, arm에서는 r0에 저장한다.
    • key1 함수와 key2, key3 함수의 마지막 부분에서 r0에 무엇을 넣는 지를 추적하면 각 함수의 반환값을 알 수 있다. 이는 디스어셈블 코드를 통해 알 수 있다.
  • return address는 lr에 넣음
    • 함수가 호출되면 함수 호출 다음 라인의 주소가 lr에 들어간다.

각 함수를 분석하여 반환값을 알아보자. 다음은 key1 함수의 내용이다. pc값을 r3에 넣고, 그 값이 r0에 옮겨져 함수 반환값으로 이용된다. mov r3, pc코드를 실행할 당시 pc값은 다다음에 실행할 코드의 주소이므로, 0x00008ce4 이다. 즉, key1의 return값은 0x00008ce4 이다.

(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>: push {r11}  ; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>: add r11, sp, #0
@> 0x00008cdc <+8>: mov r3, pc
@> 0x00008ce0 <+12>: mov r0, r3
   0x00008ce4 <+16>: sub sp, r11, #0
   0x00008ce8 <+20>: pop {r11}  ; (ldr r11, [sp], #4)
   0x00008cec <+24>: bx lr
End of assembler dump.

다음은 key2 함수이다. 위와 동일한 논리로 mov r3, pc 코드에서의 pc값은 0x00008d08 이다. 여기에 다시 4를 더하므로, key2 함수의 반환값은 0x00008d0c이다.

(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>: push {r11}  ; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>: add r11, sp, #0
   0x00008cf8 <+8>: push {r6}  ; (str r6, [sp, #-4]!)
   0x00008cfc <+12>: add r6, pc, #1
   0x00008d00 <+16>: bx r6
@> 0x00008d04 <+20>: mov r3, pc
@> 0x00008d06 <+22>: adds r3, #4
   0x00008d08 <+24>: push {r3}
   0x00008d0a <+26>: pop {pc}
   0x00008d0c <+28>: pop {r6}  ; (ldr r6, [sp], #4)
@> 0x00008d10 <+32>: mov r0, r3
   0x00008d14 <+36>: sub sp, r11, #0
   0x00008d18 <+40>: pop {r11}  ; (ldr r11, [sp], #4)
   0x00008d1c <+44>: bx lr
End of assembler dump.

key3 함수의 내용이다. lr 레지스터에 있는 값을 반환한다. lr 레지스터는 함수를 마치고 돌아갈 리턴 주소를 저장하므로, key3 함수를 호출한 다음 코드의 주소를 가지고 있다.

(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>: push {r11}  ; (str r11, [sp, #-4]!)
   0x00008d24 <+4>: add r11, sp, #0
@> 0x00008d28 <+8>: mov r3, lr
@> 0x00008d2c <+12>: mov r0, r3
   0x00008d30 <+16>: sub sp, r11, #0
   0x00008d34 <+20>: pop {r11}  ; (ldr r11, [sp], #4)
   0x00008d38 <+24>: bx lr
End of assembler dump.

이는 0x00008d80 이다. 따라서, key3함수의 반환값은 0x00008d80 이다.

   0x00008d7c <+64>: bl 0x8d20 <key3>
@> 0x00008d80 <+68>: mov r3, r0

따라서, key는 세 값을 더한 0x1A770이다. 입력값을 scanf("%d", &key);로 받으므로, 10진수 형태로 입력해야 한다. 따라서 답은 108400이다.

flag는 My daddy has a lot of ARMv5te muscle! 이다.


tags: writeup, pwnable, elf file, linux, c lang, armasm