软件破解

外挂与破解

  1. 破解是将源程序改掉。
  2. 外挂是另一个程序,可以影响当前程序。

软件从开发到运行的过程

  1. 如下图

    图1

  2. 为什么c++和汇编代码的转换是不可逆的?

    1. 其中一个原因是:完全不同的C++代码生成的汇编可能是完全一样的

      图1

破解软件需要哪些知识储备

  1. Windows平台软件破解必备知识
    1. 文件格式:PE文件
      1. 每一个系统都有自己的可执行文件(双击就可以运行)
      2. 比如:
        1. Windows
          1. PE文件(exe)
        2. linux\android
          1. ELF文件
        3. MAC/iOS
          1. Mach-O文件
      3. 每种系统的可执行文件都规定了他们的代码段、数据段、堆栈段等,分布在哪里。
      4. 因此只要我们熟悉一种系统的可执行文件内部的详细分布,那么我们就可以根据我么的需要来获取内容。
    2. 汇编语言 x86、x64汇编
    3. OllyDbg(OD)\IDA
      1. 可以看到程序在内存中运行的情况。
      2. 破解步骤就是:
        1. 通过OD或者IDA将可执行程序载入内存
        2. OD或IDA可以看到程序在内存中运行的情况
        3. 然后覆盖掉内存中自己想改变的代码
        4. 然后在保存成可执行文件(exe)
      3. OD常用快捷键

         F2:切换断点 
         F9:运行程序 
         F7:Step Into 
         F8:Step Over 
         Ctrl + G:搜索代码
        
    4. Windows API
      1. 实现跨进程访问
  2. 序列号破解举例
    1. 破解方法:
      1. 找到序列号
        1. 你输入序列号,他肯定要跟正确的序列号比对,那么我们就可以找到那个用于比对的正确序列号。
      2. 暴力破解
        1. 不管输入啥都正确。
  3. OD的使用
    1. 解压“吾爱破解专用版Ollydbg”
    2. 运行:“吾爱破解[LCG].exe”
    3. 将输入序列号程序(01-CrackMe.exe)扔进去
    4. 在OD的工具栏中,点击运行(右三角),程序就跑起来了。
    5. 此时序列号程序载入内存的代码,已经被OD监控了。
    6. 接下来就是要监控,一旦输入内容,点击check的时候,干了什么
    7. 有下面几种方法监控到:
      1. 懂WindowsAPI,可以找到点击check是调用的那个函数。
      2. OD监控下打断点,监控,就可以看到正确的密码了
      3. 暴力破解:用OD修改逻辑代码
        1. 找到你想要改的代码,右击
        2. 二进制,用NOP指令填充
        3. 右击 复制到可执行文件,所有修改,全部复制,则就生成一个EXE了,右击保存文件即可。
      4. OD顶部菜单栏有个“插件-》”“中文搜索引擎”-》智能搜索-》ctrl + F 搜索你想要的文字-找到位置,右击follow,就能定位到那个地方了。
  4. 破解总结
    1. 通过一些工具将exe文件编译成汇编暴露在我们面前
    2. 我们根据需要修改汇编代码
    3. 重新保存我们修改后的程序为exe
  5. iOS不容易破解,因为他并不是双击就能运行的源文件
    1. ipa包中包含:
      1. 可执行文件(mach-o格式)
      2. 资源文件:nib、图片、MP3文件等
      3. 很多签名相关的东西

加壳与脱壳

  1. 一般的软件破解思路 图1

  2. 加壳和脱壳 图1

其他汇编

我们可以看到学汇编主要就是学寄存器,只要我们把一种CPU的寄存器了解,就能够很快上手该CPU的汇编。

Win32汇编

  1. 寄存器如eax、ebx、ecx、edx、eip、esp、ebp、esi、edi等都是32位的寄存器
    1. 一个eax是32位,为了兼容老版本,他可以拆分使用,前16位拆分成一个16位寄存器AX,AX又可以分为AH、AL两个8位寄存器。

    图1

  2. 段寄存器
    1. CPU有两个不同的工作方式:实模式、保护模式
    2. 实模式:使用“段地址:偏移地址”的方式访问内存数据
    3. (程序员使用)保护模式:装入段寄存器的不再是段地址,而是段选择符(Segment Selector),在编程过程中,使用偏移地址直接寻址即可

Win64汇编

  1. 寄存器如下图

    图1

