CODASM是一款针对Payload的编码工具,该工具针对红蓝队研究人员设计,可以帮助我们对Payload执行编码操作并显著降低Payload的熵值。
该工具允许您将任意数据编码为伪 ASM 指令,并将其编译到二进制文件的 .text 部分。
开销为 80-120%(例如 380KB CS shellcode => 870KB CODASM 有效负载)。
Payload(尤其是 shellcode)具有相当高的熵,在编译后的二进制文件中的大多数位置看起来都不合适。CODASM 旨在将Payload隐藏在已经具有高熵的位置:.text包含二进制文件编译代码的部分。为此,CODASM 将生成良性 shellcode,并支持嵌入任意Payload。
CODASM 是一个 Python 脚本,可以生成以下内容:
1、良性的 shellcode,用于任意Payload;
2、一个 C 头文件,你可以在程序中使用它将 shellcode 嵌入到二进制文件中,并在运行时检索它;
对于编码Payload,CODASM 执行以下操作:
1、生成有效 x86_64 函数;
2、将Payload字节嵌入到指令操作数中(例如mov eax, <4 bytes of payload>);
3、对嵌入的Payload字节进行异或加密;
为了解码Payload,生成的 C 头文件执行以下操作:
1、解析单个指令,直到检索到所需数量的Payload字节:
2、检测单个指令,确定它们是否包含Payload字节;
3、如果指令包含Payload字节,则提取并解密它们;
Python 3
由于该工具基于Python 3开发,因此我们首先需要在本地设备上安装并配置好最新版本的Python 3环境。
接下来,广大研究人员可以直接使用下列命令将该项目源码克隆至本地:
usage: codasm.py [-h] -i INPUT [-oa OUT_ASM] [-ob OUT_BIN] [-oc OUT_C] [-op OUT_P] [--rng RNG] [-vbmin VAL_BYTES_MIN] [-vbmax VAL_BYTES_MAX] [-vbch VAL_BYTES_CHANCE] [-v]
CODASM encoding utility
options:
-h,
-i INPUT,
要编码为ASM/二进制指令的输入文件的路径
-oa OUT_ASM,
将生成的ASM指令写入的路径
-ob OUT_BIN,
将生成的二进制指令写入的路径
-oc OUT_C,
将生成的CODASM解码器写入的路径
-op OUT_P,
将嵌入式Payload写入的路径
-vbmin VAL_BYTES_MIN,
编码到单个方法中的最小字节数(默认值64)
-vbmax VAL_BYTES_MAX,
编码到单个方法中的最大字节数(默认256)
-vbch VAL_BYTES_CHANCE,
操作成为编码数据而不是虚拟数据的机会(0.1-0.9,默认值0.1)
-v,
Note: ASM output is meant to be used for manual reference, not for compiling!
1、准备 shellcode (例如 CS/BR shellcode) 并另存为shellcode.bin;
2、使用 CODASM 对 shellcode 进行编码:
3、复制codasmloader.h到您最喜欢的加载器或独立的最小加载器中(例如/demo/codasm.c);
4、确保decode在调用 shellcode 之前调用 CODASM:
#include
INTEXT uint8_t payload[5978] = {
0x50, 0x53, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53,
};
int main() {
uint64_t xor_key = 0xFFFDA6803A51E3FB;
uint8_t* input = (uint8_t*)payload;
uint8_t* output = (uint8_t*)malloc(sizeof(payload));
uint32_t output_length = 0xac4;
int32_t res = 0;
if ((res = decode(input, sizeof(payload), output, output_length, xor_key)) < 0)
return 1;
return 0;
}
5、编译你的加载器;
6、确保生成的二进制文件删除所有调试信息;
$ cat test.txt
my secret message
$ ./codasm.py -i test.txt -ob test.bin -oa test.asm -oc test.c
$ xxd test.bin
00000000: 5053 5152 4150 4151 4152 4153 4154 4155 PSQRAPAQARASATAU
00000010: 4156 4157 4883 ec28 751b 757f c705 5696 AVAWH..(u.u...V.
00000020: 44e9 6df6 61d6 84c0 488d 0d91 9e02 2640 D.m.a...H.....&@
00000030: 32ff b91b 8052 f975 8283 f99c 85c9 7513 2....R.u......u.
00000040: 4883 c428 415f 415e 415d 415c 415b 415a H..(A_A^A]A\A[AZ
00000050: 4159 4158 5a59 5b68 c3cc AYAXZY[X..
$ cat test.asm
push rax
...
push r15
sub RSP, 0x28
jnz 0x1B
jnz 0x7F
mov 0xE9449656,0xD661F66D
test al,al
lea 0x26029E91
xor dil,dil
mov ecx,0xF952801B
jnz 0x82
cmp ecx,0x9C
test ecx,ecx
jnz 0x13
add RSP, 0x28
pop r15
...
pop rax
retn
; Padding
$ sed -i "s/\/\* Generated.*//" test.c
$ sed -i "s/Sample usage://" test.c
$ sed -i "s/}\*\//}/" test.c
$ tail -n 30 test.c
INTEXT uint8_t payload[90] = {
0x50, 0x53, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, /* ... */
int main() {
uint64_t xor_key = 0xFBE38A21E5760676;
uint8_t* input = (uint8_t*)payload;
uint8_t* output = (uint8_t*)malloc(sizeof(payload));
uint32_t output_length = 0x11;
int32_t res = 0;
if ((res = decode(input, sizeof(payload), output, output_length, xor_key)) < 0)
return 1; // Some doo-doo happened, investigate value of res
// You successfully recovered the payload, do something fun with it here :)
return 0;
}
$ x86_64-w64-mingw32-gcc test.c -o test.exe
$ objdump -d test.exe
...
0000000140002da0 :
140002da0: 50 push %rax
...
140002db2: 41 57 push %r15
140002db4: 48 83 ec 28 sub $0x28,%rsp
140002db8: 75 1b jne 140002dd5
140002dba: 75 7f jne 140002e3b
140002dbc: c7 05 56 96 44 e9 6d movl $0xd661f66d,-0x16bb69aa(%rip) # 12944c41c <__size_of_stack_reserve__+0x12924c41c>
140002dc3: f6 61 d6
140002dc6: 84 c0 test %al,%al
140002dc8: 48 8d 0d 91 9e 02 26 lea 0x26029e91(%rip),%rcx # 16602cc60 <.debug_ranges+0x25fde970>
140002dcf: 40 32 ff xor %dil,%dil
140002dd2: b9 1b 80 52 f9 mov $0xf952801b,%ecx
140002dd7: 75 82 jne 140002d5b
140002dd9: 83 f9 9c cmp $0xffffff9c,%ecx
140002ddc: 85 c9 test %ecx,%ecx
140002dde: 75 13 jne 140002df3
140002de0: 48 83 c4 28 add $0x28,%rsp
140002de4: 41 5f pop %r15
...
140002df7: 58 pop %rax
140002df8: c3 ret
140002df9: cc int3
...
嵌入的payload在IDA中如下所示:
您可以使用以下 YARA 规则来潜在地检测嵌入 CODASM 生成的输出的 PECOFF 文件。您可以在代码块下方找到规则的简要说明:
import "pe"
import "math"
private rule IsPE
{
meta:
description = "Tests whether the file starts with the MZ header."
author = "Moritz Thomas"
date = "2024-07-24"
condition:
uint16(0) == 0x5A4D
}
private rule ExampleUsage
{
meta:
description = "Detects malloc and invoking the decode function, passing in references to the .data section"
author = "Moritz Thomas"
date = "2024-07-24"
strings:
$AllocDecode = {
8b 0d ?? ?? ?? ??
89 4c 24 ??
e8 ?? ?? ?? ??
8b 15 ?? ?? ?? ??
44 8b 4c 24 ??
48 8d 0d ?? ?? ?? ??
48 89 c3
48 8b 05 ?? ?? ?? ??
49 89 d8
48 89 44 24 ??
e8 ?? ?? ?? ??
89 c2
85 c0
79 13
48 8d 0d ?? ?? ?? ??
e8 ?? ?? ?? ??
b8 01 00 00 00
eb ??
83 f8 42
75 ??
b9 22 00 00 00
e8 ?? ?? ?? ??
}
$PseudoCall= {
83 f8 42
75 ??
b9 22 00 00 00
e8 ?? ?? ?? ??
}
condition:
IsPE and $AllocDecode and $PseudoCall
}
private rule Decode
{
meta:
description = "Detects parameter validation (null-checks), returning -2 and performing a pseudo call RBX(22h)"
author = "Moritz Thomas"
date = "2024-07-24"
strings:
$NullTest = {
48 85 c9
74 ??
4d 85 c0
74 ??
}
$ReturnMinusTwo = {
b8 fe ff ff ff
}
$PseudoCall = {
b9 22 00 00 00
ff d3
}
condition:
IsPE and $NullTest and $ReturnMinusTwo and $PseudoCall
}
private rule PEAnalysis
{
meta:
description = "Detects PE files with very large .text sections (>=90%) that have reasonable entropy (5.0 < e(.text) < 7.0)."
author = "Moritz Thomas"
date = "2024-07-24"
condition:
IsPE and
for any i in (0..pe.number_of_sections - 1) : (
pe.sections[i].name == ".text" and
pe.sections[i].raw_data_size > (filesize * 0.9) and
math.in_range(
math.entropy(pe.sections[i].raw_data_offset, pe.sections[i].raw_data_size),
5.0, 7.0
)
)
}
rule CODASMed
{
condition:
ExampleUsage or (Decode and PEAnalysis)
}
本项目的开发与发布遵循MIT开源许可协议。
CODASM:
https://github.com/NVISOsecurity/codasm