回 帖 发 新 帖 刷新版面

主题:[转帖]菜鸟谈外挂编写(5)上篇

这一篇中偶们主要讲下API字串参考和和内存断点。这个上篇主要讲述API函数参考。有一点需要指出,破解是调试游戏程序的基础,其中涵盖了OD的使用方法。上一篇文章的字串参考,不仅是出错字串查找,不仅仅是注册破解的时候能用到,你可以给它扩展,应用到游戏调试里边。这一篇讲的APi函数参考,也不仅是注册时候能用的到,也可以应用到其他方面。具体的偶也不清楚,偶也是菜鸟,现学现卖而已。入正题,引入原作者的例子:一个名称为 CrackHead 的crackme,附件已上传,无壳。老规矩,先运行一下这个程序看看:夷,竟然没找到输入注册码的地方!别急,我们点一下程序上的那个菜单“Shit”(真是 Shit 啊,呵呵),在下拉菜单中选“Try It”,会来到一个对话框。我们点一下那个“Check It”按钮试一下,哦,竟然没反应!我再输个“78787878”试试,还是没反应。再试试输入字母或其它字符,输不进去。由此判断注册码应该都是数字,只有输入正确的注册码才有动静。用 PEiD 检测一下,结果为 MASM32 / TASM32,怪不得程序比较小。信息收集的差不多了,现在关掉这个程序,我们用 OllyDBG 载入,按 F9 键直接让它运行起来。再一次,Shit-->Try It,出现个上边的对话框。先不要点那个“Check It”按钮,保持原状态。现在我们没有什么字串好参考了,我们就在 API 函数上下断点,来让被调试程序中断在我们希望的地方。我们在 OllyDBG 的反汇编窗口中右击鼠标,在弹出菜单中选择 查找->当前模块中的名称 (标签),或者我们通过按 CTR+N 组合键也可以达到同样的效果(注意在进行此操作时要在  OllyDBG 中保证是在当前被调试程序的领空,我在第一篇中已经介绍了领空的概念,如我这里调试这个程序时 OllyDBG 的标题栏显示的就是 “[CPU - 主线程, 模块 - CrackHea]”,这表明我们当前在被调试程序的领空)。通过上面的操作后会弹出一个对话框,名称为  名称位于CrackHea的对话框。对于这样的编辑框中输注册码的程序我们要设断点首选的 API 函数就是 GetDlgItemText 及 GetWindowText。每个函数都有两个版本,一个是  ASCII 版,在函数后添加一个 A 表示,如 GetDlgItemTextA,另一个是 UNICODE 版,在函数后添加一个 W 表示。如  GetDlgItemTextW。对于编译为 UNCODE 版的程序可能在 Win98 下不能运行,因为 Win98并非是完全支持 UNICODE 的系统。而 NT 系统则从底层支持 UNICODE,它可以在操作系统内对字串进行转换,同时支持 ASCII 和  UNICODE 版本函数的调用。一般我们打开的程序看到的调用都是 ASCII 类型的函数,以“A”结尾。又跑题了,呵呵。现在回到我们调试的程序上来,我们现在就是要找一下我们调试的程序有没有调用 GetDlgItemTextA 或 GetWindowTextA 函数。如果调用了就在这儿设断,那么这个断点的附近肯定提供了正确注册码的相关算法。偶们在这个对话框中敲GetW,还好,找到一个 GetWindowTextA。在这个函数上右击,在弹出菜单上选择“在每个参考上设置断点”,我们会在 OllyDBG 窗口最下面的那个状态栏里看到“已设置 2 个断点”。另一种方法就是那个 GetWindowTextA 函数上右击,在弹出菜单上选择“查找输入函数参考”(或者按回车键),将会出现一个名为“参考位于CrackHea:.text到USER32.GetWindowTextA”的对话框。对话框中显示出2条断点。我们可以把两条都设上断点。这个程序只需在第一条指令设断点就可以了。好,我们现在按前面提到的第一条方法,就是“在每个参考上设置断点”,这样上图中的两条指令都会设上断点。断点设好后我们转到我们调试的程序上来,现在我们在被我们调试的程序上点击那个“Check It”按钮,被 OllyDBG 断下。为什么要设GetWindowTextA断点呢?因为你填入注册码可能调用了这个API,实际上他调用的也是这个
GetWindowTextA。下面提供断点处的相关代码:
00401323 |. E8 4C010000         CALL <JMP.&USER32.GetWindowTextA>           ; GetWindowTextA
00401328 |. E8 A5000000         CALL CrackHea.004013D2                      ; 关键,要按F7键跟进去
0040132D |. 3BC6                CMP EAX,ESI                                 ; 比较
0040132F |. 75 42               JNZ SHORT CrackHea.00401373                 ; 不等则完蛋
00401331 |. EB 2C               JMP SHORT CrackHea.0040135F
00401333 |. 4E 6F 77 20 7>      ASCII "Now write a keyg"
00401343 |. 65 6E 20 61 6>      ASCII "en and tut and y"
00401353 |. 6F 75 27 72 6>      ASCII "ou're done.",0
0040135F |> 6A 00               PUSH 0                                      ; Style = MB_OK|MB_APPLMODAL
00401361 |. 68 0F304000         PUSH CrackHea.0040300F                      ; Title = "Crudd's Crack Head"
00401366 |. 68 33134000         PUSH CrackHea.00401333                      ; Text = "Now write a keygen and tut and you're done."
0040136B |. FF75 08             PUSH DWORD PTR SS:[EBP+8]                   ; hOwner
0040136E |. E8 19010000         CALL <JMP.&USER32.MessageBoxA>              ; MessageBoxA
看00401361 |. 68 0F304000         PUSH CrackHea.0040300F                      ; Title = "Crudd's Crack Head"
00401366 |. 68 33134000         PUSH CrackHea.00401333                      ; Text = "Now write a keygen and tut and you're done."
0040136B |. FF75 08             PUSH DWORD PTR SS:[EBP+8]                   ; hOwner
0040136E |. E8 19010000         CALL <JMP.&USER32.MessageBoxA>              ; MessageBoxA
只有注册码填入正确才会出现成功提示。那么这4句代码的注释怎么得来的,看:
00401333 |. 4E 6F 77 20 7>      ASCII "Now write a keyg"
00401343 |. 65 6E 20 61 6>      ASCII "en and tut and y"
00401353 |. 6F 75 27 72 6>      ASCII "ou're done.",0
这个是成功提示框的内容,再自己找找看,就会清楚00401361 |. 68 0F304000         PUSH CrackHea.0040300F                      ; Title = "Crudd's Crack Head"
这个是标题。这个0040136B |. FF75 08             PUSH DWORD PTR SS:[EBP+8]                   ; hOwner 偶不太清楚,可能是将前2句指令的内容入栈吧。请教高手指点迷津。这一句0040136E |. E8 19010000         CALL <JMP.&USER32.MessageBoxA>              ; MessageBoxA 是调用成功提示对话框。由此可以看出0040132F |. 75 42               JNZ SHORT CrackHea.00401373                 ; 不等则完蛋
有一点需要注意,只有点“Check It”按钮,这个GetWindowTextA才真正被调用了。好了今天就到这里,米时间了。不足之处请各位大虾指点。这个上篇还米完,改天再叙。附件先传上去。

