Cobalt Strike 初探

Cobalt Strike

研究背景

本次其实主要还是研究其流量特征,并且由于是DFI检测实际上我觉得还挺难看出来的。

但是还是研究一下shellcode的行为看看能不能找出其流量特征。

准备工作

写一个shellcode加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <windows.h>

int main()
{
char shellcode[] = "<shellcode>";
LPVOID lpAlloc = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(lpAlloc, shellcode, sizeof shellcode);
_asm{
int 3
int 3
}
((void(*)())lpAlloc)();
return 0;
}

两个int 3是我用来定位shellcode用的

首先cld将标志寄存器DF置为0(方向标志 Direction Flag 0为增加方向)

压入了wininet 这个字符串,应该是后面需要加载这个dll文件

ebp之前有一个pop ebp行为,ebp此时存放的是跳转之前的地址。

就是很奇怪这个0x4c772607是干嘛的

压入寄存器

fs:[edx+0x30]由于 xor edx ,edx,此时edx为0,故而是fs:[0x30],fs在三环下指向的是TEB。

TEB结构

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
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID :当前进程ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB 当前进程的PEB指针
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
+0x0c4 CurrentLocale : Uint4B
+0x0c8 FpSoftwareStatusRegister : Uint4B
+0x0cc SystemReserved1 : [54] Ptr32 Void
+0x1a4 ExceptionCode : Int4B
+0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
+0x1bc SpareBytes1 : [24] UChar
+0x1d4 GdiTebBatch : _GDI_TEB_BATCH
+0x6b4 RealClientId : _CLIENT_ID
+0x6bc GdiCachedProcessHandle : Ptr32 Void
+0x6c0 GdiClientPID : Uint4B
+0x6c4 GdiClientTID : Uint4B
+0x6c8 GdiThreadLocalInfo : Ptr32 Void
+0x6cc Win32ClientInfo : [62] Uint4B
+0x7c4 glDispatchTable : [233] Ptr32 Void
+0xb68 glReserved1 : [29] Uint4B
+0xbdc glReserved2 : Ptr32 Void
+0xbe0 glSectionInfo : Ptr32 Void
+0xbe4 glSection : Ptr32 Void
+0xbe8 glTable : Ptr32 Void
+0xbec glCurrentRC : Ptr32 Void
+0xbf0 glContext : Ptr32 Void
+0xbf4 LastStatusValue : Uint4B
+0xbf8 StaticUnicodeString : _UNICODE_STRING
+0xc00 StaticUnicodeBuffer : [261] Uint2B
+0xe0c DeallocationStack : Ptr32 Void
+0xe10 TlsSlots : [64] Ptr32 Void
+0xf10 TlsLinks : _LIST_ENTRY
+0xf18 Vdm : Ptr32 Void
+0xf1c ReservedForNtRpc : Ptr32 Void
+0xf20 DbgSsReserved : [2] Ptr32 Void
+0xf28 HardErrorsAreDisabled : Uint4B
+0xf2c Instrumentation : [16] Ptr32 Void
+0xf6c WinSockData : Ptr32 Void
+0xf70 GdiBatchCount : Uint4B
+0xf74 InDbgPrint : UChar
+0xf75 FreeStackOnTermination : UChar
+0xf76 HasFiberData : UChar
+0xf77 IdealProcessor : UChar
+0xf78 Spare3 : Uint4B
+0xf7c ReservedForPerf : Ptr32 Void
+0xf80 ReservedForOle : Ptr32 Void
+0xf84 WaitingOnLoaderLock : Uint4B
+0xf88 Wx86Thread : _Wx86ThreadState
+0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
+0xf98 ImpersonationLocale : Uint4B
+0xf9c IsImpersonating : Uint4B
+0xfa0 NlsCache : Ptr32 Void
+0xfa4 pShimData : Ptr32 Void
+0xfa8 HeapVirtualAffinity : Uint4B
+0xfac CurrentTransactionHandle : Ptr32 Void
+0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME
+0xfb4 SafeThunkCall : UChar
+0xfb5 BooleanSpare : [3] UChar

fs:[edx+0x30]指向了PEB。PEB+0xc指向的是Ldr 进程加载模块链表

