首页
社区
课程
招聘
突破两个实例限制-----一次不完美的PEDIY
发表于: 2005-5-13 22:37 11793

突破两个实例限制-----一次不完美的PEDIY

2005-5-13 22:37
11793

突破两个实例限制
                                               -----一次不完美的PEDIY
【破解作者】 winndy [FCG][PYG]
【作者邮箱】 CNwinndy@hotmail.com
【使用工具】 PEID v0.93  OllyDbg v1.10 fly修改版
【破解平台】 Winxp SP2
【软件名称】 天骄2外挂--菲菲0502版
【官方网址】 http://bbs.tj2wg.com/
【编写语言】 VC6
【软件介绍】 不用多说了...
【破解声明】 For study ,For Fun,
【破解说明】 只能运行两个实例,无壳,0510版的是Armadillo 3.78 -> Silicon Realms Toolworks加的壳,不会脱,
             所以找这个0502来揉一揉...
             玩到最后,可以运行3个实例,但是第3个实例运行,hook住天骄后,不能启动热键F12,...
             最后实在找不出原因,不过想想.这次不完美的pediy,也费了不少周折,自己写个总结吧...
             失误之处还请各位老大指出!
【破解过程】
             OD载入菲菲,反键,"搜索"--"所有参考文本串",
             在下面设断点,然后观察和比较,实例一和实例二的代码的区别,以及寄存器的状态等值.

