ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Toddler's Bottle] leg write up
    System hacking training/pwnable.kr 2019. 1. 28. 17:22


    arm에 관한 문제를 풀어봤습니다.

    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)

    이 문제는 arm 어셈블리언어를 알아야 풀 수 있습니다.

    주어진 두 링크를 들어가보면 C코드와 어셈블리 코드가 주어집니다.
    여기서 저는 어셈블리 코드로 분석을 진행했는데요.

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

    C 코드를 보면 key1, key2, key3 함수의 반환값을 더한 값과 input값이 같아야하는데,
    key1()를 살펴보니 mov r3, pc 즉 r3라는 레지스터에 pc(program counter)의 값을 넣는 것을 알 수 있습니다.

    근데 이 C코드만 보고서는 pc에 해당하는 값이 무엇인지 알 수 없겠죠?
    따라서 어셈블리 코드 분석이 필수적으로 요구됩니다.

    문제를 풀기 위해 arm 어셈블리어를 알아봅시다.

    [레지스터]
    - R0 ~ R15 (16개)

    - R0 ~ R12 : 범용 레지스터, 인자값 및 임시 계산 저장소 등
    - R13(SP) : Stack Pointer // x86에서 ESP와 비슷한 역할을 수행
    - R14(LR) : Link Register, 함수 호출 전 LR에 리턴 주소를 저장하고 점프함
    - PC : x86에서 EIP와 동일한 역할 수행, 다음 실행할 코드의 주소를 저장

    여기서 조금 중요하다고 생각되는 부분이 있는데요.
    바로 PC와 LR입니다.

    먼저 PC는 앞서 설명했듯 Program Counter의 약자인데요.
    다음 실행할 주소를 담고 있는 레지스터입니다.

    따라서 x86에서 EIP와 아주 동일하다고 생각할 수 있는데,
    역할은 다음 실행할 주소를 담고 있는 것으로 동일하지만, 그 "다음 실행할 주소"가 EIP와는 다르게 다음 다음을 의미한다는 것입니다.

    따라서 key1()의 어셈코드를 살펴보면

    (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    // -> pc : 0x00008ce4
       0x00008ce0 <+12>:      mov     r0, r3
       0x00008ce4 <+16>:      sub     sp, r11, #0
       0x00008ce8 <+20>:      pop     {r11}           ; (ldr r11, [sp], #4)
       0x00008cec <+24>:      bx      lr    // -> lr : 0x00008d6c
    End of assembler dump.

    key1 + 8 부분에 pc가 가지고 있는 주소는 0x00008ce4가 됩니다.

    이렇듯 pc는 우리가 알고있는 EIP와 동일한 역할을 수행하지만 담기는 주소값이 다른것 을 알 수 있습니다.

    다음으로 설명할 부분은 LR입니다.
    LR은 Link Register로 함수 호출 전 LR에 주소를 저장하고 점프하는 레지스터입니다.

    쉽게 풀어서 설명하면 LR은 함수 호출 전에 다시 되돌아가서 실행할 다음 주소를 담고 있다고 생각하면 됩니다.

    따라서 main()를 살펴보면 LR은 0x00008d6c임을 알 수 있습니다.

    (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>    // -> lr : 0x00008d6c
       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.

    이렇게 pc와 lr을 알아봤습니다.
    문제풀이에 있어 핵심이 되는 내용이기 때문에 먼저 알아봤고, 이제 차례대로 슥삭! 풀이해봅시다.



    main()부터 분석해보겠습니다.

    (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.

    여기서 볼 수 있듯 함수의 return 값은 r0에 들어있는 것 같네요.

    별 내용은 없고, r0의 값을 다른 레지스터에 넣고 마지막에 모두 더한 후 input값과 비교하는 것 같습니다.
    (input : [r11, #-16])
    따라서 각 함수에서 r0에 무엇이 들어가는지 보면 되겠습니다.

    다음으로 key1()를 분석해보겠습니다.

    (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.

    key1()에서 pc값을 r3에 넣고, 이 값을 다시 r0에 넣음으로서 r0의 값은 pc값이 되었네요.
    pc의 값은  0x00008ce4 이므로 key1()의 return 값은 0x00008ce4 가 됩니다.

    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.

    key2()는 +32줄에 r3의 값이 r0에 들어가는데,
    r3에는 pc + 4의 값이 들어가기 때문에 0x00008d08 + 4 =>0x8d0c

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

    key3() 역시 r3의 값이 들어가는데, r3의 값은 lr이 들어갑니다.
    따라서 return 값은 0x00008d80
    각 함수의 return 값을 모두 더해서 input값으로 넣어주면 됩니다.

    이상으로 leg write up을 마칩니다.



    반응형
Designed by Tistory.