CrackMe003:NAG窗口(4C法)和浮点计算

下面是网上的160个CrackMe的部分逆向笔记,包括逆向思路、注册机实现和逆向中常用的知识整理

注意:逆向前一定要先操作一下软件,熟悉软件的运行现象;逆向一定要自己操作一遍,看是很难看会的(高手除外)

CrackMe002和CrackMe003是同一个作者用VB写的小程序;CrackMe003多了一个NAG窗口,难度也不是很大

在这里插入图片描述

1.现象

运行软件,会先弹出一个NAG窗口,什么也不能操作,几秒钟后会弹出验证用户名和序列号的主窗口

要求:需要去掉NAG窗口和逆向出正确的序列号
在这里插入图片描述

2.NAG窗口破解

思路1:Timer思路(特殊场景)

前置知识

  • 基地址:PE文件加载到内存后被称为映像文件(Image File),映像文件优先载入的地址是由PE文件中的ImageBase决定(相对地址),默认是0x400000
  • 第一行代码地址:是由PE文件的AddressOfEntryPoint(即脱壳中的EP)决定,默认是0x01000

image-20220521150105221
因此,正常的程序在0x401000(= 0x400000 + 0x01000)开始执行第一行代码

  • 1.猜测

程序的NAG几秒钟后会自动消失,猜测可能是类似定时器的机制控制的

注意:逆向就是要大胆猜测,然后实验猜测是否正确?有时要实验很多种才会成功,其实这与调试程序bug是一样的思路

  • 2.实验

在内存窗口中,先定位到0x401000位置,然后Ctrl+B搜索Timer(Timer是VB程序默认的定时器变量),搜索结果如下:
image-2022032733943

VB中计时器的单位是ms,上图中0x1B58 = 7000ms = 7s;重新运行程序,看一看是不是大概7s时间,NAG窗口消失,发现果然是,说明猜测是正确的(可以说是运气好,但是只有逆向多了才会有这种好运气的)

  • 3.破解思路

因此,Timer思路就是将0x1B5改成一个很小的值(比如0x001);现象就是NAG一闪而过,很难被捕捉到,给人的感觉就是NAG窗口被去掉了

  • 4.破解操作

    下面是破解的操作步骤

    • 1.OD加载程序,内存窗口中Ctrl+G,定位到0x401000
    • 2.Ctrl+B搜索Timer,修改计数器变量对应秒数为0x0001(要注意好大小端)

局限性:计时器默认名如果不是Timer,内存中搜索的方法就会失效

思路2:偏移4C思路(通用法)

VB程序启动顺序

VB程序的每个窗口都有启动顺序,这个顺序就保存在程序开始执行位置的偏移4C附近定义的一个结构体中

  • 当前顺序:现在程序中会先启动NAG窗口,后启动序列号验证窗口

  • 破解思路:如果找到启动顺序位置,将NAG窗口放在序列号验证窗口后面,自然就自动去除掉NAG窗口了(想法是好的,就是不知道怎么实现?继续看下面吧…)

下面是VB程序可能的框架:
在这里插入图片描述

VB涉及到的相关结构体:

//VBHeader结构定义
Signature               array[1..4] of char;        //00H,签名,必须为VB5!
LanguageDll             array[1..14] of char;       //06H,语言链接库名,通常为”*"或者vb6chsdll
...
AGUTTable               DWORD;                      //4CH,指针,指向Form GuI描述表,***关键位置***
...
    
//AGUTTable结构定义(大小是0x50)
Signature                DWORD                    //00H,必须是50000000
FomID                    TGUID                    //04H,可能是以GUID方式命名的formID
Index                    BYTE                     //24H,窗体的序号,***关键位置***
Flag1                    BYTE                     //28H,第一个窗体的启动标志,可能是90 也可能是10
AGUIDescriptionTable     DWORD                    //48H,指针指向以“FFCC…“开始的FormGUI表
Flag3                    DWORD                    //4CH,意义不明

AGUTTable结构体中,偏移是0x24的Index就是启动顺序;索引到AGUTTable位置是通过VBHeader为基准,偏移0x4C;因此使用修改启动顺序的方法也叫4C思路

破解步骤

