[pwnable.kr]Toddler/leg

 
Daddy told me I should study arm.
But I prefer to study my leg!

Download : http://pwnable.kr/bin/leg.c
Download : http://pwnable.kr/bin/leg.asm

ssh leg@pwnable.kr -p2222 (pw:guest)

문제에서 leg.c 파일과 leg.asm 파일이 주어진다.


0x00. Analysis

#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;
}

leg.c 파일의 소스 코드는 위와 같다. main함수를 살펴보면 key1(), key2(), key3()의 리턴 값을 다 더한 값과, 사용자가 입력한 값이 같으면 문제가 풀린다.

따라서 key1~3 함수의 리턴 값을 각각 구해야 한다.


먼저 leg.asm에서 main함수 부분을 살펴보자. (key1~3 호출 부분)

----(생략)----
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
----(생략)----

asm에서 함수의 리턴 값은 r0로 들어간다. key1함수의 리턴 값은 r4로 들어가고. key2함수의 리턴 값은 r3로 들어간 후 r3r4가 더해서 r4로 들어간다. 이후 key3의 리턴 값은 r3로 들어가고 다시 r4r3와 더해서 r2로 들어간다.

이후 사용자가 입력한 값 r3와 key1~3함수의 리턴 값을 더한 r2와 비교를 하게 된다.


leg.asm에서 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

함수의 리턴 값은 r0에 들어가기 때문에 r0 레지스터를 중심으로 추적해보면 된다.

key1+12부분에서 r0r3값이 들어간다. r3를 추적하면 r3에는 pc값이 들어간다.

pc는 실행되는 명령어의 주소를 가리킨다. 때문에 0x8cdc를 가리킬 것 같지만 ARM에서는 다르다.(ARM의 Pipeline 를 참조하자.)

ARM Pipeline구조에 의해서 실제 실행되는 주소는 0x8ce4가 된다. 따라서 key1함수의 리턴값은 0x8ce4이다.


이제 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

똑같은 방법으로 r0부터 추적을 시작해보면, r0에는 r3가 들어가고, r3에는 pc가 들어간 후 4가 더해진다. 여기서도 똑같이 ARM Pipeline 구조에 의해서 pc값에는 0x8d04가 아닌 0x8d08의 값이 들어가고, 여기서 4를 더한 0x8d0c값이 key2함수의 리턴 값이 된다.


마지막으로 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

마찬가지로 r0에는 r3가 들어가고 r3에는 lr값이 들어간다.

lr 레지스터는 해당 함수가 끝나고 돌아갈 주소값을 가지고 있다. (Intel Core에서 SFP값과 같다.)

 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

main함수에서 key3 함수가 실행되고 복귀 할 주소는 0x8d80이므로, key3의 리턴 값은 0x8d80이다.


이제 key1함수 부터 key3함수까지의 리턴 값을 더하면 된다.

  • key1의 리턴 값 : 0x8ce4
  • key2의 리턴 값 : 0x8d0c
  • key3의 리턴 값 : 0x8d80
>>> 0x8ce4 + 0x8d0c + 0x8d80
108400

0x01. Exploit

/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!

끄읏..!