ipwn

[pwnable.kr] leg 본문

Write up/Pwnable.kr

[pwnable.kr] leg

ipwn 2018. 7. 13. 09:49

이번에 올릴 라업은 leg문제이다.



보면 어셈이랑 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코드인데 key함수 3개 return값 다 더한 값이랑 입력값이랑 똑같으면 flag를 읽어와주는데 인라인으로 asm 코딩 돼 있는 부분을 보면 arm아키텍쳐인 걸 알 수 있다.


arm아키텍쳐에서 알아둬야할 레지스터들과 그 레지스터들의 역할을 알아보겠다.


PC(Program Counter) or r15


현재 Instruction을 Fetch해 온 위치를 가리킨다. 현재 실행하는 위치가 아니라 Fetch해온 위치를 가리킨다.


즉 PC레지스터는 현재 진행되는 명령어의 다음 다음 명령어의 주소값을 가진다.


lr(link register) or r14


현재의 함수가 끝나고 돌아갈 곳의 주소값을 가리킨다.


sp(stack pointer) or r13


sp는 intel 아키텍쳐의 esp와 비슷한 역할을 하는데, stack pointer을 의미한다.


r0 ~ r12


그 외 레지스터는 intel 아키텍쳐와 같이 연산 작업에서 각자의 역할을 맡아서 하는데, 그 중에서도 r0레지스터는 함수의 return값을 가진다.


대충 이 정도만 기억하고 있는다면 이 문제는 간단히 풀 수 있다.


우리는 r0이 함수의 리턴값인 것을 알고 key1, key2, key3의 함수값을 모두 더한 값과 내 입력값이 같으면 플래그를 읽어와주는 걸 알기 때문에 key1, key2, key3이 각 함수들의 r0 레지스터의 값만 추적하면 된다.


이제 함수 하나하나마다의 어셈을 보자.


(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


이 곳이 메인함수인데 빨간색으로 칠해져있는 부분들이 각각의 key1, key2, key3의 lr의 값을 가지게 된다는 것을 알게될 것이다.


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


key1 함수이다. r0레지스터를 추적해보자.

보면 r3에 pc레지스터의 값을 담고 r0에 r3 레지스터의 값을 담고 함수가 종료된다.

즉 r3에 pc 레지스터의 값을 옮겼을 때의 값이 즉 0x8ce4의 값이 리턴값으로 들어가게 될 것이다.


이제 key2 함수를 보도록 하자.


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


이 어셈블리어가 key2의 함수인데 이것도 마찬가지로 r0에 r3의 값을 넣어주는데, r3의 시작점을 보면 r3에 pc레지스터의 값이 들어가게 되고, 그 뒤에 다시 4를 더해준다.


즉 0x8d08의 값 + 4이므로 0x8d0c의 값이 key2 함수의 return값이 될 것이다.


이제 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의 함수인데, 마찬가지로 r0의 레지스터에 r3레지스터의 값이 들어가고, r3 레지스터는 lr의 값이 들어가는 걸 확인할 수 있다.


위의 메인함수의 어셈블리를 다시 한 번 보자. 0x8d80이 key3의 함수가 끝나고 돌아가는 위치의 주소값임을 알 수 있다. 즉 key3 함수의 return값은 0x8d80의 값이 들어가게 된다.


즉 각 함수들의 리턴값을 모두 더한 값을 입력으로 넘겨주게 된다면 flag를 성공적으로 읽어올 수 있게 될 것이다.



ㅇㅋ 끝

'Write up > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] memcpy  (0) 2018.07.21
[pwnable.kr] input  (0) 2018.03.13
[pwnable.kr] coin1  (0) 2018.03.12
[pwnable.kr] uaf  (0) 2018.02.19
[pwnable.kr] shellshock  (0) 2018.01.06
Comments