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 就可以了,再去看就可以发现字体多了
膜拜神贴,后面的请保持队形~
页:
[1]