step 1.找VBHeader结构起始地址

OD加载程序,没有加壳的程序默认会停在OEP处,VB程序典型的OEP附近汇编如下,多调试几次VB程序自己就会发现

00401164   .- FF25 4CB14000   jmp     dword ptr ds:[40B14C]  ; MSVBVM50.EVENT_SINK_Release
0040116A   $- FF25 8CB14000   jmp     dword ptr ds:[40B18C]  ; MSVBVM50.ThunRTMain
00401170 > $  68 D4674000     push    4067D4				 ; 00401170是OEP指向地址,OD加载后会停在这行
00401175   .  E8 F0FFFFFF     call    0040116A               ; <jmp.&MSVBVM50.#100>
#说明:入口点处的push压入stack的数据就是VBHeader首地址,即VBHeader首地址是4067D4
step 2.找VBHeader中偏移4C处的内容

内存窗口中,Ctrl+G,定位到4067D4地址,偏移4C处(AGUTTable结构)的内容是0x406868

在这里插入图片描述

step 3.更改窗口的启动顺序

内存窗口中,定位到0x406868,会发现2块类似的结构(AGUTTable结构),每块的大小是0x50个bytes,查看每块结构的偏移0x24

  • NAG窗口:启动顺序是00,即第一个被程序加载的窗口

  • 序列号验证窗口:启动顺序是01
    在这里插入图片描述

去掉NAG窗口方法:直接将NAG窗口和序列号验证窗口的启动顺序互换,保存二进制修改

经验证,的确不再显示NAG窗口了;至此,NAG被去掉了,使用了2种方式,推荐使用第2种

3.序列号破解

这个程序与CrackMe002的逆向思路是一样的,暴力破解和直接通过函数栈帧找到正确序列号的逻辑在CrackMe002里有介绍,下面是总结的操作流程:

  • 1.先输入错误的用户名和序列号(test123/456),根据错误弹窗里的字符串先找到调用弹窗的代码和关键跳转

  • 2.在关键跳转所在函数起始位置设置断点,反复调试函数起始位置和关键跳转之间的代码,分析根据用户名生成序列号的逻辑

笨方法,继续用上面的思路破解CrackMe003的序列号;为了写出注册机,下面将调试时发关键节点整理成了笔记(一定要自己操作一下,你才会知道下面说的是什么意思)

key 1.用户名生成临时序列号

调试中发现了如下计算临时序列号的公式:

临时序列号 = 用户名长度 * 0x15B38 + 用户名第一个字符的ASCII码,结果用十进制表示的字符串

示例:用户名test123对应的临时序列号(字符串)是622332(记住这个值,后面会用到)
image-2022032830668

key 2.连续3次浮点运算

用户名计算出临时序列号会经历3次浮点运算操作,操作套路基本一样(如果不熟悉浮点运算,通过单步调试观察栈帧,一样可以得到下面的结果);下面直接给出了3次操作的截图

  • 第一次操作622332 -> 622334
    在这里插入图片描述

  • 第二次操作622334*3-2=1867000
    在这里插入图片描述

  • 第三次操作1867000+15 -> 1867015
    在这里插入图片描述

注册机

经过上面key1和key2,很容易写出简单的注册机

#include "StdAfx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    char user_name[100] = {0};					//用户名
    printf("请输入用户名:");
    scanf_s("%s", user_name, 100);
    
	//计算序列号
    int user_serial = strlen(user_name) * 0x15B38 + user_name[0];//用户名计算临时序列号
	float serial = 0.0;
	serial = user_serial + 2.0;					//第一次操作
	serial = serial * 3.0 -2.0;					//第二次操作
	serial = serial + 15.0;						//第三次操作

    printf("用户名:%s, 对应的序列号:%f\n", user_name, serial);
    system("pause");
    return 0;
}

验证结果如下,说明整个逆向的思路和注册机是正确的

在这里插入图片描述

如果你是第一次逆向浮点运算,可以看一下我以前整理的浮点汇编的笔记(直接粘贴在下面了);如果想学一下C++代码逆向成汇编的典型特征,推荐看《C++反汇编与逆向分析》;如果是大牛,可以直接忽略下面

4.扩展:浮点类型的汇编

