[Pwnable.kr] leg 풀이 (2pt)
Wargame/pwnable.kr

[Pwnable.kr] leg 풀이 (2pt)

풀이

2개의 소스 코드가 주어진다.

 

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;
}

leg.asm

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

처음보는 asm 관련 문제가 나와서 좀 고민을 많이 했다.

일단 차근차근 봐보도록 하자.

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);
	}

먼저 main 함수에서는 key 값을 입력받고, key1, key2, key3의 리턴 값이 key 값과 같으면 flag를 출력한다.

당연하게도 문제를 풀기 위해서는, 함수의 리턴 값이 뭔지 알아야 한다.

일단 asm을 봐보면, 함수의 리턴 값을 r0에 저장하는 듯하다. 그렇다면 key1, key2, key3에서의 r0 값을 알면..?


먼저 key1부터 봐보자.

int key1(){
	asm("mov r3, pc\n");
}
(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.

해당 코드에서는 2줄만 보면 된다.

0x00008cdc mov r3, pc

0x00008ce0 mov r0, r3

처음 보는 pc라는 레지스터가 있어, 뭔지 찾아봤다.

PC: 다음 실행할 코드의 주소를 저장한다. (EIP) & 다음 인출(Fetch) 될 명령어의 주소를 가지고 있는 레지스터
출처: https://technote.kr/310[TechNote.kr]

찾아보니 CPU에는 4단계 명령어 처리 구조가 존재한다고 한다. pc 레지스터에는 Fetch 될 명령어의 주소를 가지고 있으므로, 해당 위치에서 두 단계 뒤 주소가 pc에 들어간다고 한다.

 

그렇다면, 0x8cdc에서 해당 구문을 처리하니, pc에 들어가는 값은 +8한 주소인 0x8ce4이다.

그렇게 되면? r3에는 0x8ce4 값이 들어가고 r0에 다시 0x8ce4 값이 들어가니까? key1에서의 값은 0x8ce4이다.


다음은 key2이다.

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"
	);
}
(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.

r0에 무슨 값이 들어가나 보면 최종적으로 r3의 값이 r0에 들어간다.

r3값이 처음으로 정의되는 구문을 보자.

0x00008d04 mov r3, pc

 

그러나 여기서 주의할 점이 있다. 답이 계속 틀려 정보를 모으게 되었는데..

ARM에는 ARM modeThumb mode가 있다.

bx 명령어를 이용해 ARM에서는 ARM mode <-> Thumb mode를 왔다 갔다 할 수 있다고 한다. 정말 어렵다...

ARM mode는 32bit를 사용하고, Thumb mode는 16bit를 사용하니 PC가 두 단계가 아니라 한 단계 뒤를 가리킨다.

 

그러므로 key2에서는 pc값이 0x8d08이고, adds r3, #4 구문을 실행해 4를 더하니, 최종 값은 0x8d0c이다.


제일 쉬운 key3이다.

int key3(){
	asm("mov r3, lr\n");
}
(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.

여기서는 딱 하나만 알아도 된다.

0x00008d28 mov r3, lr

lr 레지스터에는 리턴 어드레스가 들어간다고 하니까? key3 함수가 종료되고 난 후에 주소를 넣어주면 된다.

main에서 보면 알 수 있듯이, 다음 주소는 0x8d80이다.


최종적으로 key1, key2, key3의 리턴 값을 더하면 0x8ce4 + 0x8d0c + 0x8d80 = 0x1a770이다.

key값은 정수로 받으니, 0x1a770 -> 108,400 해주면 flag를 얻을 수 있다.

이번 문제를 풀기 위해서 정말 많은(?) 지식이 필요한 거 같다.

'Wargame > pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] shellshock 풀이 (1pt)  (0) 2020.07.21
[Pwnable.kr] mistake 풀이 (1pt)  (0) 2020.07.20
[Pwnable.kr] input2 풀이 (4pt)  (0) 2020.07.20
[Pwnable.kr] random 풀이 (1pt)  (0) 2020.07.19
[Pwnable.kr] passcode 풀이 (10pt)  (0) 2020.07.19