본문 바로가기
Security/pwnable.kr

[pwnable.kr] Toddler's Bottle - leg

by 단월໒꒱ 2022. 6. 19.

이번에는 leg 문제를 풀어보았다.

 

 

 

 

아빠는 arm을 공부하라 하지만 자기는 leg를 공부하고 싶다고 한다.

 

아래에 있는 명령어를 통해 접속해준다.

 

 

 

 

접속 완료했다.

 

 

 

 

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

 

 

길어서 일단 main 함수 부분을 가져왔다.

 

 

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

 

 

정수 값을 입력 받아서 key 변수에 저장하고 있다.

이후에 key1 함수, key2 함수, key3 함수의 반환값들을 더한 값이 key 값과 같으면 flag를 보여주고 다르면 보여주지 않는다.

 

 

key1, key2, key3 함수는 어셈블리어로 작성되어 있는데 어차피 어셈블리어인 거 asm 파일도 있길래 그거를 바탕으로 살펴보았다.

 

 

먼저 main 함수이다.

 

 

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

 

 

+48, +56, +68 등의 부분을 보면 함수 key1, key2, key3을 호출한 후에 반환되는 값이 r0에 저장되는 것을 볼 수 있다.

그러므로 key1, key2, key3 함수들을 보면서 r0에 저장되는 값이 무엇인지 확인하면 될 것 같다.

 

 

다음은 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
End of assembler dump.

 

 

+8 부분에서는 pc 값을 r3에 저장하고, +12 부분에서는 r3의 값이 r0에 저장된다.

 

pc가 뭔지 모르겠어서 구글링해보았다.

pc(program counter)는 ARM에서 다음에 실행해야할 명령어의 주소를 가리킨다.

그러면 보통 그 다음 주소값이 pc에 들어갈텐데, 좀 더 찾아보니  다음의 주소값이 아니라 다음다음의 주소값이 들어간다고 한다.

ARM에서는 명령어를 실행할 때 fetch(명령어를 가져옴) -> decode(해석) -> execute(실행) 단계로 진행되는데, 아래의 과정과 같이 실행된다고 한다.

 

 

출처 :&nbsp;https://n9ne-tail.tistory.com/15

 

 

따라서 +8에 존재하는 pc에는 0x00008ce4가 들어가므로, r0의 값은 0x00008ce4이다.

 

 

다음은 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.

 

 

+20에서 pc 값이 r3에 저장되고, +22 부분에서 r3에 4를 더해주고, +32 부분에서 r3의 값이 r0에 저장된다.

여기서 pc 값은 0x00008d08이고 여기에 4를 더해주었으니 r0의 값은 0x00008d0c이다.

 

 

마지막으로 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.

 

 

+8에서 lr의 값이 r3에 저장되고, +12 부분에서 r3의 값이 r0에 저장된다.

 

여기서 또 lr에 대해 찾아보았다.

lr(link register)는 함수를 호출한 후에 반환하는 주소를 저장한다.

 

그렇다면 여기에서는 lr에 저장되는 값은 key3 함수를 호출하고 반환되는 주소인데, 이거는 main 함수의 asm 코드를 보면 확인할 수 있다. 즉, 0x00008d80이 된다.

 

 

결과적으로 위의 과정들을 통해 구한 r0에 저장된 값들을 찾아봐서 더하면 된다.

 

 

 

 

더한 값은 0x1a770인데, 입력 받는 값이 정수이므로 10진수로 변환해주면 108400이다.

 

108400을 입력해주면

 

 

 

 

성공적으로 flag를 획득할 수 있다.

 

 

 

 

댓글