CrackMe003的生成序列号算法使用了浮点的相关运算,下面简要介绍逆向中浮点类型的相关知识

C++中,32位操作系统中2种浮点数据类型:

  • 单精度(float):内存中占用4个bytes
  • 双精度(double):8个bytes

浮点类型的编码就不详细介绍了,对于逆向来说,只要知道基本格式(符号位 + 指数部分 + 科学计数法中的位数部分)就行,下面主要介绍一下浮点数指令

浮点寄存器

  • 区别:普通数据类型操作使用的是通用寄存器(eaxecx等),浮点数据类型操作使用的是浮点寄存器(ST0 ~ ST7

  • stack实现:浮点寄存器是通过使用栈(stack)来实现的,共8个栈空间组成(ST0 ~ ST7),且每个浮点寄存器占用8个bytes,浮点寄存器使用类似于入栈和出栈的操作

  • 使用顺序:每次操作的都是ST0,从而影响其他浮点寄存器(ST1 ~ ST7

    • 入栈:如果ST0中没有数据,直接放进ST0;如果ST0中有数据,ST7数据丢弃,依次将ST6数据放入ST7,…,ST0数据放入ST1,新数据放入ST0
    • 出栈:执行相反操作

常用浮点指令

指令使用方法说明
fld/fildfld/fild PUSH_DATA将浮点数/整数PUSH_DATA压入ST0中
fst/fistfst/fist POP_ADDR将ST0中的数据以浮点/整数形式存入POP_ADDR地址中
fstp/fistpfstp/fistpPOP_ADDR相对于fst/fist,会多执行一次出栈操作
fldz、fld1fldz、fld1将0.0、1.0压入ST0中
faddfadd addr将addr地址内的数据与ST0做加法,结果存入ST0中
fcomfcom addr将addr地址内的数据与ST0进行实数比较,影响对应标志位
ftstftst比较ST0是否是0.0,影响对应标志位

说明:以p结尾的指令基本都会执行一次出栈操作;其他运算指令与普通指令类似,只需要在指令前面加一个f即可

示例

Debug版本的浮点操作示例(C++代码与反汇编代码对应)

#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
;---生成栈帧和stack上分配部分空间的初始化代码--- 
	int test_int = 3;
0096B65E  mov         dword ptr [test_int],3  	

	float test_float = (float)test_int;			//通过ST0做中转,将int转成float类型
0096B665  fild        dword ptr [test_int] 						;将整数test_int压入ST0中 
0096B668  fstp        dword ptr [test_float]  					;将ST0中的数据以浮点的形式存入test_float,且ST0中数据出栈

	printf("test_float=%f", test_float);		//float数据通过ST0中转后,放入stack上预留好空间
0096B66B  fld         dword ptr [test_float] 					;将浮点数test_float压入ST0中  
0096B66E  sub         esp,8  									;浮点数作为变参函数的参数,需要转换成双精度浮点存储在stack上
0096B671  fstp        qword ptr [esp] 							;将ST0中的数据以浮点的形式存入eap执行的地址,且ST0中数据出栈 
0096B674  push        offset string "test_float=%f" (9BDC7Ch)  
0096B679  call        @ILT+4360(_printf) (96A10Dh)  
0096B67E  add         esp,0Ch  

	test_int = (int)test_float;
0096B681  fld         dword ptr [test_float] 					;将浮点数test_float压入ST0中  
0096B684  call        @ILT+2085(__ftol2_sse) (96982Ah)  		;调用_ftol函数进行浮点数转换
0096B689  mov         dword ptr [test_int],eax  

	return 0;
0096B69D  xor         eax,eax  
}

简要结论:

  • 1.通过ST0寄存器实现整数和浮点数的转换

  • 2.float类型的浮点数占用4个bytes空间,但是在实际处理时都是按照8个bytes方式进行处理

  • 3.浮点数作为参数,不能直接压入stack中;因为push指令只能传4个bytes大小,会出现数据损失(printf以整数方式输出浮点数会出现错误的根因);作为返回值也有类似的处理

至此,CrackMe003逆向完了,想学逆向就尽量不要爆破掉程序就完事,尽量多学点里面的基础知识

5.参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值