004047DD    .  8B83 580F0000 mov eax,dword ptr ds:[ebx+F58]
004047E3    .  8B4B 20       mov ecx,dword ptr ds:[ebx+20]
004047E6    .  33ED          xor ebp,ebp
004047E8    .  50            push eax                            ; /lParam
004047E9    .  55            push ebp                            ; |wParam => 0
004047EA    .  68 80000000   push 80                             ; |Message = WM_SETICON
004047EF    .  51            push ecx                            ; |hWnd
004047F0    .  FFD6          call esi                            ; \SendMessageA
004047F2    .  8B35 24604100 mov esi,dword ptr ds:[<&KERNEL32.Op>;  kernel32.OpenMutexA
004047F8    .  68 ACA24100   push TJMan9.0041A2AC                ; /MutexName = "TJMANRunOnlyOneInstance1"
004047FD    .  55            push ebp                            ; |Inheritable => FALSE
004047FE    .  68 01001F00   push 1F0001                         ; |Access = 1F0001
00404803    .  89AB 54020000 mov dword ptr ds:[ebx+254],ebp      ; |
00404809    .  FFD6          call esi                            ; \OpenMutexA
0040480B    .  51            push ecx
0040480C    .  3BC5          cmp eax,ebp
0040480E    .  8983 00030000 mov dword ptr ds:[ebx+300],eax
00404814    .  8BCC          mov ecx,esp
00404816       75 7E         jnz short TJMan9.00404896                                ;实例1已运行,去运行实例2

00404818    .  896424 14              mov dword ptr ss:[esp+14],esp

//d esp
//00125C30  71 FB 92 7C              q?|.

0040481C    .  68 A0A24100            push TJMan.0041A2A0                             ;  ASCII "外挂1开!"
00404821    .  E8 5CF20000            call <jmp.&MFC42.#537>
00404826    .  8BCB                   mov ecx,ebx                                     ; |
00404828    .  E8 430B0000            call TJMan.00405370                             ; \TJMan.00405370
0040482D    .  BF 8CA24100            mov edi,TJMan.0041A28C                          ;  ASCII "TJ2HANDLE1byParker"
00404832    .  83C9 FF                or ecx,FFFFFFFF
00404835    .  33C0                   xor eax,eax
00404837    .  8D5424 24              lea edx,dword ptr ss:[esp+24]
0040483B    .  F2:AE                  repne scas byte ptr es:[edi]
0040483D    .  F7D1                   not ecx
0040483F    .  2BF9                   sub edi,ecx
00404841    .  68 ACA24100            push TJMan.0041A2AC                             ; /MutexName = "TJMANRunOnlyOneInstance1"
00404846    .  8BC1                   mov eax,ecx                                     ; |
00404848    .  8BF7                   mov esi,edi                                     ; |
0040484A    .  8BFA                   mov edi,edx                                     ; |
0040484C    .  55                     push ebp                                        ; |InitialOwner
0040484D    .  C1E9 02                shr ecx,2                                       ; |
00404850    .  F3:A5                  rep movs dword ptr es:[edi],dword ptr ds:[esi]  ; |
00404852    .  8BC8                   mov ecx,eax                                     ; |
00404854    .  55                     push ebp                                        ; |pSecurity
00404855    .  83E1 03                and ecx,3                                       ; |
00404858    .  F3:A4                  rep movs byte ptr es:[edi],byte ptr ds:[esi]    ; |
0040485A    .  FF15 20604100          call dword ptr ds:[<&KERNEL32.CreateMutexA>]    ; \CreateMutexA
00404860    .  3BC5                   cmp eax,ebp
00404862    .  8983 00030000          mov dword ptr ds:[ebx+300],eax
00404868    .  74 11                  je short TJMan.0040487B
0040486A    .  FF15 1C604100          call dword ptr ds:[<&KERNEL32.GetLastError>]    ; [GetLastError
00404870    .  3D B7000000            cmp eax,0B7
00404875    .  0F85 C9000000          jnz TJMan.00404944
0040487B    >  55                     push ebp
0040487C    .  55                     push ebp
0040487D    .  68 70A24100            push TJMan.0041A270                             ;  ASCII "系统错误1,外挂不能运行!"
00404882    .  E8 3FF00000            call <jmp.&MFC42.#1200>
00404887    .  8B13                   mov edx,dword ptr ds:[ebx]
00404889    .  8BCB                   mov ecx,ebx
0040488B    .  FF92 CC000000          call dword ptr ds:[edx+CC]
00404891    .  E9 AE000000            jmp TJMan.00404944

//运行实例2
00404896    >  896424 14              mov dword ptr ss:[esp+14],esp

//d esp
//00125C30  C0 5B 12 00              累..
//这个esp好像很重要,在手动增加第3个实例的代码之后,esp为00125C34,运行出错了,
//最后比较寄存器的状态,再往堆栈里压入一个参数,然后esp变为00125C30,就可以正常启动第3个实例了.

0040489A    .  68 68A24100            push TJMan.0041A268                             ;  ASCII "外挂2开"
0040489F    .  E8 DEF10000            call <jmp.&MFC42.#537>
004048A4    .  8BCB                   mov ecx,ebx                                     ; |
004048A6    .  E8 C50A0000            call TJMan.00405370                             ; \TJMan.00405370
//通过后来的第三个实例可以知道,上面四行代码是类似用来初始化实例(ini instance)的代码,

//////////////////
//这段代码在实例一种是放在上面五行代码前面,现在把它放后面了,注意比较区别
//也就是说,实例2是先初始化,然后调用OpenMutexA来看实例是否已运行,
//而实例一是先调用OpenMutexA来看实例是否已运行,然后再来初始化,
//显然,应该先判断实例是否存在,然后再来初始化.
//在手动构造了实例3的代码之后,会发现实例2的这种顺序所带来的影响,在后来,我又手动调换了这两段小代码的顺序.

004048AB    .  68 4CA24100            push TJMan.0041A24C                             ;  ASCII "TJMANRunOnlyOneInstance2"
004048B0    .  55                     push ebp
004048B1    .  68 01001F00            push 1F0001

{  对照比较两个实例的代码:
实例1中这里有一句
00404803    .  89AB 54020000 mov dword ptr ds:[ebx+254],ebp      ; |

}

004048B6    .  FFD6                   call esi                            ;ESI 7C80EC1B kernel32.OpenMutexA
{实例1中这里有一句
0040480B    .  51            push ecx   
ECX 7C92FB71 ntdll.7C92FB71
在后来建立实例3后,堆栈为00125C34,运行错误,为了修正堆栈,压入了此7C92FB71.
不过当调换实例2的初始化和openmutex后,由于同样的堆栈原因,把push 7C92FB71 NOP了,
运行正常(这个参数好像没用),只要保证esp和ecx为00125C30就行了.
}

004048B8    .  3BC5                   cmp eax,ebp
004048BA    .  8983 00030000          mov dword ptr ds:[ebx+300],eax
{
实例1中这里有一句
00404814    .  8BCC          mov ecx,esp
ecx=00125C30
d ecx
00125C30  71 FB 92 7C              q?|.

注意,这正是0040480B处push的ecx.

}
004048C0    .  75 6C                  jnz short TJMan.0040492E                  
////第二个实例已运行,显示"只支持双开".
//所需要作的就是在适当的位置增加一段创建实例3的代码,然后从这里跳去.基本思想就是这样.
//创建实例3的代码,是从实例2的代码拷过去的,从00404896到0040492C.
///////////////////

004048C2    .  BF 38A24100            mov edi,TJMan.0041A238                          ;  ASCII "TJ2HANDLE2byParker"
004048C7    .  83C9 FF                or ecx,FFFFFFFF
004048CA    .  33C0                   xor eax,eax
004048CC    .  8D5424 24              lea edx,dword ptr ss:[esp+24]     ;edx=esp+24=00125C34+24=00125C58

004048D0    .  F2:AE                  repne scas byte ptr es:[edi]
004048D2    .  F7D1                   not ecx
004048D4    .  2BF9                   sub edi,ecx
004048D6    .  68 4CA24100            push TJMan.0041A24C                             ; /MutexName = "TJMANRunOnlyOneInstance2"
004048DB    .  8BC1                   mov eax,ecx                                     ; |
004048DD    .  8BF7                   mov esi,edi                                     ; |
004048DF    .  8BFA                   mov edi,edx                                     ; |
004048E1    .  55                     push ebp                                        ; |InitialOwner
004048E2    .  C1E9 02                shr ecx,2                                       ; |
004048E5    .  F3:A5                  rep movs dword ptr es:[edi],dword ptr ds:[esi]  ; |
004048E7    .  8BC8                   mov ecx,eax                                     ; |
004048E9    .  55                     push ebp                                        ; |pSecurity
004048EA    .  83E1 03                and ecx,3                                       ; |
004048ED    .  F3:A4                  rep movs byte ptr es:[edi],byte ptr ds:[esi]    ; |
004048EF    .  FF15 20604100          call dword ptr ds:[<&KERNEL32.CreateMutexA>]    ; \CreateMutexA
004048F5    .  3BC5                   cmp eax,ebp
004048F7    .  8983 00030000          mov dword ptr ds:[ebx+300],eax
004048FD    .  74 0D                  je short TJMan.0040490C
004048FF    .  FF15 1C604100          call dword ptr ds:[<&KERNEL32.GetLastError>]    ; [GetLastError
00404905    .  3D B7000000            cmp eax,0B7
0040490A    .  75 16                  jnz short TJMan.00404922

0040490C    >  55                     push ebp
0040490D    .  55                     push ebp
0040490E    .  68 1CA24100            push TJMan.0041A21C                             ;  ASCII "系统错误2,外挂不能运行!"
00404913    .  E8 AEEF0000            call <jmp.&MFC42.#1200>
00404918    .  8B13                   mov edx,dword ptr ds:[ebx]
0040491A    .  8BCB                   mov ecx,ebx
0040491C    .  FF92 CC000000          call dword ptr ds:[edx+CC]
00404922    >  C783 54020000 01000000 mov dword ptr ds:[ebx+254],1,  //////////
0040492C    .  EB 16                  jmp short TJMan.00404944

//下面这段代码是用来显示信息的.
//把下面全部NOP掉,然后用来做长转移的跳板!
//然后把这段代码放在实例3的代码后面,最后再跳回来00404944.
//
0040492E    >  55                     push ebp
0040492F    .  55                     push ebp
00404930    .  68 00A24100            push TJMan.0041A200                             ;  ASCII "对不起,外挂仅仅支持双开!"
00404935    .  E8 8CEF0000            call <jmp.&MFC42.#1200>
0040493A    .  8B03                   mov eax,dword ptr ds:[ebx]
0040493C    .  8BCB                   mov ecx,ebx
0040493E    .  FF90 CC000000          call dword ptr ds:[eax+CC]

未修改前的代码就是上面这些了
=====================================================

//下面就要在.text段中找一块空白地方,把下面的代码(创建实例3,从实例2拷贝而来,然后再修改部分代码,主要是一些call,比如call 00413A82,
//在不同的位置调用,生成的机器代码不一样,因为采用相对寻址方式)加进去
//修改PE的工具我采用了raindy 汉化的studyPE,很好用.向作者和汉化作者表示敬意!
//用studyPE代开Tjman.exe,点"区段",在NO01,.text区段这行点右键,"前往区段开头"
//然后记住我们要拷贝的代码是从00404896到0040492C,减去基址00401000后可得到相对位移,然后再右键,"copy"这段代码,
//在这个区段的后面找个空白地方,点右键,"粘贴"过去.我选的开始位置是000157B0.

//000157B0  (000157B0)
896424146868A24100E8DEF100008BCBE8C50A0000684CA24100556801001F00FFD63BC5898300030000756CBF38A2410083C9FF33C08D542424F2AEF7D12BF9684CA241008BC

18BF78BFA55C1E902F3A58BC85583E103F3A4FF15206041003BC5898300030000740DFF151C6041003DB700000075165555681CA24100E8AEEF00008B138BCBFF92CC000000C7

835402000001000000EB16
//////

下面要做的就是把前面提到的部分不准确的call修改过来.
改完后看到的还是ASCII "TJ2HANDLE2byParker",MutexName = "TJMANRunOnlyOneInstance2",ASCII "系统错误2,外挂不能运行!",以及ASCII "对不起,外挂仅仅

支持双开!"

因此,还需要做的就是在NO03,区段.data中为实例3增加显示的数据.这部分工作很简单,把实例2的数据拷贝过去,
然后32改成33,把二的内码改成C8FD就行了(但是OD还是不能正常显示'三',不知为什么).
然而后来看到,事情不是想象那么简单.刚开始,我们这段数据放在.data的便宜000010C0处,几乎紧跟在原来的数据后面了.
后来调试发现,实例一运行后,这段数据就被修改了,如"/MutexName = "??""等,看来程序执行的时候还会用到这部分空间,
于是这段数据又得搬家,这次远点搬吧,搬到了偏移000018C0处,后来就没问题了.

后来运行第3个实例,报错,发现是堆栈的问题,
见下面的代码,看它是在哪一行出错的

00405370   /$  6A FF         push -1

//eax=00125B40
//d [eax]
//00374040  CD E2 B9 D2 33 BF AA 00  外挂3开.
//00374048  0D F0 AD BA 0D F0 AD BA  .瓠?瓠

00405372   |.  68 D0484100   push TJMan3.004148D0                                ;  SE handler installation
00405377   |.  64:A1 0000000>mov eax,dword ptr fs:[0]
0040537D   |.  50            push eax
0040537E   |.  64:8925 00000>mov dword ptr fs:[0],esp
00405385   |.  83EC 14       sub esp,14
00405388   |.  53            push ebx
00405389   |.  55            push ebp
0040538A   |.  56            push esi
0040538B   |.  57            push edi
0040538C   |.  8BF1          mov esi,ecx
0040538E   |.  8D4C24 10     lea ecx,dword ptr ss:[esp+10]
00405392   |.  C74424 2C 000>mov dword ptr ss:[esp+2C],0
0040539A   |.  E8 75E50000   call <jmp.&MFC42.#540>
0040539F   |.  8D4C24 20     lea ecx,dword ptr ss:[esp+20]
004053A3   |.  C64424 2C 01  mov byte ptr ss:[esp+2C],1
004053A8   |.  E8 67E50000   call <jmp.&MFC42.#540>
004053AD   |.  68 80A14100   push TJMan3.0041A180                                ;  ASCII "
"
004053B2   |.  8D4C24 18     lea ecx,dword ptr ss:[esp+18]
004053B6   |.  C64424 30 02  mov byte ptr ss:[esp+30],2
004053BB   |.  E8 C2E60000   call <jmp.&MFC42.#537>
004053C0   |.  8D4424 34     lea eax,dword ptr ss:[esp+34]
004053C4   |.  8D4C24 10     lea ecx,dword ptr ss:[esp+10]
004053C8   |.  50            push eax
004053C9       C64424 30 03  mov byte ptr ss:[esp+30],3
004053CE   |.  E8 23E50000   call <jmp.&MFC42.#858>  

//程序在004053CE这里报错了,跟进去,发现异常在73D34973处,是什么原因呢?
原来在73D3496F处edi=00125C34,而[00125C34]=0,所以73D34973处就是mov eax,[0],
显然出错,怎么修正呢?跟进实例1和实例2看看!发现它们在73D3496F处的edi都是00125C30,
照猫画虎看看,修改edi,运行,这里不再报错.

73D3496B M>  8BFF            mov edi,edi
73D3496D     56              push esi
73D3496E     57              push edi
73D3496F     8B7C24 0C       mov edi,dword ptr ss:[esp+C]
73D34973     8B07            mov eax,dword ptr ds:[edi]   
73D34975     8BF1            mov esi,ecx
73D34977     8B0E            mov ecx,dword ptr ds:[esi]
73D34979     3BC8            cmp ecx,eax
73D3497B     74 39           je short MFC42.73D349B6
73D3497D     8379 F4 00      cmp dword ptr ds:[ecx-C],0
73D34981     7D 0B           jge short MFC42.73D3498E
73D34983     83C1 F4         add ecx,-0C
73D34986     3B0D C486E073   cmp ecx,dword ptr ds:[73E086C4]                     ; MFC42.73E086C8
73D3498C     75 06           jnz short MFC42.73D34994
73D3498E     8378 F4 00      cmp dword ptr ds:[eax-C],0
73D34992     7D 0D           jge short MFC42.73D349A1

然而这个00125C34是哪里传进来的呢,最后找到这里:

004157B0    > \896424 14     mov dword ptr ss:[esp+14],esp

而这里是实例3的语句,它是从00404934   长跳转过来的,
查看实例2我们发现可以在这个长jmp前push一个字,便可纠正堆栈,
在不知道这个压入的参数是什么意思的情况下,还是压入和实例2一样的字吧!
push 7C92FB71
然后再jmp TJMan3.004157B0

OK了!

实例3可以正常调出来.
但是再面板里看到了两行文字
"外挂2开
外挂3开"
应该只有一行"外挂3开"的,为什么呢?
回头看看前面的代码,发现从实例2跳过来时,出了点问题.
因为它先初始化,然后再Openmutex,打开互斥量,这个顺序在逻辑上有问题.
下面我们再交换这个两段小代码的顺序!

还是用StudyPE,copy,paste,copy,paste,...
修改call的偏移量,运行,报错...
再仔细来看看寄存器的状态,比较一下为什么出错.

下面的good是在交换两段代码前的正常运行的状态,bad是交换后的状态,
通过修改可以发现,只要将bad的ECX该为good的ECX值,便可以正常运行了!
看来这个00125C30真重要啊!

====
good

EAX 000000E8
ECX 00125C30
EDX 7C92EB94 ntdll.KiFastSystemCallRet
EBX 00126320 ASCII "0nA"
ESP 00125C30
EBP 00000000
ESI 7C80EC1B kernel32.OpenMutexA
EDI 00000000

bad

EAX 00000000
ECX 7C92FB71 ntdll.7C92FB71
EDX 00000002
EBX 00126320 ASCII "0nA"
ESP 00125C30
EBP 00000000
ESI 7C80EC1B kernel32.OpenMutexA
EDI 00000000

仅修复ecx=00125c30就行了
============
于是再想办法修复ECX,通过观察发现,只要在 mov dword ptr ss:[esp+14],esp

前面加一句mov ecx,esp就可以了(实例一中有这一句)

再启动StudyPE,把代码搬家,腾出空间...

后来发现,mov ecx,esp前面的jnz short 00404934得变成jnz long 00404934了,
原来只有2字节,现在却是6字节,没办法,再搬家,累....

搬完后还要修改call得偏移,
最后实例2可以运行了!

可是发现实例3又不能运行了,交换之前是
push 7C92FB71
mov ecx,esp
jmp TJMan3.004157B0

而交换后的esp已经为00125C30,不要再修正堆栈了,
这句push 7C92FB71 NOP掉!
实例三也可以打开了!

下面是修改后的代码:


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (11)
雪    币: 151
活跃值: (66)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
支持...
2005-5-14 09:30
0
雪    币: 98761
活跃值: (201044)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习
2005-5-14 09:37
0
雪    币: 2054
活跃值: (292)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
4
支持!!!
2005-5-14 10:55
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
5
支持!

有几个问题不太清楚。

你这里说的实例是不是就是程序啊?
你这个DIY的目的是不是要让程序可以多开呢?
“在手动构造了实例3的代码之后”,这里的手动构造是啥意思?直接再打开一个程序不就完了么?
2005-5-14 11:11
0
雪    币: 234
活跃值: (370)
能力值: ( LV9,RANK:530 )
在线值:
发帖
回帖
粉丝
6
学习 ,支持
2005-5-14 14:37
0
雪    币: 201
活跃值: (24)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
学习中,不过有几个地方没看懂,正在请教高手
2005-5-14 16:52
0
雪    币: 538
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
恩,不错,学习ing
2005-5-14 16:59
0
雪    币: 440
活跃值: (827)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
9
最初由 nbw 发布


你这里说的实例是不是就是程序啊?



这里的实例是程序的一次运行,一个程序可有多个实例。

最初由 nbw 发布


你这个DIY的目的是不是要让程序可以多开呢?



这个DIY的目的是为了让程序可以多开,因为它只支持双开,这个DIY可以开三个,即增加了一个实例。

最初由 nbw 发布


“在手动构造了实例3的代码之后”,这里的手动构造是啥意思?直接再打开一个程序不就完了么?



Good Question!
这个程序采用了互斥量,程序运行时首先OpenMutex,检查是否已运行相应实例(参见参考),如果运行了,则去运行下一个实例,
等两个实例都运行后,就跳去报错“对不起,只支持双开”,
这个DIY正是在实例2后面增加了一段三开的代码(通过观察实例一和二的代码,可发现其有很多共同性,基本差不多,因此拷贝了实例2的程序过去,再修改之)。

请高手发表意见!
2005-5-15 00:41
0
雪    币: 221
活跃值: (137)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
10
太牛了,我再过5年也不行呀,我太笨了
2005-5-15 11:10
0
雪    币: 258
活跃值: (230)
能力值: ( LV12,RANK:770 )
在线值:
发帖
回帖
粉丝
11
多开了一个事例,,,楼主是为了完游戏的时候有帮助?...
2005-5-15 17:15
0
雪    币: 440
活跃值: (827)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
12
如果哪个玩家有3个账号,而只能挂两个,那岂不是很郁闷?
2005-5-15 18:38
0
游客
登录 | 注册 方可回帖
返回
//