IT(개발, 보안)/pwnable

[Pwnable 풀이] 11. leg

인도호랑이 2022. 12. 2. 09:01

이번문제는 arm 어셈블리 언어에 대한 문제이다 해당 링크의 파일을 확인해보자

 

#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.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)

leg.asm의 내용

 

이번 문제는 우리에게 익숙한 인텔 어셈블리어가 아닌 arm 어셈블리어에 관한 문제로 해당 문제를 푸는 동안 아래 블로그의 어셈블리 명령어 설명을 참조했다. 더 공부하고 싶으신 분들은 아래 링크 참조.

https://johyungen.tistory.com/506

 

ARM 기초 (레지스터 종류, 명령어 등, LDR, STR)

레지스터 종류 R0 ~ R12 : 범용 레지스터 (다목적 레지스터), R11(스택 프레임 포인터) R0 : 함수 리턴 값 저장 (EAX 같은 느낌) R0 ~ R3 : 함수 호출 인자 전달 R13 ~ R15 : 특수 레지스터 R13(SP) : 스택 포인터

johyungen.tistory.com

 

LDR과 STR 명령어 이해한다고 눈알 빠지는줄 알았지만 결론적으로 문제를 해결하는데에는 큰 이해는 필요하지 않았다.먼저 코드를 살펴보면 key1, key2, key3 함수의 리턴 값을 모두 더해 입력값과 비교해서 flag를 출력해준다. 세가지 함수 모두 어셈블리어로 적혀져 있기에 어셈블리어로 함수를 뜯어보자.

 

먼저 메인 함수에서 세가지 함수를 호출하는 명령어 부분이다.

   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

모든 함수에서 리턴값은 r0에 저장되는 것으로 보이기 때문에 세개 함수 모두 r0에 저장되는 값을 눈여겨 보면 될 것이다.

 

 

<key1>

   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3

아주 간단하다, r3에 pc 값이 저장되는데 pc는 program counter 값으로 arm 아키텍처의 pc 레지스터에는 fetch 단계에 있는 명령어의 주소가 저장되어 있다.

 

보통 프로세서의 명령어 처리는 fetch->decode->execute 과정으로 파이프라이닝되어 수행되어서, 해당 key1 <+8> 라인이 실행될 때 fetch 단계에 있는 명령어는 key1 <+8>의 다음 다음 명령어인 key1 <+16>이다. 따라서 r3 레지스터에 저장되는 값은 0x8ce4 값이다.

 

 

<key2>

   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

<+24>, <+26>, <+28> 라인은 무시해도 좋다. 여기도 r3와 관련된 것만 확인하자면 <+20>에서 pc값이 저장되고 <+22>에서 4가 더해지기 때문에 0x8d0c 값은 쉽게 구할 수 있다. 하지만 <+12>와 <+16>에서 조금 헤맸기 때문에 문제 해결에 직접적인 관련은 없지만 잠깐 설명하고 넘어가겠다.

 

<+16> 라인 이후로 명령어의 크기가 작아졌음을 확인할 수 있다, 라인 한줄마다 메모리 주소가 4씩 증가하다 2씩 증가하고 있기 때문이다. 이는 bx 명령어의 용도 때문인데 bx 명령어는 값으로 주어진 레지스터의 주소로 이동하는 역할을 한다. 그런데 이때 레지스터에 주어진 값이 홀수이면 arm 아키텍쳐의 저전력 모드인 thumb 모드로 전환을 하고 짝수이면 arm 모드로 전환을 하기 때문이다. 

 

ARM과 Thumb 모드란?

원래 ARM은 32bit RISC machine으로 word 단위가 32bit, 한번에 32비트씩 처리할 수 있지만, 16bit를 word 단위로 하는 Thumb 모드로 전환을 통해 code 압축 효과와 성능 개선효과가 있고, 구 시대의 16bit word 단위로 작성된 언어 또한 작동 시킬수 있다는 장점이 있다.

 

때문에 <+20> 라인에서 원래 arm 모드에서 PC였다면 +8을 더한 값이었을 텐데 thumb 모드로 전환했기 때문에 다음다음 명령어의 주소가 +4를 더한 값이다.

 

 

<key3>

   0x00008d28 <+8>:	mov	r3, lr
   0x00008d2c <+12>:	mov	r0, r3

여기선 r3에 lr의 주소를 저장하는데 lr은 함수가 돌아가서 실행하는 명령어의 주소이기 때문에 값은 찾아보면 0x8d80 임을 알 수 있다.

 

때문에 세가지 값을 더한 값을 정수로 변환한 108400을 입력값으로 주면 flag를 얻을 수 있다.

반응형