软件逆向
简介
什么为逆向分析
扩展功能分类
模拟按键
实现原理
模拟键盘
一个简易的cpp按键模拟
#include <Windows.h>
#include <iostring>
int main() {
std::cout << "holle world!\n";
input input[4] = { 0 };
//设置按键模式
input[0].type = input[1].type = input[2].type = input[3].type = input_keyboard;
input[0].ki.wvk = input[3].ki.wvk = vk_lwin;
input[1].ki.wvk = input[2].ki.wvk = "d";
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
sendinput(4, input, sizeof(input));
return 0;
}
sendInput函数文档 win官方:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
内存修改
大多数的软件功能扩展都是内存修改 的方式,这个是相对比较简单
伪造数据包
软件的产生
如何分析
基础知识
寄存器
常用的汇编指令
- JMP:无条件跳转
- JE:等于跳转
- JNE: 不等于跳转
- JGE:跳转大于等于
- JG: 跳转大于
- JL: 跳转小于
- JLE: 跳转小于等于
例子
堆栈
操作
逆向生长的堆栈
为什么栈会逆生长
对栈的大小是有限制的,在达到一定大小后会终止对栈分配,只会分配给堆,这个时候通常会报错
使用
- win的seh结构化异常:
Intel CPU的四个 特权级
- Ring0 , 内核层、驱动层
- Ring1,未使用
- Ring2, 未使用
- Ring3,用户层
在用户层调用 内核层的函数:(不允许)
想要实现对程序的修改,你只能在高层次的对抗
-
编写驱动,在ring0层进行对抗:怎么写:windows来帮你:
https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk
X64带来的变化
- 驱动程序签名,未签名的程序不加载(开发者模式例外)
- PatchGuard(补丁卫士),windows内核的金钟罩
虚拟化
在特权级层上是上帝的角色:Ring-1层,比Ring0权限还要高
防护
- 加壳(类似于加密,把二进制文件弄的一团糟),防止程序被分析
- 添加
Anti-Debug
功能,防止被调试 - 修改系统的关键函数 :WriteProcessMemrroy (写入进程)+CreateRemoteThrad
- 实时监测自己的函数
检测修改它的软件
- 查找字符串(一般通过注入dll来修改软件,这样会有一些特殊的注入语句)
- 查找DLL导出的函数名
- 检查二进制代码
函数的序言、尾声
递归函数的使用,每一次的执行函数都会有函数序言和尾声,会大大消耗内存
第一个逆向分析程序
-
把xc代码转换为汇编代码
-
汇编语言的两种主流语体:intel、AT&T
-
X64寄存器、X86寄存器、16寄存器
-
CPU访问寄存器的速度比访问内存的速度快1000倍以上,所以在传递参数的时候尽可能的使用寄存器来传递,而不是使用内存(也就是堆栈),在64位中优先使用寄存器(RCX,RDX,R8,R9)来传递参数,比如这个程序:
00007FF728F92330 lea rdx,[string "Hello World!\n" (07FF728F9AC28h)] 这里使用RDX
32位中一般使用堆栈来传递
一个语言的Hello World
#include <iostream>
int f() {
return 0x123;
}
int main()
{
f();
std::cout << "Hello World!\n";
}
debug
使用debug x86运行,查看反汇编码
#include <iostream>
int f() {
00342410 push ebp
00342411 mov ebp,esp
00342413 sub esp,0C0h
00342419 push ebx
0034241A push esi
0034241B push edi
0034241C mov edi,ebp
0034241E xor ecx,ecx
00342420 mov eax,0CCCCCCCCh
00342425 rep stos dword ptr es:[edi]
00342427 mov ecx,offset _6088A5C4_L0001@cpp (034E067h)
0034242C call @__CheckForDebuggerJustMyCode@4 (0341375h)
return 0x123;
00342431 mov eax,123h //函数返回值放到eax寄存器
}
00342436 pop edi
00342437 pop esi
00342438 pop ebx
00342439 add esp,0C0h
0034243F cmp ebp,esp
00342441 call __RTC_CheckEsp (034128Ah)
00342446 mov esp,ebp
00342448 pop ebp
00342449 ret
int main()
{
003424D0 push ebp
003424D1 mov ebp,esp
003424D3 sub esp,0C0h
003424D9 push ebx
003424DA push esi
003424DB push edi
003424DC mov edi,ebp
003424DE xor ecx,ecx
003424E0 mov eax,0CCCCCCCCh
003424E5 rep stos dword ptr es:[edi]
003424E7 mov ecx,offset _6088A5C4_L0001@cpp (034E067h)
003424EC call @__CheckForDebuggerJustMyCode@4 (0341375h)
f();
003424F1 call f (034116Dh)
std::cout << "Hello World!\n";
003424F6 push offset string "Hello World!\n" (0349B30h)
003424FB mov eax,dword ptr [__imp_std::cout (034D0C8h)]
00342500 push eax
00342501 call std::operator<<<std::char_traits<char> > (03411A4h)
00342506 add esp,8
}
00342509 xor eax,eax //函数返回值放到eax寄存器
0034250B pop edi
0034250C pop esi
0034250D pop ebx
0034250E add esp,0C0h
00342514 cmp ebp,esp
00342516 call __RTC_CheckEsp (034128Ah)
0034251B mov esp,ebp
0034251D pop ebp
0034251E ret
x64
#include <iostream>
int f() {
00007FF728F92270 push rbp
00007FF728F92272 push rdi
00007FF728F92273 sub rsp,0E8h
00007FF728F9227A lea rbp,[rsp+20h]
00007FF728F9227F lea rcx,[__6088A5C4_L0001@cpp (07FF728FA3067h)]
00007FF728F92286 call __CheckForDebuggerJustMyCode (07FF728F913D4h)
return 0x123;
00007FF728F9228B mov eax,123h //函数返回值放到eax寄存器
}
00007FF728F92290 lea rsp,[rbp+0C8h]
00007FF728F92297 pop rdi
00007FF728F92298 pop rbp
00007FF728F92299 ret
...
int main()
{
00007FF728F92310 push rbp
00007FF728F92312 push rdi
00007FF728F92313 sub rsp,0E8h
00007FF728F9231A lea rbp,[rsp+20h]
00007FF728F9231F lea rcx,[__6088A5C4_L0001@cpp (07FF728FA3067h)]
00007FF728F92326 call __CheckForDebuggerJustMyCode (07FF728F913D4h)
f();
00007FF728F9232B call f (07FF728F91195h)
std::cout << "Hello World!\n";
00007FF728F92330 lea rdx,[string "Hello World!\n" (07FF728F9AC28h)] 【使用lea指令把字符串加载到寄存器rdx】
00007FF728F92337 mov rcx,qword ptr [__imp_std::cout (07FF728FA1198h)]
00007FF728F9233E call std::operator<<<std::char_traits<char> > (07FF728F91087h)
}
00007FF728F92343 xor eax,eax //函数返回值放到eax寄存器
00007FF728F92345 lea rsp,[rbp+0C8h]
00007FF728F9234C pop rdi
00007FF728F9234D pop rbp
00007FF728F9234E ret
Release(发布调试) 会优化代码
x86
f();
std::cout << "Hello World!\n";
009F1000 mov ecx,dword ptr [__imp_std::cout (09F204Ch)]
009F1006 call std::operator<<<std::char_traits<char> > (09F1010h)
}
009F100B xor eax,eax //函数返回值放到eax寄存器
009F100D ret
x64
#include <iostream>
int f() {
return 0x123;
}
int main()
{
00007FF652E41000 sub rsp,28h
f();
std::cout << "Hello World!\n";
00007FF652E41004 mov rcx,qword ptr [__imp_std::cout (07FF652E42080h)]
00007FF652E4100B call std::operator<<<std::char_traits<char> > (07FF652E41020h)
}
00007FF652E41010 xor eax,eax //函数返回值放到eax寄存器
00007FF652E41012 add rsp,28h
00007FF652E41016 ret
断点变少(优化)
继续执行会到这,看到有一个invoke_mian() 函数,这个函数调用了mian()函数
最后会调用exit结束程序
看到这,我们有初步有两个结论
- debug和release调式差别很大
- 一个子程序返回一般最终都会放到
eax
寄存器中
; Listing generated by Microsoft (R) Optimizing Compiler Version 19.34.31935.0 //由这个软件来创建
TITLE G:\WorkSpace\microsoftWorkSpace\模拟按键\L005\L005\L005.obj //标题
.686P //686处理器
.XMM //xmm指令
include listing.inc //包含这个文件
.model flat //模式
//包含的库
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
//数据段
CONST SEGMENT
$SG32946 DB 'Hello World!', 0aH【/n】, 00【0-字符串的结束标志】H
?_Fake_alloc@std@@3U_Fake_allocator@1@B ORG $+1 ; std::_Fake_alloc
CONST ENDS
//公用函数
PUBLIC ??2@YAPAXIPAX@Z ; operator new
PUBLIC ??0exception@std@@QAE@QBD@Z ; std::exception::exception
PUBLIC ??0exception@std@@QAE@QBDH@Z ; std::exception::exception
PUBLIC ??0exception@std@@QAE@ABV01@@Z ; std::exception::exception
程序中未初始化,或者初始化为0的全局变量或者静态变量,是可以写的,程序开始之前这一段自动清0
TEXT SEGMENT
_main PROC(函数序言,标记名为标签的过程块的开始和结束,开始)
; File G:\WorkSpace\microsoftWorkSpace\模拟按键\L005\L005\L005.cpp
; Line 6
push ebp
mov ebp, esp
; Line 7
push OFFSET $SG32946【把字符常量的指针压入堆栈】
push OFFSET ?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::cout
call ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >【调用函数打印】
add esp, 8 【堆栈指针位置加8,8个字节,64位,这里可以理解为一个pop指令,但是pop是把数据放到每个寄存器中,但是这里直接修改,pop指令只占一个字节,这里是占三个】
; Line 8
xor eax, eax 【异或,相同为0,不同为1,这里两个相同,所以返回0,存在eax寄存器中】
; Line 9
pop ebp 【开始对ebp做了push操作,这里把他还原,结果是这个程序只会有esp被修改】
ret 0 【把控制权交出】
_main ENDP(函数尾声,结束)
_TEXT ENDS