3DNes字符串汉化不成功的问题定位解决
这篇文章的起因是来自 shaokui123 在论坛的求助帖:
汉化字符大于原始长度(字符是主程序读取资源文件) - 『脱壳破解讨论求助区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
(原帖附件火绒报毒并成功杀毒,我在百度云盘传了一份杀过毒的以供练手:
链接:https://pan.baidu.com/s/1QD-ffT2LAalOoE7jXxaYQw 提取码:0fkp)
shaokui123 在汉化 3DNes 的过程中遇到两处汉化不成功的字符串(位于文件“\3DNes\3dnes_Data\level0”中):
1、文件偏移 0x1E7A0 处的字符串“Depth”
2、文件偏移 0x197B8 处的字符串“Open”
一、“Depth”汉化
需要汉化为两个字的 UTF8 编码汉字,占用 6 各字节
字符串会有长度信息和不带长度信息两种,前者根据长度信息读取相应位数的字符串,后者遇到0才停止读取
再看看字符串之前4个字节,“05”这个数字正好是字符串的长度,猜想有可能是长度信息,把它改成“06”试试,果然就成功了。这处比较简单,下一处就有点麻烦了
二、“Open”汉化
这个字符串藏得比较深,打开程序(注意勾选窗口化,否则全屏了),点击“Play”按键,再点击“Open”按键,下一界面出现的“Open”按键才是汉化不成功的目标字符串
这个字符串之前也有长度信息,但改过之后,整个按键的文字都不显示了,猜测还有地方限制了显示长度
这个软件是个 Unity 程序,试过用 AssetStudio 查找资源,也没有头绪,看来还得硬钢
首先想到的是要找到这个字符串出现的代码位置,再跟踪程序是如何处理这个字符串的,看看是否有线索
(“Open”在文件中出现两次,为了便于分析,把目标字符串改成了“1pen”)
1、先在内存中搜索“1pen”这串字符,找不到,改成搜索 Unicode,这次倒是搜到了,但这已经是转换过的,不是数据来源
考虑到字符串保存在文件“level0”中,要使用肯定要先读取
用调试器 x32dbg 载入主程序,在 API ReadFile 上下断点,运行至断下后回溯到用户代码
00B491BD | FF15 00837301 | call dword ptr ds:[<ReadFile>] | |
00B491C3 | 8BF0 | mov esi,eax |
发现读取了很多不同的文件,“level0”也是分段多次读取,怎样才准确定位到“1pen”读入的时机呢?
在地址 0xB491C3 下断点(此时 ReadFile 已执行完毕将文件读入内存),并编辑断点条件如下:
暂停条件:$result>0 (找到结果暂停)
命令:findallmem 0,"31 70 65 6E" (内存查找“1pen”)
命令条件:strstr(utf8(ebx),"level0" (文件名包含“level0”)
运行后一会儿成功断下,查看内存,字符串“1pen”已经读入
2、在字符串“1pen”上设置硬件访问断点,继续执行后断下,但断下的位置看不出端倪
我们来到“堆栈调用”标签中查看堆栈回溯,看到经过了多级调用才来到断点位置
地址 | 返回到 | 返回自 | 大小 | 方 | 注释 |
1142FACC | B28852 | 010CF6D8 | 24 | 用户模块 | 3dnes.UnwindUp1+4 |
1142FAF0 | A60CB7 | 00B28852 | 1C | 用户模块 | 3dnes.CachedReader::Read+22 |
1142FB0C | C72C5D | 00A60CB7 | 34 | 用户模块 | 3dnes.StreamedBinaryRead<0>::TransferSTLStyleArray+67 |
1142FB40 | C823A5 | 00C72C5D | 18 | 用户模块 | 3dnes.TransferField_NonArray<StreamedBinaryRead<0>,Converter_String>+2D |
1142FB58 | FED400 | 00C823A5 | 2C | 用户模块 | 3dnes.Transfer_String<StreamedBinaryRead<0>,0>+15 |
1142FB84 | C84651 | 00FED400 | 64 | 用户模块 | 3dnes.ExecuteSerializationCommands<ProxyTransfer>+50 |
1142FBE8 | BF38EB | 00C84651 | 24 | 用户模块 | 3dnes.TransferScriptingObject<StreamedBinaryRead<0> >+B1 |
1142FC0C | CABBBC | 00BF38EB | DC | 用户模块 | 3dnes.MonoBehaviour::TransferEngineAndInstance<StreamedBinaryRead<0> >+5B |
1142FCE8 | CA6E8B | 00CABBBC | B4 | 用户模块 | 3dnes.SerializedFile::ReadObject+26C |
1142FD9C | C8794F | 00CA6E8B | C4 | 用户模块 | 3dnes.PersistentManager::LoadFileCompletelyThreaded+2EB |
1142FE60 | C859F1 | 00C8794F | 18 | 用户模块 | 3dnes.LoadSceneOperation::Perform+2AF |
1142FE78 | C85A7B | 00C859F1 | 8 | 用户模块 | 3dnes.PreloadManager::Run+A1 |
1142FE80 | CC7D5D | 00C85A7B | 18 | 用户模块 | 3dnes.PreloadManager::Run+B |
1142FE98 | 4BFA29 | 00CC7D5D | 10 | 系统模块 | 3dnes.Thread::RunThreadWrapper+2D |
1142FEA8 | 8A7A9E | 764BFA29 | 5C | 系统模块 | kernel32.@BaseThreadInitThunk@12+19 |
1142FF04 | 8A7A6E | 778A7A9E | 10 | 系统模块 | ntdll.__RtlUserThreadStart+2F |
1142FF14 | 000000 | 778A7A6E | 用户模块 | ntdll.__RtlUserThreadStart@8+1B |
以上堆栈记录的调用都是公共函数,经过多次执行才来到我们设置的断点,我们现在需要准确定位到“1pen”这个字符串的处理流程
在堆栈调用中点击应用程序空间的每一级调用,返回地址的上一句调用 call 上下断点,从上至下注释好调用层级,设置中断条件为“0”(不中断只记录触发次数)
3、取消步骤 2 的硬件断点,重启程序,执行至步骤 1 的断点,在字符串“1pen”上设置硬件访问断点,继续执行至断下
此时查看断点清单,1-8 级调用均有命中,并记录下了命中次数,先取消未命中断点和步骤 1 的断点
然后用这个命中次数为调用 call 设置点断条件(比如 1 级命中 4156 次设置断点条件 $breakpointcounter==.4156)
这样设置后确保调用 call 断下的时候正在处理目标字符串,设置完后我的机器上断点清单如下:
地址 | 反汇编 | 命中 | 摘要(前面数字是注释的调用层级) |
00A60CB2 | call <3dnes.public: void __thiscall StreamedBinaryRead<0>::ReadDirect(void *, int)> | 1114 | 2, 暂停条件($breakpointcounter==.1114) |
00B2884D | call <3dnes._memcpy> | 4156 | 1, 暂停条件($breakpointcounter==.4156) |
00BF38E6 | call <3dnes.void __cdecl TransferScriptingObject> | 31 | 7, 暂停条件($breakpointcounter==.31) |
00C72C58 | call <3dnes.public: void __thiscall StreamedBinaryRead<0>::TransferSTLStyleArray> | 69 | 3, 暂停条件($breakpointcounter==.69) |
00C823A0 | call <3dnes.void __cdecl TransferField_NonArray> | 69 | 4, 暂停条件($breakpointcounter==.69) |
00C8464C | call <3dnes.void __cdecl ExecuteSerializationCommands> | 31 | 6, 暂停条件($breakpointcounter==.31) |
00CABBBA | call edx | 1631 | 8, 暂停条件($breakpointcounter==.1631) |
00FED3FE | call eax | 678 | 5, 暂停条件($breakpointcounter==.678) |
4、接着我们来看看将字符串长度改变会发生什么,将字符串长度信息由“04”改为“06”,字符串“1pen”改为“1penen”
重新载入程序,一路执行,发现执行到第 1 级调用 0xB2884D 的 call <3dnes._memcpy> 时出现问题了
本该内存拷贝复制的目标字符串没有出现,是哪里出了问题呢?
重启程序,到第 2 级调用 call 的时候 F7 单步进入第 1 级段首
00B28830 | 55 | push ebp | |
00B28831 | 8BEC | mov ebp,esp | |
00B28833 | 83EC 08 | sub esp,0x8 | |
00B28836 | 53 | push ebx | |
00B28837 | 8B5D 0C | mov ebx,dword ptr ss:[ebp+0xC] | 字符串长度 |
00B2883A | 57 | push edi | |
00B2883B | 8BF9 | mov edi,ecx | edi指向字符串地址 |
00B2883D | 8B07 | mov eax,dword ptr ds:[edi] | 字符串地址 |
00B2883F | 8D0C18 | lea ecx,dword ptr ds:[eax+ebx] | 字符串最后位+1的地址 |
00B28842 | 3B4F 08 | cmp ecx,dword ptr ds:[edi+0x8] | |
00B28845 | 77 18 | ja 3dnes.B2885F | 改过会跳过 |
00B28847 | 8B55 08 | mov edx,dword ptr ss:[ebp+0x8] | |
00B2884A | 53 | push ebx | |
00B2884B | 50 | push eax | |
00B2884C | 52 | push edx | |
00B2884D | E8 2E6D5A00 | call <3dnes._memcpy> | 1 |
00B28852 | 83C4 0C | add esp,0xC | |
00B28855 | 011F | add dword ptr ds:[edi],ebx | |
00B28857 | 5F | pop edi | |
00B28858 | 5B | pop ebx | |
00B28859 | 8BE5 | mov esp,ebp | |
00B2885B | 5D | pop ebp | |
00B2885C | C2 0800 | ret 0x8 | |
00B2885F | 56 | push esi |
单步执行下来,执行到 0xB28842 将字符串最后位+1 的地址和 ds:[edi+0x8] 保存的地址进行比较
ds:[edi+0x8] 保存的地址指向的是“1penen”的第 5 位,刚好是我们修改增加后的内容,这里很可疑
我们将 ds:[edi+0x8] 保存的地址增加 2,即指向“1penen”地址+6 的地方,继续执行后成功显示了
5、现在我们要找到 ds:[edi+0x8] 的数据来源,按数据产生的流程当然要从最高层级开始分析
首先在第 8 级调用 call 的段首设置条件断点,断点条件跟第 8 级调用 call 一样
然后重启程序,执行到第 8 级调用 call 的段首断下,此时“1penen”已经在内存中
找到“1penen”地址,因为步骤 4 中错误的 ds:[edi+0x8] 地址为“1penen”地址+4,我们在内存中搜索这个地址
现在还找不到这个地址,因为还没有开始处理(注意这个地址在内存中是倒序存放的(小端存储))
我们单步向下执行,遇到 call 步过,然后搜索一下内存是否出现上面的错误地址
执行到 0xCABB63 call <3dnes.public: void __thiscall CachedReader::InitRead 后目标地址出现了
6、继续向下分析“1penen”地址+4 的来源,重启程序,执行到 0xCABB63 的时候 F7 单步进入分析
00B28653 | 8B4D 10 | mov ecx,dword ptr ss:[ebp+0x10] | 定位数据,原为0x104,修改为6位应为0x106 |
00B28656 | 8D140B | lea edx,dword ptr ds:[ebx+ecx] | |
00B28659 | 8B4E 0C | mov ecx,dword ptr ds:[esi+0xC] | |
00B2865C | 8956 1C | mov dword ptr ds:[esi+0x1C],edx | |
00B2865F | 8D7E 08 | lea edi,dword ptr ds:[esi+0x8] | |
00B28662 | 57 | push edi | |
00B28663 | 8D56 04 | lea edx,dword ptr ds:[esi+0x4] | |
00B28666 | 895E 18 | mov dword ptr ds:[esi+0x18],ebx | |
00B28669 | 52 | push edx | |
00B2866A | 8946 10 | mov dword ptr ds:[esi+0x10],eax | |
00B2866D | 8B09 | mov ecx,dword ptr ds:[ecx] | |
00B2866F | 50 | push eax | |
00B28670 | 8BC1 | mov eax,ecx | |
00B28672 | 8B4E 0C | mov ecx,dword ptr ds:[esi+0xC] | |
00B28675 | 8B40 08 | mov eax,dword ptr ds:[eax+0x8] | |
00B28678 | FFD0 | call eax | |
00B2867A | 8B4E 14 | mov ecx,dword ptr ds:[esi+0x14] | |
00B2867D | 0FAF4E 10 | imul ecx,dword ptr ds:[esi+0x10] | |
00B28681 | 8B46 1C | mov eax,dword ptr ds:[esi+0x1C] | |
00B28684 | 2BC1 | sub eax,ecx | |
00B28686 | 0346 04 | add eax,dword ptr ds:[esi+0x4] | 字符串最后位+1地址(“1penen”地址+4) |
执行到 0xB28686 的时候出现了目标地址,经分析,这个地址是用 0xB28653 的数据经过计算得来
0xB28653 的数据是个偏移,与载入内存的地址计算得出绝对地址,我们姑且称它为“定位数据”
这个“定位数据”来自上级 call 的第 4 个参数,还得回到上一级来分析
(这里的分析没有什么好讲的,纯粹是硬啃汇编指令,结合动态分析会更容易一些)
7、重启程序来到第 8 级调用 call 的段首,从 0xCABB63 开始向上看
00CAB97C | E8 CFC5FFFF | call <3dnes.struct std::pair<__int64, struct SerializedFile::ObjectInfo> const * __cdecl std::_Lower_bound<struct std::pair<__int64, struct SerializedFile::ObjectInfo> const *, __int64, int, class vector_map<__int64, struct SerializedFile::ObjectInfo, struct std::les> | 读入字符串和定位数据 |
00CAB981 | 83C4 14 | add esp,0x14 | |
00CAB984 | 3BC6 | cmp eax,esi | |
00CAB986 | 74 15 | je 3dnes.CAB99D | |
00CAB988 | 8B55 0C | mov edx,dword ptr ss:[ebp+0xC] | |
00CAB98B | 3B50 04 | cmp edx,dword ptr ds:[eax+0x4] | |
00CAB98E | 7C 0D | jl 3dnes.CAB99D | |
00CAB990 | 7F 07 | jg 3dnes.CAB999 | |
00CAB992 | 8B4D 08 | mov ecx,dword ptr ss:[ebp+0x8] | |
00CAB995 | 3B08 | cmp ecx,dword ptr ds:[eax] | |
00CAB997 | 72 04 | jb 3dnes.CAB99D | |
00CAB999 | 8BF8 | mov edi,eax | 定位数据=[eax+c] |
00CAB99B | EB 02 | jmp 3dnes.CAB99F | |
00CAB99D | 8BFE | mov edi,esi | |
00CAB99F | 3BFE | cmp edi,esi | |
00CAB9A1 | 0F84 59020000 | je 3dnes.CABC00 | |
00CAB9A7 | 8D47 10 | lea eax,dword ptr ds:[edi+0x10] | |
00CAB9AA | 50 | push eax | |
00CAB9AB | 8BCB | mov ecx,ebx | |
00CAB9AD | E8 DEE7FFFF | call <3dnes.public: class SerializedFile::Type & __thiscall std::map<int, class SerializedFile::Type, struct std::less<int>, class std::allocator<struct std::pair<int const, class SerializedFile::Type>>>::operator[](int const &)> | |
00CAB9B2 | 807B 34 00 | cmp byte ptr ds:[ebx+0x34],0x0 | |
00CAB9B6 | 8B75 20 | mov esi,dword ptr ss:[ebp+0x20] | |
00CAB9B9 | 8945 FC | mov dword ptr ss:[ebp-0x4],eax | |
00CAB9BC | 74 72 | je 3dnes.CABA30 | |
00CAB9BE | 8378 28 FF | cmp dword ptr ds:[eax+0x28],0xFFFFFFFF | |
00CAB9C2 | 75 6C | jne 3dnes.CABA30 | |
00CAB9C4 | 8B56 08 | mov edx,dword ptr ds:[esi+0x8] | |
00CAB9C7 | C1EA 15 | shr edx,0x15 | |
00CAB9CA | 3B57 10 | cmp edx,dword ptr ds:[edi+0x10] | |
00CAB9CD | 75 61 | jne 3dnes.CABA30 | |
00CAB9CF | 68 DF060000 | push 0x6DF | |
00CAB9D4 | 68 70BF7301 | push <3dnes.""> | |
00CAB9D9 | 6A 10 | push 0x10 | |
00CAB9DB | 6A 4B | push 0x4B | |
00CAB9DD | 6A 30 | push 0x30 | |
00CAB9DF | E8 3C83E9FF | call <3dnes.void * __cdecl operator new(unsigned int, enum MemLabelIdentifier, int, char const *, int)> | |
00CAB9E4 | 83C4 14 | add esp,0x14 | |
00CAB9E7 | 85C0 | test eax,eax | |
00CAB9E9 | 74 0E | je 3dnes.CAB9F9 | |
00CAB9EB | 6A 4B | push 0x4B | |
00CAB9ED | 8BC8 | mov ecx,eax | |
00CAB9EF | E8 0C130000 | call <3dnes.public: __thiscall TypeTree::TypeTree(enum MemLabelIdentifier)> | |
00CAB9F4 | 8945 20 | mov dword ptr ss:[ebp+0x20],eax | |
00CAB9F7 | EB 07 | jmp 3dnes.CABA00 | |
00CAB9F9 | C745 20 00000000 | mov dword ptr ss:[ebp+0x20],0x0 | |
00CABA00 | 8B43 30 | mov eax,dword ptr ds:[ebx+0x30] | |
00CABA03 | 8B4D 20 | mov ecx,dword ptr ss:[ebp+0x20] | |
00CABA06 | 0D 00200000 | or eax,0x2000 | |
00CABA0B | 50 | push eax | |
00CABA0C | 51 | push ecx | |
00CABA0D | 56 | push esi | |
00CABA0E | E8 0D0B0000 | call <3dnes.void __cdecl GenerateTypeTree(class Object &, class TypeTree *, enum TransferInstructionFlags)> | |
00CABA13 | 8B55 20 | mov edx,dword ptr ss:[ebp+0x20] | |
00CABA16 | 8B4D FC | mov ecx,dword ptr ss:[ebp-0x4] | |
00CABA19 | 83C4 0C | add esp,0xC | |
00CABA1C | 52 | push edx | |
00CABA1D | E8 3EB9FFFF | call <3dnes.public: void __thiscall SerializedFile::Type::CompareAgainstNewType(class TypeTree *)> | |
00CABA22 | 8B45 20 | mov eax,dword ptr ss:[ebp+0x20] | |
00CABA25 | 6A 4B | push 0x4B | |
00CABA27 | 50 | push eax | |
00CABA28 | E8 93CEFFFF | call <3dnes.void __cdecl delete_internal<class TypeTree>(class TypeTree *, enum MemLabelIdentifier)> | |
00CABA2D | 83C4 08 | add esp,0x8 | |
00CABA30 | 8B43 30 | mov eax,dword ptr ds:[ebx+0x30] | |
00CABA33 | 83C8 01 | or eax,0x1 | |
00CABA36 | 807B 2C 00 | cmp byte ptr ds:[ebx+0x2C],0x0 | |
00CABA3A | 8945 0C | mov dword ptr ss:[ebp+0xC],eax | |
00CABA3D | 74 08 | je 3dnes.CABA47 | |
00CABA3F | 0D 00020000 | or eax,0x200 | |
00CABA44 | 8945 0C | mov dword ptr ss:[ebp+0xC],eax | |
00CABA47 | 837D 10 01 | cmp dword ptr ss:[ebp+0x10],0x1 | |
00CABA4B | 75 08 | jne 3dnes.CABA55 | |
00CABA4D | 0D 00008000 | or eax,0x800000 | |
00CABA52 | 8945 0C | mov dword ptr ss:[ebp+0xC],eax | |
00CABA55 | 8B4D 14 | mov ecx,dword ptr ss:[ebp+0x14] | |
00CABA58 | 51 | push ecx | |
00CABA59 | 8BCE | mov ecx,esi | |
00CABA5B | E8 F05CDCFF | call <3dnes.private: void __thiscall Object::SetIsPersistent(bool)> | |
00CABA60 | 8B43 10 | mov eax,dword ptr ds:[ebx+0x10] | |
00CABA63 | 0347 08 | add eax,dword ptr ds:[edi+0x8] | |
00CABA66 | 8B4D FC | mov ecx,dword ptr ss:[ebp-0x4] | |
00CABA69 | 8945 20 | mov dword ptr ss:[ebp+0x20],eax | |
00CABA6C | 33C0 | xor eax,eax | |
00CABA6E | 3941 24 | cmp dword ptr ds:[ecx+0x24],eax | |
00CABA71 | 0F84 AD000000 | je 3dnes.CABB24 | |
00CABA77 | 3941 28 | cmp dword ptr ds:[ecx+0x28],eax | |
00CABA7A | 0F84 A4000000 | je 3dnes.CABB24 | |
00CABA80 | 8D8D 3CFFFFFF | lea ecx,dword ptr ss:[ebp-0xC4] | |
00CABA86 | E8 850DFFFF | call <3dnes.public: __thiscall SafeBinaryRead::SafeBinaryRead(void)> | |
00CABA8B | 8B55 FC | mov edx,dword ptr ss:[ebp-0x4] | |
00CABA8E | 8B42 24 | mov eax,dword ptr ds:[edx+0x24] | |
00CABA91 | 8BCE | mov ecx,esi | |
00CABA93 | 8945 B8 | mov dword ptr ss:[ebp-0x48],eax | |
00CABA96 | C745 BC 00000000 | mov dword ptr ss:[ebp-0x44],0x0 | |
00CABA9D | E8 DE5DDCFF | call <3dnes.public: enum MemLabelIdentifier __thiscall Object::GetMemoryLabel(void) const> | |
00CABAA2 | 8B4D 0C | mov ecx,dword ptr ss:[ebp+0xC] | |
00CABAA5 | 8B57 0C | mov edx,dword ptr ds:[edi+0xC] | |
00CABAA8 | 50 | push eax | |
00CABAA9 | 8B45 20 | mov eax,dword ptr ss:[ebp+0x20] | |
00CABAAC | 51 | push ecx | |
00CABAAD | 52 | push edx | |
00CABAAE | 50 | push eax | |
00CABAAF | 8D4D B8 | lea ecx,dword ptr ss:[ebp-0x48] | |
00CABAB2 | 51 | push ecx | |
00CABAB3 | 8D8D 3CFFFFFF | lea ecx,dword ptr ss:[ebp-0xC4] | |
00CABAB9 | E8 620AFFFF | call <3dnes.public: class CachedReader & __thiscall SafeBinaryRead::Init(class TypeTreeIterator const &, int, int, enum TransferInstructionFlags, enum MemLabelIdentifier)> | |
00CABABE | 8B57 0C | mov edx,dword ptr ds:[edi+0xC] | |
00CABAC1 | 8B4D 20 | mov ecx,dword ptr ss:[ebp+0x20] | |
00CABAC4 | 52 | push edx | |
00CABAC5 | 8B53 74 | mov edx,dword ptr ds:[ebx+0x74] | |
00CABAC8 | 51 | push ecx | |
00CABAC9 | 52 | push edx | |
00CABACA | 8BC8 | mov ecx,eax | |
00CABACC | 8945 10 | mov dword ptr ss:[ebp+0x10],eax | |
00CABACF | E8 5CCBE7FF | call <3dnes.public: void __thiscall CachedReader::InitRead(class CacheReaderBase &, unsigned int, unsigned int)> | |
00CABAD4 | 8B06 | mov eax,dword ptr ds:[esi] | |
00CABAD6 | 8B50 14 | mov edx,dword ptr ds:[eax+0x14] | |
00CABAD9 | 8BCE | mov ecx,esi | |
00CABADB | FFD2 | call edx | |
00CABADD | 8B06 | mov eax,dword ptr ds:[esi] | |
00CABADF | 8B50 40 | mov edx,dword ptr ds:[eax+0x40] | |
00CABAE2 | 8D8D 3CFFFFFF | lea ecx,dword ptr ss:[ebp-0xC4] | |
00CABAE8 | 51 | push ecx | |
00CABAE9 | 8BCE | mov ecx,esi | |
00CABAEB | FFD2 | call edx | |
00CABAED | 8B4D 10 | mov ecx,dword ptr ss:[ebp+0x10] | |
00CABAF0 | E8 BBCBE7FF | call <3dnes.public: unsigned int __thiscall CachedReader::End(void)> | |
00CABAF5 | 2B45 20 | sub eax,dword ptr ss:[ebp+0x20] | |
00CABAF8 | 8B4F 0C | mov ecx,dword ptr ds:[edi+0xC] | |
00CABAFB | 3BC1 | cmp eax,ecx | |
00CABAFD | 76 0F | jbe 3dnes.CABB0E | |
00CABAFF | 56 | push esi | |
00CABB00 | 50 | push eax | |
00CABB01 | 0FBF47 14 | movsx eax,word ptr ds:[edi+0x14] | |
00CABB05 | 51 | push ecx | |
00CABB06 | E8 35D7FFFF | call <3dnes.OutOfBoundsReadingError> | |
00CABB0B | 83C4 0C | add esp,0xC | |
00CABB0E | 8B45 1C | mov eax,dword ptr ss:[ebp+0x1C] | |
00CABB11 | 8D8D 3CFFFFFF | lea ecx,dword ptr ss:[ebp-0xC4] | |
00CABB17 | C600 01 | mov byte ptr ds:[eax],0x1 | |
00CABB1A | E8 510DFFFF | call <3dnes.public: __thiscall SafeBinaryRead::~SafeBinaryRead(void)> | |
00CABB1F | E9 C1000000 | jmp 3dnes.CABBE5 | |
00CABB24 | 8945 C0 | mov dword ptr ss:[ebp-0x40],eax | |
00CABB27 | 8945 C4 | mov dword ptr ss:[ebp-0x3C],eax | |
00CABB2A | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax | |
00CABB2D | 8D4D CC | lea ecx,dword ptr ss:[ebp-0x34] | |
00CABB30 | 3843 2C | cmp byte ptr ds:[ebx+0x2C],al | |
00CABB33 | 75 4A | jne 3dnes.CABB7F | |
00CABB35 | E8 66C7E7FF | call <3dnes.public: __thiscall CachedReader::CachedReader(void)> | |
00CABB3A | 8BCE | mov ecx,esi | |
00CABB3C | E8 3F5DDCFF | call <3dnes.public: enum MemLabelIdentifier __thiscall Object::GetMemoryLabel(void) const> | |
00CABB41 | 8B57 0C | mov edx,dword ptr ds:[edi+0xC] | 定位数据 |
00CABB44 | 8B4D 0C | mov ecx,dword ptr ss:[ebp+0xC] | |
00CABB47 | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax | |
00CABB4A | 8B43 10 | mov eax,dword ptr ds:[ebx+0x10] | |
00CABB4D | 0347 08 | add eax,dword ptr ds:[edi+0x8] | |
00CABB50 | 52 | push edx | 定位数据 |
00CABB51 | 894D C0 | mov dword ptr ss:[ebp-0x40],ecx | |
00CABB54 | 8B4B 74 | mov ecx,dword ptr ds:[ebx+0x74] | |
00CABB57 | 50 | push eax | |
00CABB58 | 51 | push ecx | |
00CABB59 | 8D4D CC | lea ecx,dword ptr ss:[ebp-0x34] | |
00CABB5C | C745 C4 00000000 | mov dword ptr ss:[ebp-0x3C],0x0 | |
00CABB63 | E8 C8CAE7FF | call <3dnes.public: void __thiscall CachedReader::InitRead(class CacheReaderBase &, unsigned int, unsigned int)> | 出现字符串最后位+1地址 |
0xCABB63 第 4 个参数来自 0xCABB50,0xCABB50 的数据来自 0xCABB41,0xCABB41 的数据来自 0xCAB999
我们先执行到 0xCAB999,看看 eax 指向的地址存放的是什么
[eax+c] 的地址存放的就是我们要找的定位数据,将此处“04”改为“06”,结果是意料之中的
8、eax 是 0xCAB97C 的返回值,理论上应该进入这个 call 进一步分析来源
但我们有更简单的方法,那就是:猜!!!
从前后结构来看 eax 指向的这块数据挺有规律,就像一张表,我们先到文件里找找看
在文件“level0”中搜索“04 01 00 00”这一串数据,搜到很多项,我们不断扩大特征码来缩小范围
搜索到“04 01 00 00 FC FF FF FF 72 00 1F 00 00 00 00 00”的时候,再扩大特征码就搜不到数据了
我们看看搜到的数据,再与程序内存数据比较一下
第一处搜到的数据后面跟着“25 03 00 00”,内存中也有这串数据,只不过中间多了“00 00 00 00”
向后看看,下一项数据也能对上,这里很可能就是关键的数据
不管它,先改改看,改完运行,结果很完美,成功找到了关键数据位置
最终修改之处有三,分别是字符串长度、字符串字符数据及显示长度,前后对比如下:
后记:
调试过程中的一些办法(非明文字符串的搜索、关键流程的准确定位)是我在平时调试过程中自己摸索出来的,在这里抛砖引玉,如果有更好更快捷的方法不妨提出来大家共同进步。字符串是很多程序执行流程的关键提示,越来越多的程序将字符串做了加密,导致内存中搜索不到,但只要显示了,它肯定得先解密,用完再销毁,我们先粗略定位,再通过 x32dbg 灵活的条件断点(复杂一点的需要编写脚本),最终很可能就找到关键位置了,我用这个办法在不同软件中也找到过加密字符串,最终实现了自己想要的目标。x32dbg 是个很强大的工具,我也一直在学习中,多用、善用工具,可以大大提高自己的工作效率。
下面是我在这次分析过程中写的一个脚本,这个脚本初始的设想是用于定位比特流的处理过程,具体方法从头开始执行程序,遇到 call 就检测目标比特流,检测到目标出现就记录日志并进入这个 call 继续向下寻找,最后查看日志就可以看到这个比特流处理的整个执行流程,从而分析到程序的关键处理流程。这个脚本最终在这次分析中没有发挥作用,因为写得匆忙,仅仅是一个初步的思路,也许可以应付一些简单的程序,还有没考虑到的地方(如线程等),可能还有 bug 需要修改,这里也分享出来供大家参考,希望各路高手能提供更好更完善的分析思路。
$addr=0
reload:
init "D:\Down\汉化字符大于原始长度(字符是主程序读取资源文件)\3DNes\3dnes.exe"
cmp $addr,0
je cont
g $addr
cont:
tocnd "dis.iscall(cip)"
$addr=cip
step
findallmem 0,"31 70 65 6E"
cmp $result,0
je cont
log "found pattern at "{$addr}
jmp reload