본문 바로가기

Pwnable.kr

Pwnable.kr [leg]

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

pwnable.kr/bin/leg.c에 있는 코드

 

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

pwnable.kr/bin/leg.asm에 있는 코드

 

ARM 어셈블리가 섞여 있고, 학교에서 배운적이 있어 그나마 검색 조금으로 더듬더듬 풀 수 있었다.

주로 임베디드 시스템에서 강세를 보이는 아키텍쳐로 배웠다.

 

어셈 코드 자체는 어렵지 않으니, 기반 지식으로 필요한 부분은 PC와 LR 정도.

(b계열은 branch로 call 정도로 생각하면 됨)

 

ARM 아키텍쳐에서는 레지스터별로 특정 용도를 주고 레지스터에다가

바로 푸쉬 팝을 하면서 효율성을 올리는 것으로 알고 있는데, PC와 LR이 그런 용도이다. 

(사실 R15, R14이지만 이름을 붙여서 직관성을 높임)

 

PC는 IP와 비슷하지만, 다른 점은 명령어 실행시, fetch, decode, execution 등으로 단계를 나누어서 하고

PC가 가리키는 곳은 다음 fetch할 주소이기 때문에, IP와 약간의 차이가 난다. 

명령어 실행 사이클의 경우 4단계, 6단계? 등에다가 파이프 라인을 이용한 여러 방식이 있었던 거로 기억하는데,

자세한 내용은 생략하고, 

 

간단히 현재 명령어 주소가 0xa0이고, 다음 명령어 주소가 0xab, 그 다음 명령어 주소가 0xad라면

현재 명령어에서의 PC는 0xab가 아니라 0xad이다.

(0xa0 : execution 단계, 0xab : decode 단계, 0xad : fetch 단계)

물론 중간에 branch instruction이 있다면

미리 fetch한 인스트럭션이 쓸모 없어져 버리기 때문에,

다른 루틴(branch 예상 방식 등)을 도는 경우도 있는 것으로 알고 있다.

 

LR의 경우 그냥 리턴주소를 담고 있다. 스택을 사용하지 않고, 레지스터에 넣어놓는 게 특이점.

 

ARM에 대해서 분석할 경우도 많은가? 자주 나온다면 나중에 더 자세히 보도록 하자.

'Pwnable.kr' 카테고리의 다른 글

Pwnable.kr [shellshock]  (0) 2019.05.01
Pwnable.kr [mistake]  (0) 2019.05.01
Pwnable.kr [input]  (0) 2019.04.30
Pwnable.kr [random]  (0) 2019.04.29
Pwanble.kr [passcode]  (0) 2019.04.29