PEB

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
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar 调试标志
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void 映像基址
+0x00c Ldr : Ptr32 _PEB_LDR_DATA 进程加载模块链表
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : Ptr32 Void
+0x024 FastPebUnlockRoutine : Ptr32 Void
+0x028 EnvironmentUpdateCount : Uint4B
+0x02c KernelCallbackTable : Ptr32 Void
+0x030 SystemReserved : [1] Uint4B
+0x034 AtlThunkSListPtr32 : Uint4B
+0x038 FreeList : Ptr32 _PEB_FREE_BLOCK
+0x03c TlsExpansionCounter : Uint4B
+0x040 TlsBitmap : Ptr32 Void
+0x044 TlsBitmapBits : [2] Uint4B
+0x04c ReadOnlySharedMemoryBase : Ptr32 Void
+0x050 ReadOnlySharedMemoryHeap : Ptr32 Void
+0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void
+0x058 AnsiCodePageData : Ptr32 Void
+0x05c OemCodePageData : Ptr32 Void
+0x060 UnicodeCaseTableData : Ptr32 Void
+0x064 NumberOfProcessors : Uint4B
+0x068 NtGlobalFlag : Uint4B
+0x070 CriticalSectionTimeout : _LARGE_INTEGER
+0x078 HeapSegmentReserve : Uint4B
+0x07c HeapSegmentCommit : Uint4B
+0x080 HeapDeCommitTotalFreeThreshold : Uint4B
+0x084 HeapDeCommitFreeBlockThreshold : Uint4B
+0x088 NumberOfHeaps : Uint4B
+0x08c MaximumNumberOfHeaps : Uint4B
+0x090 ProcessHeaps : Ptr32 Ptr32 Void
+0x094 GdiSharedHandleTable : Ptr32 Void
+0x098 ProcessStarterHelper : Ptr32 Void
+0x09c GdiDCAttributeList : Uint4B
+0x0a0 LoaderLock : Ptr32 Void
+0x0a4 OSMajorVersion : Uint4B
+0x0a8 OSMinorVersion : Uint4B
+0x0ac OSBuildNumber : Uint2B
+0x0ae OSCSDVersion : Uint2B
+0x0b0 OSPlatformId : Uint4B
+0x0b4 ImageSubsystem : Uint4B
+0x0b8 ImageSubsystemMajorVersion : Uint4B
+0x0bc ImageSubsystemMinorVersion : Uint4B
+0x0c0 ImageProcessAffinityMask : Uint4B
+0x0c4 GdiHandleBuffer : [34] Uint4B
+0x14c PostProcessInitRoutine : Ptr32 void
+0x150 TlsExpansionBitmap : Ptr32 Void
+0x154 TlsExpansionBitmapBits : [32] Uint4B
+0x1d4 SessionId : Uint4B
+0x1d8 AppCompatFlags : _ULARGE_INTEGER
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
+0x1e8 pShimData : Ptr32 Void
+0x1ec AppCompatInfo : Ptr32 Void
+0x1f0 CSDVersion : _UNICODE_STRING
+0x1f8 ActivationContextData : Ptr32 Void
+0x1fc ProcessAssemblyStorageMap : Ptr32 Void
+0x200 SystemDefaultActivationContextData : Ptr32 Void
+0x204 SystemAssemblyStorageMap : Ptr32 Void
+0x208 MinimumStackCommit : Uint4B

mov edx,[edx+0x14] 是内存中的模块列表

LDR

1
2
3
4
5
6
7
8
9
typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

其0x14指向的是一个有前后指针的链表地址。

1
2
3
4
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

如上

随后从Flink找到0x28偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nt!_LDR_DATA_TABLE_ENTRY
…………
+0x010 DllBase : Ptr32
+0x014 EntryPoint : Ptr32
+0x018 SizeOfImage : Uint4B
FullDllName : _UNICODE_STRING
+0x01c Length : Uint2B
+0x01e MaximumLength : Uint2B
+0x020 字符串地址Buffer : Ptr32
BaseDllName : _UNICODE_STRING
+0x024 Length : Uint2B
+0x026 MaximumLength : Uint2B
+0x028 字符串地址Buffer : Ptr32
…………

basedllname。unicode型的

这是我的木马名字。

lods byte ptr [esi]把esi指向的内存单元的字节存进了al。

al此时为63,与61不等。

发现就是去循环找到所需要的dll,猜测所需要ws2_32,wininet,ntdll和kernel32。

以上这段汇编应该是找函数地址了。

这个函数应该是要用到的,LoadLibraryEXA。

这里jmp去LoadLibraryA了

然后调用了这个函数

没找到用来干嘛的。。。

InternetOpenUrlA

这里应该是对链接的一些配置。

调用InternetConnectW函数

/LkKZ应该是特征之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String MSFURI() {
String[] arrayOfString = toArray("a, b, c, d, e, f, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9");
while (true) {
String str = "/" + pick(arrayOfString) + pick(arrayOfString) + pick(arrayOfString) + pick(arrayOfString);
if (checksum8(str) == 92L)
return str;
}
}

public static String MSFURI_X64() {
String[] arrayOfString = toArray("a, b, c, d, e, f, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9");
while (true) {
String str = "/" + pick(arrayOfString) + pick(arrayOfString) + pick(arrayOfString) + pick(arrayOfString);
if (checksum8(str) == 93L)
return str;
}
}

感觉这个特征是随机的,但是总之字符集有了 ,随机的四位字符。

