实验相关背景介绍:
(侵权联系删除)
第一阶段:软件保护破解
方法一:爆破(至少两种方式)
- 查找显示注册结果相关代码
- 查找注册码验证相关代码
- 修改程序跳转
方法二:编写注册机
- 查找显示注册结果相关代码
- 查找注册码验证相关代码
- 根据注册码验证代码编写注册机
第二阶段:软件反动态调试分析
- 分析CrackMe1.exe是如何通过父进程检测实现反OllyDbg调试的
- 分析除父进程检测外,该程序用到的反动态调试技术
第三阶段:壳
- 加壳脱壳深入理解
- 尝试手动脱壳
第一阶段:软件保护破解
方法一:爆破(至少两种方式)
- 查找显示注册结果相关代码
首先尝试输入Sssss 55555 错误,弹框Bad boy。那么bad boy有关的代码就是注册结果相关代码。
之后用OllyDbg打开crack.exe。
查找所有参考文本子串。
双击 good boy(bad boy 也可)。
就找到了注册结果相关代码。
2. 查找注册码验证相关代码
向上查看代码。
可以看到一段循环后,做cmp,然后jnz,选择是否跳转到Bad boy相关字段。(地址401107)
还是正确,直接good boy。
即为注册码验证相关代码。
3.修改程序跳转
爆破方法一:修改跳转条件
JNZ 改为JZ。
在OllyDbg修改相关代码,汇编,F9运行,查看效果。
F9运行
修改为jz。汇编
爆破完成
修改PE文件。
使用Lordpe 查看crack的文件信息。点击sections。 .text节在V偏移,文件中偏移都为1000,所以节偏移为0。之后下图给出了文件偏移地址的计算公式。
010Editor工具打开crack。 地址10F9 750C (机器指令),与OD一致
改为jz后可见机器码为 740C 。
在pe文件,改为74
运行
爆破成功。
另一种找到地址的方式
在OD
004010F9 右键
在下方数据窗口,右键查看可执行文件,可见地址10F9
爆破方法二:
修改比较方式。
由以上分析可知,crack程序验证时,cmp eax,ecx,若不等(JNZ 401107)
那么我们修改为相等即可。直接cmp eax eax。
爆破成功。
机器码为3BC0
在pe文件的相应位置修改即可。
地址为000010F7.
方法二:编写注册机
- 查找显示注册结果相关代码
同方法一,不再赘述。
- 查找注册码验证相关代码
同方法一,不再赘述。
- 根据注册码验证代码编写注册机
我们在上面找到了验证相关代码,现在通过动态调试分析具体的验证过程。
在XOR(异或) EAX,EAX 处F2下断点,F9运行。
弹窗,此时输入了name abcde,serial 12345.
之后F7单步运行,观察寄存器值和堆栈信息,分析验证过程。
窗口右边有寄存器值。
F7单步运行
单步运行可见ECX值变为abcde,EAX清零,EDX00001908
ECX值变为61,”a”的ascii值
这里cmp eax,ebx ebx=5,是arg1的长度。每次循环eax+1,判断输入的是不是5位
下方堆栈信息
通过单步运行,观察寄存器,汇编和堆栈可以分析出程序的保护方式是根据name计算serial,然后和用户输入的serial比较。
执行完CALL语句就将输入的字符串转化为整型
循环计算对应的注册码,并判断输入的用户名长度是否合法(必须是5位),否则有提示。
Arg3存储输入的name。Arg2(ECX)存储计算得到的注册码
Arg4存储输入的注册码,字符串转换为整型后存到EAX
然后就是CMP EAX ECX
计算过程用C++形式表示如下
#include<iostream> #include<cstring> using namespace std; int main() { cout<<"Enter the name"<<endl; string name; cin>>name; string arg3=name; int arg2=6408; int ebx=arg3.size(); int ecx; for(int eax=0;eax<ebx;eax++) { arg2+=ebx; ecx=arg3[eax]; ecx*=arg2; arg2=ecx; } cout<<(arg2^0xA9F9FA)<<endl; }
编写相关程序,编译运行,输入五位字符,就会计算出相应的注册码。如图所示。
输入name aaaaa,计算出其serial 为978652603.
我们在crack程序验证,确实如此。于是就生成了注册码生成机。
第二阶段:软件反动态调试分析
- 分析CrackMe1.exe是如何通过父进程检测实现反OllyDbg调试的
OllyDbg打开,查看调用树。
去地址40192C查看。
在这里下断点,开始调试。
Ntdll是获取父进程id,push到edi
获取父进程名字 GetModuleFileNmeExA
这里有关闭句柄操作。
再往下
可以看到ARG2是作为比较的explorer的路径名称,而arg1即为上面获取到的当前打开craackme1的父进程的路径名称。
Push到edx后,call5027FE去进行比较。
5027FE
在第二个cmp处跳转
Jne 502829
Je没有jump。
Jmp 00502705
00502705是大的循环,比较过程。
循环可以分为三个段。
前两个做大小写转换,第三个做比较
cmp19 判断是大写还是小写,是大写+20转化为小写
test eax,eax判断循环是否结束。结束跳转到502738
JE 相同的话跳转到2712继续循环比较后续字符
注意看右侧的寄存器值
一开始edx为c:xxxx
一次比较后
c比较过了,就去掉。
比较不等,返回到call5027FE的下一条,进行后续操作。(这时已经判断出有问题了,父进程不是explorer)
2. 分析除父进程检测外,该程序用到的反动态调试技术
该程序用到的关键函数还有
作用分别为:
1.GetCurrentProcessId
GetCurrentProcessId(
);
说明:
获取当前进程的标示符(PID)
返回值:
返回一个标示符(PID)
库文件:kernel32.dll
2.OpenProcess
库文件:Kernel32.dll
OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。
函数原型
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
3.GetModuleFileNameExA 通过进程句柄获取进程文件名
DWORD GetModuleFileNameExA(
HANDLE hProcess,
HMODULE hModule,
LPTSTR lpstrFileName,
DWORD nsize
);
库文件:Kernel32.dll
4.CreateToolhelp32Snapshot
可以获取系统中正在运行的进程信息,线程信息
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);
库文件:Kernel32.dll
5.Process32Next
是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用Process32Next函数来获得下一个进程的句柄。
BOOLWINAPIProcess32Next(
__inHANDLEhSnapshot,
__outLPPROCESSENTRY32lppe
);
库文件:Kernel32.dll
6.EnumWindows()
函数原型:
BOOL WINAPI EnumWindows(
_In_ WNDENUMPROC lpEnumFunc,
_In_ LPARAM lParam
);
lpEnumFunc: 应用程序定义的回调函数的指针
lParam: 传递给回调函数的应用程序定义的值
MSDN中对EnumWindows的解释:
Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function. EnumWindows continues until the last top-level window is enumerated or the callback function returns FALSE.
即:
枚举屏幕上的所有的顶层窗口,轮流地将这些窗口的句柄传递给一个应用程序定义的回调函数。EnumWindows会一直进行下去,直到枚举完所有的顶层窗口,或者回调函数返回了FALSE.
库文件:user32.dll
7.GetWindowTextA
GetWindowTextA将指定窗口的标题栏(如果有的话)的文字拷贝到缓冲区内。如果指定的窗口是一个控件(control),那么该控件的text属性将被拷贝(到缓冲区)。但是,GetWindowText 不能取回其他程序中控件的text。
int WINAPI GetWindowText(
_In_ HWND hWnd,
_Out_ LPTSTR lpString,
_In_ int nMaxCount
);
库文件:User32.dll
对该程序反动态调试技术的理解:
该程序被打开后,通过windows api函数获取父进程的路径,存储到寄存器中,然后与explorer.exe(预期的父进程名)的路径作比较,比较时先转换为小写。之后比较相同,则认为没有被动态调试,否则认为是调试器。之后进行操作阻止调试。
第三阶段:壳
- 加壳脱壳深入理解
用PEid检测CrackmeUPX,可以发现该程序是UPX方法加壳的。
用OllyDbg打开运行,查看壳的加载过程。
409BF0pushad保存现场环境。
单步运行查看右下角堆栈信息。
将信息保存到12FFA4开始的位置,当壳加载完成退出后,在弹出保存好的信息。
F7单步运行查看加载壳的具体过程
完成后Popad弹出保存好的信息。