大理寺少卿 发表于 2025-3-24 23:22:22

X86C++反汇编02.算术指令

逆向一个序列号有3种层次

1. 打个补丁,改一下跳转(暴力破解)
2. 推导出一组可以用的账号密码
3. 还原算法,并写出例算法(注册机,彻底破解)

彻底破解的难点是还原算法,和 写出例算法(可能变成解题问题)

### 优化

优化方向

1. 体积优化

- 内存优化    例如:大数据   , 蓝光视频   ,写shellcode
- 磁盘优化    例如: 病毒   压缩

1. 速度优化

- 执行速度的优化
- 编译速度的优化

现在的优化一般以执行速度为主,Debug版优化是以便于调试为主所以里面带有详细的信息,效率是次要的,但是并不代表不优化

!(./notesimg/1657087315752-4a89fc79-9957-490b-b2cc-a02203e04171.png)

编译器对算法进行编译,运算结果的参与传递才会参与编译,否则不会参与编译(Debug 和Release 都会这样)

参与传递的3种情况

1. 传参
2. 传返回值
3. 赋值运算

例如下面代码:

!(./notesimg/1657087612859-3439928f-e142-4a17-b790-634c0a8f94b2.png)

运行会发现此处的断点是无效的,而且通过反汇编窗口看,是没有反汇编的代码的,因为他没有参与编译

!(./notesimg/1657087767112-30791989-e91b-489d-a4ab-51e8a8c516e0.png)

#### 整形加法的优化

!(./notesimg/1657088191587-d7952520-05c7-497c-b6e7-f67c91fd8a17.png)

##### Release

把上述代码生成的Release 版文件用IED查看,会发现代码都被优化掉了

!(./notesimg/1657088240813-d83ea09f-de38-4596-a6ec-7dbf863d92cb.png)

##### Debug

Debug也会优化,但不会优化得那么彻底,因为那样无法单步,只会在单行代码内优化,和把当行代码换成等价的更优代码,例如:-5可能 +5的补码

!(./notesimg/1657088699617-7a8e9b9e-d304-44b0-8863-ba4b52178b2f.png)

#### 窥孔优化

窥孔优化:优化一段代码,以几行为单位进行优化,如果源码发生了优化,

```
就会回到第一行开始重新开始,直到源码没有发生改变
```


1. 常量传播: 假如一个变量初始化或者赋值为常量,值中间没有任何修改, 然后把他代入到另一个表达式的时候,可以用常量直接替换掉他
2. 常量折叠:一个表达式,不管多复杂,只要参与运算的都是常量 ,就会触发常量折叠,编译期间直接对表达式求值,获得最后的常量结果
3. 删除未使用的变量:如果优化到最后,变量没有使用,就直接删除

1   int n = 3 + 3;
2n = n + 5;
3   n = n * 5;
4   n = n - 5;
5   n = n / 5;
6   printf("%d\r\n",n - 8);
return 0;

1触发常量折叠   3+3直接变成6       intn= 6
2触发常量传播用6直接替换n = 5+6      源码发生改变 从来开始
到 2 又 再触发常量折叠    n = 11在从 1 开始
3   触发常量传播用11 直接替换n      n = 11* 5;    源码发生改变 从来开始
到3又触发常量折叠      n = 55    ;    源码发生改变 从来开始
4触发常量传播用55直接替换n   n = 55 - 5      源码发生改变 从来开始
到4又触发常量折叠      n = 50    ;    源码发生改变 从来开始
5 触发常量传播用50直接替换n   n = 50 /5      源码发生改变 从来开始
到5又触发常量折叠      n = 10    ;    源码发生改变 从来开始
6触发常量传播用10 直接替换n    printf("%d\r\n",10 - 8);      源码发生改变 从来开始
到6又触发常量折叠   printf("%d\r\n",2);   ;    源码发生改变 从来开始
到6又触发删除未使用的变量   printf("%d\r\n",2);   前面的代码直接删除

