본문 바로가기
포너블(pwnable)/CTF(pwnable.kr)

[Toddler's Bottle] leg 문제풀이

by LIZ0904 2021. 4. 28.
반응형

오늘의 문제

오늘의 문제는 다운로드 파일이 두 개가 주어지고, ssh 링크가 주어진다.

 

 

파일 다운로드

wget 명령을 이용해서 leg.c와 leg.asm을 다운로드 받아준다.

 

 

cat leg.c

cat 명령을 사용해 leg.c 파일을 확인해보았다. key1~3() 함수가 있고, main 함수가 있다.

main함수부터 살펴보도록 하자!

scanf를 사용해 key 값을 입력하고, 그 key값이 key1()+key2()+key3()의 결과와 동일하면, flag 파일이 open 되는 형식이다.

 

 

cat leg.asm

cat명령을 사용해 leg.asm 파일을 확인해보니, gdb를 사용해 분석한 내용들이 들어있다. 아마 leg.c의 실행파일을 분석한거겠죠?

 

//leg.c 코드의 key1 함수 부분
int key1(){
	asm("mov r3, pc\n");
}
//leg.asm 파일의 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.

첫번째로, key1 함수의 c코드와 어셈블리코드 비교를 통해 확인해보자!

c코드를 확인해보면 mov 명령을 통해 pc 값을 r3에 넣고 있다.

디스어셈블 코드를 확인해보면, mov명령을 통해 pc 값을 r3에 넣은 뒤, r3 값을 r0에 넣고 있다. 

 

PC(Program Counter) 레지스터는 다음 인출(Fetch)될 명령어의 주소를 갖고 있는 레지스터이다.

cpu는 하나의 명령을 실행할 때, *fetch->*decode->*execute의 단계를 거친다. 

하지만 파이프라인(Pipeline)이라는 병렬처리 방식으로 인해, 2개의 opcode(명령)을 실행할 때 6번의 작업을 4번으로 줄여주는 효율적인 방식이 사용되고 있다. 아래 표에서 이에 대한 차이에 대해 설명한다.

*fetch: 메모리에서 명령어를 가져옴

*decode: 명령어 읽기 및 해독, 레지스터를 읽음

*excute: 연산수행 또는 주소계산

1 2 3 4 5 6
fetch decode excute fetch decode execute

 

직렬처리 방식을 사용하면, 위와 같이 2개의 opcode에서 fetch->decode->execute->fetch->decode->execute로 총 6번의 작업을 거친다.

1 2 3 4
fetch decode execute  
  fetch decode execute

하지만 병렬처리 방식을 사용하면, fetch->decode->fetch->execute->decode->fetch->excute->...의 과정을 거친다. 만약 내 현재 단계가 execute라면 fetch 명령의 주소는 다다음 주소가 되게 된다.

즉, 쉽게 말해서 pc에는 다다음 명령의 주소가 들어가게 된다.

현재 mov r3,pc 명령의 다다음 주소인 0x00008ce4가 pc의 값이다.

key1 결과값 : 0x00008ce4

 

//leg.c 파일의 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.

leg.c 파일의 key2() 부분을 확인해보면, key1 함수와 비슷하게 mov r3, pc 명령을 통해 pc값을 r3에 넣고 있다.

leg.asm 파일의 key2() 부분을 보면 mov r3, pc 명령을 통해 pc 값을 r3에 넣어준다. 그 다음 adds r3, #4 명령을 통해 r3 값에 4를 더해주고 있다.

즉, pc 값은 0x0008d08이며, 여기에 4를 더해주면 0x00008d0c가 된다.

key2 결과값: 0x0008d0c

 

//leg.c 파일의 key3 함수 코드부분
int key3(){
	asm("mov r3, lr\n");
}
//leg.asm 파일의 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.

leg.c 파일의 key3() 부분을 확인해보면, mov r3, lr 명령을 통해 lr 값을 r3에 넣어준 뒤, r3 값을 r0에 넣어주고 있다.

lr은 링크레지스터로, 리턴 주소를 저장하고 있는 레지스터이다.

main 함수의 key3

main 함수 어셈블리코드에서 key3 함수의 호출 부분을 확인해보자. key3 함수 호출 부분인 0x00008d7c에서, key3 함수가 종료되고 나면 그 다음 주소인 0x00008d80으로 돌아가게 될 것이다. 즉, lr은 0x00008d80이다.

key3 결과값: 0x00008d80

 

 

총 정리를 해보면, 

key1+key2+key3 = 0x00008ce4+0x0008d0c+0x00008d80 = 0x0001A770 이다.

0x1A770은 10진수로 108400이므로 정답을 입력해줄 차례이다.

 

ssh

정답 입력을 위해 ssh 주소로 접속해준다.

 

ls

ls 명령을 통해 확인해보면, leg 실행파일이 있다.

 

정답 입력

입력값으로 아까 구했던 108400을 입력해주면, flag를 확인할 수 있다!

 

flag 입력

flag를 입력해주면 정답~~!!

 

 

flag : My daddy has a lot of ARMv5te muscle!

 

 

 

반응형

댓글