본문 바로가기
wargame/pwnable.kr

[포너블] pwnable.kr 8 - leg

by 아메리카노와떡볶이 2022. 2. 8.
728x90
적용 완료 텍스트

오늘은 8번째 문제인 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;
}

key1,2,3 함수의 리턴값을 유추해서 키값을 맞추는 문제인거 같다.

근데 각 키 함수들이 어셈으로 작성되어있다.

 

좀 더 명확하게 알아보기 위해 어셈을 열어보자.

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

색칠한 부분을 보면 key 함수를 call 한뒤에 리턴값이 레지스터 r0에 저장되는것을 볼 수 있다.

따라서 각 key func 에서 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

r11은 스택포인터를 위해 사용된 레지스터니까 신경안써도되고

r0을 보면 pc가 저장된다. pc는 program counter로 다음 실행할 명령어의 주소를 담고있다.

즉 key1+8 에서 pc는 0x00008ce0 일것이고 r3을 통해 r0으로 옮겨졌으므로 key1의 리턴값은 0x00008ce0이다.

 

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.

마찬가지로 r0 레지스터의 값만 추적하면 pc -> r3 -> r3+4 -> r0 이기때문에

key2+20 에서 pc는 8d06이고 +4를 하면 8d0A이다.

 

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.

마찬가지로 보면 lr -> r3 -> r0 으로 값이 이동된다. lr은 함수를 call 한뒤에 돌아갈 리턴주소를 가지고 있기때문에

메인을 다시 올라가서 보면 main+68 즉 8d80 이다.

 

정리하면 key값은 8ce0 + 8d0A +8d80 이 된다. 

1a76a ==> 108394 인줄 알았으나 안돼서 구글링해보았더니

pc값이 다음 명령어의 주소가 아니라 다 다음 명령어의 주소를 담고 있었다.

 

fetch -> decode -> execute 로 명령어가 수행되는데 다음명령어는 이미 추출되어 decode 상태이고, pc값에 들어있는 것은 fetch를 위한 다 다음 명령어의 주소라고 한다.따라서 8ce4 + 8d0C + 8d80 ==> 1a770 ( 10진수로 108400)

 

이 문제는 pc 레지스터와 lr 레지스터를 정확히 알고있는가를 물어본거같다.

 

728x90

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

[포너블] pwnable.kr - shellshock  (0) 2022.02.18
[포너블] pwnable.kr 9 - mistake  (0) 2022.02.18
[포너블] pwnable.kr 7 - input  (0) 2022.02.08
[포너블] pwnable.kr 6 - random  (0) 2022.02.07
[포너블] pwnable.kr 5 - passcode  (0) 2022.02.07

댓글