a1ra1rhthm

re-vctf2024-vm

Airhthm·2024-04-22 16:04·68 次阅读

re-vctf2024-vm

vctf2024-vm#

一.vctf2024vm题的题解,一直没有整理,是赛后看大佬wp才知道是upx魔改+rc4的。。#

二.去upx魔改#

1.去upx魔改:#

VCTF 2024 ezvm (虚拟机逆向初探)_vctf vm-CSDN博客

[原创] UPX源码学习和简单修改-加壳脱壳-看雪-安全社区|安全招聘|kanxue.com

加壳流程:(博客总结)

a.写入文件的elf头与程序表头,写入I_info结构

b.对每个PT_LOAD的段进行压缩存储,其中第一个PT_LOAD=elf头+程序头表+其他数据

c.压缩存储其他段

d.写入其他数据-PackerHeader和overlay_offset,其中overlay_offset是I-info的偏移值,一般为F4 00 00 00

三.vm分析#

1)定义关键结构体:动调程序将函数参数的int v1修改成_cpu* v1#

( 指路之前博客:https://www.cnblogs.com/a1rhthm/p/18092363
b站大佬的讲解:https://www.bilibili.com/video/BV1gv4y1u7t1?vd_source=69ffcd703762aa7a204e6cc6f57ba69d

2)dump下关键指令并跟踪程序指令,查看每一步进行的操作,先跟进第一个opcode指令,观察数据变化#

Copy
//第一处循环:为了方便理解,由于反汇编代码中存在大量的类型转换,这里的eax[0]指的是低位八字节,eax[1]指的是高位八字节,与下文给出的脚本中的高低位相反 0xF0, 0xE0, 0x02, 0x00, 0xF0, 0xE0, 0x00, 0xE0, 0x02, //eax=1 03 00 00 00 73h, eax=1 03 00 00 00 04h 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x02, //eax不变 0xF0, 0xE0, 0x01, 0x10, 0xF2, 0xE0, 0x00, 0xE0, 0x01, 0xF1, 0xE0, 0x00, 0x20, 0x02, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, //eax=10 00 00 02 24 eax[0]=v3=0x5F eax=10 00 00 00 5Fh 0xF0, 0xE0, 0x01, 0xE0, 0x02, //eax=10 00 00 00 5F eax[1]=eax[2] eax=10 00 00 00 5F 0xF1, 0xE0, 0x01, 0x00, 0x01, //v3=1, v1=(1+4)<<8+eip[3]+eax[v3]=0x104, eax[1]=v3, //eax=104 0000005F 这里有个dword*,是双字 占四个字节 0xF0, 0xE1, 0xE0, 0x01, 0xE0, 0x00, //eax不变,修改s的值,s存放的应该是个字符串 0xF3, 0xE0, 0x02, //(Dword*)eax[2]++,eax不变,eax=1040000005Fh,但(Dword*)eax[2] //指向ebx,实际上是ebx++ 0xF6, 0xE0, 0x02, 0x00, 0x01, //ebx<256 0xF7, 0x04, //跳转到第一条opcode,这是个循环指令,ebx用来记录循环次数

3)写个脚本查看一下eax的变化#

