- 一、隐藏技术(各类注入都可)
- 4、进程伪装
- 5、进程隐藏
- 二、通信技术(ws2_32.lib)
- 三、恶意代码常用寄存格式
- 四、绕过杀软&签名验证
- 五、常用工具
- 六、注入技术(hook 11种)
- 七、反动态调试技术(明/暗)
- 八、反虚拟机技术
- 九、常用代码混淆技术(静态)
- 十、持久化技术
- 十一、提权技术
- 十一、反静态调试技术(反编译)
- 十三、非常规代码加载
- 十四、虚拟机保护
- 十五、Linux下的rootkit驻留技术
- 十六、Linux下反调试技术
- include
- include
- 三、getenv
- 四、ptrace
- 十七、加密技术
- 十八、常见恶意行为
- define PROJECT_NAME L”KeyboardDll”
- 十九、恶意行为前的校验
- 二十、分析技巧
一、隐藏技术(各类注入都可)
1、进程隐藏
- API拦截属于进程伪隐藏方式(API Hook) 通过利用Hook技术监控并截获系统中某些程序对进程显示的API 函数调用,然后修改函数返回的进程信息,将自己从结果中删除,导致任务管理器等工具无法显示该木马进程。
- 远程线程注入属于进程真隐藏方式 主要是利用CreateRemoteThread函数在某一个目标进程中创建远程线程,共享目标进程的地址空间,并获得目标进程的相关权限,从而修改目标进程内部数据和启动DLL 木马。远程线程注入代码通过直接拷贝程序代码到某进程的内存空间来达到注入的目的。因为程序代码存在于内存中, 不仅进程列表中无法检测,即使遍历进程加载的内存模块也无法找到被隐藏程序的踪迹
- 基于DLL 的进程隐藏技术:远程注入Dll技术
DLL文件没有程序逻辑,不能独立运行,由进程加载并调用,所以在进程列表中不会出现DLL文件。如 果是一个以DLL形式存在的程序,通过某个已有进程进行加载, 即可实现程序的进程隐藏。
- 查看伪装进程的父进程判断程序是否可靠
隐藏注册表的创建
- 原理:win32 API和Native API对字符串处理的差异
注册表键值名称经过特殊构造: 以”\0”作为开头,后面加上任意字符(不能为数字)对于Windows系统,”\0”(即0x0000)会被识别为字符串的结束符,所以在对该字符串读取的过程中,遇到开头的”\0”,会被解析成结束符,提前截断,导致读取错误而使用Native API设定注册表,需要使用结构体
OBJECT_ATTRIBUTES
作为参数, 指定读取的字符串长度只要长度设定正常,就能够读取正确的字符串,避免这个bug。所以,我们可以通过Native API来创建这个特殊的注册表名更为重要的是,像regedit.exe和其他对注册表的操作,通常会调用Win32 API,这就导致该注册表无法被读取,也就实现了所谓的”隐藏”。综上,创建方法为: 通过Native API创建一个以”\0”开头的键值
- 原理:win32 API和Native API对字符串处理的差异
在任务管理器和Process Explorer之类的工具中隐藏指定进程
创建服务
流程
- CreateProcess/SuspendThread
- VirtualAlloc
- WriteProcessMemory
- GetThreadContext
- SetThreadContext
- ResumeThread
CONTEXT Context; //定义一个CONTEXT结构
SuspendThread(hThread); //挂起线程
ThreadContext.ContextFlags = CONTEXT_ALL; //修改对Context操作的权限
GetThreadContext(hThread, &Context); //获得Context信息
BufferData = VirtualAllocEx() //在目标线程申请内存 来执行我们的shellcode
//编写shellcode
WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL) // 写入shellcode
ThreadContext.Eip = (UINT32)BufferData //修改EIP指向
Context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(hThread, &Context); //重新设置线程上下文
ResumeThread(hThread); //恢复线程,现在线程开始从BufferData这个地方开始执行指令
3、dll劫持
定义
- 从合法dll接管dll,持久化和权限提升
- 利用dll加载时的搜索顺序,将fake dll放到优先级高的路径中
应用程序所在目录——>当前目录(GetCurrentDirectory)——>系统目录(GetSystemDirectory,System32)——>Windows目录(GetWindowsDirectory,Windows)——>环境变量PATH中所有目录。
新版win10 dll劫持思路
dll替换:恶意的dll替换掉合法的dll,要确保原dll所有功能均保持不变
- dll搜索顺序劫持
- 虚拟dll劫持
- dll重定向:修改环境变量等
4、进程伪装
- 流程
- NtQueryInformationProcess:获取指定进程PEB
- ReadProcessMemory
- WriteProcessMemory
5、进程隐藏
- hook技术
- WINDOW的消息处理机制为了能在应用程序中监控系统的各种事件消息,提供了挂接各种反调函数(HOOK)的功能。这种挂钩函数(HOOK)类似扩充中断驱动程序,系统产生的各种消息首先被送到各种挂接函数,挂接函数根据各自的功能对消息进行监视、修改和控制等,然后交还控制权或将消息传递给下一个挂接函数以致最终达到窗口函数。
HOOK ZwQuerySystemInformation(脱钩)
服务端(建立连接和传输数据的套接字一般不同)
- 创建套接字(socket)
- 将套接字绑定到一个本地地址和端口上(bind)
- 将套接字设为监听模式,准备接收客户端请求(listen)
- 接收客户端连接请求,返回一个新的对应与此次连接的套接字(accept)
- 和客户端通信(send/recv)
- 返回,等待另一客户请求
- 关闭套接字
- 客户端
- 创建套接字(socket)
- 向服务器发出连接请求(connect)
- 和服务端通信(send/recv)
- 关闭套接字
UDP连接
- 服务端
- 创建套接字(socket)
- 将套接字绑定到一个本地地址和端口上(bind)
- 等待接收数据(recvfrom)
- 关闭套接字
客户端
建立会话:InternetOpen
- 建立连接:InternetConnect
- 打开ftp文件:hFTPFile = FtpOpenFile
- 打开文件:hFile = CreateFile
- 获取文件大小,new空间:UploadDateFile=GetFileSize
- 读取文件数据到内存:ReadFile -> pUploadData
上传数据:InternetWriteFile(hFTPFile, pUploadData, UploadDataSize)
download
建立会话:InternetOpen
- 建立连接:InternetConnect
- 打开ftp文件:FtpOpenFile
- 获取ftp文件大小:FtpGetFileSize
- 申请内存接收数据:new,RtlZeroMemory
- 循环接收数据:InternetReadFile, RtlCopyMemory
- 写入本地文件:CreateFile、WriteFile、CloseHandle
3、HTTP连接
upload
- 建立会话:InternetOpen
- 建立连接:InternetConnect
- 创建请求句柄并且把参数存储在句柄中:HttpOpenRequest
- 请求参数送到HTTP服务器:HttpSendRequest
HttpQueryInfo:查询一个http请求的信息(状态字)
download
-
4、HTTPS连接
ssl层
- InternetOpen
- InternetConnect
- lpHostName,//指定要连接的主机
- INTERNETDEFAULT_HTTPS_PORT,//指定https端口,默认端口为443_
HttpOpenRequest
端口复用技术 它让木马服务端程序共享其他网络程序已打开的端口和客户端进行连接,从而防止重新开启端口降低隐蔽性。关键之处在于,木马程序应增设一个数据包转交判断模块,该模块控制主机对数据报的转交选择。
- 利用ICMP 和HTTP 协议 通常网络防火墙和入侵检测系统等安全设备只检查ICMP 报文的首部,对数据部分不做处理。因此,可以将木马程序的通信数据隐藏在ICMP 报文格式的选项数据字段进行传送,如把服务端程序向客户端程序传输的数据伪装成回显请求报文,而把客户端程序向服务端程序传输的数据伪装成回显应答报文。这样,就可以通过PING\PINGRESPONSE的方式在木马服务端程序和客户端程序之间建立起一个高效的秘密会话信道。利用ICMP 协议传输数据还有一个很大的优点,即ICMP 属于IP 层协议,它在传输数据时并不使用任何端口,从而具有更好的隐蔽性。
- 注册服务
三、恶意代码常用寄存格式
- .docx宏病毒
- .rtf格式:.docx的扩展,方便混淆
- au3或者a3x文件:使用Autolt解析
四、绕过杀软&签名验证
1.恶意程序在解密时,应当也进行代码混淆(加花、加壳)
2.当恶意文件在内存中运行解密代码时,我们必须要保证在不重定位绝对地址的情况下进行
3.恶意软件是否在沙箱环境中运行,如果是,则立马停止恶意文件的解密
4.应当只对 PE 文件中的 shellcode 或 只有二进制文件的.text部分进行加密,而不是对整个 PE 文件进行,以便把信息熵和降到最低
5、改变特征码
- 驼峰式大小写
- 换体技术
五、常用工具
- LordPE:将程序加载到内存中后,右键能够dump full(程序在内存中的映射情况)
- Resource Hacker
- eXeScope
- die 、 PEiD、exeinfo:查壳
- file:查看文件属性
- DiE(Detect it easy):查壳,查文件类型
- 010Editor:十六进制编辑器,文件解析器(需要导入.bt文件),计算文件校验值
- binwalk:固件分析工具
- ProcessMonitor(类似火绒剑):系统进程监控软件,对系统中的文件系统、网络行为、进程线程行为(父子关系树)以及注册表操作进行监视
- 进程是线程的容器,线程的特有资源存在TLS
- wireshark:选择捕获哪个网络适配器中的数据包
- 右键目标报文,选择追踪流,再选择不同的流,可以看到TCP流,UDP流,SSL流和HTTP流。这样就可以看到服务端和客户端之间传输的数据内容。
- 常用的一些过滤方式如下所示
- 过滤源IP、目的IP:如查找目的地址为168.1.100的包—ip.dst==192.168.1.100.查找源地址为192.168.1.100的包—ip.src==192.168.1.100。
- 过滤某个IP:addr==192.168.1.100。显示源IP或目标IP为192.168.1.100的数据包。
- 过滤端口:比如过滤80端口—tcp.port==80。这条规则是把源端口和目的端口为80的数据包都过滤出来。使用tcp.dstport==80只过滤目的端口为80的包,tcp.srcport==80只过滤源端口为80的包。
- 协议过滤:直接在过滤框中输入要过滤的协议即可,如HTTP,TCP,UDP等。
- HTTP模式过滤:如过滤get包—http.request.method==“GET”。过滤post包,http.request.method==”POST”。
- APKTool:拆解Apk文件,反编译其中的资源文件,将它们反编译为可阅读的AndroidManifest.xml文件和res文件。
- Dex2jar:将dex格式的文件,转换成jar文件。dex文件时Android虚拟机上面可以执行的文件,jar文件大家都是知道,其实就是java的class文件。
- Jd-gui:查看jar的java源代码
- IDA
- 地址跳转:G
- 导航历史记录:preview-Esc Next-Crtl+Enter
- 文本搜索:ALT+T
- 二进制搜索:ALT+B
- 函数重命名:N
- 创建数组(Edit->Array)、创建结构体(Insert)
- OD
- 软件断点
- 条件断点
- 条件记录断点
- 内存断点
- 硬件断点
- WinDbg
- Dnspy:针对 .NET 程序的逆向工程工具
- PCHunter
1.查看系统中运行的所有进程(包括隐藏进程)显示进程下挂模块、包含的线程、进程占用的快捷键;对进 程、线程进行终止、挂起、删除。
2.查看和管理驱动模块,对模块进行卸载,删除等操作。
3.内核查看,定位进程、模块的文件路径、检查文件数字签名。
4.内核、应用层钩子检测。
5.网络管理,查看进程占用网络端口的连接状态和连接地址;清理IE浏览器插件、IE右键菜单;修复LSP;自定义或清空hosts。
6.注册表编辑。
7.文件管理。
8.启动信息管理。
9.系统相关功能修复。
- Pyinstaller
- 将py文件封装为exe
- peepdf
- PDF综合性审计工具peepdf。它具备PDF文档审计所需要的各种功能。它可以解析文档,显示文档结构,并列出概要信息、元数据、更新日志、错误信息等。渗透测试人员可以直接查看文档中的对象、流等内容,并对校验文档进行校验。peepdf还对PDF嵌入的Javascript代码提供专门的分析功能,可以提取Javascript脚本进行解码、反转义、执行等各种操作。同时,peepdf也支持创建和编辑功能,渗透测试人员可以根据需要构建特殊的PDF文档,用于渗透测试
- Dependency Walker
- 包含在微软Visual Studio的一些版本与其他微软开发包中,支持列出可执行文件的动态链接函数
- CFF Explorer
- 查看详细PE信息
- 网络分析工具
- fakenet:构建安全虚拟网络环境
- systracer.exe(类似火绒剑)
- 监控程序的行为
- tasklist
1、使用注册表注入dll
- AppInit_DLLs用来全局注入dll模块,凡是导入了user32.dll的程序都会 主动加载这个键值下的模块
- 在注册表路径HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersioin\Windows\下,将自己写的DLL文件的路径值写入AppInit_Dlls键中,再创建一个名为LoadAppInit_Dlls,类型为DWORD的注册表项,并将其值设为1。当User32.dll被映射到一个新的进程时,会受到DLL_PROCESS_ATTACH通知,当User32.dll对它进行处理的时候,会获取上述注册表键的值,并调用LoadLibrary来载入这个字符串中指定的每个DLL。
2、windows挂钩注入dll(全局钩子注入)
常用API
设置钩子API,通常设置SetWindowsHook的类型为WH_GETMESSAGE
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook, 设置钩子的类型.意思就是我要设置的钩子是什么钩子. 可以是监视窗口过程.可以是监视消息队列.
_In_ HOOKPROC lpfn, 根据钩子类型.设置不同的回调函数.
_In_ HINSTANCE hMod, 钩子设置的Dll实例句柄,就是DLL的句柄(回调函数所在dll)
_In_ DWORD dwThreadId 设置钩子的线程ID. 如果为0 则设置为全局钩子.
);
HHOOK 返回值. 是一个钩子过程句柄.
获取模块句柄API
HMODULE WINAPI GetModuleHandle(
_In_opt_ LPCTSTR lpModuleName 获取的实例句柄的文件名.可以是Dll可以使exe 如果为NULL 这是当前dll/exe的实例句柄
); 返回值 返回实例句柄.
取消设置钩子API
BOOL WINAPI UnhookWindowsHookEx(
_In_ HHOOK hhk 参数一是 SetWindowHookEx的返回值.也就是钩子过程句柄.
);
继续调用钩子链中的钩子过程.
LRESULT WINAPI CallNextHookEx(
_In_opt_ HHOOK hhk, 保存的钩子过程,也就是SetWindowsHookEx返回值.
_In_ int nCode, 根据SetWindowsHookEx设置的钩子回调而产生的不同的nCode代码. 什么意思? 意思就是如果设置的钩子类型是鼠标消息.那么那个nCode就是鼠标消息.如果是键盘这是键盘
_In_ WPARAM wParam, 同2参数一样.附加参数. 根据钩子回调类型.附加参数有不同的意义.比如如果是鼠标.那么这个有可能代表的就是鼠标的x位置.键盘就可能是键代码
_In_ LPARAM lParam 同3参数一样.附加参数.
);
钩子回调
//根据SetWindowsHookEx参数1来设定的
注入步骤
- 调用SetWindowsHookEx设置钩子.。
- 在设置过程中,需要一个回调。所以我们填入一个回调。
- 回调函数中调用CallNextHookEx函数。 如果不调用,那么相当于我们设置了不反悔,程序可能出现问题。当然是按需返回。
- 取消HOOK设置。
- 举例:进程A使用SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc,hInstDll,0)函数安装挂钩
- 进程B中的一个线程准备向一个窗口派送一条消息
- 系统检查该线程是否安装了WH_GETMESSAGE挂钩
- 系统检查GetMsgProc所在的DLL是否已经被映射到进程B的地址空间中,如果DLL尚未被映射,那么系统会强制将该DLL映射到进程B的地址空间中,并将进程B中该DLL的锁计数器递增
- 由于DLL的hInstDll是在进程B中映射的,因此系统会对他进行检查,看他在进程A中的位置是否相同,如果相同,那么在两个进程空间中,GetMsgProc函数位于相同的位置,系统就可以直接在进程A的地址空间中调用GetMsgProc。如果不相同,那么系统必须确定GetMsgProc函数在进程B的地址空间中的虚拟内存地址。使用公式GetMsgProc B=hInstDll B+(GetMsgProc A-hInstDll A)获得
- 系统在进程B中递增该DLL的锁计数器
- 系统在进程B的地址空间中调用GetMsgProc函数
- 当GetMsgProc返回的时候,系统递减该DLL在进程B中的锁计数器
3、使用远程线程注入dll
- 先在目标进程的内存空间里开辟一块新地方,往新地方里面写入DLL的路径,再创建远程线程找到LoadLibrary() 函数,并在刚才开辟的新地方中读取DLL路径,进而加载我们自己写的DLL。
- 步骤
- 获得目标进程句柄(OpenProcess)【非必须】
- 分配一块内存(VirtualAllocEx)【如果是在目标进程内存空间中后面就不用使用LoadLibrary了,直接填该新分配的地址即可)
- 把注入的dll名称写入分配的内存中(WriteProcessMemory)
- 在远程进程中创建一个线程(CreateRemoteThread),让该线程调用LoadLibrary(用GetProcAddress找到LoadLibrary)加载上面内存地址处的dll
- 线程退出后,释放第一步分配的内存(VirtualFreeEx)
- 使用函数CreateRemoteThread在远程进程中创建一个线程,让该线程调用FreeLibrary(GetProcAddress找到)函数并在参数中传入远程DLL的
4、动态库劫持
- dll文件替换
- WIndows上的Dll加载有一个默认的规则,就是先在主程序目录下查找B.dll,如果没有就在系统路径下找,如果还没有,就去环境变量路径里找,就因为这个我们可以轻松的在相应的位置给做劫持,然后问题就是如果实现劫持,就要知道B.Dll里面的所有函数名字以及函数参数,这个地方比较不好搞,此地不考虑。
写文件(写新dll,该dll里要包含旧dll的所有导出文件)
CreateFileA
CreateFileMappingA
MapViewOfFile
CopyFileA
exe文件内容替换:把exe中旧dll的名称替换为新dll的名称
FindFirstFile
FindNextFile
strcmp+qmemcpy
5、APC注入
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下(挂起->插入队列->执行):
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
- 步骤
- 用CreateProcess以CREATE_SUSPENDED的方式启动目标进程,到达中断状态(停在OEP)
- 分配一块内存(VirtualAllocEx)
- 把注入的dll名称写入分配的内存中(WriteProcessMemory)
- 利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。
6、使用CreateProcess注入代码
- 用CreateProcess以CREATE_SUSPENDED的方式启动目标进程
- 进程的主线程没活动,这个时候的状态可以认为是用Olldbg加载进程后停在OEP处的状态,EIP指向入口地址,代码段和数据段映射完毕,IAT也计算并加载完毕。
- 找到目标进程的入口(pNTHeader->OptionalHeader.AddressOfEntryPoint + pNTHeader->OptionalHeader.ImageBase)
- 将目标进程入口的代码保存起来
- 在目标进程的入口写LoadLibrary(MyDll)实现Dll注入(搭配WriteProcMemory)
- 用ResumeThread运行目标进程
- 恢复运行
- 目标进程就运行了LoadLibrary(MyDll),实现DLL的注入
- 目标进程运行完LoadLibrary(MyDll)后,将原来的代码写回目标进程的入口
- 目标进程jmp到原来的入口,继续运行程序
7、Inline Hook(类似inline patch)
- 相关API
- CreateToolhelp32Snapshot() 和 EnumProcess():这两个API均在其内部调用了ntdll.ZwQuerySystemInformation
- ZwQuerySystemInformation():该API可获得运行中的所有进程信息(结构体),形成一个链表,操作该链表从链表中删除相关进程即可达到隐藏的目的。
- 在内存中对要拦截的函数进行定位,从而得到它的内存地址
- 把这个函数起始的几个字节保存到我们自己的内存中
- 使用jmp指令来覆盖这个函数起始的几个字节(WriteProcessMemory),这条jmp指令用来跳转到我们的替代函数的内存地址。当然,我们的替代函数的函数签名必须与要拦截的函数的函数签名完全相同:所有的参数必须相同,返回值必须相同,调用约定也必须相同
- 现在,当线程调用被拦截函数的时候,跳转指令实际上会跳转到我们的替代函数。这时,我们就可以执行自己想要执行的任何代码
- 为了撤销对函数的拦截,需要把第二步保存下来的字节放回被拦截函数起始的几个字节中
- 我们调用被拦截函数(现在已经不再对它进行拦截了),让函数执行它正常处理
- 当原来的函数返回时,我们再次执行第二步和第三步,这样替代函数将来还会被调用到。
VOID HookMessageBoxA() //通过修改API MessageBoxA 函数入口来进行Inline HOOK
{
JMPCODE jcode;
jcode.jmp = 0xe9;//jmp的硬编码
jcode.addr = (DWORD)myMessageBox - (DWORD)RealMessageBox - 5;
::WriteProcessMemory(GetCurrentProcess(),MessageBoxA,&jcode,sizeof(JMPCODE),NULL);//修改API MessageBoxA前5个字节实现跳转到自定义的钩子函数myMessageBox。
//GetCurrentProcess() 获取当前进程句柄(内存基址)。
}
8、IDT Hook
- IDT:Interrupt Descriptor Table 中断描述表,IDT是一个有256个入口的线形表,每个IDT的入口是8字节的描述符,所以整个IDT表的大小为256*8=2048 bytes,每个中断向量关联了一个中断处理过程。所谓的中断向量就是把每个中断或者异常用一个0-255的数字识别
- 操作系统使用IDTR寄存器来记录idt位置和大小。IDTR寄存器是48位寄存器,用于保存idt信息。其中低16位代表IDT的大小,大小为7FFH,高32位代表IDT的基地址。我们可以利用指令sidt读出IDTR寄存器中的信息,从而找到IDT在内存中的位置。
- 过程:找到目标入口后,将我们的函数与其原来的函数进行替换即可。
9、SSDT Hook(!)
- SSDT:System Service Dispath Table,用户模式( User mode)的所有调用,如 Kernel32,User32.dll,Advapi32.dll等提供的API, 最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式, 通过服务ID,在SSDT中分派系统函数。SSDT就是一个表,这个表中有内核调用的函数地址。
- 当用户层调用FindNextFile函数时, 最终会调用内核层的NtQueryDirectoryFile函数, 而这个函数的地址就在SSDT表中, 如果我们事先把这个地址改成我们特定函数的地址,那么就实现了SSDT Hook。
- 要对SSDT进行Hook,首先需要改变SSDT的内存保护,因为系统对SSDT都是只读的,不能写。如果视图去写,就会造成蓝屏。一般可以修改内存的方法有通过cr0寄存器和Memory Descriptor List(MDL)。
- 通过cr0寄存器:Windows对内存的分配,是采用的分页管理,其中有个cr0寄存器,其中第一位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写执行;如果为0,则只可以读执行。所以我们要将这一位设为1.
- 通过MDL:将原来的SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写就行了。
-
10、IAT Hook
IAT:Import Address Table
IAT Hook的原理就是把目标函数的地址改成我们自己写的函数的地址
11、EAT Hook
EAT:Export Address Table
12、Hook底层API(NativeAPI)
ntdll.dll中的Native API函数是成对出现的,分别以”Nt” “Zw”开头,在ntdll.dll中,它们的本质是一样的,只是名字不同而已。当kernel32.dll中的API通过ntdll.dll执行时,会完成参数检查工作,再调用一个中断(int 2E 或者 SysEnter指令),从Ring3进入Ring0。在内核ntoskrnl.exe中有一个SSDT,里面存放了与ntdll.dll中对应的SSDT系统服务处理函数,即内核态的Nt*系列函数,它们与ntdll.dll中的函数一一对应。
NtQuerySystemInformation\ZwQuerySystemInformation
- 因为我们遍历进程通常是调用系统 WIN32 API 函数 EnumProcess 、CreateToolhelp32Snapshot 等函数来实现,这些 WIN32 API 它们内部最终是通过调用 ZwQuerySystemInformation 这个函数实现的获取进程列表信息。所以,我们只要 HOOK ZwQuerySystemInformation 函数,对它获取的进程列表信息进行修改,把有我们要隐藏的进程信息从中去掉,那么 ZwQuerySystemInformation 就返回了我们修改后的信息,其它程序获取这个被修的信息后,自然获取不到我们隐藏的进程,这样,指定进程就被隐藏起来了。
13、使用SetThreadContext注入代码
- 我们先打开目标进程,枚举目标线程采用的是系统快照的方式,比较线程所属的进程是否是我们的目标进程,SuspendThread挂起线程,GetThreadContext获得eip/rip,在目标进程空间写入Shellcode,SetThreadContext将eip/rip设置为我们shellcode的地址,shellcode执行load dll的工作,最后跳转回之前的eip继续执行。
CONTEXT Context; //定义一个CONTEXT结构
SuspendThread(hThread); //挂起线程
ThreadContext.ContextFlags = CONTEXT_ALL; //修改对Context操作的权限
GetThreadContext(hThread, &Context); //获得Context信息
BufferData = VirtualAllocEx() //在目标线程申请内存 来执行我们的shellcode
//编写shellcode
WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL) // 写入shellcode
ThreadContext.Eip = (UINT32)BufferData //修改EIP指向
Context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(hThread, &Context); //重新设置线程上下文
ResumeThread(hThread); //恢复线程,现在线程开始从BufferData这个地方开始执行指令
七、反动态调试技术(明/暗)
1、IsDebuggerPresent/CheckRemoteDebuggerPresent
- 查询进程环境块(PEB)中的IsDebugged标志,如果进程没有运行在调试器环境中,函数返回0;如果调试附加了进程,函数返回一个非零值。
- CheckRemoteDebuggerPresent:不仅可以探测进程自身是否被调试,同时可以探测系统其他进程是否被调试
2、FindWindow
- 查找目标窗口,如果找到,可以禁用窗口,也可以直接退出程序
3、EnumWindow+GetWindowText
- 使用EnumWindow函数枚举窗口,并且为每一窗口调用一次回调函数,在回调函数中可以调用GetWindowText获取窗口的标题。与目标窗口名进行比对,如果比对成功,则说明发现调试器。
4、枚举进程(进程名称)
- CreateToolhelp32Snapshot:获取进程快照
- Process32First:获得第一个进程句柄
- Process32Next:获得下一个进程句柄
- 比较:wcsstr(strTemp, str)
5、查看父进程是否是Explorer
- 当我们双击运行应用程序的时候,父进程都是Explorer,如果是通过调试器启动的,父进程就不是Explorer。
- 通过GetCurrentProcessId()获得当前进程的ID
- 通过桌面窗口类和名称获得Explorer进程的ID
- 使用Process32First/Next()函数枚举进程列表,通过PROCESSENTRY32.th32ParentProcessID 获得的当前进程的父进程ID与Explorer的ID进程比对。如果不一样的很可能被调试器附加
6、检查系统时钟
- 记录执行一段操作前后的时间戳,然后比较这两个时间戳,如果存在滞后,则可以认为存在调试器
- 记录触发一个异常前后的时间戳。
- 使用rdtsc指令/QueryPerformanceCounter/GetTickCount,它返回至系统重新启动以来的时钟数,并且将其作为一个64位的值存入edx:eax中。恶意代码运行两次rdstc指令,然后比较两次读取之间的差值来判断是否存在调试器。
7、PEB相关标志
- fs:[30]可以获得PEB地址
- NtGlobalFlag(偏移 0x68)值为 0,如果进程被调试通常值为 0x70
8、GuardPages(内存断点)
通过 PAGE_GUARD 页面保护修改符来设置的,如果访问的内存地址是受保护页面的一部分,将会产生一个 STATUS_GUARD_PAGE_VIOLATION(0x80000001)异常。如果进程被 OllyDbg 调试并且受保护的页面被访问,将不会抛出异常,访问将会被当作内存断点
9、软件断点
在它的代码中查找机器码0xCC来扫描调试器对它代码的INT 3修改。
10、封锁键盘,鼠标输入(BlockInput)
11、NtQueryInformationProcess
12、SetUnhandledExceptionFilter
- 检测异常处理例程有没有执行
- 把代码放到SetUnhandledExceptionFilter设定的函数里面。通过人为触发一个unhandled exception来执行。由于设定的UnhandledExceptionFilter函数只有在调试器没有加载的时nhandledExceptionFilter是否调用取决于系统内核的判断)
13、 v0 = __indword(0x5658u);
13、OutputDebugString
-
Final:
防止恶意代码使用API进行反调试的最简单的方法是在恶意代码运行期间修改恶意代码,使其不能调用探测调试器的API函数,或者修改这些API函数的返回值,确保恶意代码执行合适的路径。与这些方法相比,较复杂的方法是挂钩这些函数,比如使用rootkit技术。
八、反虚拟机技术
1、VMWARE(VirtualBox类似)
1)查找注册表
2)搜索特定程序
Vmtoolsd.exe、vmacthlp.exe、VMwareUser.exe、VMwareTray.exe、VMUpgradeHelper.exe
3)通过执行特权指令(IN)
运行特权指令“IN”,在虚拟机中不会发生异常。在指定功能号0A(获取VMware版本)的情况下,它会在EBX中返回其版本号“VMXH”;而当功能号为0x14时,可用于获取VMware内存大小,当大于0时则说明处于虚拟机中。
mov edx, 'VX' // 端口号
in eax, dx // 从端口dx读取VMware版本到eax
//若上面指定功能号为0x14时,可通过判断eax中的值是否大于0,若是则说明处于虚拟机中
4)查找特定的驱动模块&底层模块
hgfs.sys vmhgfs.sys prleth.sys prlfs.sys prlmouse.sys prlvideo.sys prl_pv32.sys vpc-s3.sys vmsrvc.sys vmx86.sys vmnet.sys
5)CPUID
当eax=1时,运行CPUID之后,ecx的高31位可以判断出是否在虚拟机中,如果ecx的高31位为0表示在虚拟机下,否则在主机下
mov eax, 1;
cpuid;
6)查询csrss.exe内存中的可疑字符
csrss.exe
VM常见的三种MAC地址前三个字节通常为”00-05-69”、 “00-0c-29”、 “00-50-56”
- cat /proc/scsi/scsi 返回虚拟机信息
8)__asm { vpcext 7, 0Bh }
2、SandBox
1)检查系统对象(比如新增端口等)
2)是否包含目标动态库(特殊的dll)
3)检查CPU个数
九、常用代码混淆技术(静态)
1、加壳
2、加密
3、寡态
- 寡态技术需要在恶意代码中包含一个解密器库,每次执行完或传播的时候会随机的从库中选择一个解密器,重新对代码体进行加密并将新的解密器加载在加密后的代码上。
4、变形
垃圾指令插入(Dead-code Insertion)
命名复杂化、混淆字符串、常量拆分
寄存器重分配(Register Reassignment)
子程序重排(Subroutine Reordering)、间接调用、混淆控制流、代码块分割
指令替换(Instruction Substitution)
MOV Mem,Imm
CMP/TEST Reg,Mem --> CMP/TEST Reg,Imm
Jcc @xxx Jcc @xxx
mov模拟其他指令伪代码
case 1: // mov检查值是否相等:
x==y?
mov [x], 0
mov [y], 1
mov R, [x]
编译器混淆
- 编译器生成二进制代码时插入混淆代码
十、持久化技术
1、文件隐藏
-
2、注册表启动(开机自启动项)
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run:每次开机时运行
- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce:自配置起第一次重启时运行,以后不再运行
- HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
3、计划任务(自启动)
schtasks命令原理
- 流程
- CoInitialize初始化Com组件
- 通过CoCreateInstance创建服务对象ITaskService,此对象允许您在指定的文件夹中创建任务。
- 获取要在其中创建任务的任务文件夹,使用ITaskService:: GetFolder方法获取文件夹,并使用ITaskService:: NewTask方法创建ITaskDefinition对象。
- 使用ITaskDefinition对象定义任务的相关信息,例如任务的注册信息。
- 使用ITaskDefinition 的 trigger 属性创建一个登录触发器,以访问该任务的ITriggerCollection接口。
- 使用 ITaskDefinition 的 “操作” 属性访问任务的IActionCollection接口,为任务创建要执行的操作。
- 使用ITaskFolder:: RegisterTaskDefinition方法注册任务。
-
4、快速启动目录
原理:windows系统有自带的快速启动文件夹,只要把程序(一般是程序快捷方式)放入到这个快速启动文件夹中,系统在启动时就会自动地加载并运行相应的程序,实现开机自启动功能。
流程
- 快速启动目录并不是一个固定的目录,每台计算机的快速启动目录都不相同。需要使用 SHGetSpecialFolderPath函数获取Windows系统中快速启动目录的路径,快速启动目录的CSIDL标识值为CSIDL_STARTUP。
使用CopyFile函数,拷贝文件到获取到的路径
BOOL CSelfStartDlg::FastStartDirectory(const char* lpszSrcFilePath)
{
BOOL bRet = FALSE;
char szStartupPath[MAX_PATH] = { 0 };
char szDestFilePath[MAX_PATH] = { 0 };
char TempSrcPath[MAX_PATH] = { 0 };
strcpy_s(TempSrcPath, MAX_PATH, lpszSrcFilePath);
//获取快速启动目录的路径
bRet = SHGetSpecialFolderPathA(NULL, szStartupPath, CSIDL_STARTUP, TRUE);
//保存快速启动目录
USES_CONVERSION;
m_FastStartDir = A2W(szStartupPath);
if (FALSE == bRet)
{
return FALSE;
}
//获取目标文件名
PathStripPathA((char*)lpszSrcFilePath);
//拼接路径
wsprintfA(szDestFilePath, "%s\\%s", szStartupPath, lpszSrcFilePath);
//拷贝文件到快速启动目录下
bRet = CopyFileA(TempSrcPath, szDestFilePath, FALSE);
if (FALSE == bRet)
{
return FALSE;
}
return TRUE;
}
5、创建服务启动
windows
- OpenSCManager:打开服务管理器
- CreateService:创建服务
- OpenService:打开已有服务
- StartService:启动服务
- ControlService:停止服务
- DeleteService:删除服务
- CloseServiceHandle
十一、提权技术
1、令牌提权
- 远程线程注入时先将本程序提权,以防失败
流程
什么是UAC?
- User Account Control
- 通过 UAC,应用程序和任务可始终在非管理员帐户的安全上下文中运行,除非管理员特别授予管理员级别的系统访问权限。UAC 可以阻止未经授权的应用程序自动进行安装,并防止无意中更改系统设置。
- UAC的实现
- windows中所有资源都有ACL(Access Control List),这个列表决定了拥有何种权限的用户/进程能够使用这个资源。
- 开启UAC用户不能自动获取管理员权限,如果用户以管理员权限登录,会生成两份访问令牌,一份是完整的管理员访问令牌,一份是标准用户令牌,一般以标准用户权限启动explorer.exe进程,如果用户同意则赋予完整管理员权限访问令牌。
- UAC触发流程
- 系统创建consent.exe进程:用来确定是否要创建管理员进程
- CreateProcess创建进程
- 将进程信息传递给RAiLuanchAdminProcess函数:验证进程路径是否在白名单中,将结果传递给consent.exe;验证被请求的进程签名以及发起者的权限是否符合要求,决定是否弹出UAC框。
- 这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。
- 用户确认后,调用CreateProcessAsUser函数以管理员权限启动请求的进程
- 绕过方法
- 工具:UACME
- 各类的UAC白名单程序的DLL劫持
- 白名单程序:进程本身具有管理员权限或可直接获得管理员权限(taskmgr.exe、rundll32.exe、explorer.exe等)
- dll注入&dll劫持
- dll加载顺序
- 伪装成白名单的方法
- 伪装进程PEB
- 通过NtQueryInformationProcess获取指定进程的PEB地址
- 使用LoadLibrary和GetProcessAddress函数从Ntdll.dll中获取该函数地址
- 使用ReadProcessMemory和WriteProcessMemory读写目标进程内存,获得PEB的值
- 根据PEB的ProcessParameters获取并修改指定进程的RTL_USER_PROCESS_PARAMETERS结构体信息,修改后实现进程伪装
- 通过NtQueryInformationProcess获取指定进程的PEB地址
- 伪装进程PEB
- 各类自动提升权限的COM接口利用(Elevated COM interface)
- CLSID:全局唯一标识符,对于不同的应用程序,文件类型,OLE对象,特殊文件夹以及各种系统组件分配的一个唯一表示它的ID代码,用于对其身份的标识和与其他对象进行区分。存放在注册表HKEY_CLASSES_ROOT\CLSID
- 能利用成功的前提
- elevation属性开启,且开启Auto Approval
- COM组件中的接口存在可以命令执行的地方
- Windows 自身漏洞提权
- 远程注入
十一、反静态调试技术(反编译)
1、栈指针平衡
- 当按F5提示:Decompilation failure,positive sp value has benn found时说明有栈指针不平衡的情况
- 解决方法
- IDA中设置显示栈指针:Options->General-Disassembly->”Stack pointer”
- 正常来讲栈在使用完后(push->pop)esp和ebp的值是一样的,如果发现偏移不一样说明存在问题,这就引起了栈指针不平衡
- 需要手动调节栈指针,使其恢复平衡
- ida中每条语句前的栈指令是这条语句未执行的栈指令
- 在IDA里使用Alt+K修改栈指针,使retn对应的栈指针为0(每个栈单元4个字节【32位】或8个字节【64位】)
2、花指令
- IDA有追踪堆栈的功能,一旦函数的开头和结尾栈指针不一致,函数结尾就会标注”sp-analysis failed”,一般编程中,不同的函数调用约定(如stdcall&_cdcel call)可能会出现这种情况;另外,为了实现代码保护而加入代码混淆(特指用push/push+ret实现函数调用)技术也会出现这种情况。
- 花指令如何影响栈指针
- 补充:
- asm的作用:用于调用内联汇编程序,并且可在 C 或 C++ 语句合法时出现, asm后跟一个程序集指令、一组括在大括号中的指令或者至少一对空大括号。
- emit的作用:编译器不认识的指令,拆成机器码来写;插入垃圾字节来反跟踪,又称花指令。用emit就是在当前位置直接插入数据(实际上是指令),一般是用来直接插入汇编里面没有的特殊指令,多数指令可以用asm内嵌汇编来做,没有必要用emit来做,除非你不想让其它人看懂你的代码。
- 补充:
- 花指令种类总结
- 第一种:(call&jmp混合)
- 先有一个push 操作,然后jmp到一个Call,Call后面跟一个POP
- push保存环境,jmp到xxx
- call xxx:将下一条指令的地址入栈,跳转到xxx地址,xxx保存的是pop reg,jmp reg,第三次跳转到call xxx的下一条指令
- 解决方案:单步跟踪时,只需要F8即可过掉。(想在IDA内看结构,就把它们全部NOP掉就好了)
- 第二种:(JZ/JNZ混合) JX/JNX
- 无效指令:如CLC,CLD,FNOP;
- 无效跳转:mov 寄存器,A;cmp 寄存器,A;JNZ 迷惑跳转;
- 第三种:(pop/push混合)
- push ss;pop ss 成对出现。在执行push ss,pop ss语句后,会自动执行下一条语句,当下一条为jmp或着Call是就会跑飞/关键功能丢失。因为pop ss会抑制所有中断,直到执行下一条指令之后
- 解决方案:如果pop ss后面跟的是Call,在Call入口下断即可
- 第四种:(修改esp)
- call跳转后立即在跳转的执行
**ADD DOWRD PTR SS:[ESP],num**
,修改了返回地址,然后执行loopd short 循环跳转 ,然后cmp ecx,XXXX(由于之前赋值的是较小数一定会跳转)最后retn返回至被修改过的函数返回地址,结束 - 解决方案:利用010去花指令:花指令特征码为B9 D2 C3 01 00 E8 0B 00 00 00 81 F9 58 FF FE 01 78 F0 79 F8 E9 83 04 24 12 E2 EF E8 替换为EB 1A,后面随便填充即可,如:EB 1A 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
- call跳转后立即在跳转的执行
- 第五种:$
- 第一种:(call&jmp混合)
3、SMC自解码
SMC:SMC(Self-Modifying Code)(自解码),可以在一段代码执行前对它进行修改。常常利用这个特性,把代码以加密的形式保存在可执行文件中,然后在程序执行的时候进行动态解析。这样我们在采用静态分析时,看到的都是加密的内容,从而阻断了静态调试的可能性。
题目实战:
- 对像数组变量的东西进行了传参,说明其为函数,查看函数发现是一大串数据,这就是典型的对某段代码进行了加密处理,上面的异或操作既是加密操作也是也解密操作,这样我们静态分析就进行不下去了。这样的情况就是SMC自解码问题。
- 解题思路:动态分析
4、MOV混淆
- 特征:汇编代码的汇编指令几乎全部就是MOV,能够将比较指令拆成很多条
- 思路:
- 1、 字符串的搜索是给我们最好的提示。
- 2、 MOV混淆是不会混淆函数的逻辑的。因此函数的逻辑还是不变的。
- 3、 大多数汇编代码的意思是可以猜测的。可以大概推测出具体操作了什么。
- 工具:https://github.com/kirschju/demovfuscator
5、OLLVM混淆(LLOM)平坦化(控制流平坦化)
- LLVM:LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
- LLVM和传统的编译器(GCC)的区别
- 传统编译器
- Frontend:前端,包括词法分析、语法分析、语义分析、中间代码生成
- Optimizer:优化器,主要是对编译前端对生成的中间代码的优化
- Backend:后端,翻译中间代码为native机器码
- LLVM架构
- 传统编译器
- IR中间码需要多个pass进行一系列优化后再进行翻译
- LLVM编译器的前端其它层(优化器、后端)是分离的,LLVM专门有一个Clang子项目用来对源码进行编译,生成IR(intermediate representation)中间字节码;而传统编译器的代表(GCC)由于编译前后端耦合度太高,增加一个前端语言支持或者一个后端平台支持将会变得异常复杂。相比之下LLVM由于是分离式架构,其组件复用性就很高,增加语言/平台支持也相对容易,增加一个新的编程语言,就增加一个新的前端组件,增加一个新的平台支持,就增加一个新的后端组件。
- LLVM编译器不同的前端统一使用相同的中间码,不像GCC有各种风格(intel&ATT)
- LLVM经常被用于一些解释型语言的动态编译(优化)。
- LLVM也经常被用于一些语言的静态编译,类似的Objective-c就是使用Clang进行编译
- 基于这个架构编译器的obfuscator-llvmhttps://security.tencent.com/index.php/blog/msg/112
- 例题:
- ida直接分析(很明显用了OLLVM混淆)
- 用strings命令查看,能看到关键字符串 Obfuscator-LLVM Clang Version
- 如何去平坦化——利用符号执行
- 控制流平坦化思想:通过一个主分发器来控制程序基本块的执行流程。模糊基本块之间的前后关系,增加程序分析的难度
- 符号执行的思想:符号执行是一种重要的形式化方法和软件分析技术,通过使用符号执行技术,将程序中变量的值表示为符号值和常量组成的计算表达式,符号是指取值集合的记号,程序计算的输出被表示为输入符号值的函数,其在软件测试和程序验证中发挥着重要作用,并可以应用于程序漏洞的检测。
6、编译器优化
循环坍塌
- 多重循环坍缩为单重循环,减少循环控制指令。 对多维数组的寻址改为⼀一维数组寻址,简化寻址计算。
循环融合
削减循环内计算量
通过子表达式提取削减循环内计算量
函数内联
7、数据混淆
数据重排
- 原始数据切分,使用时拼接
- 有序数据打乱
数据编码
数据转代码
- 把静态数据或常量转化为代码计算结果
十三、非常规代码加载
二进制软件代码加载的两种常规方式
- 1、在进程启动时,由系统进程或父进程将执行代码装载到内存中;
2、在进程执⾏过程中,调⽤系统API加载动态链接库。
事实上代码加载并不需要遵循上述规则,只要代码进入内存,且该代码所在的内存页面具有执行权限,这个代码就能够被调用执行。
非常规代码加载方式
采⽤技术手段达到上述目的方式。⽐如 将代码作为数据读入内存,再调用系统接口将内存的权限标志位修改 为可执行; 或者直接分配一个可写、可执⾏的内存区域,将代码写入。
十四、虚拟机保护
- 将受保护程序翻译成另一套指令集的代码,程序中附加一个该指令集的解释器或转换器,执行过程中将翻译后的代码逐条解释执行。
- 有效保护但执行效率降低。
- 破解
- 统计规律
- 逆向解释器逻辑
- 跟踪
十五、Linux下的rootkit驻留技术
用户态
1、init利用
- 作为Linux的init程序,也就是PID 1,负责启动之后的所有进程,所有的服务都是由它管理,因此它是实现rootkit驻留的最常见手段。
sysvinit,常见驻留点
/etc/init.d
/etc/rc[runlevel].d
/etc/rc.local
systemd,常见驻留点
/etc/systemd/system
/etc/systemd/user
/lib/systemd/system
/lib/systemd/user
~/.local/share/systemd/user
~/.config/systemd/user
systemctl --user enable service
可以使服务随用户登录启动,systemctl enable service
可以让服务随系统启动。bashrc或者zshrc等文件会随着shell的运行而被执行,利用时只需在里面加入恶意的shell script即可
/etc/profile
~/.bashrc
~/.bash_profile
~/.bash_logout
2、利用图形化环境
/etc/xdg/autostart下的desktop文件会被主流桌面环境在启动时执行
3、crond
很多恶意软件并不仅仅会把自己写入用户的crontab(如/var/spool/cron/root),它们会把自己写入软件包使用的crontab里面,如/etc/cron.d,这样更不容易引起用户注意。
4、替换开源项目源码
5、动态链接库劫持
- libc会被几乎所有的ELF调用,而特定的lib则会被特定的ELF调用,只要某个ELF的执行概率够高,我们同样可以用我们重新编译的恶意so替换掉它所链接的某个so文件,达到执行恶意代码的效果。
内核态
1、LKM – 可加载内核模块
2、initrd的利用
- initrd很少受到关注和保护,它又一定会在启动时被加载入内存,我们可以在这个内存文件系统中插入自己的LKM并修改init脚本使我们的LKM在启动时被加载,从而实现恶意代码执行。
十六、Linux下反调试技术
一、int3
- int3 其实就是专门为调试器准备的一个指令,调试器把想下断点的地方的机器码修改为
0xCC
,CPU运行到此处时便会触发SIGTRAP
中断,从而被调试器接管。同时,用户空间也会产生相应的 SIGTRAP 信号,我们只要注册回调函数就可以忽略该信号。 ```cinclude
include
void handler(int signo){}
int main(void) { signal(SIGTRAP, handler); asm(“nop\n\t” “int3\n\t”); printf(“Hello from main!\n”); return 0; }
<a name="4897c95503a8f8c703c7d3e6689301cd"></a>
## 二、获取进程信息
<a name="28e3fba3fdb633e858c4a32fb4595308"></a>
### 1、Process ID(PID)
- Linux中标识进程的一个数字,它的值是不确定的,是由系统分配的(但是有一个例外,启动阶段,kernel运行的第一个进程是init,它的PID是1,是所有进程的最原始的父进程),每个进程都有唯一PID,当进程退出运行之后,PID就会回收,可能之后创建的进程会分配这个PID
<a name="f467809a0d18ad46bdef8825b7b8ec0f"></a>
### 2、Parent Process ID(PPID)
- 父进程的PID
- 调试器要附加进程,必须成为该进程的父进程,也就是说主程序一定是调试器的子进程。通过getpid,我们可以获取到 /proc/pid/cmdline 下程序的启动参数。若启动参数不为 `-bash` 或 `-init`,那么就可以判断其使用了调试器。
```c
pid_t ppid = getppid();
printf("getppid: %d\n", ppid);
if (get_name_by_pid(ppid, name))
return -1;
if (strcmp(name, "-bash") == 0 || strcmp(name, "-init") == 0)
printf("OK!\n");
3、Process Group ID(PGID)
PGID就是进程所属的Group的Leader的PID,如果PGID=PID,那么该进程是Group Leader
4、Session ID(SID)
和PGID非常相似,SID就是进程所属的Session Leader的PID,如果SID==PID,那么该进程是session leader
在同一个终端内,程序的 sid 是不会变的,等于 group leader 的 pid。而如果使用了调试器,程序的父进程便不再是
-bash
,而是-bash
的子进程。所以getsid(getpid()) ≠ getppid()
,故检测出程序使用了调试器if (getsid(getpid()) != getppid()) {
printf("traced!\n");
三、getenv
bash有一个环境变量叫
$_
,它保存的是上一个执行的命令的最后一个参数。getenv 用于获取环境变量,若上一个执行的命令不是 argv[0],则说明在调用程序之前,可能启动了调试器。if(strcmp(argv[0], (char *)getenv("_")))
printf("traced!\n");
四、ptrace
- 调试器在调试目标程序时会进行 fork() 产生子进程,并且 exec() 来执行相应的程序。如果对自己所在的进程进行 ptrace 时,该进程已经被 gdb 附加了,那么 ptrace 就会报错返回 -1,这也从另外一种角度揭示了调试器的存在。
if ( ptrace(PTRACE_TRACEME, 0, 0, 0) < 0 ) {
printf("traced!\n");
return 1;
}
十七、加密技术
1、windows系统库(advapi32.dll)
- CSP:加密服务提供程序,是 Windows 操作系统中提供一般加密功能的硬件和软件组件。
- CryptAcquireContext:获取指定CSP密钥容器句柄
- CryptReleaseContext:释放CSP句柄
- CryptEnumProviders
- CryptCreateHash
- CryptGetHashParam
- CryptEncrypt
- CryptDecrypt
- CryptGenKey
- CryptDestroyKey
-
2、Crypto++
一套关于应用密码学的开源类库
#include <cryptopp/aes.h>
using namespace CryptoPP;
十八、常见恶意行为
1、文件创建
CreateFile
- 文件名
- 文件属性:GENERIC_READ|GENERIC_WRITE
- 访问权限:FILE_SHARE_READ
- 创建操作:TRUNCATE_EXISTIN(打开一个已有文件并删除文件里的内容)
- 设置文件属性:FILE_ATTRIBUTE_NORMAL
HANDLE handle = CreateFile(_T("D:\\1.txt"),GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,TRUNCATE_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(handle != INVALID_HANDLE_VALUE)
{
CloseHandle(handle);
}
2、文件删除
DeleteFile
DeleteFile(file_path)
3、键盘hook
Linux
keys_fd = open ("/dev/input/event2", O_RDONLY);
read(keys_fd, &t, sizeof (t));
Windows
// 全局钩子 HHOOK g_hHook = NULL;
//消息函数 //wParam和lParam代表了消息的附加信息,附加信息会随着消息类型的不同而不同 //当键盘消息发出时,wParam的值为按下按键的虚拟键码,lParam则存储按键的相关状态信息。因此,如果窗口对传入的键盘消息处理时,只需要判断wParam的值即可。 //当鼠标消息发出时,wParam值为鼠标按键的信息,而lParam则储存鼠标的坐标,高字节代表y坐标,低字节代表x坐标。即g_y =HIWORD(lParam), g_x = LOWORD(lParam) LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam, LPARAM lParam) { if(code<0 || code == HC_NOREMOVE) { // return CallNextHookEx(g_hHook,code,wParam,lParam); } char szKeyName[200]; // 【参数1】LPARAM类型,代表键状态 // 【参数2】缓冲区 // 【参数3】缓冲区大小 GetKeyNameText(wParam, (LPWSTR)szKeyName, 100); // 写入文件 char szWriteText[100]; // 改写字符串 sprintf_s(szWriteText, “%s\n”, szKeyName); fwrite(szWriteText, 1, strlen(szWriteText), fp); // 关闭文件 fclose(fp); // 将钩子往下传 return CallNextHookEx(g_hHook, code, wParam, lParam); } BOOL InstallHook(){ // 【参数1】钩子的类型,这里代表键盘钩子 // 【参数2】钩子处理的函数 // 【参数3】实例句柄,PROJECT_NAME为DLL的项目名称,标识包含参数2所指的子程的DLL。 // 【参数4】线程的ID,如果是全局钩子的话,这里要填0,如果是某个线程的钩子,那就需要写线程的ID g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, GetModuleHandle(PrOJECT_NAME),0); }
- 卸载钩子
```cpp
BOOL UninstallHook(){
return UnhookWindowsHookEx(g_hHook);
}
4、进程遍历
十九、恶意行为前的校验
1、互斥体校验
二十、分析技巧
1、关注系统异常处理
一般“”开头的是系统默认名称,但是要特别注意“**except_handler3**”这种异常处理流程
2、F5提示“positive sp value has been found ”
堆栈不平衡,手工平衡
- 在401A92按Alt+k打开堆栈窗口
- 修改
- 继续向上同样操作,直到堆栈平衡
3、MFC和VC程序的区别
- VC
- 程序的入口点是在WinMain函数
- MFC
- 首先会通过AfxGetThread()->PumpMessage()开始消息处理,然后经过一系列复杂的操作,最后才到用户的代码。