标题:蜘蛛纸牌也来辅助一下
作者:cyane
时间:2009/11/18, 0:32
连接:http://bbs.pediy.com/showthread.php?t=101435
由于工作需要,到了外蒙,到了驻地才发现这里没有网络,连为数不多的电视都是接收的卫星信号,我快崩溃了......
在工作之余,除了领略广袤的大草原的,还时不时的感受着不知从哪个方向刮来的大风,屋外虽算不上冰天雪地,但也是寒冷无比啊,在百无聊赖之际,就有了这篇东西(没什么技术,只是一时兴趣,希望大家不要笑话)。
记得曾在论坛里看到过有人把扫雷改成秒破版的,当时下载下来,挺有意思,想着自己有时间也改个玩玩。这下好了,到了外蒙,除了不缺时间,什么都缺,于是动手改了这个蜘蛛纸牌的游戏
在反编译游戏之前,我先玩了几局,感觉上不是很有意思 :<
首先OD载入,打开后,来看看它调用的API函数吧,有很多,其中BitBlt比较明显,我们首先从它入手吧。可能有人会问为什么要首先在这个函数上设断,玩了这个游戏后你会发现它有很多位图的移动过程,所以免不了要复制图片从一点到另一点,所以我觉得这个函数比较重要,如果这里实在没什么收获,可以再选择嘛,呵呵。
BP BitBlt后
F9运行程序来看看
01002799 |. 8B46 0C mov eax, dword ptr [esi+C]
0100279C |. 2B46 04 sub eax, dword ptr [esi+4]
0100279F |. 8955 E8 mov dword ptr [ebp-18], edx
010027A2 |. 8B55 10 mov edx, dword ptr [ebp+10]
010027A5 |. 3BD0 cmp edx, eax
010027A7 |. 8955 18 mov dword ptr [ebp+18], edx
010027AA |. 0F8D BF000000 jge 0100286F
010027B0 |> 8B55 14 /mov edx, dword ptr [ebp+14]
010027B3 |. 8B5D 18 |mov ebx, dword ptr [ebp+18]
010027B6 |. 3B5A 0C |cmp ebx, dword ptr [edx+C]
010027B9 |. 0F8D B0000000 |jge 0100286F
010027BF |. 8B55 EC |mov edx, dword ptr [ebp-14]
010027C2 |. 8B5D 10 |mov ebx, dword ptr [ebp+10]
010027C5 |. 03D3 |add edx, ebx
010027C7 |. 3955 18 |cmp dword ptr [ebp+18], edx
010027CA |. 7D 10 |jge short 010027DC
010027CC |. 8B55 EC |mov edx, dword ptr [ebp-14]
010027CF |. 6A 40 |push 40
010027D1 |. 58 |pop eax
010027D2 |. 2BC2 |sub eax, edx
010027D4 |. 8955 F8 |mov dword ptr [ebp-8], edx
010027D7 |. 8945 F0 |mov dword ptr [ebp-10], eax
010027DA |. EB 1B |jmp short 010027F7
010027DC |> 8B55 E8 |mov edx, dword ptr [ebp-18]
010027DF |. 8365 F0 00 |and dword ptr [ebp-10], 0
010027E3 |. 2BC2 |sub eax, edx
010027E5 |. 2B45 10 |sub eax, dword ptr [ebp+10]
010027E8 |. 8955 F8 |mov dword ptr [ebp-8], edx
010027EB |. 3945 18 |cmp dword ptr [ebp+18], eax
010027EE |. 74 07 |je short 010027F7
010027F0 |. C745 F8 40000>|mov dword ptr [ebp-8], 40
010027F7 |> 8B5D 0C |mov ebx, dword ptr [ebp+C]
010027FA |. EB 5A |jmp short 01002856
010027FC |> 8B45 14 |/mov eax, dword ptr [ebp+14]
010027FF |. 3B58 08 ||cmp ebx, dword ptr [eax+8]
01002802 |. 7D 56 ||jge short 0100285A
01002804 |. 8B55 0C ||mov edx, dword ptr [ebp+C]
01002807 |. 8D0417 ||lea eax, dword ptr [edi+edx]
0100280A |. 3BD8 ||cmp ebx, eax
0100280C |. 7D 0A ||jge short 01002818
0100280E |. 6A 3F ||push 3F
01002810 |. 58 ||pop eax
01002811 |. 897D FC ||mov dword ptr [ebp-4], edi
01002814 |. 2BC7 ||sub eax, edi
01002816 |. EB 17 ||jmp short 0100282F
01002818 |> 8B45 E4 ||mov eax, dword ptr [ebp-1C]
0100281B |. 2BC8 ||sub ecx, eax
0100281D |. 2BCA ||sub ecx, edx
0100281F |. 3BD9 ||cmp ebx, ecx
01002821 |. 8945 FC ||mov dword ptr [ebp-4], eax
01002824 |. 74 07 ||je short 0100282D
01002826 |. C745 FC 3F000>||mov dword ptr [ebp-4], 3F
0100282D |> 33C0 ||xor eax, eax
0100282F |> 68 2000CC00 ||push 0CC0020 ; /ROP = SRCCOPY
01002834 |. FF75 F0 ||push dword ptr [ebp-10] ; |YSrc
01002837 |. 50 ||push eax ; |XSrc
01002838 |. FF75 F4 ||push dword ptr [ebp-C] ; |hSrcDC
0100283B |. FF75 F8 ||push dword ptr [ebp-8] ; |Height
0100283E |. FF75 FC ||push dword ptr [ebp-4] ; |Width
01002841 |. FF75 18 ||push dword ptr [ebp+18] ; |YDest
01002844 |. 53 ||push ebx ; |XDest
01002845 |. FF75 08 ||push dword ptr [ebp+8] ; |hDestDC
01002848 |. FF15 7C100001 ||call dword ptr [<&GDI32.BitBlt>] ; \BitBlt
0100284E |. 8B4E 08 ||mov ecx, dword ptr [esi+8]
01002851 |. 035D FC ||add ebx, dword ptr [ebp-4]
01002854 |. 2B0E ||sub ecx, dword ptr [esi]
01002856 |> 3BD9 | cmp ebx, ecx
01002858 |.^ 7C A2 |\jl short 010027FC
0100285A |> 8B45 F8 |mov eax, dword ptr [ebp-8]
0100285D |. 0145 18 |add dword ptr [ebp+18], eax
01002860 |. 8B46 0C |mov eax, dword ptr [esi+C]
01002863 |. 2B46 04 |sub eax, dword ptr [esi+4]
01002866 |. 3945 18 |cmp dword ptr [ebp+18], eax
01002869 |.^ 0F8C 41FFFFFF \jl 010027B0
首先是在这里被断下了,我们先来看看这个函数体,有没有什么信息,往上瞧瞧
0100271F |. 53 push ebx
01002720 |. 56 push esi
01002721 |. 57 push edi
01002722 |. FF75 08 push dword ptr [ebp+8] ; /hDC
01002725 |. FF15 74100001 call dword ptr /<CreateCompatibleDC>
0100272B |. 68 BC120001 push 010012BC ; /RsrcName = "FELT"
01002730 |. FF35 00200101 push dword ptr [1012000] ; |hInst = 01000000
01002736 |. 8945 F4 mov dword ptr [ebp-C], eax ; |
01002739 |. FF15 80120001 call dword ptr [<&USER32.LoadBitmapW>>;
0100273F |. 50 push eax ; /hObject
01002740 |. FF75 F4 push dword ptr [ebp-C] ; |hDC
01002743 |. 8945 DC mov dword ptr [ebp-24], eax ; |
01002746 |. FF15 78100001 call dword ptr [<&GDI32.SelectObject>>;
0100274C |. 8B75 18 mov esi, dword ptr [ebp+18]
0100274F |. 8B1E mov ebx, dword ptr [esi]
01002751 |. 8B4D 14 mov ecx, dword ptr [ebp+14]
恩,有了新的发现,有个LoadBitmap函数,这是加载位图的函数,加载的位图名称已经显示出来了,就是那个"FELT",用RESHACKER打开程序看看,在位图资源处应该会看到这个名为"FELT"的图片,只是用我的RESHACKER没有打开,换eXeScope试试,可以看到这个图片就是程序的背景图片,还有52张扑克图片,扑克的点数图片咱就不说了,"CARDBACK"是蜘蛛的扑克背景图片,标识号106的是关于对话框的图片,标识号108的是放置扑克的图片,这里只要一一对照知道是哪些相互对应就可以了。
可以看到"FELT"应该是程序的背景图片,这里可以大胆猜猜测一下,程序中当前循环的目的就是画出程序的背景,这里可以先放一下,把这里BitBlt的断点先取消,接着F9往下看看
010028A7 |. 8945 FC mov dword ptr [ebp-4], eax
010028AA |. 8B45 14 mov eax, dword ptr [ebp+14]
010028AD |. 83F8 01 cmp eax, 1
010028B0 |. 56 push esi
010028B1 |. 8B75 08 mov esi, dword ptr [ebp+8]
010028B4 |. 57 push edi
010028B5 |. 8975 D0 mov dword ptr [ebp-30], esi
//[ebp-30]=esi
010028B8 |. 7C 1D jl short 010028D7
010028BA |. 83F8 34 cmp eax, 34
010028BD |. 7F 18 jg short 010028D7
//如果EAX<[ebp-30]或EAX>0x34(也就是52)跳到10028D7
//如果上述不满足向下走
010028BF |. 50 push eax ; /<%d>
010028C0 |. 8D45 D4 lea eax, dword ptr [ebp-2C] ; |
010028C3 |. 68 DC120001 push 010012DC ; |Format = "CARD%d"
010028C8 |. 50 push eax ; |s
010028C9 |. FF15 84120001 call dword ptr [<&USER32.wsprintfW>] ; \wsprintfW
//以Format函数格式输出,这里可以应该是 输出“CARD1”~“CARD52”
010028CF |. 83C4 0C add esp, 0C
010028D2 |. 8D45 D4 lea eax, dword ptr [ebp-2C]
010028D5 |. EB 1B jmp short 010028F2
//上面不符合条件的跳转到这里
010028D7 |> 83F8 69 cmp eax, 69
010028DA |. 75 07 jnz short 010028E3
010028DC |. 68 BC120001 push 010012BC ; UNICODE "FELT"
010028E1 |. EB 10 jmp short 010028F3
010028E3 |> 83F8 68 cmp eax, 68
010028E6 |. 75 07 jnz short 010028EF
010028E8 |. 68 C8120001 push 010012C8 ; UNICODE "CARDBACK"
//可以看到会压入堆栈1个值,这里可以看到FELT 或 CARDBACK 其中之一
//从上面对于图片的分析知道,这是2幅图片的值
//如果不是这2幅图片则向下跳转到10028F3
010028ED |. EB 04 jmp short 010028F3
010028EF |> 0FB7C0 movzx eax, ax
010028F2 |> 50 push eax
//把刚才得到的CARD? 或 FELT 或 CARDBACK压入堆栈
010028F3 |> FF35 00200101 push dword ptr [1012000] ; |hInst = 01000000
010028F9 |. FF15 80120001 call dword ptr [<&USER32.LoadBitmapW>>;
//用LoadBitmap来读取EAX中指定的图片
010028FF |. 56 push esi ; /hDC
01002900 |. 8945 CC mov dword ptr [ebp-34], eax ; |
01002903 |. FF15 74100001 call dword ptr [<&GDI32.CreateCompati>;
01002909 |. FF75 CC push dword ptr [ebp-34] ; /hObject
0100290C |. 8B3D 78100001 mov edi, dword ptr [<&GDI32.SelectOb>; |
01002912 |. 8BF0 mov esi, eax ; |
01002914 |. 56 push esi ; |hDC
01002915 |. FFD7 call edi ;| SelectObject
01002917 |. 8B5D 0C mov ebx, dword ptr [ebp+C]
0100291A |. 68 2000CC00 push 0CC0020 ; /ROP = SRCCOPY
0100291F |. 6A 00 push 0 ; |YSrc = 0
01002921 |. 6A 00 push 0 ; |XSrc = 0
01002923 |. 56 push esi ; |hSrcDC
01002924 |. 6A 60 push 60 ; |Height = 60 (96.)
01002926 |. 6A 47 push 47 ; |Width = 47 (71.)
01002928 |. FF75 10 push dword ptr [ebp+10] ; |YDest
0100292B |. 8945 C8 mov dword ptr [ebp-38], eax ; |
0100292E |. 53 push ebx ; |XDest
0100292F |. FF75 D0 push dword ptr [ebp-30] ; |hDestDC
01002932 |. FF15 7C100001 call dword ptr [<&GDI32.BitBlt>]; |
//拷贝指定的图片到某个位置
恩,分析这么多差不多了,把这里的断点取消,F9再往下看,我们会发现程序已经出现窗口了,出现了可以供选择难易程度的对话框,我们先选择 "初级",然后点击"确定"看看
我们会发现程序又被断下了,由于下面的程序段比较长,我省略一部分
01005CEA |. 8B35 74100001 mov esi, dword ptr [<&GDI32.CreateCo>;
01005CF0 |. 53 push ebx ; /hDC
01005CF1 |. FFD6 call esi ; \CreateCompatibleDC
01005CF3 |. 53 push ebx ; /hDC
01005CF4 |. 8945 A8 mov dword ptr [ebp-58], eax ; |
01005CF7 |. FFD6 call esi ; \CreateCompatibleDC
01005CF9 |. 68 C8120001 push 010012C8 ; /RsrcName = "CARDBACK"
//这里是CARDBACK后面有LoadBitmap函数,应该就是装载CARDBACK图片的
01005CFE |. FF35 00200101 push dword ptr [1012000] ; |hInst = 01000000
01005D04 |. 8945 B8 mov dword ptr [ebp-48], eax ; |
01005D07 |. FF15 80120001 call dword ptr [<&USER32.LoadBitmapW>>;
01005D0D |. 50 push eax ; /hObject
01005D0E |. FF75 B8 push dword ptr [ebp-48] ; |hDC
01005D11 |. 8945 94 mov dword ptr [ebp-6C], eax ; |
01005D14 |. FF15 78100001 call dword ptr [<&GDI32.SelectObject>>; \
.........................................................
01005F0E |> \8B45 C8 ||mov eax, dword ptr [ebp-38]
//EAX=[EBP-38]
01005F11 |. 83F8 68 ||cmp eax, 68
01005F14 |. 75 29 ||jnz short 01005F3F
//EAX与0x68比较,还记得0x68和0x69吗?
//呵呵 就是那2幅图片啊,如果忘记了,看看我们前面分析的那个函数段吧
//如果不是CARDBACK图片就向下到1005f3f
01005F16 |. 394D A0 ||cmp dword ptr [ebp-60], ecx
01005F19 |. 68 2000CC00 ||push 0CC0020
01005F1E |. 51 ||push ecx
01005F1F |. 51 ||push ecx
01005F20 |. FF75 B8 ||push dword ptr [ebp-48]
//[ebp-60]不为0,就PUSH 60
01005F23 |. 75 16 ||jnz short 01005F3B
01005F25 |. 6A 09 ||push 9
01005F27 |> 6A 47 ||push 47 ; |Width = 47 (71.)
01005F29 |. FF75 CC ||push dword ptr [ebp-34] ; |YDest
01005F2C |. FF75 D0 ||push dword ptr [ebp-30] ; |XDest
01005F2F |. 53 ||push ebx ; |hDestDC
01005F30 |. FF15 7C100001 ||call dword ptr [<&GDI32.BitBlt>] ; \BitBlt
//分析知道程序根据[EBP-60]和ECX的数值进行比较,从而确定绘制的图高0x60或者是9
//这里我们可以理解成绘制一幅位图或者是绘制一幅图的一部分
01005F36 |. E9 84000000 ||jmp 01005FBF
01005F3B |> 6A 60 ||push 60
01005F3D |.^ EB E8 ||jmp short 01005F27
//上面知道,如果不是CARDBACK图片就到这里了,再接着分析
01005F3F |> 83F8 01 ||cmp eax, 1
01005F42 |. 7C 1D ||jl short 01005F61
01005F44 |. 83F8 34 ||cmp eax, 34
01005F47 |. 7F 18 ||jg short 01005F61
//EAX如果小于1,或大于52,就跳到1005F61
//EAX如果是 1~52 时,这个值也是可以查到(eXeScope)
01005F49 |. 50 ||push eax ; /<%d>
01005F4A |. 8D45 D4 ||lea eax, dword ptr [ebp-2C] ; |
01005F4D |. 68 DC120001 ||push 010012DC ; |Format = "CARD%d"
01005F52 |. 50 ||push eax ; |s
01005F53 |. FF15 84120001 ||call dword ptr [<&USER32.wsprintfW>>; \wsprintfW
//如果EAX属于1~52 之间就输入 CARD1~CARD52,和刚才分析过的程序段一样吧
01005F59 |. 83C4 0C ||add esp, 0C
01005F5C |. 8D45 D4 ||lea eax, dword ptr [ebp-2C]
01005F5F |. EB 0F ||jmp short 01005F70
01005F61 |> 83F8 69 ||cmp eax, 69
01005F64 |. 75 07 ||jnz short 01005F6D
01005F66 |. 68 BC120001 ||push 010012BC ; UNICODE "FELT"
//这里是EAX 和 0x69的比较,就是是否画出图FELT
01005F6B |. EB 04 ||jmp short 01005F71
01005F6D |> 0FB7C0 ||movzx eax, ax
01005F70 |> 50 ||push eax
01005F71 |> FF35 00200101 ||push dword ptr [1012000] ; |hInst = 01000000
01005F77 |. FF15 80120001 ||call dword ptr [<&USER32.LoadBitmap>;
01005F7D |. 50 ||push eax ; /hObject
01005F7E |. FF75 A8 ||push dword ptr [ebp-58] ; |hDC
01005F81 |. 8945 A4 ||mov dword ptr [ebp-5C], eax ; |
01005F84 |. FF15 78100001 ||call dword ptr [<&GDI32.SelectObjec>; \
01005F8A |. 68 2000CC00 ||push 0CC0020 ; /ROP = SRCCOPY
01005F8F |. 6A 00 ||push 0 ; |YSrc = 0
01005F91 |. 6A 00 ||push 0 ; |XSrc = 0
01005F93 |. FF75 A8 ||push dword ptr [ebp-58] ; |hSrcDC
01005F96 |. 8945 90 ||mov dword ptr [ebp-70], eax ; |
01005F99 |. 6A 60 ||push 60 ; |Height = 60 (96.)
01005F9B |. 6A 47 ||push 47 ; |Width = 47 (71.)
01005F9D |. FF75 CC ||push dword ptr [ebp-34] ; |YDest
01005FA0 |. FF75 D0 ||push dword ptr [ebp-30] ; |XDest
01005FA3 |. 53 ||push ebx ; |hDestDC
01005FA4 |. FF15 7C100001 ||call dword ptr [<&GDI32.BitBlt>] ; \BitBlt
根据这里的BitBlt中Height和Width 2个值我们可以分析出每张扑克牌的高度是0x60,宽度是0x47,这里我们可以截取一幅扑克图片的大小来比较一下,可以知道这个数值确实是单张扑克的尺寸,这里我们就可以猜测一下了,函数的功能就是绘制左边这幅图。
这里我们再次取消BitBlt的断点后,F9运行,可以发现程序已经运行,没有再被断下来了。
呵呵,好了,初步分析可以告一段落了,这里我们首先来分析一下,最初的那个疑问,首个BitBlt是否是用来画出背景的呢?这里可以用个简单的方法,根据我们刚才的分析知道
push 010012BC 对应的是 "FELT"
push 010012C8 对应的是 "CARDBACK"
这里我们可以把"FELT"替换成"CARDBACK"试一下,恩,效果是很明显的。
这样刚才的分析应该大体都成立,因为都用到了LoadBitmap来加载图片,而且我们可以分析具体显示的是哪幅位图,思路应该没什么问题。
下面我们再来具体的分析一下,刚才所遇到的每个被断下位置处的函数体的作用。看能不能更快找到关键地方呢(下面我所说的函数体一词,指的是包含BitBlt函数的那个整体函数,即PUSH EBP, MOV EBP, ESP下的所有部分)。
首先分析第一个函数,从具体功能来说,我们分析出它会绘制出程序的背景,试想一下,它和后面我们遇到的函数比较而言,其中不太可能有产生扑克点数函数,恩,可以先放一下。
再来分析一下遇到的第二个函数,可以看到wsprintfw函数将输出CARD1~CARD52间的某个值,也就是对应扑克牌中的具有具体花色和具体点数的牌,这可以从上面分析中得知(好像挺啰嗦:)这里我们试想一下,程序的工作流程应该是先初始化产生一组随机数,然后通过随机数来计算从而得到要输出的扑克,然后来才会在画面上绘制出具体的扑克。有了这个思想,我们来看一下wsprintfw函数距离函数体开始处的位置,可以发现距离是很近的,可以断定这里不可能有初始化的函数,只可能是初始化好后,程序运行到这里把位图拷贝到桌面才对。
好的,我们再来看一下第三个函数吧,根据我们刚才的思路,这第三个参数是最有可能的,我们就先把注意力集中到这里吧。
我们先在地址01005FA4 这里的BitBlt下断点,其他的断点都先删除,经过跟踪,我们发现一直都是在一个循环体内跑,而且只要想点开程序窗体,马上又被断下来了,仔细想想,BitBlt这个函数肯定是不断的被用来重画画面的,如果一直这样循环怎么行呢,恩,我们再想想有没有什么其他的方法呢?恩,有的,就是wsprintfw函数,可以想到只有当显示扑克点数的时候它才会运行到这里,所以这里应该不会总是处在循环状态,我们来测试一下,就在wsprintfw函数下断点,其他的断点删除,下面是堆栈显示的图片。
很明显这张应该是CARD44,应该是 黑桃5。
然后我们F8单步来走,当函数跳回到开始处时,发现了一个特别的CALL,经验证这是个关键CALL
01005DEE |. 3945 BC ||cmp dword ptr [ebp-44], eax
01005DF1 |. 0F9DC1 ||setge cl
01005DF4 |. 894D A0 ||mov dword ptr [ebp-60], ecx
01005DF7 |. 8B4D C4 ||mov ecx, dword ptr [ebp-3C]
01005DFA |. E8 17CFFFFF ||call 01002D16 ; 关键CALL
01005DFF |. 8945 C8 ||mov dword ptr [ebp-38], eax
//那我们F7跟进,看看
01002D16 /$ 8BFF mov edi, edi GDI32.SetPixel
01002D18 |. 55 push ebp
01002D19 |. 8BEC mov ebp, esp
01002D1B |. 837D 0C FF cmp dword ptr [ebp+C], -1
01002D1F |. 56 push esi
01002D20 |. 8BF1 mov esi, ecx
01002D22 |. 75 05 jnz short 01002D29
01002D24 |. 6A 6C push 6C
01002D26 |. 58 pop eax
01002D27 |. EB 42 jmp short 01002D6B
01002D29 |> 8B4E 08 mov ecx, dword ptr [esi+8] //关键地址
01002D2C |. 57 push edi
01002D2D |. FF75 0C push dword ptr [ebp+C] //扑克的行数
01002D30 |. FF75 08 push dword ptr [ebp+8] //扑克的列数
01002D33 |. E8 97510000 call 01007ECF //关键函数1
01002D38 |. 8B4E 04 mov ecx, dword ptr [esi+4]
01002D3B |. 8BF8 mov edi, eax //edi=eax
01002D3D |. 57 push edi
01002D3E |. E8 DB460000 call 0100741E
01002D43 |. 85C0 test eax, eax
01002D45 74 20 je short 01002D67 //关键跳转
//这里如果NOP掉,程序上端的扑克都将正面向上
01002D47 |. 8B76 04 mov esi, dword ptr [esi+4]
01002D4A |. 53 push ebx
01002D4B |. 57 push edi
01002D4C |. 8BCE mov ecx, esi
01002D4E |. E8 E6460000 call 01007439 //关键函数2
01002D53 |. 8BD8 mov ebx, eax
01002D55 |. 57 push edi
01002D56 |. 6BDB 0D imul ebx, ebx, 0D
01002D59 |. 8BCE mov ecx, esi
01002D5B |. E8 F3460000 call 01007453 //关键函数3
01002D60 |. 8D4403 01 lea eax, dword ptr [ebx+eax+1]
01002D64 |. 5B pop ebx
01002D65 |. EB 03 jmp short 01002D6A
01002D67 |> 6A 68 push 68
01002D69 |. 58 pop eax
01002D6A |> 5F pop edi
01002D6B |> 5E pop esi
01002D6C |. 5D pop ebp
01002D6D \. C2 0800 retn 8
下面我们具体分析每个关键的作用
01002D45地址处的跳转控制这程序上端的扑克是否处于正面显示状态,如果NOP掉,可以看到那些图片都正面向上了
最初在这里我自己改写了一下,把间距拉大了一些,好看了一些,见下图
后来分析出计算扑克点数的地址后,就没再用这种方法了,后面的方法更好用,呵呵
[ESP+C]表示显示扑克的行数
[ESP+8]表示显示扑克的列数
接下来看一下 关键函数1吧 F7跟进
01007ECF /$ 8BFF mov edi, edi GDI32.SetPixel
01007ED1 |. 55 push ebp
01007ED2 |. 8BEC mov ebp, esp
01007ED4 |. 8B45 08 mov eax, dword ptr [ebp+8] ①
//EAX=[EBP+8]是扑克的列数
01007ED7 |. 8B0C81 mov ecx, dword ptr [ecx+eax*4] ②
//这里需要用到上个函数的关键地址了,这里指向另一个地址
//这里可以看成是一个数组在取其中的某个值
01007EDA |. 85C9 test ecx, ecx
01007EDC |. 74 10 je short 01007EEE
01007EDE |. FF75 0C push dword ptr [ebp+C]//扑克的行数为内循环的次数
01007EE1 |. E8 0CFFFFFF call 01007DF2
01007EE6 |. 85C0 test eax, eax
01007EE8 |. 74 04 je short 01007EEE
01007EEA |. 8B00 mov eax, dword ptr [eax] ③
01007EEC |. EB 03 jmp short 01007EF1
01007EEE |> 83C8 FF or eax, FFFFFFFF
01007EF1 |> 5D pop ebp
01007EF2 \. C2 0800 retn 8
在地址01007EE1 还有个CALL 跟进
01007DF2 /$ 8BFF mov edi, edi ; GDI32.SetPixel
01007DF4 |. 55 push ebp
01007DF5 |. 8BEC mov ebp, esp
01007DF7 |. 8B01 mov eax, dword ptr [ecx] ④
01007DF9 |. 33D2 xor edx, edx
01007DFB |. 3955 08 cmp dword ptr [ebp+8], edx
01007DFE |. 7E 0D jle short 01007E0D
01007E00 |> 85C0 /test eax, eax
01007E02 |. 74 09 |je short 01007E0D
01007E04 |. 8B40 08 |mov eax, dword ptr [eax+8] ⑤
01007E07 |. 42 |inc edx
01007E08 |. 3B55 08 |cmp edx, dword ptr [ebp+8] //注意这里是循环体
01007E0B |.^ 7C F3 \jl short 01007E00
01007E0D |> 5D pop ebp
01007E0E \. C2 0400 retn 4
这里我们把几个关键的语句拿下来组在一起看吧
01007ED4 |. 8B45 08 mov eax, dword ptr [ebp+8] ①
//EAX=扑克的列数
01007ED7 |. 8B0C81 mov ecx, dword ptr [ecx+eax*4] ②
//ECX=[ECX+EAX*4]的偏移地址
01007DF7 |. 8B01 mov eax, dword ptr [ecx] ④
//EAX=[ECX]的偏移地址
01007E04 |. 8B40 08 mov eax, dword ptr [eax+8] ⑤
//EAX=EAX+8
01007EEA |. 8B00 mov eax, dword ptr [eax] ③
//EAX=[EAX]的偏移地址
//别搞混了⑤ ③ 可不是连续的!
这个关系很明确了吧,这就是这2个CALL的具体作用
分析完 关键函数1 在往下看,不就是EDI=EAX了吗,这里先保存这个EDI的数值
再来F7进入 关键函数2 吧
01007439 /$ 8BFF mov edi, edi
0100743B |. 55 push ebp
0100743C |. 8BEC mov ebp, esp
0100743E |. 8B45 08 mov eax, dword ptr [ebp+8]
01007441 |. 8B49 0C mov ecx, dword ptr [ecx+C]
01007444 |. 8D0440 lea eax, dword ptr [eax+eax*2]
01007447 |. 8B0481 mov eax, dword ptr [ecx+eax*4]
0100744A |. 5D pop ebp
0100744B \. C2 0400 retn 4
可以看到主要的就4句,意思和上面刚分析的差不多,这里主要是得到EAX的数值
出了关键函数2后,会发现EBX=EAX, EBX=EBX*0xD,保存EBX,下面会用到的。
接着进入关键函数3
01007453 /$ 8BFF mov edi, edi
01007455 |. 55 push ebp
01007456 |. 8BEC mov ebp, esp
01007458 |. 8B45 08 mov eax, dword ptr [ebp+8]
0100745B |. 8B49 0C mov ecx, dword ptr [ecx+C]
0100745E |. 8D0440 lea eax, dword ptr [eax+eax*2]
01007461 |. 8B4481 04 mov eax, dword ptr [ecx+eax*4+4]
01007465 |. 5D pop ebp
01007466 \. C2 0400 retn 4
这里的主要也是4句,分析同上即可,最后得到EAX的数值,退出关键函数3后,
01002D60 |. 8D4403 01 lea eax, dword ptr [ebx+eax+1]
//EAX=EBX+EAX+1
这里看一下EAX的10进制数值,呵呵,是不是感觉很熟悉,恩,这就是扑克显示的点数,和资源里设置的是一样的,可以从wsprintfw函数中观察到。
至此,分析的应该差不多了,写个测试程序应该很简单的,于是我就写了一个测试,很奇怪的是OD调试纸牌程序的时候,测试程序好用,一旦,单独打开纸牌程序,测试程序就不好用了?恩,奇怪啊,后来发现是
01002D29 |> 8B4E 08 mov ecx, dword ptr [esi+8] //关键地址
这一句的问题,我取的ecx地址是动态改变的,而[esi+8]=[01012010]是程序的固定地址,在我电脑上显示的是1012010这个数值,应该是全局变量,呵呵,改好地址后,测试程序成功了。
这里贴一下我用VB 写的测试程序吧。
Dim AppHandle As Long '程序句柄
Dim ProcessID As Long '进程PID
Dim proHandle As Long '进程句柄
Dim ReadData(10) As Long '读取内存数据的数组
Dim lAddress As Long '暂存地址
Dim ESI4 As Long '[ESI+4]
Dim ESI8 As Long '[ESI+8]
Dim ESIC As Long '[ESI+C]
Dim n As Integer '行数变量
Dim i As Integer '列数变量
Dim m As Integer '临时变量
Dim I2 As Integer '临时变量
Dim J As Integer '临时变量
Dim B(109) As Long '计算每张扑克用到的变量的地址
List1.Clear
AppHandle = FindWindow(vbNullString, "蜘蛛")
If AppHandle = 0 Then
Form1.Caption = "游戏辅助--游戏没有启动"
Else
Form1.Caption = "游戏辅助--连接游戏成功"
GetWindowThreadProcessId WndHandle, ProcessID '获取PID
If ProcessID = 0 Then
MsgBox "获取程序PID错误"
Else
proHandle = OpenProcess(PROCESS_ALL_ACCESS, False, ProcessID)
'获取进程句柄
ReadProcessMemory proHandle, ByVal &H101200C, ReadData(0), 4, 0&
ESI4 = ReadData(0)
ReadProcessMemory proHandle, ByVal &H1012010, ReadData(0), 4, 0&
ESI8 = ReadData(0)
ReadProcessMemory proHandle, ByVal &H1012014, ReadData(0), 4, 0&
ESIC = ReadData(0)
'保存了关键的地址
For n = 0 to 9 '行数
List1.AddItem "第" & n + 1 & "列"
For i = 1 To 11 '列数
EAX = EBX = ECX = EDI = 0 '初始化
If (J = 0) Then
lAddress = ESI8 + n * 4 'EAX*4
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0)
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0)
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
EDI = ReadData(0) '保存EDI
Else
lAddress = ESI8 + n * 4 'EAX*4
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0)
For I2 = 1 To J '根据列数循环查找对应的地址
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0) + 8
Next I2
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0)
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
EDI = ReadData(0) '保存EDI
End If
J = J + 1
EAX = EDI
lAddress = ESI4 + 12
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0) + (EAX + EAX * 2) * 4
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
EBX = ReadData(0) * 13
lAddress = ESI4 + 12
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
lAddress = ReadData(0) + (EAX + EAX * 2) * 4 + 4
'B(m) = lAddress '定义的一个地址数组B(109)存放的所有地址
ReadProcessMemory proHandle, ByVal lAddress, ReadData(0), 4, 0&
EAX = EBX + ReadData(0) + 1
List1.AddItem 'CARD' & EAX '输出为CARD?的字符
m = m + 1
Next i
J = 0
Next n
CloseHandle (proHandle)
End If
End If
以上是读取每张扑克的点数,程序很简陋,只是用来测试一下的。 程序中可以注意到我定义了B(109)的数组,用来存储地址,所以就可以来修改内存,这样就可以把每张牌修改成自己想要的点数
程序格式如下
WriteProcessMemory proHandle, ByVal B(0), 0, 4, 0& '这里0表示A的意思
WriteProcessMemory proHandle, ByVal B(109), 1, 4, 0& '1表示2的意思
其他的同理修改即可。
InvalidateRect AppHandle, rect, True '用来刷新屏幕(rect取窗体大小即可)
发个图给大家看一下吧,如下图
经过测试可以在初级中实现秒级破解,在中级和高级中效果一样,不过会涉及到扑克牌花色的影响,可以再改进的,这里我没有在继续了,思路都是差不多的,有兴趣的可以试试。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)