Copy
#include <stdio.h> #include <Windows.h> //第一段循环的脚本,查看数组的变化以及_eax的值 int main() { /* 定义了三个寄存器eax,ebx,ecx,都是dq也就是8字节的int64类型 */ /* 由于在反汇编代码中存在类型转换,在此脚本中eax[0]代表eax的高位,eax[1]代表的是eax的低位,eax[2]则代表ebx (eax[0]<<8)|eax[1]=eax */ unsigned int opcode[] = { 0xF0, 0xE0, 0x02, 0x00, //第一次循环 0xF0, 0xE0, 0x00, 0xE0, 0x02, 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x02, 0xF0, 0xE0, 0x01, 0x10, 0xF2, 0xE0, 0x00, 0xE0, 0x01, 0xF1, 0xE0, 0x00, 0x20, 0x02, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, 0xF0, 0xE0, 0x01, 0xE0, 0x02, 0xF1, 0xE0, 0x01, 0x00, 0x01, 0xF0, 0xE1, 0xE0, 0x01, 0xE0, 0x00, 0xF3, 0xE0, 0x02, 0xF6, 0xE0, 0x02, 0x00, 0x01, 0xF7, 0x04, 0xF0, 0xE0, 0x02, 0x00, 0xF0, 0xE0, 0x03, 0x00, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //循环跳转处 0xF1, 0xE0, 0x03, 0xE0, 0x00, 0xF0, 0xE0, 0x00, 0xE1, 0x02, 0xF1, 0xE0, 0x00, 0x00, 0x01, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, 0xF1, 0xE0, 0x03, 0xE0, 0x00, 0xF2, 0xE0, 0x03, 0x00, 0x01, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, 0xF0, 0xE0, 0x01, 0xE1, 0xE0, 0x03, 0xF0, 0xE1, 0xE0, 0x03, 0xE0, 0x00, 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x01, 0xF3, 0xE0, 0x02, 0xF6, 0xE0, 0x02, 0x00, 0x01, //这里也是循环256次 0xF7, 0x45, }; long eax[2] = {0}; unsigned int ebx = 0; unsigned int s[600] = { 0 }; //在动调vm过程中还会观察到对一个数组进行了初始化 unsigned int enc[16] = { 0x54, 0x68, 0x69, 0x73, 0x5F, 0x31, 0x73, 0x5F, 0x66, 0x31, 0x6C, 0x4C, 0x6C, 0x6C, 0x61, 0x67 }; for(int i=0;i<256;i++) //下面的操作都是根据opcode操作读取出来的,所以看不懂的话可以尝试自己动调一遍vm并观察内存变化 { eax[1] = ebx; s[ebx] = ebx; eax[0] = 0x10; //0是高位 unsigned int v1 = eax[1] % eax[0]; eax[1] = v1; unsigned int v2 = (2 << 8) + 0x20 + eax[1]; eax[1] = v2; unsigned v3 = enc[i%16]; eax[1] = v3; eax[0] = ebx; eax[0] = (1 << 8) + 0 + eax[0]; s[eax[0]] = enc[i%16]; ebx++; printf("当前eax的值为:0x%x%x\n", eax[0]<<20,eax[1]); } for (int i = 0; i < 600; i++) { if (i % 16 == 0) printf("\n"); printf("%-d ",s[i]); } return 0; }

4)同理根据F0-F6函数进行的操作,对每段opcode进行解读#

Copy
//第二处循环: 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //循环跳转处:以下已经过高低位处理 0xF1, 0xE0, 0x03, 0xE0, 0x00, //v3=3,v1=eax[1]+ecx,ecx=v1 0xF0, 0xE0, 0x00, 0xE1, 0x02, //eax[1]=ebx 0xF1, 0xE0, 0x00, 0x00, 0x01, //v3=0,v1=1<<8+0+eax[1],eax[1]=v1 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, //v2=0xE0,v3=s[eax[0]],eax[1]=v3 0xF1, 0xE0, 0x03, 0xE0, 0x00, //v3=3,v1=eax[1]+ecx 0xF2, 0xE0, 0x03, 0x00, 0x01, //v3=3,v1=ecx%(1<<8)+0,ecx=v1 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //v2=0xE0,v3=s[ecx],eax[1]=v3=s[ecx] 0xF0, 0xE0, 0x01, 0xE1, 0xE0, 0x03, //v2=0xE0,v3=s[(int)ecx+1],eax[0]=v3=s[ecx+1] 0xF0, 0xE1, 0xE0, 0x03, 0xE0, 0x00, //v2=0xE1,s[ecx+1]=eax[1] 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x01, //v2=0xE1,s[ecx]=eax[0] 0xF3, 0xE0, 0x02, //ebx++ 0xF6, 0xE0, 0x02, 0x00, 0x01, //这里也是循环256次,ecx=1 0xF7, 0x45,
Copy
//修正后:第二处循环: 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //循环跳转处:以下已经过高低位处理 0xF1, 0xE0, 0x03, 0xE0, 0x00, //ecx=eax[1]+ecx 0xF0, 0xE0, 0x00, 0xE1, 0x02, //eax[1]=ebx 0xF1, 0xE0, 0x00, 0x00, 0x01, //eax[1]=256+eax[1] 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, //eax[1]=s[eax[1]] 0xF1, 0xE0, 0x03, 0xE0, 0x00, //ecx=eax[1]+ecx 0xF2, 0xE0, 0x03, 0x00, 0x01, //ecx=ecx%256 //这里循环交换了s[i]与s[i+1]的值 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //eax[1]=s[ecx] 0xF0, 0xE0, 0x01, 0xE1, 0xE0, 0x03, //eax[0]=s[ecx+1] 0xF0, 0xE1, 0xE0, 0x03, 0xE0, 0x00, //s[ecx+1]=eax[1] 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x01, //s[ecx]=eax[0] 0xF3, 0xE0, 0x02, //ebx++ 0xF6, 0xE0, 0x02, 0x00, 0x01, //这里也是循环256次,ecx=1 0xF7, 0x45,
Copy
0xF0, 0xE0, 0x02, 0x00, //ebx=0 0xF0, 0xE0, 0x03, 0x00, //ecx=0 //第三处循环 0xF3, 0xE0, 0x02, //ebx++ 0xF2, 0xE0, 0x02, 0x00, 0x01, //ebx=ebx%256, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //eax[1]=s[ecx] 0xF1, 0xE0, 0x03, 0xE0, 0x00, //ecx=eax[1]+ecx, 0xF2, 0xE0, 0x03, 0x00, 0x01, //ecx=ecx%256 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, //eax[1]=s[ecx] 0xF0, 0xE0, 0x01, 0xE1, 0xE0, 0x03, //eax[0]=s[ecx+1] 0xF0, 0xE1, 0xE0, 0x03, 0xE0, 0x00, //s[ecx+1]=eax[1] 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x01, //s[ecx]=eax[0] 0xF1, 0xE0, 0x00, 0xE0, 0x01, //eax[1]=eax[0]+eax[1] 0xF2, 0xE0, 0x00, 0x00, 0x01, //eax[1]=eax[1]%256 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, //eax[1]=s[eax[1]] 0xF0, 0xE0, 0x01, 0xE0, 0x02, //eax[0]=ebx 0xF4, 0xE0, 0x01, //eax[0]-- 0xF1, 0xE0, 0x01, 0x00, 0x02, //eax[0]=512+eax[0] 0xF0, 0xE0, 0x01, 0xE1, 0xE0, 0x01, //eax[0]=s[ebx] 0xF5, 0xE0, 0x00, 0xE0, 0x01, //eax[1]^=eax[0] :eax[1]存放的是加密后的数据 0xF1, 0xE0, 0x00, 0xE0, 0x02, //eax[1]=ebx+eax[1] :eax[1]+i 0xF0, 0xE0, 0x01, 0xE0, 0x02, //eax[0]=ebx+eax[0] 0xF4, 0xE0, 0x01, //eax[0]-- 0xF1, 0xE0, 0x01, 0x00, 0x02, //eax[0]=ebx+eax[0] 0xF0, 0xE1, 0xE0, 0x01, 0xE0, 0x00, //s[ebx]=eax[1] :将加密后的数据存放到s[ebx]中 0xF0, 0xE0, 0x01, 0xE0, 0x02, //eax[0]=ebx 0xF4, 0xE0, 0x01, //eax[0]-- 0xF6, 0xE0, 0x01, 0x20, 0x00, //循环32次 0xF7, 0x94,