这个应该也是特征。

然后调用InternetReadFileExA。

c2 profile
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
# default sleep time is 60s
set sleeptime "10000";
# jitter factor 0-99% [randomize callback times]
set jitter "0";
# maximum number of bytes to send in a DNS A record request
set maxdns "255";
# indicate that this is the default Beacon profile
set sample_name "001";

stage {
set stomppe "true";
set obfuscate "true";
set cleanup "true";
transform-x86 {
strrep "ReflectiveLoader" "misakaloader";
}
transform-x64 {
strrep "ReflectiveLoader" "misakaloader";
}
}
# define indicators for an HTTP GET
http-get {
# Beacon will randomly choose from this pool of URIs
set uri "/ca /dpixel /__utm.gif /pixel.gif /g.pixel /dot.gif /updates.rss /fwlink /cm /cx /pixel /match /visit.js /load /push /ptj /j.ad /ga.js /en_US/all.js /activity /IE9CompatViewList.xml";
client {
# base64 encode session metadata and store it in the Cookie header.
metadata {
base64;
header "Cookie";
}
}

server {
# server should send output with no changes
header "Content-Type" "application/octet-stream";

output {
print;
}
}
}

# define indicators for an HTTP POST
http-post {
# Same as above, Beacon will randomly choose from this pool of URIs [if multiple URIs are provided]
set uri "/submit.php";

client {
header "Content-Type" "application/octet-stream";

# transmit our session identifier as /submit.php?id=[identifier]
id {
parameter "id";
}

# post our output with no real changes
output {
print;
}
}

# The server's response to our HTTP POST
server {
header "Content-Type" "text/html";

# this will just print an empty string, meh...
output {
print;
}
}
}

后续分析

反射DLL注入

这块我也不太清晰,由于时间原因我大概写了一写自己的理解,后续仔细分析后在改进。

反射注入的流程
  1. 注入器将要注入的DLL打开,读进自己的内存空间。(注意不是映射。)
  2. 注入器提权,打开目标进程,申请内存空间,将DLL文件写入目标进程。
  3. 注入器手动搜索DLL的导出表,寻找DLL导出的自主实现的ReflectiveLoader函数。
  4. 注入器创建远程线程,线程启动点为ReflectiveLoader函数,DLL会加载自身,并执行DllMain。
ReflectiveLoader的工作流程

ReflectiveLoader首先利用一个重定位技巧找到自身所在的大致位置:

1
2
ULONG_PTR caller( VOID ) { return(ULONG_PTR)_ReturnAddress(); }

其中函数_ReturnAddress()返回的是当前调用函数的返回地址,也就是caller()的下一条指令的地址。这个地址位于ReflectiveLoader的内部,而ReflectiveLoader位于被注入的DLL文件内部,因此这个地址离DLL文件的头部不远了。

借助上文找到的地址,我们逐字节的向上遍历,当查找到符合PE格式的文件头之后,就可以认为找到了DLL文件在内存中的地址了。

获取需要的API函数地址

有四个API函数是我们需要用的

LoadLibraryA() :加载DLL所需的导入模块

GetProcAddress():修正导入表的函数地址

VirtualAlloc():申请内存空间

NtFlushInstructionCache()函数:刷新指令,重定向相关。

根据fs寄存器的0x30位置获得PEB,PEB中的三根链表就可以遍历所有已加载的模块,就可以找到Kernel32和ntdll从而获得函数地址

申请PE文件的空间

因为DLL现在是以文件的方式储存在内存空间。所以我们需要根据PE文件可选头中的SizeOfImage获得加载后的大小,申请SizeOfImage大小的内存空间。

拷贝PE文件头和各个节

分配了用于装载的空间后,ReflectiveLoader将DLL文件的头部,拷贝到新的空间的首部。再根据PE文件的节表将各个节复制到相应的位置中。 (这里应该是按照PE文件映射到虚拟内存的映射规则去排的位置 需要进行一下验证)

修正导入表

目前为止我们对导入表没有任何操作,也就是说其还处于原始的双桥结构的状态,IAT中还没有储存相应的函数地址。我们要根据导入表内容,通过0x01获得的LoadLibrary加载所需模块,按函数名导入的通过GetProcAddress获得地址填入IAT,按序号导入的暴力搜索其所在模块获得地址。

修正重定向数据

我们的ReflectiveLoader中肯定没有需要重定向的数据,但是DLL中的其他API中可能存在需要重定向的数据。我们根据DLL自身的重定向表修改数据。

重定向完成后记得调用NtFlushInstructionCache,确保指令的正确执行。

调用DllMain函数

DllMain函数的RVA在可选头中的AddressOfEntryPoint中储存。

至此DLL就完成了映射,运行了起来。

请我喝杯咖啡吧~

支付宝
微信