这次用OD分析Brad Soblesky.2的算法分析,并带着一步步写对应的注册机。
程序可以自行网上找。这次先不考虑有无壳的问题,着重在分析思路。
1、信息收集
先简单运行程序试试功能,直接输入1/2
获得提示信息1:user name must have at least 5 characters.
再输入正常点的用户名密码
获得提示信息2:incorrect!!, try again.
得出结论程序的功能为:
-
用户名长度至少要五个字符
-
用户名和注册码有对应关系(后续分析发现注册码并不是写死,而是通过算法计算出来)
2、程序分析
这里要先找到主要的处理逻辑,提示信息可以用OD本身的功能或插件直接搜索
查找->所有参考文本字串 //OD自带
Ultra String Reference->Find ASCII //插件查找
- 双击跳转到对应汇编代码,F2下断点,F9运行输入1/2 执行,到断点处
在 00401575 处看到有 cmp 5,提示词又是用户名要5个字符以上,猜测此处是做用户名长度判断,[ebp-1C] 是用户名长度
在 00401579 处跳转到 004015BE,跳过了用户名长度不够的提示信息,猜测跳转后才是真正的处理逻辑,即 004015BE 处的后续才是具体的注册码处理逻辑。
-
在 004015BE 处下断点输入 admin/123456 执行,满足用户名长度后被断下来,继续分析:
来捋一捋这一段在干嘛,边执行边猜功能。
-
有个很明显的红箭头跳转,从 00401618 处跳转到前面 004015C7,猜测是个循环,中间还有个 jge 跳转到循环外的地址,猜测是break作用
-
[ebp-20] 处初始值为0,然后第一次循环从 004015C5 跳转到 004015D0,不执行加1。后续每次循环 [ebp-20] 都加1
-
jge 是基于 [ebp-1c] 和 [ebp-20] 的对比,查看内存 [ebp-1c] 值为5,猜测是用户名长度,因此此处的循环的次数是用户名的长度
那我们接下去看看每次循环都干了什么。执行到 004015DC ,查看内存 [ebp-14]对应的值
看起来存的是个地址,继续看存的什么
这不就是我们的用户名吗,因此 [ebp-14] 存的就是用户名的地址
在 004015E4 处有个 call,直接看汇编代码看不太出干嘛的,但执行完 call 后,ECX是输入的用户名,下一行代码为 movsx edx, al,而当前 al 的值为 61(此处是十六进制,61H对应的字符就是a),猜测此处这个 call,就是根据从0开始的序号取出对应用户名的字符值。
-
此处其实就当前的信息继续分析,看不出所以然,也不知道什么时候对应用户名的注册码才出来,先继续往下走
跳出那多次循环后往下执行,执行后 00401627 这个 call 后,发现 EAX 出现了一串值,猜测这就是序列号
猜测正确,此处的函数调用 push了EAX和%lu,后者是用于格式化字符串,即当时的EAX是通过用户名计算出来的序列号值,而EAX是刚结束循环后的 [ebp-10] 。那可以大胆猜测,上面的循环计算,最后就是把序列号放到 [ebp-10],那我们再重新看下循环内的 [ebp-10] 。
-
断点下在循环初始处,执行到 [ebp-10]
经过循环和多次执行发现,每次循环刚开始,[ebp-10] 的值都是81276345,猜测这是一个种子值。然后在每次循环过程中经过各种计算,[ebp-10] 的值一直在变化,到循环结束后[ebp-10] 的值就是序列号的十六进制,再转换成十进制就是我们应该输入的序列号。
注册机的编写(一)
通过上面分析可知,算法就是这个循环,循环完后序列号的十六进制已经出来,后续只是做个格式转换。
此处投机取巧,直接用汇编写注册机,就不用理解算法再重新实现了。但随之问题也来了,要分析那些 ebp对应的值是什么,提前初始化好,根据前面的分析,汇总如下:
[ebp-14] : 用户名地址
[ebp-10] : 初始化种子值为81276345
[ebp-1C] : 用户名长度
[ebp-20] : 索引/序号,从0开始到len(user)-1
利用插件把这个循环的代码复制出来,这里用的是 Asm2Clipboard 插件
需要复制的是这段汇编代码
利用插件复制出来是这样的
mov dword ptr [ebp-20], 0 jmp L005 L002: mov edx, dword ptr [ebp-20] add edx, 1 mov dword ptr [ebp-20], edx L005: mov eax, dword ptr [ebp-20] cmp eax, dword ptr [ebp-1C] jge short 0040161A mov ecx, dword ptr [ebp-20] push ecx lea ecx, dword ptr [ebp-14] call 00401900 movsx edx, al mov eax, dword ptr [ebp-10] add eax, edx mov dword ptr [ebp-10], eax mov ecx, dword ptr [ebp-20] shl ecx, 8 mov edx, dword ptr [ebp-10] xor edx, ecx mov dword ptr [ebp-10], edx mov eax, dword ptr [ebp-20] add eax, 1 mov ecx, dword ptr [ebp-1C] imul ecx, dword ptr [ebp-20] not ecx imul eax, ecx mov edx, dword ptr [ebp-10] imul edx, eax mov dword ptr [ebp-10], edx jmp L002
可以看到,其中 [ebp-10],[ebp-14],[ebp-1C] 的值需要我们去定义,这部分前面已经写过,直接替代
[ebp-14] : 用户名地址
[ebp-10] : 初始化种子值为81276345
[ebp-1C] : 用户名长度
[ebp-20] : 索引/序号,从0开始到len(user)-1
用汇编写个简易的窗口程序,源码如下
;Register.Asm .386 .model flat, stdcall ;32 bit memory model option casemap :none ;case sensitive include Register.inc .data g_szCode db 256 dup(0) ;用于存储注册码 g_szFmt db "%lu", 0 ;用于格式化字符串 .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke InitCommonControls invoke DialogBoxParam,hInstance,IDD_DIALOG1,NULL,addr DlgProc,NULL invoke ExitProcess,0 ;######################################################################## OnBtnRegister proc hWin:HWND LOCAL @dwUserLen:DWORD LOCAL @dwIdx:DWORD LOCAL @szBuff[256]:BYTE LOCAL @dwSeed:DWORD ;getuser invoke GetDlgItemText, hWin, EDT_USER, addr @szBuff, 256 mov @dwUserLen, eax ;此处eax为DialogBoxParam返回的用户名长度 mov @dwSeed, 81276345h ;注册序列号初始种子值 mov @dwIdx, 0 jmp L005 L002: mov edx, @dwIdx ;index add edx, 1 mov @dwIdx, edx L005: mov eax, @dwIdx cmp eax, @dwUserLen jge LEND ;此处代码里为函数调用,进入函数摘取关键操作 ;mov ecx, dword ptr [ebp-20] ;push ecx ;lea ecx, dword ptr [ebp-14] ;call 00401900 mov ecx, @dwIdx ;index lea eax, @szBuff mov al, [eax+ecx];getchar movsx edx, al mov eax, @dwSeed add eax, edx mov @dwSeed, eax mov ecx, @dwIdx shl ecx, 8 mov edx, @dwSeed xor edx, ecx mov @dwSeed, edx mov eax, @dwIdx add eax, 1 mov ecx, @dwUserLen imul ecx, @dwIdx not ecx imul eax, ecx mov edx, @dwSeed imul edx, eax mov @dwSeed, edx jmp L002 LEND: invoke wsprintf, offset g_szCode, offset g_szFmt, @dwSeed invoke SetDlgItemText, hWin, EDT_CODE, offset g_szCode ;return value in code ret OnBtnRegister endp DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM mov eax,uMsg .if eax==WM_INITDIALOG .elseif eax==WM_COMMAND mov eax, wParam .if ax == BTN_REGISTER invoke OnBtnRegister, hWin .endif .elseif eax==WM_CLOSE invoke EndDialog,hWin,0 .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgProc endp end start
;Register.Inc include windows.inc include kernel32.inc include user32.inc include Comctl32.inc include shell32.inc includelib kernel32.lib includelib user32.lib includelib Comctl32.lib includelib shell32.lib DlgProc PROTO :HWND,:UINT,:WPARAM,:LPARAM .const IDD_DIALOG1 equ 101 EDT_USER equ 1002 EDT_CODE equ 1003 BTN_REGISTER equ 1001 ;######################################################################### .data? hInstance dd ? ;#########################################################################
执行试试,随便输入用户名
注册机的编写(二)
python实现注册机
本人拙劣,直接照着算法的一步步操作复刻,汇编里的循环算法图已经在上边就不贴了。
说下编写过程中的几个点:
1、内存中是以二进制进行操作,位与、异或、取反等操作,代码中操作时,操作数是十进制或二进制结果相同,直接操作就行,输出时默认转换为十进制
2、内存中负数的表示是补码,代码中计算出来的是值,所以如果是负值要转换为对应的补码
def limit(user_name): if len(user_name) < 5: print("user name must have at least 5 characters.") exit() #操作数转换成十进制/十六进制进行操作结果相同 def pojie(user_name): seed = 0x81276345 #int length = len(user_name) for i in range(length): seed = hex(seed + ord(user_name[i])) tmp_index = i << 8 seed = int(seed,16) ^ tmp_index tmp_index = i + 1 tmp = i * length tmp = ~tmp & 0xFFFFFFFF # 取反并按需截断至32位 tmp_seed = tmp_index * tmp seed = (seed * tmp_seed) & 0xFFFFFFFF # 取反并按需截断至32位,实现imul #seed = hex(seed * hex(tmp_length)) return seed if __name__ == '__main__': user_input = str(input("请输入用户名:")) limit(user_input) register_code = pojie(user_input) print("用户名: "+user_input+",对应的注册是", register_code)
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)