所以最后优化后编译的代码就是

printf("%d\r\n",2);

窥孔一方面用来优化代码,把代码由多变少,换个方向就是写反逆向工程 (代码混淆膨胀),把代码由少变多(只需要稳定可靠的的让1 行代码变2行就可以),2者互为对抗

#### VC6.0导出汇编代码

!(./notesimg/1657103438251-7ac67ad3-1c2c-4fc1-be01-5b801a51b4d4.png)

设置之后编译构建就会产生源码对应的汇编文件,再通过命令行编译和链接就可以产生可执行文件,

只需要对对应的汇编文件进行混淆就可以了

### 乘法优化

1. 常量乘 常量直接折叠
2. 变量 乘常量

- 常量是2 的整数幂    直接位移

Debug

!(./notesimg/1657104441591-d1e3c251-5d72-454a-8838-ae0427af2f69-170642588327221.png)

注意    shl   eax,3    等价于   leaeax ,[ eax * 8 ]

leaeax ,[ eax * n ]=mul   eax , n       n必须是2的整数次幂

Release

!(./notesimg/1657104720350-c950bbc9-aa60-44a8-81d9-b09a60e4951b-170642588675422.png)

- 常量不是2 的整数幂    拆分成 2的整数次幂的组合 ,但是高版本取消了这个的优化
- 例如:

argc * 7   =argc *8-argc

对应的汇编代码是

leaeax ,[ eax * 8- eax]

argc * 25   = (argc* 5)*5

对应的汇编代码是

mov eax,argc

leaeax ,[ eax * 4+ eax]      折叠就是   argc* 5

leaeax ,[ eax * 4+ eax]   折叠就是   (argc* 5) * 5   => argc* 25

argc * 52   =( argc   +(argc   *3)*4) * 4

对应的汇编代码是

mov eax,argc

leaecx,[ eax * 2+ 1]    就是   argc* 3

leaedx,[ eax + ecx * 4 ]    就是    ( argc   +(argc   *3)*4)   折叠一下   就是   argc**13

shledx,2   就是    *argc**13*4    *折叠一下   就是   argc**52

1. 变量 * 变量   无法优化

argc *argc

对应的汇编代码是

mov eax,argc

mov ecx,eax

imulecx,eax

### 除法优化

高版本和低版本都会对除法进行优化,因为除法消耗比较大


1. 当除数是变量,没有优化空间
2. 无符号数除法,且除数时2的整数次幂

```
直接右移位
```
1. 有符号数除法,且除数时2的整数次幂

计算机除法有取整问题

正数 或者 无符号数除法    结果都是像下取整

负数                            除法    结果都是像上取整

即计算机的有符号 除法 是向0 取整的

所以在计算机中   (-a)/b!= -( a/b)

在计算机中求以下结果

a / b = q 余 r

r =a- q*b

10 %3   =   1      // r=10 - 3 * 3 =1

-10 %3    = -1       // r=-10 - ( - 3 ) * 3 =-1

10 % - 3    =   1       // r=10 - ( - 3 ) * (-3) =1

-10 %-3   = -1      // r=-10 -   3* (-3) =   - 1

从上面可以看到   余数的符号 跟 被除数相关(即符号相同)

除法原型:

a / b = c .... r

6/ 4 = 1 ...2

1. |r|< |b|   : 余数的绝对值,绝对会小于除数的.比如6 / 4 = 1 .... 2那么 余数2 不关是正数还是父数,绝对都是绝对会小于除数的,也就是4
   2. a = c * b + r : 求被除数,被除数是商*除数+余数
   3. b=(a - r)/c   : 求除数,除数等于 被除数-余数 / 商
   4. c = (a - r)/b : 求商: 被除数 - 余数 / 除数
   5. r = a - (c * b)          : 求余数 被除数 - (商 * 除数)
   !(./notesimg/1657112086087-e61871ce-974d-46d0-b9c5-6a9e99bda4fc.png)

