CobaltStrike动调
Teamserver的配置与Beacon生成
Teamserver的配置
(使用管理员权限启动)
1 | .\teamserver.bat 192.168.18.132 password |
···
生成Beacon
.启动监听器
生成beacon程序
选择监听器,然后点击attacks,生成X64 beacon
点击Generate并选择保存位置
在目标上线
在目标机上运行Beacon
成功上线
测试功能
选中目标机右键点击interact,开始交互功能
-
screenshot
- 捕获目标系统的屏幕截图 -
keylogger
- 记录目标系统的键盘输入 -
download
- 从目标系统下载文件 -
upload
- 上传文件到目标系统 -
shell
- 在目标系统上执行 shell 命令 -
getprivs
- 尝试提升目标系统的权限 -
run
- 在目标系统上执行自定义脚本
processes
Screenshots
keystrokes
Beacon调试外壳分析
以c语言形式生成shellcode
1 | /* length: 893 bytes */ |
对Windows开发和shellcode loader不熟悉,询问chat并进行修改,生成shellcode loader
1 |
|
测试发现成功上线,说明shellcode loader可用
IDA静态分析
先使用die打开大致查看生成的shellcode loader
(图1)
(图2)
图1是我自己使用gcc编译链接的shellcode loader,图2是我直接使用cs生成的,发现cs也是使用的是MinGW编译生成的并知道程序入口点是0x04014b
main
函数
使用ida打开cs直接生成X64 beacon,定位观察main函数
调用了三个函数:sub_402A60
、sub_401795
、__imp_Sleep
sub_402A60
函数
像是简单的赋值操作
sub_401795
函数
这个函数定义了一些变量,然后变量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
函数
sub_4015D0
函数
继续跟进分析sub_4015D0
函数
1 | int __fastcall sub_4015D0(char *lpBuffer, signed int nNumberOfBytesToWrite) |
while (nNumberOfBytesToWrite > 0 && WriteFile(NamedPipeA, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0i64))
1、在连接成功的情况下,开始循环写入数据到管道。
2、nNumberOfBytesToWrite
要写入的字节数。
3、lpBuffer
要写入的数据缓冲区的地址。
4、&NumberOfBytesWritten
用于记录实际写入的字节数。
5、0i64
表示使用默认的重叠 I/O 参数。
sub_401742
函数
1 | __int64 sub_401742() |
sub_4016A2
函数
1 | __int64 __fastcall sub_4016A2(char *lpBuffer, signed int nNumberOfBytesToRead) |
FileA = CreateFileA(Buffer, 0x80000000, 3u, 0i64, 3u, 0x80u, 0i64);
用于创建或打开文件。
-
Buffer
: 这个参数是一个字符串,代表要创建或打开的文件名。 -
0x80000000
: 这是一个十六进制的文件访问标志,表示以读写模式打开文件。 -
3u
: 这个参数是文件共享模式标志,表示允许文件被其他进程读取和写入。 -
0i64
: 这个参数是文件属性标志,传0表示使用默认属性。 -
3u
: 这个参数是文件创建选项标志,表示如果文件不存在则创建,如果存在则打开。 -
0x80u
: 这个参数是文件属性和标志,表示以普通文件模式打开。 -
0i64
: 这个参数是文件安全描述符,传0表示使用默认值。
ReadFile(FileA, lpBuffer, nNumberOfBytesToRead, &NumberOfBytesRead, 0i64)
读取文件数据。
-
FileA
: 这是前面创建或打开文件时返回的文件句柄。 -
lpBuffer
: 这是一个指针,指向存储读取数据的缓冲区。 -
nNumberOfBytesToRead
: 这个参数指定要读取的字节数。 -
&NumberOfBytesRead
: 这是一个输出参数,用于存储实际读取的字节数。 -
0i64
: 这是一个可选的重叠(overlapped)结构指针参数,在异步读取时使用。在这里传0i64
表示使用同步读取模式。
总的来说这个函数实现了从Buffer中读取数据并存储到指定缓冲区的操作
sub_40152E
函数
申请了一个a2大小的空间给变量v7
参数a1指向的数据和a3指向的数据以特定的顺序进行异或运算,然后赋值给了v7指定的空间中
sub_4014F3
函数是将GetModuleHandleA
函数与GetProcAddress
函数地址存到指定地址
将地址为 v7
、大小为 v3
字节的内存块的访问保护属性修改为允许读、写和执行。
返回入口点为StartAddress的进程
sub_4014F3
函数
前面分析了这个函数,大致作用
StartAddress
函数
一个简单返回lpThreadParameter指针所指地址的函数
静态分析完成main函数大致逻辑
shellcode分析
远端shellcode加载
获取模块基址
进入shellcode之后,发现有call loc_200D2,步入观察
将74656E696E6977h存储到r14寄存器中,并将r14入栈,其中74656E696E6977h是wininet的特征码
使用遍历PEB模块链表加载模块
1 | debug025:0000000000020014 mov rdx, gs:[rdx+60h] //找到PEB结构体 |
rsi指向了这个程序本身也就是之前为这个程序取的t.exe
1 | debug031:000000000002002D loc_2002D: ; CODE XREF: debug031:000000000002003E↓j |
这里就是计算每个模块名称的哈希值,并将结果存储在 R9 寄存器中
直接打断点跳转到结束语句,查看rsi寄存器
LoadLibraryExA
是 Windows 系统中的一个 API 函数,用于动态加载 DLL 库。
LoadLibraryExA
函数去调用了winiet
总结:通过双向链表指向进程装载的模块,然后通过HASH API寻址,然后将LoadLibraryExA函数的地址存入RAX等待调用
加载功能函数
因为rax中存的是kernelbase_LoadLibraryA函数
步入查看详细逻辑
这段代码实现了一个 kernelbase_LoadLibraryA
函数,它在加载 DLL 时会先检查是否为 “twain_32.dll”,如果是则尝试从 Windows 目录下加载。如果加载失败或者 DLL 名称不是 “twain_32.dll”,则直接调用 kernelbase_LoadLibraryExA
函数进行加载。
直接跳出kernelbase_LoadLibraryA
函数,继续分析当前指令
0A779563Ah新的特征值,一直运行到call rbp 观察rbp发现存的地址是0x000000000002000A,跳转回去,直接运行到
A779563Ah->winiet_InternetOpenA
winiet_InternetOpenA
函数用于打开一个 Internet 连接的 API 函数
继续调试发现同样的规律,有点类似于c语言中的switch
()case
语句
照相同手法调试发现
0C69F8957h–>wininet_InternetConnectA
3B2E55EBh->wininet_HttpOpenRequestA
7B18062Dh->wininet_HttpSendRequestA(这个会多次循环,需要打开cs,调试到这里时cs中靶机就成功上线了)
E553A458h->kernel32_VirtualAlloc(调试这个的时候可能出现内存访问错误,打开cs之后再进行调试)
E2899612h->wininet_InternetReadFile
这些函数一起使用的效果就是发送HTTP请求接收并响应,然后分配一个虚拟内存,并Internet 连接中读取数据存到刚才的虚拟内存中
远程文件读取
前面三个函数是为了发送Http请求,主要分析后两个函数查看wininet_InternetReadFile的执行过程获取远程文件的保存位置
kernelbase_VirtualAlloc
函数
1 | LPVOID kernelbase_VirtualAlloc( |
四个参数,找到申请是空间地址
lpAddress=0014F9D8h ;申请开辟内存空间首地址
dwSize=400000 ;申请内存空间大小
flAllocationType=1000 ;申请内存类型
flProtect=40 ;内存保护类型
跳转返回,查看下一个函数
wininet_InternetReadFile
是Windows系统中 WinInet API 提供的一个函数,用于从 Internet 连接中读取数据
1 | BOOL wininet_InternetReadFile( |
一个标准的wininet_InternetReadFile函数,没什么好看的,直接跳转返回
进行了循环读取
内存动态解密
文件读取完毕,将会跳转到文件内存存储地址
观察rax和rbx发现文件起始位置是0x032F0000,文件结束位置是0x03330447,计算得文件大小为0x40447,
存在加密,貌似是smc,调试自解密,起始地址为0x0324003F;直接p强制识别为函数,看伪代码
直接跳转到解密结束,rax指向了0x03410047,使用x64dbg dump出来
这里使用x64 dump的时候运行程序崩了,所以直接使用ida中的idc脚本进行dump
1 | static main(void) |
将dump出来的文件进行重编译
1 |
|
测试上线成功
使用ida反编译
远端文件装载
这一阶段程序将重装载从远程下载的PE文件,约定称重装载前为源PE文件,重装载后为新PE文件
内存文件基址的定位
继续刚才的shellcode分析,单步进入,之后ida会将刚才的数据转换为汇编,或则在步入之前点击c键强制转换也可以
call 这里步入
这里进行了一些栈空间的清理工作,然后继续步入loc_3316413
看到一个loc_3316393,这里只是从栈上读取一个值到 RAX 寄存器中,快速步过
程序在寻找4D5Ah和4500h,这是DOS文件头与NT头的标志,即在进行PE文件的定位
直接跳转到ret返回
1 | debug078:000000000331619B call loc_3316413 |
这里同样还在进行检索,步入loc_33164A3
获取模块基址
又进行了模块寻址的过程,并将其存储到栈中备用 并在检查是否拿到链表指针后
接续执行,发现特征码6A4ABC5Bh,打上断点到跳过这一寻找过程,可知现在找到的是kernel32模块(通过查看rax寄存器
进行了和前面提到的hash api寻址一样的操作,找到功能函数
加载功能函数
在上面的基础上跳出循环,直接来到特征码(注意跳出上面的hash api寻址的循环,来到下面这一步
第一个识别的特征码是0D3324904h,0D3324904h->kernel32_GetModuleHandleA,继续同样的操作
1 | debug083:00000000035B67CA loc_35B67CA: ; CODE XREF: debug083:00000000035B67AB↑j |
7C0DFCAAh->kernel32_GetProcAddress
调试得到:
0EC0E4E8Eh->kernel32_LoadLibraryA
753A4FCh->kernel32_LoadLibraryExA
91AFCA54h->kernel32_VirtualAlloc
7946C61Bh->kernel32_VirtualProtect
功能函数加载完毕,存入栈中等待调用
DLL文件重装载
开辟用于重装载的空间
在上面步骤之后继续运行返回之后,继续运行,经过一些内存处理之后,开始调用栈中的函数,首次调用函数为 VirtualAlloc,开辟用于重装载的空间
直接步过这个call 然后观察rax寄存器发现开辟的空间首地址,即空间首地址为0000000003610000
栈空间开辟完成之后,进行栈的清理工作,观察rcx发现清空了4E000h长度的内存
PE文件头复制
继续运行注意这个call loc_3226C93,步入运行到
程序将PE文件装载到新开辟的内存空间中,从03610000到0000000003610400,总计0400h;PE头内容(5A 41 52 55 48 89 E5 48……
各区段复制
将上面存到03610400的PE文件头copy下来,使用010 editor新建成为一个文件,然后去010官网下载exe.bt的模板,运行模板之后得到
根据PE头所标识的节表数量,可知有五节
观察5给节区的DWORD SizeOfRawData可知即将载入的内容长度
第一段:观察rdi和rcx可知是从0x03611000到0x363BE00共2AE00h
第二段:从0x0363C000到0x0364BA00,总共FA00h
同样的操作,循环得到
第三段:从0x0364C000到0x0364E600,共2600h
第四段:从0x0365A000到0x0365C200,共2200h
第五段:从0x0365D000到0x0365E000,共1000h
结束之后,区段装载完毕
导入表修复
目前重装载的PE文件还不完整,还需要进行修复
过程:
- 从导入表复制模块名称
- 调用kernel32_LoadLibraryA进行模块加载
- 从导入表复制模块函数名称
- 调用kernel32_GetProcAddress获取功能函数调用地址
- 、将函数的调用地址写入新的导入表
将源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)
继续运行开始从导入表中复制模块名称,并按名加载模块
获取模块名称
此段主要复制重装载PE文件存储的模块名称到指定的临时存储区
1 | debug079:00000000033D6EC2 loc_33D6EC2: ; CODE XREF: debug079:00000000033D70EB↓j |
在这个代码中间有一个call loc_33D63A3跟进
模块按名加载
loc_33D63A3调用kernel32_LoadLibraryA进行模块加载,参数为先前复制到空白区的模块名称存于RAX寄存器的返回值为模块的调用地址,压入栈中等待调用
获取功能函数名称
复制的模块名称为VirtualProtectEx
同样在loc_33D7049下
功能模块按名加载
到这的时候ida崩了,所以地址随机化可能和前面不一样,以汇编为主
1 | debug083:00000000033A707C movzx r8d, byte ptr [rsp+0A0h] ; R8=0 |
继续执行loc_32570B4,判断是否还有未载入的功能函数
当所有功能函数的调用地址更新进IAT后,程序将直接返回loc_3256F5B段,进行下一个模块的函数加载
1 | debug081:00000000032570B4 loc_32570B4: ; CODE XREF: debug081:0000000003257047↑j |
每当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 | debug085:0000000003267113 loc_3267113: ; CODE XREF: debug085:00000000032662EF↑p |
继续调试loc_326717B ,这里进行了:
- 重定位表处理进度的判断
- 块中偏移地址个数的计算
1 | debug085:000000000326717B loc_326717B: ; CODE XREF: debug085:000000000326739A↓j |
loc_32671C9进行待处理偏移地址个数的判断,并进行标记为Ah(1010b)的偏移地址 的处理,更新对应的偏移值;IMAGE_REL_BASED_DIR64
1 | debug085:00000000032671C9 loc_32671C9: ; CODE XREF: debug085:000000000326737D↓j |
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,返回
堆栈与临储清理,修改入口点
临储区清理:
入口点修改:
1 | debug085:000000000326631E mov rax, [rsp+38h] ; 源PE文件:PE头 |
到这里远程文件的下载和装载都已经完成
装载文件运行
查看提取出的PE文件,发现入口点修改的结果
DLL的入口点调用与初始化
用户定义的dll入口点函数:DllMain()
用户定义的dll入口点函数,用于beacon.dll初始化
fdwReason=1,运行sub_18001876C()函数,推测其可能为beacon_int()函数, 进行了Beacon的初始化
当fdwReason=4时,程序将运行sub_18000CA74()函数,推测其可能为beacon的主功能函数, 进行上线,等候任务下发
动态调试beacon_init()
通过上面的Dll入口点的非用户行为,直接跳转,通过分析得到call sub_176C2C即DllMain()
Beacon的初始化:Beacon_int()
通过分析得知,sub_17876C()就是Beacon_int()
注意xor解密这里,使用了19C030h的0x1000个数据与0x2e进行xor解密
发现这里是解密了beacon的配置文件
解密完成后sub_173F08()函数对刚解密的数据进行了操作,跟进分析这个函数的作用
这是对解密后的数据赋值给了v10,以及存储配置文件长度
接下来进入while循环
对应beacon_Src项目参考为:
1 | for (int index = BeaconDataShort(&c2profile);;index = BeaconDataShort(&c2profile)) |
而while循环中的v8 = sub_173FB4(v10);
就是调用BeaconDataShort()函数
美化函数:
1 | short BeaconDataShort(datap* parser) |
接下来又配置了两次,直接跳过
执行case1
1 | case 1: |
将处理过的配置内容放入*(v5 + qword_1A7708 + 8),即表达式计算出一个新的内存地址
程序将在此循环来处理所有读取到的配置,不同的case对应不同类型内容的处理
当case全部完成后,进行返回
1 | return memset(byte_19C030, 0i64, 4096i64);//将存储区归零并返回 |
beacon_init结束后,又跳转回了原程序,fdwReason=4调用beacon的入口点
正式上线:Beacon_main()
如果
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函数
1 | __int64 sub_1ACA74() |
查看资料,优化之后为:
1 | int main() |
通过源码分析,直接进入while
循环EA60h,即等待上线时间
当运行到 v16 = v15;成功上线,即call near ptr unk_1ADF80结束之后,对比源码得知
1 | set_winit_http(ServerHost_buffer, ServerPort, lpszAgent); |
第一行代码设置了 HTTP 连接所需的参数,第二行代码执行了一个 HTTP GET 请求并获取服务器的响应数据
beacon接收指令
查看静态汇编
1 | v13 = sub_18000DF80(Buffer, &dword_180047720, v9, v12); |
这个是每次循环中的请求指令
当接收到符和的指令,就会进入if判断,如果命令符和,就会调用sub_180016654(v9, v14);函数,进行命令执行
跟进分析
对接收到的命令进行解析,然后进入sub_180015F98选择执行
跟进sub_180015F98函数之后发现一堆的if判断,这里就是对不同命令进行解析后通过if判断,选择对应功能函数进行执行
Dll入口点的非用户行为
加载beacon.dll调用了其入口点函数DllEntryPoint()
DLL的入口函数有三个参数:
hinstDLL
:- 这是一个
HINSTANCE
类型的参数,代表当前 DLL 的实例句柄。 - 当 DLL 被加载时,此参数包含 DLL 的加载基地址。
- 这是一个
fdwReason
:这是一个
DWORD
类型的参数,表示触发 DLL 入口点函数的原因。它可以取以下值之一:
-
DLL_PROCESS_ATTACH
:当 DLL 被加载到进程地址空间时调用。 -
DLL_THREAD_ATTACH
:当新线程被创建时调用。 -
DLL_PROCESS_DETACH
:当 DLL 被从进程地址空间卸载时调用。 -
DLL_THREAD_DETACH
:当线程终止时调用。
-
lpReserved
:- 这是一个
LPVOID
类型的参数,当fdwReason
为DLL_PROCESS_ATTACH
或DLL_PROCESS_DETACH
时,它包含额外的信息。 - 当
fdwReason
为DLL_THREAD_ATTACH
或DLL_THREAD_DETACH
时,此参数为NULL
。
- 这是一个
继续刚才的动态调试
这里调用DllEntryPoint函数前,入栈了一些参数
- RCX=003650000h,即加载DLL的基址
- EDX=1,即本次调用的原因:DLL_PROCESS_ATTACH
- R8=0,即本次为动态加载
BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
这里步入loc_36732B4,其实就是静态分析的
1 | if ( fdwReason == 1 ) |
_security_init_cookie
是 Microsoft Visual C++ 运行时库 (CRT) 提供的一个函数,用于初始化一个安全 cookie 值,以帮助检测栈溢出漏洞。
人话:安全检测函数
因为已经知道这个函数的具体作用就直接步过,下一步就是进行_DllMainCRTStartup()函数
的调用
\_DllMainCRTStartup
是 Microsoft Visual C++ 运行时库 (CRT) 提供的一个特殊的 DLL 入口点函数,用于处理 DLL 的生命周期事件。它是 Windows 动态链接库 (DLL) 的标准入口点函数之一。
_DllMainCRTStartup()函数没有调用_security_init_cookie()进行安全存根校验,这是 因为VCRuntime允许在进程附加时立刻调用_security_init_cookie()进行安全存根校验,即 DllEntryPoint()中的行为
1 | __int64 __fastcall _DllMainCRTStartup(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) |
功能分析
1 | (sub_19DFD4)(v2, v3, v23); |
进入分析sub_19DFD4
api-ms-win-http-time-l1-1-0
是 Windows 操作系统中的一个 API 集合,属于 Microsoft 的 API 微软 API 版本管理体系。它通常用于处理 HTTP 请求和时间相关的功能。
off_1BC548处是一个wininet_InternetSetOptionA函数所以sub_19DFD4函数是发送请求
wininet_InternetSetOptionA
是 Windows API 中的一个函数,属于 wininet
库,用于设置 Internet 连接的选项。这个函数允许开发者配置许多 Internet 相关的行为和设置,影响 HTTP、FTP 等协议的网络请求。
函数概述
功能:
InternetSetOptionA
函数用于设置 Internet 连接的特定选项,如超时、代理设置、缓存行为等。语法:
c复制
1
2
3
4
5
6BOOL InternetSetOptionA(
HINTERNET hInternet,
DWORD dwOption,
LPVOID lpBuffer,
DWORD dwBufferLength
);
参数
HINTERNET hInternet
:- 一个句柄,指向一个 Internet 连接。如果为
NULL
,则表示全局设置。
- 一个句柄,指向一个 Internet 连接。如果为
DWORD dwOption
:指定要设置的选项。可以是以下常量之一:
-
INTERNET_OPTION_PROXY
:设置代理服务器。 -
INTERNET_OPTION_TIMEOUT
:设置连接超时。 -
INTERNET_OPTION_USER_AGENT
:设置用户代理字符串等。
-
LPVOID lpBuffer
:- 指向包含选项值的缓冲区。其类型和内容取决于
dwOption
的值。
- 指向包含选项值的缓冲区。其类型和内容取决于
DWORD dwBufferLength
:-
lpBuffer
指向的缓冲区的大小(以字节为单位)。
-
返回值
- 如果函数成功,返回值为非零值。如果失败,返回值为零,可以通过调用
GetLastError
来获取错误代码。
跳出这个函数继续调试进入19DF80偏移处的函数
这一步成功上线
ls(列出当前目录中的文件和子目录
上线之后在客户端完成交互,输入ls,之后会预先循环一次
unk_1C0A80这里实现了心跳60s,可以在客服端修改时间,为了方便调试,sleep 5修改心跳时间
进入sub_1C6654函数,继续执行到功能选择的那个函数内部
sub_1C0594函数就说实现了ls功能的函数,步进查看详细逻辑
做了一个do while()循环将当前目录下的所有文件的size、 type、 Last Modified、 Name;结束之后退出,到达sub_1BE188函数,发送了Internet请求,然后客户端接收到数据
ps操作也是和ls操作结构类似使用kernel32_GetCurrentProcess等win api函数,获取process然后储存到特定内存等到发送完Internet请求之后在客户端回显靶机的process
screenshot
上线之后发送screenshot
操作,和上面ls调试类似进入功能选择函数,进行判断,它会先通过sub_150CE8函数,这个函数实现了一个启动傀儡进程并注入dll的完整操作
为了方便使用之前dump的静态文件进行分析
进入sub_1800189B0继续分析
通过动调分析180018AF0似乎进行了sysnative的调用,进入sub_180015298
通过静态分析,15A0A0是memset函数,
这个函数创建了一个进程,并挂起,启动傀儡进程的关键步骤
创建了一个size=30C00h的内存空间,在内存里面找到这个dll,dump出来后面分析;WriteProcessMemory
函数将代码注入到指定内存中
这里恢复挂起之后就,相当于将前面创建,并注入的线程进行运行,启动了傀儡线程?
这里是创建了一个file,将截屏保存,后面转换为网络数据,发送回到客户端
keylogger
命令与screenshot类似,差距在进程注入那里的dll
补充:
实现一个傀儡进程并将 DLL 加载到内存中
实现步骤
- 创建傀儡进程:
使用CreateProcess
创建一个新的进程,该进程将作为傀儡进程。 - 获取 DLL 的内存:
将 DLL 的字节流保存在内存中,而不是从磁盘加载。 - 注入 DLL:
使用VirtualAllocEx
在目标进程的内存中分配空间,并使用WriteProcessMemory
将 DLL 的字节流写入目标进程的内存。然后,通过CreateRemoteThread
或NtCreateThreadEx
在目标进程中执行LoadLibraryA
,加载该 DLL。
示例代码
1 |
|