回复列表 (共8个回复)

沙发

类似这些资料,看雪上图文并茂的一大把啊!

板凳

菜鸟谈外怪编写(5)上篇还米讲完,接着讲。首先说下,偶的这些文章或转帖只是把外挂编写这个框架思路整理一下,这系列文章还有2篇,最后一篇是讲WPE的。有关OD的具体应用可以去看雪去找原文。建议去http://ddcrack.vicp.net/crackit/crackme.htm这个网站去找些crakeme去练习。好了,续接上文。从上面的代码,我们很容易看出 00401328 地址处的 CALL CrackHea.004013D2 是关键,必须仔细跟踪。而注册成功则会显示一个对话框,标题是“Crudd's Crack Head”,对话框显示的内容是 “Now write a keygen and tut and you're done.”现在我按一下 F8,准备步进到 00401328 地址处的那条 CALL CrackHea.004013D2 指令后再按 F7 键跟进去。等等,怎么回事?怎么按一下 F8 键跑到这来了:
00401474 $- FF25 2C204000      JMP DWORD PTR DS:[<&USER32.GetWindowText>    ; USER32.GetWindowTextA
0040147A $- FF25 30204000      JMP DWORD PTR DS:[<&USER32.LoadCursorA>]     ; USER32.LoadCursorA
00401480 $- FF25 1C204000      JMP DWORD PTR DS:[<&USER32.LoadIconA>]       ; USER32.LoadIconA
00401486 $- FF25 20204000      JMP DWORD PTR DS:[<&USER32.LoadMenuA>]       ; USER32.LoadMenuA
0040148C $- FF25 24204000      JMP DWORD PTR DS:[<&USER32.MessageBoxA>]     ; USER32.MessageBoxA
原来是跳到另一个断点了。很显然这个断点我们米用到,按一下 F2 键删掉它吧。删掉 00401474 地址处的断点后,我再按 F8 键,呵,完了,跑到  User32.dll 的领空了。看一下 OllyDBG 的标题栏:“[CPU - 主线程, 模块 - USER32],跑到系统领空了, OllyDBG 反汇编窗口中显示代码是这样:

77D3213C 6A 0C                 PUSH 0C
77D3213E 68 A021D377           PUSH USER32.77D321A0
77D32143 E8 7864FEFF           CALL USER32.77D185C0

怎么办?别急,我们按一下 ALT+F9 组合键,呵,回来了:
00401328 |. E8 A5000000        CALL CrackHea.004013D2                     ; 关键,要按F7键跟进去
0040132D |. 3BC6               CMP EAX,ESI                                ; 比较
0040132F |. 75 42              JNZ SHORT CrackHea.00401373                ; 不等则完蛋
光标停在 00401328 地址处的那条指令上。现在我们按 F7 键跟进:

3 楼

004013D2 /$ 56                PUSH ESI                                    ; ESI入栈
004013D3 |. 33C0              XOR EAX,EAX                                 ; EAX清零
004013D5 |. 8D35 C4334000     LEA ESI,DWORD PTR DS:[4033C4]               ; 把注册码框中的数值送到ESI
004013DB |. 33C9              XOR ECX,ECX                                 ; ECX清零
004013DD |. 33D2              XOR EDX,EDX                                 ; EDX清零
004013DF |. 8A06              MOV AL,BYTE PTR DS:[ESI]                    ; 把注册码中的每个字符送到AL
004013E1 |. 46                INC ESI                                     ; 指针加1,指向下一个字符
004013E2 |. 3C 2D             CMP AL,2D                                   ; 把取得的字符与16进制值为2D的字符(即“-”)比较,这里主要用于判断输入的是不是负数
004013E4 |. 75 08             JNZ SHORT CrackHea.004013EE                 ; 不等则跳
004013E6 |. BA FFFFFFFF       MOV EDX,-1                                  ; 如果输入的是负数,则把-1送到EDX,即16进制FFFFFFFF
004013EB |. 8A06              MOV AL,BYTE PTR DS:[ESI]                    ; 取“-”号后的第一个字符
004013ED |. 46                INC ESI                                     ; 指针加1,指向再下一个字符
004013EE |> EB 0B             JMP SHORT CrackHea.004013FB
004013F0 |>  2C 30             SUB AL,30                                   ; 每位字符减16 进制的30,因为这里都是数字,如1的ASCII码是“31H”,减30H后为1,即我们平时看到的数值
004013F2 |. 8D0C89            LEA ECX,DWORD PTR DS:[ECX+ECX*4]            ; 把前面运算后保存在ECX中的结果乘5再送到ECX
004013F5 |. 8D0C48            LEA ECX,DWORD PTR DS:[EAX+ECX*2]            ; 每位字符运算后的值与2倍上一位字符运算后值相加后送ECX
004013F8 |. 8A06              MOV AL,BYTE PTR DS:[ESI]                    ; 取下一个字符
004013FA |. 46                INC ESI                                     ; 指针加1,指向再下一个字符
004013FB |> 0AC0              OR AL,AL
004013FD |.^ 75 F1            JNZ SHORT CrackHea.004013F0                 ; 上面一条和这一条指令主要是用来判断是否已把用户输入的注册码计算完
004013FF |. 8D040A            LEA EAX,DWORD PTR DS:[EDX+ECX]              ; 把EDX中的值与经过上面运算后的ECX中值相加送到EAX
00401402 |. 33C2              XOR EAX,EDX                                 ; 把EAX与EDX异或。如果我们输入的是负数,则此处功能就是把EAX中的值取反
00401404 |. 5E                POP ESI                                     ; ESI出栈。看到这条和下一条指令,我们要考虑一下这个ESI的值是哪里运算得出的呢?
00401405 |. 81F6 53757A79     XOR ESI,797A7553                            ; 把ESI中的值与797A7553H异或
0040140B \. C3                RETN

4 楼

跟进去的代码,大部分都能看得明白吧。004013D5 |. 8D35 C4334000     LEA ESI,DWORD PTR DS:[4033C4]               ; 把注册码框中的数值送到ESI   
这行代码004033C4地址装的是注册码,后文内存断点可以分析出来。
004013F2 |. 8D0C89            LEA ECX,DWORD PTR DS:[ECX+ECX*4]            ; 把前面运算后保存在ECX中的结果乘5再送到ECX
004013F5 |. 8D0C48            LEA ECX,DWORD PTR DS:[EAX+ECX*2]            ; 每位字符运算后的值与2倍上一位字符运算后值相加后送ECX
这2行指令的ECX从何而来,别着急下文内存断点会分析到。
00401405 |. 81F6 53757A79     XOR ESI,797A7553                            ; 把ESI中的值与797A7553H异或
这行指令的异或又是怎么回事,同样内存断点中会分析到。我们接着按 F8 键往下走,来到 0040140B 地址处的那条 RETN 指令(这里可以通过在调试选项的“命令”标签中勾选“使用 RET 代替 RETN”来更改返回指令的显示方式),再按一下 F8,我们就走出 00401328 地址处的那个 CALL 了。现在我们回到了这里:
0040132D |. 3BC6             CMP EAX,ESI                                  ; 比较
0040132F |. 75 42            JNZ SHORT CrackHea.00401373                  ; 不等则完蛋
光标停在了 0040132D 地址处的那条指令上。根据前面的分析,我们知道 EAX 中存放的是我们输入的注册码经过计算后的值。我们来看一下信息窗口:

ESI=E6B5F2F9
EAX=FF439EBE
为什么要计算输入的注册码呢?下篇内存断点会分析到的。左键选择信息窗口中的 ESI=E6B5F2F9,再按右键,在弹出菜单上选“修改寄存器”,我们会看到这样一个窗口:名称修改ESI,里边有十六进制,无符号,有符号3项内容的窗口。其中后边的无符号数,有符号数都是这个16进制寄存器数(esi)的10进制转换,一个无符号,一个有符号。可能你的显示跟我不一样,因为这个 crackme 中已经说了每个机器的序列号不一样。关掉上面的窗口,再对信息窗口中的 EAX=FF439EBE 做同样操作。我们知道了原来前面分析的对我们输入的注册码进行处理后的结果就是把字符格式转为数字格式。我们原来输入的是字串“12345666”,现在转换为了数字 12345666。这下就很清楚了,随便在上面那个修改 ESI 图中显示的有符号或无符号编辑框中复制一个,粘贴到我们调试的程序中的编辑框中试一下。呵呵,成功了。且慢高兴,这个 crackme 是要求写出注册机的。我们先不要求写注册机,但注册的算法我们要搞清楚。还记得我在前面说到的那个 ESI 寄存器值的问题吗?现在看看我们上面的分析,其实对做注册机来说是没有多少帮助的。要搞清注册算法,必须知道上面那个 ESI 寄存器值是如何产生的,这弄清楚后才能真正清楚这个 crackme 算法。今天就先说到这里,关于如何追出 ESI 寄存器的值我就留到下一篇 门系列内存断点 中再讲吧。插一句前边所提到的问题,为什么要对输入的注册码进行计算,原因在于把偶们输入的注册码的字符格式转为数字格式。好了,上篇完毕。

5 楼

这不叫编写吧,应该叫分析吧?!~

6 楼

这个上篇有的部分分析不够透彻,有个问题漏讲了。偶会在下篇内存断点中补上,不好意思啦。各位大虾海涵。

7 楼

不会走哪会跑呢,原理大同小异。好高骛远可不好噢。

8 楼

算了,遗漏之处偶还是先给出来吧。
遗漏部分:(1)上文结尾部分有漏讲之处。ESI寄存器不动,修改EAX寄存器有符号和无符号数都改为12345666,假设刚才输入的是这个注册码。然后再把ESI=E6B5F2F9对应的十进制有符号数或无符号数随便粘一个就可以了。
这里选的是3870683897.这个就是新的注册码。从上文
0040132D |. 3BC6             CMP EAX,ESI                                  ; 比较
0040132F |. 75 42            JNZ SHORT CrackHea.00401373                  ; 不等则完蛋
得知,EAX和ESI必须相等才能给出成功提示。偶们是在0040132D这个地址行修改的EAX寄存器吧。那么每当程序执行到这里的时候,EAX的值OD自动给改过来。重新打开这个程序填入3870683897,点"check it "按钮,才会出现成功提示。
否则程序会在你点"Check it"按钮这个GetWindowTextA处断下来,不会给出成功提示,除非你把断点清掉可以。当重新打开运行程序(但不是重新加载这个程序),程序运行到0040132D这个地址行,EAX的值OD自动给改成你修改后的值。
当你关闭OD之后再输入3870683897,程序就会米反应,因为这个时候修改EAX无效了。断点对重新打开的这个程序米断点作用,它只对OD调试的这个程序有效,但是对寄存器的修改同样有效,只要不关掉OD调试的这个程序。
偶们可以直接看出来,ESI中的值并不是偶们输入对话框中的值,当然它是通过某种转化了的。偶们推测,填入3870683897再经过运算,然后存入ESI中的值是12345666,这样就相等了。真的能够对等吗,
ESI中的值又从何而来,它又是怎么把注册码转化了的呢?看下文:内存断点。
遗漏部分:(2)00401474   $- FF25 2C204000 JMP DWORD PTR DS:[<&USER32.GetWindowText>;  USER32.GetWindowTextA
00401323  |.  E8 4C010000   CALL <JMP.&USER32.GetWindowTextA>        ; \GetWindowTextA
前一句是后一句的子程序,前一句是GetWindowText这个API是怎样执行的,由系统库USER32.DLL自己做,不用你管,所以这里不用设断点。后
一句指令,表示执行GetWindowText这个API操作。什么时候可能执行GetWindowText这个API操作,是偶们关心的问题,所以这里应当设断点。后边的注释
都是OD给好了的。同理:0040136E  |.  E8 19010000   CALL <JMP.&USER32.MessageBoxA>           ; \MessageBoxA

是执行调用对话框API的操作。
遗漏部分:(3)这个软件填写的时候,偶们总不能开着OD去注册吧,要编写原作者口中的注册机。这个值3870683897记住,剩下的问题就是用VB把
EAX的值给改了。注意EAX的值改早了,EAX已经清0了,改晚了EAX值已经运算完了。所以要在点check it调用GetWindowText这个时候改,做个判断。
那么在判断里如何改写EAX呢?这里偶给出VB中嵌入汇编语言的办法,对不起偶忘记了,以后想起来再给出来。在VC里边就方便多了,可以直接写汇编代码,而VB不可以。





我来回复

您尚未登录,请登录后再回复。点此登录或注册