从上面可以知道

A/   2^n

当 A > 0      , 结果需要向 下取整

当 A< 0   , 结果需要向 上取整    那么就等于         ( A + 2^n -1)/ 2^n向下取整

所以

argc /4

当argc   >0            =    argc    >> 2

当argc   <0         =    ( argc   + 4 - 1)>> 2

所以

if(   argc>= 0){

argc /4   ==   argc    >> 2

}

else

{

```
argc /4   ==    ( argc   + 4 - 1)>> 2
```
}

但是上面有分支,如何实现无分支呢

cdq指令,如果eax 不小于0 那么edx 为0 ,否则为-1

moveax , argc

cdq                  ;   eax >= 0 , edx =0   否则   eax<0   ,edx = 0xFFFFFFFF

and edx ,3      ;eax >= 0 , edx =0 ,edx = 0   否则   eax<0   ,edx = 0xFFFFFFFF    dex=3

addeax,dex   ;eax >= 0 , edx =0, edx = 0 ,eax=0   否则   eax<0,edx = 0xFFFFFFFF,dex=3, eax=3

shr eax,2          ;eax >> 2

因此可以得到代码定式

a / 2^n    的反汇编代码是

1      moveax , a

2      cdq

3      and edx ,2^n - 1

4      addeax,dex

5      shr eax, n

必须要验证一下    第3行 是否满足值   等于 第5行的    2^n-1       例如

1   moveax , a

2   cdq

3   and edx , 7

4   addeax,dex

5   shr eax,   2

是错的   7 != 2^2 -1

argc /2

1    moveax , a

2   cdq

3    sub   eax,dex            ; 2^n-1 ,n = 1    =>   1=0 - (-1)

4   shr eax, 1

注意除数是2情况有些特殊 ,指令序列不一样但数学模型是一样的

练习:

argc /32

1    moveax , a

2    cdq

3    and edx ,31

4    addeax,dex

5   shr eax, 5

### VC6 .0 字体补丁

正常情况下VC6.0 可选字体很少,因此需要打补丁

用OD 附加 进程 VC6.0

!(./notesimg/1657091232731-d03d679f-f565-4305-a3d1-cd2a5c8a1926.png)

当我们选择字体时,肯定要用到列举字体的函数

!(./notesimg/1657091341293-a003cf59-3b26-4388-945e-162005c38663.png)

列举字体的函数的函数为   **EnumFontFamiliesEx    在**    Gdi32.lib里面

!(./notesimg/1657091603642-e1c46747-1428-4a5c-ac0c-1011870a48ae.png)

!(./notesimg/1657091720424-5b11133e-e00c-48c2-a4f8-8f032b1af987.png)

函数比较多,无法确定使用哪一个,可以到主模块去看看

!(./notesimg/1657096762448-f5d80428-34de-4094-a1e7-d3529bfb55a1.png)

!(./notesimg/1657096804922-6bcd1087-71b4-4ece-8a5f-d73e52d5fa5d.png)

可以看到是这个

!(./notesimg/1657096861731-69de5c81-afd4-4728-be89-2c08f0d3af16.png)

!(./notesimg/1657097073205-a63f9129-563b-4d3b-b73b-84a00e3741b2.png)

下断点,因为我们换字体肯定会调这个函数

!(./notesimg/1657097119930-1dcfc4e6-68d6-48a8-976b-747894366a5e.png)

去换字体就会断下来,继续跟可以发现有检查,那如果把检查跳转去掉不跳走直接 执行下面的指令

!(./notesimg/1657097478036-b7c0383d-6e92-4c6a-a6b7-7b577c9140c4.png)

然后把修改复制到可执行文件,覆盖掉原来的 dll 就可以了,再去看就可以发现字体多了

xxxxbzn1 发表于 2025-3-25 09:43:17

膜拜神贴,后面的请保持队形~
页: [1]
查看完整版本: X86C++反汇编02.算术指令