콘텐츠로 건너뛰기

leg

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!
/ $ 

답글 남기기