Description
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 [email protected] -p2222 (pw:guest)
Source Code
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; }
제대로 문제를 이해하기 위해 위 소스코드를 그대로 복붙해서
armv5teji 아키텍처로 정적 크로스 컴파일을 한 다음,
qemu로 실행시키고 디버거를 통해 직접 분석해보려고 한다.
seo@raspberrypi:~/Desktop $ arm-linux-gnueabi-gcc -march=armv5tej -static -o leg leg.c leg.c: In function ‘main’: leg.c:31:11: warning: implicit declaration of function ‘read’; did you mean ‘fread’? [-Wimplicit-function-declaration] 31 | int r = read(fd, buf, 100); | ^~~~ | fread leg.c:32:3: warning: implicit declaration of function ‘write’; did you mean ‘fwrite’? [-Wimplicit-function-declaration] 32 | write(0, buf, r); | ^~~~~ | fwrite seo@raspberrypi:~/Desktop $ qemu-arm ./leg Daddy has very strong arm! : asdf I have strong leg :P
실행이 잘되는 것을 확인할 수 있다.
Disassembled Code
(gdb) disas main Dump of assembler code for function main: 0x000105bc <+0>: push {r4, r11, lr} 0x000105c0 <+4>: add r11, sp, #8 0x000105c4 <+8>: sub sp, sp, #116 ; 0x74 0x000105c8 <+12>: mov r3, #0 0x000105cc <+16>: str r3, [r11, #-24] ; 0xffffffe8 0x000105d0 <+20>: ldr r3, [pc, #196] ; 0x1069c <main+224> 0x000105d4 <+24>: add r3, pc, r3 0x000105d8 <+28>: mov r0, r3 0x000105dc <+32>: bl 0x16d20 <printf> 0x000105e0 <+36>: sub r3, r11, #24 0x000105e4 <+40>: mov r1, r3 0x000105e8 <+44>: ldr r3, [pc, #176] ; 0x106a0 <main+228> 0x000105ec <+48>: add r3, pc, r3 0x000105f0 <+52>: mov r0, r3 0x000105f4 <+56>: bl 0x16dec <__isoc99_scanf> 0x000105f8 <+60>: bl 0x10548 <key1> 0x000105fc <+64>: mov r4, r0 0x00010600 <+68>: bl 0x10568 <key2> 0x00010604 <+72>: mov r3, r0 0x00010608 <+76>: add r4, r4, r3 0x0001060c <+80>: bl 0x1059c <key3> 0x00010610 <+84>: mov r3, r0 0x00010614 <+88>: add r2, r4, r3 0x00010618 <+92>: ldr r3, [r11, #-24] ; 0xffffffe8 0x0001061c <+96>: cmp r2, r3 0x00010620 <+100>: bne 0x1067c <main+192> 0x00010624 <+104>: ldr r3, [pc, #120] ; 0x106a4 <main+232> 0x00010628 <+108>: add r3, pc, r3 0x0001062c <+112>: mov r0, r3 0x00010630 <+116>: bl 0x2299c <puts> 0x00010634 <+120>: mov r1, #0 0x00010638 <+124>: ldr r3, [pc, #104] ; 0x106a8 <main+236> 0x0001063c <+128>: add r3, pc, r3 0x00010640 <+132>: mov r0, r3 0x00010644 <+136>: bl 0x34020 <open> 0x00010648 <+140>: str r0, [r11, #-16] 0x0001064c <+144>: sub r3, r11, #124 ; 0x7c 0x00010650 <+148>: mov r2, #100 ; 0x64 0x00010654 <+152>: mov r1, r3 0x00010658 <+156>: ldr r0, [r11, #-16] 0x0001065c <+160>: bl 0x34144 <read> 0x00010660 <+164>: str r0, [r11, #-20] ; 0xffffffec 0x00010664 <+168>: sub r3, r11, #124 ; 0x7c 0x00010668 <+172>: ldr r2, [r11, #-20] ; 0xffffffec 0x0001066c <+176>: mov r1, r3 0x00010670 <+180>: mov r0, #0 0x00010674 <+184>: bl 0x341f0 <write> 0x00010678 <+188>: b 0x1068c <main+208> 0x0001067c <+192>: ldr r3, [pc, #40] ; 0x106ac <main+240> 0x00010680 <+196>: add r3, pc, r3 0x00010684 <+200>: mov r0, r3 0x00010688 <+204>: bl 0x2299c <puts> 0x0001068c <+208>: mov r3, #0 0x00010690 <+212>: mov r0, r3 0x00010694 <+216>: sub sp, r11, #8 0x00010698 <+220>: pop {r4, r11, pc} 0x0001069c <+224>: andeq pc, r5, r8, ror #5 0x000106a0 <+228>: strdeq pc, [r5], -r0 0x000106a4 <+232>: ; <UNDEFINED> instruction: 0x0005f2b8 0x000106a8 <+236>: ; <UNDEFINED> instruction: 0x0005f2b0 0x000106ac <+240>: andeq pc, r5, r4, ror r2 ; <UNPREDICTABLE> End of assembler dump. (gdb) disas key1 Dump of assembler code for function key1: 0x00010548 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x0001054c <+4>: add r11, sp, #0 0x00010550 <+8>: mov r3, pc 0x00010554 <+12>: nop ; (mov r0, r0) 0x00010558 <+16>: mov r0, r3 0x0001055c <+20>: add sp, r11, #0 0x00010560 <+24>: pop {r11} ; (ldr r11, [sp], #4) 0x00010564 <+28>: bx lr End of assembler dump. (gdb) disas key2 Dump of assembler code for function key2: 0x00010568 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x0001056c <+4>: add r11, sp, #0 0x00010570 <+8>: push {r6} ; (str r6, [sp, #-4]!) 0x00010574 <+12>: add r6, pc, #1 0x00010578 <+16>: bx r6 0x0001057c <+20>: mov r3, pc 0x0001057e <+22>: adds r3, #4 0x00010580 <+24>: push {r3} 0x00010582 <+26>: pop {pc} 0x00010584 <+28>: pop {r6} ; (ldr r6, [sp], #4) 0x00010588 <+32>: nop ; (mov r0, r0) 0x0001058c <+36>: mov r0, r3 0x00010590 <+40>: add sp, r11, #0 0x00010594 <+44>: pop {r11} ; (ldr r11, [sp], #4) 0x00010598 <+48>: bx lr End of assembler dump. (gdb) disas key3 Dump of assembler code for function key3: 0x0001059c <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x000105a0 <+4>: add r11, sp, #0 0x000105a4 <+8>: mov r3, lr 0x000105a8 <+12>: nop ; (mov r0, r0) 0x000105ac <+16>: mov r0, r3 0x000105b0 <+20>: add sp, r11, #0 0x000105b4 <+24>: pop {r11} ; (ldr r11, [sp], #4) 0x000105b8 <+28>: bx lr End of assembler dump.
http://pwnable.kr/bin/leg.asm
문제 서버에 있는 어셈블리 코드랑 살짝 다르지만, 뭐.. 크게 다를건 없다!
디버거를 attach해서 하나씩 분석해보자
프롤로그 분석
0x000105bc <+0>: push {r4, r11, lr}
(gdb) info reg r4 r11 sp lr pc r4 0x10d5c 68956 r11 0x0 0 sp 0x40800298 0x40800298 lr 0x10908 67848 pc 0x105bc 0x105bc <main> (gdb) x/3wx 0x4080028c 0x4080028c: 0x00000000 0x00010d5c 0x000108cc (gdb) stepi 0x000105c0 in main () (gdb) info reg r4 r11 sp lr pc r4 0x10d5c 68956 r11 0x0 0 sp 0x4080028c 0x4080028c lr 0x10908 67848 pc 0x105c0 0x105c0 <main+4> (gdb) x/3wx 0x4080028c 0x4080028c: 0x00010d5c 0x00000000 0x00010908
push 명령어에 의해 r4 r11 lr 레지스터값이 각각 sp 주소에 들어간다.
push 호출전 sp 주소는 0x40800298인데,
스택이 높은 주소에서 낮은 방향으로 저장되는 리틀엔디안 방식이기에
0x4080028c -> 0x00010d5c (r4)
0x40800290 -> 0x0 (r11)
0x40800294 -> 0x00010908 (lr)
이렇게 저장되며,
하나의 opcode를 실행할때마다 pc는 항상 4씩 증가한다.
arm 아키텍처가 4바이트의 고정 길이인 opcode를 가지고 있는 RISC 방식이기에 항상 4이다.
(THUMB 모드일 경우, +2)
sp 레지스터값은 이제 r4, r11, sp가 저장되었기에
sp값은 12를 뺀 0x4080028c 값이 된다.
0x000105c0 <+4>: add r11, sp, #8
r11 레지스터에 sp 값 + 8을 더한 값이 저장된다.
r11 레지스터는 보통 프레임 포인터로 사용되며, 지역 변수나 매개 변수에 접근하는데 사용된다.
0x000105c4 <+8>: sub sp, sp, #116 ; 0x74
sp 값에 0x74를 빼서
뺀 크기만큼 지역변수에 담을 만한 공간을 생성하기 위해 스택 공간을 확장한다.
여기까지 ARM 아키텍처에서의 프롤로그라고 보면 될 것 같다.
이제 key1, key2, key3 함수에 대해 살펴보자.
key1
(gdb) disas key1 Dump of assembler code for function key1: 0x00010548 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x0001054c <+4>: add r11, sp, #0 0x00010550 <+8>: mov r3, pc 0x00010554 <+12>: nop ; (mov r0, r0) 0x00010558 <+16>: mov r0, r3 0x0001055c <+20>: add sp, r11, #0 0x00010560 <+24>: pop {r11} ; (ldr r11, [sp], #4) 0x00010564 <+28>: bx lr End of assembler dump.
x86_64에서 리턴값이 rax로 저장되는 것과 같이
ARM 아키텍처에서의 리턴값은 보통 r0 레지스터에 해당된다.
r3 레지스터값을 통해 리턴값이 지정되는 것을 알 수 있는데,
여기서 주의할 점은 pc값이 0x10550으로 r3 레지스터에 저장되지 않고 +8을 더한 0x10558 값이 된다는 점이다!
이러한 이유는 ARM 파이프라인 특성 때문이라고 한다.
Reference:
https://cheesehack.tistory.com/104
ARM 파이프라인 특성
ARM 파이프라인은 execute 단계를 완전히 통과할 때까지 명령어를 처리하지 않기 때문에,
execute 단계에서는 PC(Program Counter)가 항상 명령어 주소 + 8 바이트를 가리킨다.
(단, 프로세서가 Thumb 상태인 경우 PC는 항상 명령어 주소 + 4 바이트를 가리킨다.
Thumb 모드 = 16비트 프로그램 호환성을 위해 최적화된 모드)
pc fetch
pc - 4 decode
pc - 8 execute
위 표는 fetch -> decode -> execute 로 이어지는 파이프라인 특성때문에 pc 값이 fetch 단계의 주소를 가리키고 있어 pc 를 읽어 들이는 실제 명령수행 단계에서는 ARM 모드의 경우 8byte, Thumb 모드에서는 4 바이트만큼 항상 앞서 있다
따라서 key1의 리턴값은 0x10558이 되겠다.
key2
(gdb) disas key2 Dump of assembler code for function key2: 0x00010568 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x0001056c <+4>: add r11, sp, #0 0x00010570 <+8>: push {r6} ; (str r6, [sp, #-4]!) 0x00010574 <+12>: add r6, pc, #1 0x00010578 <+16>: bx r6 0x0001057c <+20>: mov r3, pc 0x0001057e <+22>: adds r3, #4 0x00010580 <+24>: push {r3} 0x00010582 <+26>: pop {pc} 0x00010584 <+28>: pop {r6} ; (ldr r6, [sp], #4) 0x00010588 <+32>: nop ; (mov r0, r0) 0x0001058c <+36>: mov r0, r3 0x00010590 <+40>: add sp, r11, #0 0x00010594 <+44>: pop {r11} ; (ldr r11, [sp], #4) 0x00010598 <+48>: bx lr End of assembler dump.
후반부쯤을 살펴보면
mov r0, r3…
r3 레지스터 값에 의해 리턴값이 지정되는 것을 알 수 있다.
0x0001057c <+20>: mov r3, pc
0x0001057e <+22>: adds r3, #4
여기서부터 2바이트씩 opcode가 실행되기 때문에 Thumb 상태이고,
0x0001057c <+20>: mov r3, pc 에서
r3는 pc 값에 4를 더한, 0x0001057c + 4 = 0x10580이 되고,
0x0001057e <+22>: adds r3, #4 에서
r3는 기존 r3값에 4를 더한, 0x10584 + 4 = 0x10584가 되겠다.
따라서 key2의 리턴값은 0x10584이 되겠다.
key3
(gdb) disas key3 Dump of assembler code for function key3: 0x0001059c <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x000105a0 <+4>: add r11, sp, #0 0x000105a4 <+8>: mov r3, lr 0x000105a8 <+12>: nop ; (mov r0, r0) 0x000105ac <+16>: mov r0, r3 0x000105b0 <+20>: add sp, r11, #0 0x000105b4 <+24>: pop {r11} ; (ldr r11, [sp], #4) 0x000105b8 <+28>: bx lr End of assembler dump.
후반부쯤을 살펴보면
0x000105ac <+16>: mov r0, r3
마찬가지로, r3 레지스터 값에 의해 리턴값이 지정되는 것을 알 수 있다.
위로 쭉쭉 살펴보면,
0x000105a4 <+8>: mov r3, lr
lr 레지스터값으로 r3 레지스터값이 지정되는데,
여기서 lr은 링크 레지스터로 서브루틴 후에 리턴 주소가 저장되어있다.
(gdb) disas main Dump of assembler code for function main: ... 0x0001060c <+80>: bl 0x1059c <key3> 0x00010610 <+84>: mov r3, r0 ... End of assembler dump.
돌아갈 리턴 주소는 0x00010610 주소로,
따라서 key3의 리턴값은 0x10610이 되겠다.
이렇게 모든 key의 리턴값을 더하면,
0x10558 + 0x10584 + 0x10610
= 0x310ec
= 200940이 된다.
한번 확인해보면,
seo@raspberrypi:~/Desktop $ qemu-arm ./leg Daddy has very strong arm! : 200940 Congratz!
값이 맞았다고 “Congratz!” 문자열이 나타난다!
에필로그 분석
0x00010694 <+216>: sub sp, r11, #8 0x00010698 <+220>: pop {r4, r11, pc}
0x00010694 <+216>: sub sp, r11, #8
프롤로그에서 add r11, sp, #8
명령어로 r11 레지스터값을 sp 레지스터값+8로 지정했기 때문에,
에필로그에서 이를 다시 빼줌으로써 sp 레지스터값을 원래 위치로 복원한다.
0x00010698 <+220>: pop {r4, r11, pc}
스택의 최상위 값들을 r4, r11, pc 레지스터값에 각각 복사하고
스택 포인터 sp
를 세 레지스터 값을 뺀 만큼의 크기인 12를 더하여 다시 증가시킨다.
Solution
key1 = 0x00008cdc + 8 = 0x8ce4 key2 = 0x00008d04 + 4 + 4 = 0x8d0c key3 = lr = 0x00008d80 key1 + key2 + key3 = 0x1a770 = 108400
Result
... cttyhack: can't open '/dev/ttyS0': No such file or directory sh: can't access tty; job control turned off / $ ls bin dev flag linuxrc root sys boot etc leg proc sbin usr / $ ./leg Daddy has very strong arm! : 108400 Congratz! My daddy has a lot of ARMv5te muscle! / $