-
[pwnable.kr] [Toddler's Bottle] 8. legWargame/pwnable.kr 2019. 7. 31. 18:30
문제에 나온 파일을 다운받아서 확인해보았다.
leg.c를 먼저 확인해보자.
12345678910111213141516171819202122232425262728293031323334353637383940414243#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 함수를 살펴보았다.
1234567891011121314151617181920212223242526272829303132333435363738394041(gdb) disass mainDump of assembler code for function main:0x00008d3c <+0>: push {r4, r11, lr}0x00008d40 <+4>: add r11, sp, #80x00008d44 <+8>: sub sp, sp, #120x00008d48 <+12>: mov r3, #00x00008d4c <+16>: str r3, [r11, #-16]0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>0x00008d54 <+24>: bl 0xfb6c <printf>0x00008d58 <+28>: sub r3, r11, #160x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>0x00008d60 <+36>: mov r1, r30x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>0x00008d68 <+44>: bl 0x8cd4 <key1>0x00008d6c <+48>: mov r4, r00x00008d70 <+52>: bl 0x8cf0 <key2>0x00008d74 <+56>: mov r3, r00x00008d78 <+60>: add r4, r4, r30x00008d7c <+64>: bl 0x8d20 <key3>0x00008d80 <+68>: mov r3, r00x00008d84 <+72>: add r2, r4, r30x00008d88 <+76>: ldr r3, [r11, #-16]0x00008d8c <+80>: cmp r2, r30x00008d90 <+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, #00x00008db4 <+120>: mov r0, r30x00008db8 <+124>: sub sp, r11, #80x00008dbc <+128>: pop {r4, r11, pc}0x00008dc0 <+132>: andeq r10, r6, r12, lsl #90x00008dc4 <+136>: andeq r10, r6, r12, lsr #90x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b00x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc0x00008dd0 <+148>: andeq r10, r6, r4, asr #9End 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
그러면 각 함수에서 r0에 어떤 값이 저장되는지 알아내면 될 것이다.
먼저 key1 함수를 살펴보자.
12345678910(gdb) disass key1Dump of assembler code for function key1:0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)0x00008cd8 <+4>: add r11, sp, #00x00008cdc <+8>: mov r3, pc0x00008ce0 <+12>: mov r0, r30x00008ce4 <+16>: sub sp, r11, #00x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)0x00008cec <+24>: bx lrEnd 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
1234567891011121314151617(gdb) disass key2Dump of assembler code for function key2:0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)0x00008cf4 <+4>: add r11, sp, #00x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)0x00008cfc <+12>: add r6, pc, #10x00008d00 <+16>: bx r60x00008d04 <+20>: mov r3, pc0x00008d06 <+22>: adds r3, #40x00008d08 <+24>: push {r3}0x00008d0a <+26>: pop {pc}0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)0x00008d10 <+32>: mov r0, r30x00008d14 <+36>: sub sp, r11, #00x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)0x00008d1c <+44>: bx lrEnd of assembler dump.pc가 가리키는 값은 <key2+20>의 다다음 명령어인 <key2+24>의 주소, 즉 0x8d08이다. 따라서 r3의 값은 0x8d08 + 4 = 0x8d0c이다.
마지막으로 key3 함수를 살펴보자.
r0에 저장되는 값은 r3이고, r3에는 lr의 값이 저장된다. lr은 Link Register의 약자로, 함수 호출 전으로 복귀하여 실행할 주소를 저장하고 있는 레지스터이다.
12345678910(gdb) disass key3Dump of assembler code for function key3:0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)0x00008d24 <+4>: add r11, sp, #00x00008d28 <+8>: mov r3, lr0x00008d2c <+12>: mov r0, r30x00008d30 <+16>: sub sp, r11, #00x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)0x00008d38 <+24>: bx lrEnd of assembler dump.main 함수에서 key3 함수가 호출된 곳의 주소가 0x8d7c이므로, lr에 저장된 주소는 0x8d80이다.120x00008d7c <+64>: bl 0x8d20 <key3>0x00008d80 <+68>: mov r3, r0따라서 입력해야 할 key의 값은 key1 + key2 + key3 = 0x8ce4 + 0x8d0c + 0x8d80 = 0x1A770이다.
프로그램에서 int형으로 입력값을 받기 때문에 0x1a770을 정수형으로 변환한 값인 108400를 입력해야 한다.
[참고 자료]
어셈블리 언어: https://ituner.tistory.com/134
링크 레지스터: http://recipes.egloos.com/5631403
'Wargame > pwnable.kr' 카테고리의 다른 글
[pwnable.kr][Toddler's Bottle] 10. shellshock (0) 2019.08.04 [pwnable.kr][Toddler's Bottle] 9. mistake (0) 2019.08.04 [pwnable.kr][Toddler's Bottle] 7. input (0) 2019.07.26 [pwnable.kr][Toddler's Bottle] 6. random (0) 2019.07.26 [pwnable.kr][Toddler's Bottle] 5. passcode (0) 2019.07.19