软件逆向

简介

什么为逆向分析

image-20221211192409708

扩展功能分类

image-20221211181750142

模拟按键

http://sikulix.com/

https://www.autoitscript.com/

实现原理

模拟键盘

image-20221211184707489

一个简易的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

内存修改

image-20221211185535227

image-20221211185620560

大多数的软件功能扩展都是内存修改 的方式,这个是相对比较简单

伪造数据包

image-20221211190015606

软件的产生

image-20221211190109139

如何分析

image-20221211190215036

基础知识

寄存器

image-20221211190329625

常用的汇编指令

image-20221211190451344

image-20221211190502383

  • JMP:无条件跳转
  • JE:等于跳转
  • JNE: 不等于跳转
  • JGE:跳转大于等于
  • JG: 跳转大于
  • JL: 跳转小于
  • JLE: 跳转小于等于

例子

image-20221211192247037

堆栈

操作

image-20221217134149396

逆向生长的堆栈

image-20221217204534047

为什么栈会逆生长

image-20221217204430520

对栈的大小是有限制的,在达到一定大小后会终止对栈分配,只会分配给堆,这个时候通常会报错

使用

image-20221217204950247

  • win的seh结构化异常:

image-20221217211006989

Intel CPU的四个 特权级

  1. Ring0 , 内核层、驱动层
  2. Ring1,未使用
  3. Ring2, 未使用
  4. Ring3,用户层

在用户层调用 内核层的函数:(不允许)

image-20221217141523566

想要实现对程序的修改,你只能在高层次的对抗

X64带来的变化

  • 驱动程序签名,未签名的程序不加载(开发者模式例外)
  • PatchGuard(补丁卫士),windows内核的金钟罩

image-20221217142620467

虚拟化

在特权级层上是上帝的角色:Ring-1层,比Ring0权限还要高

防护

  • 加壳(类似于加密,把二进制文件弄的一团糟),防止程序被分析
  • 添加 Anti-Debug 功能,防止被调试
  • 修改系统的关键函数 :WriteProcessMemrroy (写入进程)+CreateRemoteThrad
  • 实时监测自己的函数

检测修改它的软件

  1. 查找字符串(一般通过注入dll来修改软件,这样会有一些特殊的注入语句)
  2. 查找DLL导出的函数名
  3. 检查二进制代码

函数的序言、尾声

image-20221217203039458

递归函数的使用,每一次的执行函数都会有函数序言和尾声,会大大消耗内存

第一个逆向分析程序

  1. 把xc代码转换为汇编代码

  2. 汇编语言的两种主流语体:intel、AT&T

  3. X64寄存器、X86寄存器、16寄存器

  4. image-20221217201851214

    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  

image-20221217153540856

断点变少(优化)

image-20221217153625882

继续执行会到这,看到有一个invoke_mian() 函数,这个函数调用了mian()函数

image-20221217153742381

最后会调用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

image-20221217160000318

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

image-20221217192159978

posted @ 2023-02-04 20:25  crabin88  阅读(226)  评论(0编辑  收藏  举报