A&T汇编

  1. AT&T汇编 vs Intel汇编
    1. 基于x86架构的处理器所使用的汇编指令一般有2种格式(市面上的电脑几乎都是x86架构)
      1. Intel汇编
        1. DOS(8086处理器)、Windows
        2. Windows派系 -> VC编译器(微软的编译器)
      2. AT&T汇编
        1. Linux、Unix、Mac OS、iOS(模拟器)
        2. Unix派系 -> GCC编译器
        3. AT&T 读作 “AT and T”(American Telephone & Telegraph的缩写)
          1. 就是这家公司编写的汇编语法
        4. 作为iOS开发工程师,最主要的汇编语言是
          1. AT&T汇编 -> iOS模拟器
          2. ARM汇编 -> iOS真机设备
    2. AT&T汇编 vs Intel汇编 区别

      图1

      1. AT&T特点如下:
        1. 寄存器前面都要有%
        2. 赋值顺序是反过来的,把左边赋值给右边
        3. 立即数前必须加$
        4. mov后边紧跟着一个单位:movl、movs、movb。。。
          1. l:代表long
          2. s:代表short
          3. b:代表一个字节
          4. mac一般都是64位的,所以mac通常是movq
    3. AT&T汇编 vs Intel汇编 寻址方式

      图1

  2. 64位AT&T汇编的寄存器
    1. 下面是一个mac OS代码

       int a = 10;
       int b = 10;
       int c = a+b;
      
      1. 反汇编后如下:

         左边为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]
         
        
      2. 我们可以看出

        1. 立即数添加了$前缀
        2. 不在显示段寄存器,直接操作偏移地址
        3. 寻址方式有所变化
    2. AT&T有16个常用64位寄存器
      1. %rax、%rbx、%rcx 、%rdx、%rsi、%rdi、%rbp、%rsp
      2. %r8、%r9、%r10、%r11、%r12、%r13、%r14、%r15
        1. 这几个就是通用寄存器
    3. 寄存器的具体用途
      1. %rax作为函数返回值使用(跟8086一样)
      2. %rsp指向栈顶
      3. %rdi、%rsi、%rdx、%rcx、%r8、%r9、%r10等寄存器用于存放函数参数
        1. A&T存放函数参数是优先使用寄存器,寄存器不够用然后才使用堆栈

      图1

  3. A&T的栈帧如下:
    1. 叶子函数:函数内部没有再调用其他函数
    2. 叶子函数不会减rsp来分配空间给局部变量
    3. 非叶子函数的栈帧

      图1

      1. 由rsp指向位置以外128字节的区域被视为保留的,不应该被信号或中断处理句柄改写。 因此,函数可以将这个区域用于无需跨越函数调用的临时数据。
      2. 特别的,叶子函数可以将这个区域用作它们整个栈帧,而不是在prologue与epilogue中调整栈指针。这个区域称为红区。
      3. 简单地说,红区是一个优化。代码可以假定rsp以下128个字节不会被信号或中断处理句柄破坏,因此可以用于临时数据,无需显式地移动栈指针。最后一句是这个优化所在——递减rsp并保存它是在对数据使用红区时,可以被节省的两条指令。
      4. 不过,记住红区将被函数调用破坏,因此它通常在叶子函数(不调用其他函数的函数)中最有用
    4. 叶子函数的栈帧

      图1

      1. 因为temp函数仅有3个实参,调用它不要求栈使用,因为所有的实参都适用寄存器。
      2. 另外,因为它是一个叶子函数,gcc选择对其所有局部变量使用红区。这样,无需减少rsp(随后恢复)来为这些数据分配空间

常见语句的反汇编

  1. sizeof
    1. 是编译器特性,不是函数
    2. 一旦编译就会将sizeof直接替换为相应的值。
  2. a++代码分析
    1. 源码:

       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;
      
    2. 汇编分析:

        汇编分析如下:
        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常用指令

  1. 读取寄存器的值

     register read/格式
     register read/x  ;x表示16位
    
  2. 修改寄存器的值

     egister write 寄存器名称 数值
     register write $rax 0
    
  3. 取内存中的值

     x/数量-格式-字节大小 内存地址
     x/3xw 0x0000010
    
  4. 修改内存中的值

     memory write 内存地址 数值
     memory write 0x0000010 10
    
  5. 格式
    1. x是16进制,f是浮点,d是十进制
  6. 字节大小

     b – byte 1字节
     h – half word 2字节
     w – word 4字节
     g – giant word 8字节
    
  7. expression 表达式
    1. 可以简写:expr 表达式
     expression $rax
     expression $rax = 1
    
  8. po 表达式
  9. print 表达式

     po/x $rax
     po (int)$rax
    

汇编与C语言混用

  1. 外联汇编
    1. 用单独的.h、.s文件编写
    2. 在GCC编译器中,汇编代码文件以.s结尾
    3. 代码举例:

       //.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
      
  2. 内联汇编
    1. 直接将汇编语言嵌入到C语言中
    2. 语法比较古怪,有语法资料可以查看
    3. 代码如下

       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);
      
Table of Contents