首页 > 其他分享 >re-vctf2024-vm

re-vctf2024-vm

时间:2024-04-22 16:12:02浏览次数:25  
标签:0xE0 eax 0x01 0x00 vm 0xF0 vctf2024 re 0x02

vctf2024-vm

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

二.解题思路

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

2.vm逆向
1)定义关键结构体

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

动调程序将函数参数的int v1修改成_cpu* v1

2)dump下关键指令并跟踪程序指令,查看每一步进行的操作,先跟进第一个opcode指令,观察数据变化
//第一处循环:为了方便理解 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的变化
#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++)
	{
		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进行解读
//第二处循环:
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,
//修正后:第二处循环:
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,
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加密的特征

(指路博客:

6)解密脚本如下:
#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;
}
//zstuctf{xXx_team_Is_GooD



//(c语言源码解释:https://www.cnblogs.com/Moomin/p/15023601.html)
//(解密脚本:https://blog.csdn.net/weixin_45582916/article/details/121429688

标签:0xE0,eax,0x01,0x00,vm,0xF0,vctf2024,re,0x02
From: https://www.cnblogs.com/a1rhthm/p/18150808

相关文章

  • 接口自动化Python+requests踩坑记录
    问题描述同一个接口,传参相同,用postman,jmeter等接口工具都能正常访问,后台也能正常返回数据,但是用requests.post()调用就会返回400jmeter传参以及响应这是一个登录接口,如图所示的传参,是可以正常登录的  postman传参以及响应可以看到,两个工具的传参不一样,但是也是同样可以正......
  • grep 的使用场景
    场景1:grep中如何同时搜索A或者B?在使用grep命令进行文本搜索时,如果你想要搜索包含"A"或者"B"的行,可以使用以下两种方法:使用-e选项来为每个搜索模式指定一个表达式:grep-eA-eBfilename这里-eA表示搜索包含"A"的行,-eB表示搜索包含"B"的行。使用单个搜索模式并利用正......
  • JTCR-I/O,Try-with-Resources 及其他-11
    I/O基础Java的I/O操作通过流来实现。流是对输入、输出数据的抽象,每个流都和一个具体的物理实体关联,比如在输入中,流可以和键盘、磁盘文件或者网络输入等关联,虽然每个物理实体不同,但是流可以以同样的方式进行处理。Java定义了字节流和字符流。字节流处理的对象是二进制数据,以......
  • redis自学(40)什么是多级缓存
    传统缓存的问题传统的缓存策略一般是请求到达Tomcat后,先查询redis,如果未命中则查询数据库,存在下面的问题:l 请求要经过tomcat处理,tomcat的性能成为整个系统的瓶颈l Redis缓存失效时,会对数据库产生冲击  多级缓存方案多级缓存就是充分利用请求处理的每个环节,分别添加缓......
  • android studio Edit Custom VM Options后无法启动
    异常描述:想要修改虚拟器的内存,就百度了方法,设置了Help——EditCustomVMOptions,然后AndroidStudio就无法启动了,直接弹这个弹窗:所以,建议大家写文,还是要有头有尾,该上图上图,不能啪啪几个字让人猜啊,容易误导人的啊啊啊!!!解决问题:按这个路径查找到更改的文件,C:\Users\XXXX\AppDat......
  • ubuntu上通过kvm新建虚拟机
    KVM虚拟化架构KVM是指基于Linux内核的虚拟机(Kernel-baseVirtualMachine),增加到Linux内核是Linux发展的一个重要里程碑,这也是第一个整合到Linux主线内核的虚拟化技术。在KVM模型中,每一个虚拟机都是一个由Linux调度程序管理的标准进程,你可以在用户空间启动客户机操作系统,一个普通......
  • postgresql重置序列和自增主键
    1.问题背景数据表中插入了几条测试数据,后又手动删除,导致后面插入数据的时候报主键冲突:ERROR:duplicatekeyvalueviolatesuniqueconstraint"tableName_pkey"DETAIL:Key(id)=(1)alreadyexists.12即使采用INSERTIGNORE的方式或者REPLACEINTO的方式还是报错,所以就想......
  • k8s node节点报错 dial tcp 127.0.0.1:8080: connect: connection refused
    前言在搭建好kubernetes环境后,master节点拥有control-plane权限,可以正常使用kubectl。但其他node节点无法使用kubectl命令,即使同步过去/root/.kube/config文件到各个node节点上,也不行。解决检查KUBECONFIG变量:确保KUBECONFIG环境变量正确设置。KUBECONFIG......
  • redis 安装使用
    下载地址https://download.redis.io/releases/解压tarzxvfredis-7.0.15.tar.gz-C/usr/local/编译redis:cd/usr/local/redis-7.0.15/make编译完成后会在目录下生成生成一个src的目录安装redis:cdsrc/makeinstallPREFIX=/usr/local/redis安装完成。移动配......
  • atcoder regular 176 (ARC176) A、B题解
     A很容易有一个错误想法,就是行从1~n,列从1~n拿,这样,第三个样例,最后,第7行,第7列,需要都增加两个数,但是(7,7)这个位置只能有一个数。我的做法是优先队列/set队列,每次选择行、列之中当前已经有的数目最少的(这样它们最需要添加),这样能保证,行列需要添加的,不会出现只能选择多个行列一样的......