ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [pwnable.kr] [Toddler's Bottle] 8. leg
    Wargame/pwnable.kr 2019. 7. 31. 18:30

    Toddler's Bottle - leg

     

    문제에 나온 파일을 다운받아서 확인해보았다.

     

     

    leg.c를 먼저 확인해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #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 세 함수에서 반환하는 값의 합이 입력값 key와 같으면 flag가 열릴 것이다.

     

    이번에는 leg.c를 디스어셈블링한 파일인 leg.asm을 확인해보았다.

    먼저 main 함수를 살펴보았다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    (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.
     

     

    <main+44>부터 <main+80>까지를 살펴보자. 처음에 key1, key2, key3의 리턴값은 r0에 저장된다. (아래 key1, key2, key3 함수의 디스어셈블리 코드를 봐도 알 수 있다.) 그리고 이 리턴값들이 하나씩 더해져서 최종적으로 r2에 저장되고, 이는 r3에 저장된 입력값 key와 비교된다.

    key1 = r4, key2 = r3 → key1 + key2 = r4 → key3 = r3 → key3 + (key1 + key2) = r2

     

    disas main

     

    그러면 각 함수에서 r0에 어떤 값이 저장되는지 알아내면 될 것이다.

     

    먼저 key1 함수를 살펴보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    (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.
     
     

    여기서 r0에 저장되는 값은 pc (Program Counter)이다. PC에 대한 설명은 아래를 참조하자.

    ...더보기

    CPU가 명령어를 수행할 때에는 Fetch → Decode  → Execute 3단계를 거친다.

    1. Fetch: Instruction을 메모리로부터 가져옴

    2. Decode: 메모리로부터 불러온 Instruction을 해석하고 레지스터 값을 확인

    3. Execute: Decoding된 Instruction 실행

     

    이 세 단계를 거치기 위해서는 Fetch unit, Decode unit, Execute unit 3개의 Unit이 필요하다. 그런데 한 명령어를 수행하는데 이 3개의 unit을 반드시 순서대로 거쳐야 한다면, 한 unit에서 수행이 일어날 때 나머지 두 unit은 놀고만 있게 된다. 이러한 비효율성을 해결하기 위해 Pipe Line이라는 개념이 사용된다.

     

    만약 Pipe Line이 적용되지 않는다면 2개의 opcode를 수행하는데 6개의 cycle이 걸릴 것이다.

     

    1 2 3 4 5 6
    (op1) Fetch (op1) Decode (op1) Excute (op2) Fetch (op2) Decode (op2) Execute

     

    하지만 Pipe Line이 적용된다면, 2개의 opcode를 수행하는데 4개의 cycle밖에 걸리지 않으므로 더 효율적이다.

     

    1 2 3 4
    (op1) Fetch (op1) Decode (op1) Execute  
      (op2) Fetch (op2) Decode (op2) Execute

     

    PC(Program Counter)는 항상 Fetch하는 곳의 주소를 가리킨다. 만약 Pipe Line이 적용되지 않은 경우라면 고민할 필요도 없지만, Pipe Line이 적용된 경우라면 PC는 현재 Excute되고 있는 곳보다 앞서가게 된다. 따라서 PC가 가리키는 곳은 현재 Execute 다음으로 나오는 Fetch 단계, 즉 현재 Execute의 다다음 단계를 가리킨다.

     

    출처: http://recipes.egloos.com/4982170


    pc가 가리키는 주소값은 <key1+8>의 다다음 명령어인 <key+16>의 주소가 되므로, key1이 반환하는 값은 0x8ce4가 된다.

     

     

    이번에는 key2 함수를 살펴보자.

    key2 함수에서는 r3의 값이 r0에 저장된다. r3가 무엇인지 추적해보면, pc에 #4를 더한 값임을 알 수 있다.

    mov r3, pc → adds r3, #4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    (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.
     

    pc가 가리키는 값은 <key2+20>의 다다음 명령어인 <key2+24>의 주소, 즉 0x8d08이다. 따라서 r3의 값은 0x8d08 + 4 = 0x8d0c이다.

     

     

    마지막으로 key3 함수를 살펴보자.

    r0에 저장되는 값은 r3이고, r3에는 lr의 값이 저장된다. lr은 Link Register의 약자로, 함수 호출 전으로 복귀하여 실행할 주소를 저장하고 있는 레지스터이다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    (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.
     
    main 함수에서 key3 함수가 호출된 곳의 주소가 0x8d7c이므로, lr에 저장된 주소는 0x8d80이다.
    1
    2
    0x00008d7c <+64>:    bl    0x8d20 <key3>
    0x00008d80 <+68>:    mov    r3, r0

     

     

    따라서 입력해야 할 key의 값은 key1 + key2 + key3 = 0x8ce4 + 0x8d0c + 0x8d80 = 0x1A770이다.

    프로그램에서 int형으로 입력값을 받기 때문에 0x1a770을 정수형으로 변환한 값인 108400를 입력해야 한다.

    flag: My daddy has a lot of ARMv5te muscle!

     

    [참고 자료]

    어셈블리 언어: https://ituner.tistory.com/134

    링크 레지스터: http://recipes.egloos.com/5631403

Designed by Tistory.