Brad Soblesky.2 逆向分析&注册机编写

这次用OD分析Brad Soblesky.2的算法分析,并一步步写对应的注册机。

先不考虑有无壳的问题,着重在分析思路。

0x01 信息收集

先简单运行程序试试功能,直接输入1/2

获得提示信息1:user name must have at least 5 characters.

Brad Soblesky.2 逆向分析&注册机编写

再输入正常点的用户名密码

获得提示信息2:incorrect!!, try again.

Brad Soblesky.2 逆向分析&注册机编写

得出结论程序的功能为:

  • 用户名长度至少要五个字符

  • 用户名和注册码有对应关系(后续分析发现注册码并不是写死,而是通过算法计算出来)

0x02 程序分析

先找到主要的处理逻辑,提示信息可以用OD本身的功能或插件直接搜索

查找->所有参考文本字串  //OD自带Ultra String Reference->Find ASCII //插件查找

Brad Soblesky.2 逆向分析&注册机编写

  • 双击跳转到对应汇编代码,F2下断点,F9运行输入1/2 执行,到断点处

Brad Soblesky.2 逆向分析&注册机编写

在 00401575 处看到有 cmp 5,提示词又是用户名要5个字符以上,猜测此处是做用户名长度判断,[ebp-1C] 是用户名长度

在 00401579 处跳转到 004015BE,跳过了用户名长度不够的提示信息,猜测跳转后才是真正的处理逻辑,即 004015BE 处的后续才是具体的注册码处理逻辑。

  • 在 004015BE 处下断点输入 admin/123456 执行,满足用户名长度后被断下来,继续分析:

Brad Soblesky.2 逆向分析&注册机编写

来捋一捋这一段在干嘛,边执行边猜功能。

  1. 有个很明显的红箭头跳转,从 00401618 处跳转到前面 004015C7,猜测是个循环,中间还有个 jge 跳转到循环外的地址,猜测是break作用

  2. [ebp-20] 处初始值为0,然后第一次循环从 004015C5 跳转到 004015D0,不执行加1。后续每次循环 [ebp-20] 都加1

  3. jge 是基于 [ebp-1c] 和 [ebp-20] 的对比,查看内存 [ebp-1c] 值为5,猜测是用户名长度,因此此处的循环的次数是用户名的长度


那我们接下去看看每次循环都干了什么。执行到 004015DC ,查看内存 [ebp-14]对应的值

Brad Soblesky.2 逆向分析&注册机编写

看起来存的是个地址,继续看存的什么

Brad Soblesky.2 逆向分析&注册机编写

这不就是我们的用户名吗,因此 [ebp-14] 存的就是用户名的地址

Brad Soblesky.2 逆向分析&注册机编写

在 004015E4 处有个 call,直接看汇编代码看不太出干嘛的,但执行完 call 后,ECX是输入的用户名,下一行代码为 movsx edx, al,而当前 al 的值为 61(此处是十六进制,61H对应的字符就是a),猜测此处这个 call,就是根据从0开始的序号取出对应用户名的字符值。

  • 此处其实就当前的信息继续分析,看不出所以然,也不知道什么时候对应用户名的注册码才出来,先继续往下走

Brad Soblesky.2 逆向分析&注册机编写

跳出那多次循环后往下执行,执行后 00401627 这个 call 后,发现 EAX 出现了一串值,猜测这就是序列号

Brad Soblesky.2 逆向分析&注册机编写

猜测正确,此处的函数调用 push了EAX和%lu,后者是用于格式化字符串,即当时的EAX是通过用户名计算出来的序列号值,而EAX是刚结束循环后的 [ebp-10] 。那可以大胆猜测,上面的循环计算,最后就是把序列号放到 [ebp-10],那我们再重新看下循环内的 [ebp-10] 。

  • 断点下在循环初始处,执行到 [ebp-10] 

Brad Soblesky.2 逆向分析&注册机编写

经过循环和多次执行发现,每次循环刚开始,[ebp-10] 的值都是81276345,猜测这是一个种子值。然后在每次循环过程中经过各种计算,[ebp-10] 的值一直在变化,到循环结束后[ebp-10] 的值就是序列号的十六进制,再转换成十进制就是我们应该输入的序列号。

0x03 注册机的编写(一)

通过上面分析可知,算法就是这个循环,循环完后序列号的十六进制已经出来,后续只是做个格式转换。

此处投机取巧,直接用汇编写注册机,就不用理解算法再重新实现了。但随之问题也来了,要分析那些 ebp对应的值是什么,提前初始化好,根据前面的分析,汇总如下:

[ebp-14] : 用户名地址[ebp-10] : 初始化种子值为81276345[ebp-1C] : 用户名长度[ebp-20] : 索引/序号,从0开始到len(user)-1

利用插件把这个循环的代码复制出来,这里用的是 Asm2Clipboard 插件

需要复制的是这段汇编代码

Brad Soblesky.2 逆向分析&注册机编写

利用插件复制出来是这样的

  mov     dword ptr [ebp-20], 0  jmp L005L002:  mov     edx, dword ptr [ebp-20]  add     edx, 1  mov     dword ptr [ebp-20], edxL005:  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 modeloption 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 L005L002: mov edx, @dwIdx ;index add edx, 1 mov @dwIdx, edxL005: 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 L002LEND: 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.Incinclude windows.incinclude kernel32.incinclude user32.incinclude Comctl32.incinclude shell32.inc
includelib kernel32.libincludelib user32.libincludelib Comctl32.libincludelib shell32.lib
DlgProc PROTO :HWND,:UINT,:WPARAM,:LPARAM
.const
IDD_DIALOG1 equ 101EDT_USER equ 1002EDT_CODE equ 1003BTN_REGISTER equ 1001;#########################################################################.data?hInstance dd ?;#########################################################################


执行试试,随便输入用户名

Brad Soblesky.2 逆向分析&注册机编写

Brad Soblesky.2 逆向分析&注册机编写

0x04 注册机的编写(二)

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)

Brad Soblesky.2 逆向分析&注册机编写


Brad Soblesky.2 逆向分析&注册机编写


声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白名单。

请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与本公众号无关。


原文始发于微信公众号(白帽学子):Brad Soblesky.2 逆向分析&注册机编写

版权声明:admin 发表于 2024年7月1日 上午8:12。
转载请注明:Brad Soblesky.2 逆向分析&注册机编写 | CTF导航

相关文章