软件破解
外挂与破解
- 破解是将源程序改掉。
- 外挂是另一个程序,可以影响当前程序。
软件从开发到运行的过程
-
如下图
-
为什么c++和汇编代码的转换是不可逆的?
-
其中一个原因是:完全不同的C++代码生成的汇编可能是完全一样的
-
破解软件需要哪些知识储备
- Windows平台软件破解必备知识
- 文件格式:PE文件
- 每一个系统都有自己的可执行文件(双击就可以运行)
- 比如:
- Windows
- PE文件(exe)
- linux\android
- ELF文件
- MAC/iOS
- Mach-O文件
- Windows
- 每种系统的可执行文件都规定了他们的代码段、数据段、堆栈段等,分布在哪里。
- 因此只要我们熟悉一种系统的可执行文件内部的详细分布,那么我们就可以根据我么的需要来获取内容。
- 汇编语言 x86、x64汇编
- OllyDbg(OD)\IDA
- 可以看到程序在内存中运行的情况。
- 破解步骤就是:
- 通过OD或者IDA将可执行程序载入内存
- OD或IDA可以看到程序在内存中运行的情况
- 然后覆盖掉内存中自己想改变的代码
- 然后在保存成可执行文件(exe)
-
OD常用快捷键
F2:切换断点 F9:运行程序 F7:Step Into F8:Step Over Ctrl + G:搜索代码
- Windows API
- 实现跨进程访问
- 文件格式:PE文件
- 序列号破解举例
- 破解方法:
- 找到序列号
- 你输入序列号,他肯定要跟正确的序列号比对,那么我们就可以找到那个用于比对的正确序列号。
- 暴力破解
- 不管输入啥都正确。
- 找到序列号
- 破解方法:
- OD的使用
- 解压“吾爱破解专用版Ollydbg”
- 运行:“吾爱破解[LCG].exe”
- 将输入序列号程序(01-CrackMe.exe)扔进去
- 在OD的工具栏中,点击运行(右三角),程序就跑起来了。
- 此时序列号程序载入内存的代码,已经被OD监控了。
- 接下来就是要监控,一旦输入内容,点击check的时候,干了什么
- 有下面几种方法监控到:
- 懂WindowsAPI,可以找到点击check是调用的那个函数。
- OD监控下打断点,监控,就可以看到正确的密码了
- 暴力破解:用OD修改逻辑代码
- 找到你想要改的代码,右击
- 二进制,用NOP指令填充
- 右击 复制到可执行文件,所有修改,全部复制,则就生成一个EXE了,右击保存文件即可。
- OD顶部菜单栏有个“插件-》”“中文搜索引擎”-》智能搜索-》ctrl + F 搜索你想要的文字-找到位置,右击follow,就能定位到那个地方了。
- 破解总结
- 通过一些工具将exe文件编译成汇编暴露在我们面前
- 我们根据需要修改汇编代码
- 重新保存我们修改后的程序为exe
- iOS不容易破解,因为他并不是双击就能运行的源文件
- ipa包中包含:
- 可执行文件(mach-o格式)
- 资源文件:nib、图片、MP3文件等
- 很多签名相关的东西
- ipa包中包含:
加壳与脱壳
-
一般的软件破解思路
-
加壳和脱壳
其他汇编
我们可以看到学汇编主要就是学寄存器,只要我们把一种CPU的寄存器了解,就能够很快上手该CPU的汇编。
Win32汇编
- 寄存器如eax、ebx、ecx、edx、eip、esp、ebp、esi、edi等都是32位的寄存器
- 一个eax是32位,为了兼容老版本,他可以拆分使用,前16位拆分成一个16位寄存器AX,AX又可以分为AH、AL两个8位寄存器。
- 段寄存器
- CPU有两个不同的工作方式:实模式、保护模式
- 实模式:使用“段地址:偏移地址”的方式访问内存数据
- (程序员使用)保护模式:装入段寄存器的不再是段地址,而是段选择符(Segment Selector),在编程过程中,使用偏移地址直接寻址即可
Win64汇编
-
寄存器如下图
A&T汇编
- AT&T汇编 vs Intel汇编
- 基于x86架构的处理器所使用的汇编指令一般有2种格式(市面上的电脑几乎都是x86架构)
- Intel汇编
- DOS(8086处理器)、Windows
- Windows派系 -> VC编译器(微软的编译器)
- AT&T汇编
- Linux、Unix、Mac OS、iOS(模拟器)
- Unix派系 -> GCC编译器
- AT&T 读作 “AT and T”(American Telephone & Telegraph的缩写)
- 就是这家公司编写的汇编语法
- 作为iOS开发工程师,最主要的汇编语言是
- AT&T汇编 -> iOS模拟器
- ARM汇编 -> iOS真机设备
- Intel汇编
-
AT&T汇编 vs Intel汇编 区别
- AT&T特点如下:
- 寄存器前面都要有%
- 赋值顺序是反过来的,把左边赋值给右边
- 立即数前必须加$
- mov后边紧跟着一个单位:movl、movs、movb。。。
- l:代表long
- s:代表short
- b:代表一个字节
- mac一般都是64位的,所以mac通常是movq
- AT&T特点如下:
-
AT&T汇编 vs Intel汇编 寻址方式
- 基于x86架构的处理器所使用的汇编指令一般有2种格式(市面上的电脑几乎都是x86架构)
- 64位AT&T汇编的寄存器
-
下面是一个mac OS代码
int a = 10; int b = 10; int c = a+b;
-
反汇编后如下:
左边为A&T汇编,右边为8086汇编 0x100000f32 <+34>: movl $0xa, -0x14(%rbp) ; mov ss:[bp-0x14], 0xa 0x100000f39 <+41>: movl $0xa, -0x18(%rbp) ; mov ss:[bp-0x18], 0xb 0x100000f40 <+48>: movl -0x14(%rbp), %edi ; mov di, ss:[bp -0x14] 0x100000f43 <+51>: addl -0x18(%rbp), %edi ; add di, ss:[bp -0x18]
-
我们可以看出
- 立即数添加了$前缀
- 不在显示段寄存器,直接操作偏移地址
- 寻址方式有所变化
-
- AT&T有16个常用64位寄存器
- %rax、%rbx、%rcx 、%rdx、%rsi、%rdi、%rbp、%rsp
- %r8、%r9、%r10、%r11、%r12、%r13、%r14、%r15
- 这几个就是通用寄存器
- 寄存器的具体用途
- %rax作为函数返回值使用(跟8086一样)
- %rsp指向栈顶
- %rdi、%rsi、%rdx、%rcx、%r8、%r9、%r10等寄存器用于存放函数参数
- A&T存放函数参数是优先使用寄存器,寄存器不够用然后才使用堆栈。
-
- A&T的栈帧如下:
- 叶子函数:函数内部没有再调用其他函数
- 叶子函数不会减rsp来分配空间给局部变量
-
非叶子函数的栈帧
- 由rsp指向位置以外128字节的区域被视为保留的,不应该被信号或中断处理句柄改写。 因此,函数可以将这个区域用于无需跨越函数调用的临时数据。
- 特别的,叶子函数可以将这个区域用作它们整个栈帧,而不是在prologue与epilogue中调整栈指针。这个区域称为红区。
- 简单地说,红区是一个优化。代码可以假定rsp以下128个字节不会被信号或中断处理句柄破坏,因此可以用于临时数据,无需显式地移动栈指针。最后一句是这个优化所在——递减rsp并保存它是在对数据使用红区时,可以被节省的两条指令。
- 不过,记住红区将被函数调用破坏,因此它通常在叶子函数(不调用其他函数的函数)中最有用
-
叶子函数的栈帧
- 因为temp函数仅有3个实参,调用它不要求栈使用,因为所有的实参都适用寄存器。
- 另外,因为它是一个叶子函数,gcc选择对其所有局部变量使用红区。这样,无需减少rsp(随后恢复)来为这些数据分配空间
常见语句的反汇编
- sizeof
- 是编译器特性,不是函数
- 一旦编译就会将sizeof直接替换为相应的值。
- a++代码分析
-
源码:
int a = 1; int c = a++ + a++ + a++; 原理: 1. 取出a的值1 2. 让a+1 ,a =2 3. 取出第二个a的值2 4. 让a+1,a = 3 5. 取出a的两个值相加,实现第一个加法,1+2 6. 取出第3个a的值3 7. 让a+1,a= 4 8. 取出a的值加上之前的计算结果,1+2+3 9. 因此最后,c = 6, a = 4;
-
汇编分析:
汇编分析如下: 1. 左边为A&T汇编, 右边为8086汇编(只是省略了栈标号:) 2. 从这个帧栈可以看出: [bp-14] 就是局部变量a,[bp-18] 就是局部变量c di 是用于存放计算结果; cx,dx用于做中间计算 0x100000f12 <+34>: movl $0x1, -0x14(%rbp) ; mov ss:[bp-14], 0x1 0x100000f19 <+41>: movl -0x14(%rbp), %edi ; mov di, ss:[bp-14] 将a=1,计算结果di = 1; 0x100000f1c <+44>: movl %edi, %ecx ; mov cx, di 0x100000f1e <+46>: addl $0x1, %ecx ; add cx, 0x1 cx = 2,实现a++ 0x100000f21 <+49>: movl %ecx, -0x14(%rbp) ; mov ss:[bp -14], cx 0x100000f24 <+52>: movl -0x14(%rbp), %ecx ; mov cx, ss:[bp-14] [bp -14] = 2; 更新局部变量a的值 0x100000f27 <+55>: movl %ecx, %edx ; mov dx, cx 0x100000f29 <+57>: addl $0x1, %edx ; add dx, 0x1 dx = 3,实现a++ 0x100000f2c <+60>: movl %edx, -0x14(%rbp) ; mov ss:[bp-14], dx [bp -14] = 3; 更新局部变量a的值 0x100000f2f <+63>: addl %ecx, %edi ; add di, cx di = 3;更新局部变量di的值 di = 1+2 0x100000f31 <+65>: movl -0x14(%rbp), %ecx ; mov cx, ss:[bp-14] 0x100000f34 <+68>: movl %ecx, %edx ; mov dx, cx 0x100000f36 <+70>: addl $0x1, %edx ; add dx, 0x1 cx = 3;dx = 4; 实现a++ 0x100000f39 <+73>: movl %edx, -0x14(%rbp) ; mov ss:[bp-14], dx [bp-14] = 4;更新局部变量a的值 0x100000f3c <+76>: addl %ecx, %edi ; add di, cx di = 6;将计算结果存入到di中 di = 3+3 0x100000f3e <+78>: movl %edi, -0x18(%rbp) ; mov ss:[bp-18], di 0x100000f41 <+81>: movl -0x18(%rbp), %ecx ; mov cx,ss:[bp-18] c= di
-
lldb常用指令
-
读取寄存器的值
register read/格式 register read/x ;x表示16位
-
修改寄存器的值
egister write 寄存器名称 数值 register write $rax 0
-
取内存中的值
x/数量-格式-字节大小 内存地址 x/3xw 0x0000010
-
修改内存中的值
memory write 内存地址 数值 memory write 0x0000010 10
- 格式
- x是16进制,f是浮点,d是十进制
-
字节大小
b – byte 1字节 h – half word 2字节 w – word 4字节 g – giant word 8字节
- expression 表达式
- 可以简写:expr 表达式
expression $rax expression $rax = 1
- po 表达式
-
print 表达式
po/x $rax po (int)$rax
汇编与C语言混用
- 外联汇编
- 用单独的.h、.s文件编写
- 在GCC编译器中,汇编代码文件以.s结尾
-
代码举例:
//.h 文件,声明函数 sum.h #ifndef sum_h #define sum_h //声明函数 int sum(int a, int b); #endif /* sum_h */ //.s 文件,函数的实现 //gcc编译器要求,不添加这句就是一个私有函数 .global _sum _sum: movq %rdi, %rax addq %rsi, %rax retq
- 内联汇编
- 直接将汇编语言嵌入到C语言中
- 语法比较古怪,有语法资料可以查看
-
代码如下
int num1 = 1; int num2 = 2; int result; //这种语法比较奇葩,查看资料中的相关文档 __asm__( //Xcode编译器,%%等价于% "addq %%rbx, %%rax" : "=a"(result) //num1给ax,num2给bx : "a"(num1), "b"(num2) ); NSLog(@"%d", result);