5)可以看出来进行了三次循环,前两处opcode循环进行了256次,第三处循环进行了32次,并且有rc4加密的特征#

(指路博客:

逆向中常见的加密算法:https://www.cnblogs.com/a1rhthm/p/18102941

6)解密脚本如下:#

Copy
#include <stdio.h> #include <math.h> #include <string.h> #include<stdio.h> /* RC4初始化函数 */ void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k) { int i = 0, j = 0; char k[256] = { 0 }; unsigned char tmp = 0; for (i = 0; i < 256; i++) { s[i] = i; k[i] = key[i % Len_k]; } for (i = 0; i < 256; i++) { j = (j + s[i] + k[i]) % 256; tmp = s[i]; s[i] = s[j]; s[j] = tmp; } } /* RC4加解密函数 unsigned char* Data 加解密的数据 unsigned long Len_D 加解密数据的长度 unsigned char* key 密钥 unsigned long Len_k 密钥长度 */ void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密 { unsigned char s[256]; rc4_init(s, key, Len_k); int i = 0, j = 0, t = 0; unsigned long k = 0; unsigned char tmp; for (k = 0; k < Len_D; k++) { i = (i + 1) % 256; j = (j + s[i]) % 256; Data[k] -= i; //这里有魔改 tmp = s[i]; s[i] = s[j]; s[j] = tmp; t = (s[i] + s[j]) % 256; Data[k] = Data[k] ^ s[t]; } } void main() { //字符串密钥 unsigned char key[] = "This_1s_f1lLllag"; unsigned long key_len = sizeof(key) - 1; //数组密钥 //unsigned char key[] = {}; //unsigned long key_len = sizeof(key); //加解密数据 unsigned char data[] = { 0x56, 0x54, 0xD9, 0xB5, 0xF3, 0xB1, 0xFD, 0x67, 0x15, 0xEE, 0xB0, 0x68, 0xB7, 0x2B, 0x4A, 0x64, 0x10, 0x27, 0x52, 0xDE, 0x43, 0x26, 0x0F, 0x2A, 0x41, 0x30, 0x75, 0x30, 0x98, 0x9E, 0x79, 0x5E }; //加解密 rc4_crypt(data, sizeof(data), key, key_len); for (int i = 0; i < sizeof(data); i++) { printf("%c", data[i]); } printf("\n"); return; } //(c语言源码解释:https://www.cnblogs.com/Moomin/p/15023601.html) //(解密脚本:https://blog.csdn.net/weixin_45582916/article/details/121429688
posted @   天街如水  阅读(68)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!
点击右上角即可分享
微信分享提示
目录