这是我的第一篇关于逆向的文章,若有错误,欢迎指正!
在《云之遥》中,部分地图是无法存档的,本文便介绍如何用OD对云之遥程序进行调试并找到相应地方实现随时存档
首先,OD附加入云之遥主程序,F9让他先跑起来(若线程Suspend则Resume即可)
然后返回程序领空,右键查找参考字串,有如下这样一行:
0041B1DA 68 F8C27900 PUSH SwdCF.0079C2F8 ; ASCII “%s%cS%02d.sav”
这不就是存档路径吗?往上看,我们在0041B168下一个断点,单击“储存”,OD就会断下来,F8下去,会看到函数会返回到004FE286,继续在004FE1C0下一个断点,F9运行,然后再单击“储存”,断下来后,观察堆栈,有如下两条:
0012FBCC 004FC0DC 返回到 SwdCF.004FC0DC 来自 SwdCF.004FE1C0
0012FBD8 004FFDAD 返回到 SwdCF.004FFDAD 来自 SwdCF.004FC0A0
到004FFDAD去看下,往上在004FFB34下一个断点,再次单击“储存”可以被断下,根据刚才记录,可以看到存档CALL的地址在004FFDA8:
004FFDA8 E8 F3C2FFFF CALL SwdCF.004FC0A0 ; 存档 CALL
只要走到这里便能存档,此时我们F8一路单步走,在004FFC18处有一个JMP到存档CALL的上面,先标记一下
此时读取一个无法存档的地图的存档,直接来到储存界面单击“储存”,程序被断下来了,可以看到在这里会跳走从而无法存档
004FFBCC 74 4F JE SHORT SwdCF.004FFC1D
于是我们把这段代码整理一下:
004FFB34 57 PUSH EDI
004FFB35 85C0 TEST EAX,EAX
004FFB37 0F85 00030000 JNZ SwdCF.004FFE3D ; EAX 不为 0 跳走
004FFB3D A1 283F7E00 MOV EAX,DWORD PTR DS:[7E3F28]
004FFB42 BF 01000000 MOV EDI,1
004FFB47 C680 CC000000 0>MOV BYTE PTR DS:[EAX+CC],0 ; [7E3F28+CC] 置0
004FFB4E 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8] ; [ESP+8] -> EAX
004FFB52 3B05 243E8300 CMP EAX,DWORD PTR DS:[833E24]
004FFB58 75 09 JNZ SHORT SwdCF.004FFB63 ; EAX 不等于 [833E24] 跳走
004FFB5A 33C0 XOR EAX,EAX ; EAX 清零
004FFB5C A3 A03C8300 MOV DWORD PTR DS:[833CA0],EAX ; [833CA0] = EAX
004FFB61 EB 52 JMP SHORT SwdCF.004FFBB5
004FFB63 3B05 283E8300 CMP EAX,DWORD PTR DS:[833E28]
004FFB69 75 09 JNZ SHORT SwdCF.004FFB74 ; EAX 不等于 [833E28] 跳走
004FFB6B 8BC7 MOV EAX,EDI
004FFB6D A3 A03C8300 MOV DWORD PTR DS:[833CA0],EAX
004FFB72 EB 41 JMP SHORT SwdCF.004FFBB5
004FFB74 3B05 2C3E8300 CMP EAX,DWORD PTR DS:[833E2C]
004FFB7A 75 0C JNZ SHORT SwdCF.004FFB88 ; EAX 不等于 [833E2C] 跳走
004FFB7C B8 02000000 MOV EAX,2
004FFB81 A3 A03C8300 MOV DWORD PTR DS:[833CA0],EAX
004FFB86 EB 2D JMP SHORT SwdCF.004FFBB5
004FFB88 3B05 303E8300 CMP EAX,DWORD PTR DS:[833E30]
004FFB8E 75 0C JNZ SHORT SwdCF.004FFB9C ; EAX 不等于 [833E30] 跳走
004FFB90 B8 03000000 MOV EAX,3
004FFB95 A3 A03C8300 MOV DWORD PTR DS:[833CA0],EAX
004FFB9A EB 19 JMP SHORT SwdCF.004FFBB5
004FFB9C 3B05 343E8300 CMP EAX,DWORD PTR DS:[833E34]
004FFBA2 75 0C JNZ SHORT SwdCF.004FFBB0 ; EAX 不等于 [833E34] 跳走
004FFBA4 B8 04000000 MOV EAX,4
004FFBA9 A3 A03C8300 MOV DWORD PTR DS:[833CA0],EAX
004FFBAE EB 05 JMP SHORT SwdCF.004FFBB5
004FFBB0 A1 A03C8300 MOV EAX,DWORD PTR DS:[833CA0] ; EAX = [833CA0]
004FFBB5 83F8 04 CMP EAX,4
004FFBB8 0F87 75020000 JA SwdCF.004FFE33 ; EAX 大于 4 跳走
004FFBBE FF2485 44FE4F00 JMP DWORD PTR DS:[EAX*4+4FFE44]
004FFBC5 A0 C13D8300 MOV AL,BYTE PTR DS:[833DC1] ; AL = [833DC1]
004FFBCA 84C0 TEST AL,AL
004FFBCC 74 4F JE SHORT SwdCF.004FFC1D ; AL 为 0 跳走
004FFBCE A0 F9FA8300 MOV AL,BYTE PTR DS:[83FAF9] ; AL = [83FAF9]
004FFBD3 84C0 TEST AL,AL
004FFBD5 75 46 JNZ SHORT SwdCF.004FFC1D ; AL 不为 0 跳走
004FFBD7 B9 983C8300 MOV ECX,SwdCF.00833C98
004FFBDC E8 5FE7FFFF CALL SwdCF.004FE340
004FFBE1 B9 983C8300 MOV ECX,SwdCF.00833C98
004FFBE6 E8 65EEFFFF CALL SwdCF.004FEA50
004FFBEB B9 983C8300 MOV ECX,SwdCF.00833C98
004FFBF0 E8 7BEEFFFF CALL SwdCF.004FEA70
004FFBF5 A1 5C257E00 MOV EAX,DWORD PTR DS:[7E255C]
004FFBFA 893D A43C8300 MOV DWORD PTR DS:[833CA4],EDI
004FFC00 3BC7 CMP EAX,EDI
004FFC02 75 0D JNZ SHORT SwdCF.004FFC11
004FFC04 6A 0F PUSH 0F
004FFC06 57 PUSH EDI
004FFC07 B9 A8088300 MOV ECX,SwdCF.008308A8
004FFC0C E8 3F2BFDFF CALL SwdCF.004D2750
004FFC11 8B0D 3C257E00 MOV ECX,DWORD PTR DS:[7E253C]
004FFC17 51 PUSH ECX
004FFC18 E9 75010000 JMP SwdCF.004FFD92 ; 这里去存档
004FFC1D 6A FF PUSH -1
很明显,如果条件跳转到4FFC1D这个地址,便无法进行存档,因此必须让以下两个条件为假:
004FFBCC 74 4F JE SHORT SwdCF.004FFC1D ; AL 为 0 跳走
004FFBD5 75 46 JNZ SHORT SwdCF.004FFC1D ; AL 不为 0 跳走
因此,若要实现随时存档,写一个内存修改程序,将0x833CA0地址上的这个byte值保持为1,0x83FAF9地址上的这个byte值保持为0即可
以上研究是在繁体版云之遥上进行的,用同样的方法可以得到简体版的这两个地址,分别为0x82B779和0x8374B1
附ASM实现代码(对应简体版,建议以Timer方式启动):
.const
ProcessName db “SwdCF”,0
ErrMsg db “Error Open Process!”,0
ErrMsgT db “Error”,0
.data?
ProcessPID dd ?
ProcessHWND dd ?
———————–分割线———————–
MemoryModifyProc proc;
LOCAL Found : byte
LOCAL hWnd1 : DWORD
LOCAL bWrite : byte
mov Found, 0
.While !Found
invoke FindWindow, offset ProcessName, offset ProcessName
.if eax != 0
mov Found, 1
invoke GetWindowThreadProcessId, eax, offset ProcessPID
invoke OpenProcess, PROCESS_ALL_ACCESS, FALSE, ProcessPID
mov ProcessHWND, eax
.if eax != 0
mov bWrite, 1
invoke WriteProcessMemory,ProcessHWND,0082B779h,addr bWrite,1,NULL
mov bWrite, 0
invoke WriteProcessMemory,ProcessHWND,008374B1h,addr bWrite,1,NULL
.else
invoke MessageBox, ThisProWND, offset sErrMsg, offset sErrMsgT, MB_ICONERROR
.endif
invoke CloseHandle, ProcessHWND
.else
ret
.endif
.endW
ret
MemoryModifyProc endp
Timer方式启动的意思是?程序里不是已经while了么?
求教
FindWindow返回0的时候就RET了啊,所以用Timer
用Timer的话,FindWindow之后应该还要Kill掉才行~忘了说了