CConnection::Startupクラス関数を読み解く

2015年2月25日

この記事は次のステップに進む際の前準備的な物です。

コードから実装アイディアを捻り出すには、そのコードを読み解き、動きを把握することが重要です。
では、以下のコードを見てみましょう。

F0065d540:      0065d540 68d02f8c00            push    dword L008c2fd0
                0065d545 6801010000            push    dword 000000101h
                0065d54a ff15fc267b00          call    dword [WS2_32.dll_73]
                0065d550 85c0                  test    eax,eax
                0065d552 7416                  jz      C0065d56a
;   if {
                0065d554 6864357e00             push    dword S007e3564
                0065d559 e852c8eeff             call    near F00549db0
                0065d55e 83c404                 add     esp,byte +004h
                0065d561 ff1518277b00           call    dword [WS2_32.dll_74]
                0065d567 32c0                   xor     al,al
                0065d569 c3                     ret
;   }
C0065d56a:      0065d56a 56                    push    esi
                0065d56b 68d4347e00            push    dword S007e34d4
                0065d570 ff158c217b00          call    dword [L007b218c]
                0065d576 8b3590217b00          mov     esi,dword [L007b2190]
                0065d57c 68b8347e00            push    dword S007e34b8
                0065d581 50                    push    eax
                0065d582 a364318c00            mov     [L008c3164],eax
                0065d587 ffd6                  call    esi
                0065d589 a368318c00            mov     [L008c3168],eax
                0065d58e a164318c00            mov     eax,[L008c3164]
                0065d593 685c357e00            push    dword S007e355c
                0065d598 50                    push    eax
                0065d599 ffd6                  call    esi
                0065d59b 833d68318c0000        cmp     dword [L008c3168],byte +000h
                0065d5a2 8b3590267b00          mov     esi,dword [L007b2690]
                0065d5a8 a360318c00            mov     [L008c3160],eax
                0065d5ad 751c                  jnz     C0065d5cb
;   if {
                0065d5af 8b0d00277b00           mov     ecx,dword [WS2_32.dll_13]
                0065d5b5 6a00                   push    byte +000h
                0065d5b7 6844357e00             push    dword S007e3544
                0065d5bc 6824357e00             push    dword S007e3524
                0065d5c1 6a00                   push    byte +000h
                0065d5c3 890d68318c00           mov     dword [L008c3168],ecx
                0065d5c9 ffd6                   call    esi
;   }
C0065d5cb:      0065d5cb 833d60318c0000        cmp     dword [L008c3160],byte +000h
                0065d5d2 751c                  jnz     C0065d5f0
;   if {
                0065d5d4 8b1504277b00           mov     edx,dword [WS2_32.dll_10]
                0065d5da 6a00                   push    byte +000h
                0065d5dc 6844357e00             push    dword S007e3544
                0065d5e1 6804357e00             push    dword S007e3504
                0065d5e6 6a00                   push    byte +000h
                0065d5e8 891560318c00           mov     dword [L008c3160],edx
                0065d5ee ffd6                   call    esi
;   }
C0065d5f0:      0065d5f0 e8dbfeffff            call    near F0065d4d0
                0065d5f5 8bc8                  mov     ecx,eax
                0065d5f7 e8b4f8ffff            call    near F0065ceb0
                0065d5fc b001                  mov     al,byte 001h
                0065d5fe 5e                    pop     esi
                0065d5ff c3                    ret
                0065d600 8bc1                  mov     eax,ecx
                0065d602 33c9                  xor     ecx,ecx
                0065d604 894804                mov     dword [eax+004h],ecx
                0065d607 89483c                mov     dword [eax+03ch],ecx
                0065d60a c3                    ret

2011-12-01aRagexe.exe(iRO)をdispeでディスアセンブルしたものの抜粋で、ROのC++のソースコードのCConnection::Startup関数にあたります。
しかしながら、この状態は訓練している人なら別ですが、普通の人には難読極まりなく、読むことさえ放棄したくなるような代物です。
著者はz80のコードをハンドアセンブルして、バイナリエディタでプログラムを組むなんて経験をして育ってきているので、割と苦にならない人間ですが、それでもコードが大きくなるとシンドイとは感じます。
やはり可読性を上げる作業は必須です。

このサイトで公開しているコードに、ラベルや関数名、変数名を付けていますが、これは以前、公式からダウンロード出来た「ハイプリーストになりたい」(HighPriest_081105_JPN.exe)というミニゲームのデバッグシンボル情報から導き出し、現行ソースコードに至るまで、ある程度保守していた事によるものです。

現行exeでも、コードを照らし合わせてラベル定義をすることは可能なので、これを適応したものを提示します。ツールとしては、保守していくことも考慮に入れて、ここのサイトで紹介しているIDAを使う事をお勧めします。

では実際、読み易さがどこまで向上するかを見てみましょう。

CConnection__Startup proc near          ; CODE XREF: WinMain(x,x,x,x)
                push    offset CConnection_s_wsaData ; lpWSAData
                push    101h            ; wVersionRequested
                call    ds:WSAStartup
                test    eax, eax
                jz      short loc_65D56A
                push    offset aFailedToLoadWi ; "Failed to load Winsock library!"
                call    ErrorMsg
                add     esp, 4
                call    ds:WSACleanup
                xor     al, al
                retn
; ---------------------------------------------------------------------------

loc_65D56A:
                push    esi
                push    offset aWs2_32_dll_0 ; "ws2_32.dll"
                call    ds:LoadLibraryA
                mov     esi, ds:GetProcAddress
                push    offset aSend    ; "send"
                push    eax             ; hModule
                mov     CConnection_s_wsmodule, eax
                call    esi ; GetProcAddress
                mov     CConnection_s_wsSend, eax
                mov     eax, CConnection_s_wsmodule
                push    offset aRecv    ; "recv"
                push    eax             ; hModule
                call    esi ; GetProcAddress
                cmp     CConnection_s_wsSend, 0
                mov     esi, ds:MessageBoxA
                mov     CConnection_s_wsRecv, eax
                jnz     short loc_65D5CB
                mov     ecx, ds:send
                push    0               ; uType
                push    offset aModuleHookingE ; "Module Hooking Error"
                push    offset aGetprocaddress ; "GetProcAddress(¥"send¥") Failed."
                push    0               ; hWnd
                mov     CConnection_s_wsSend, ecx
                call    esi ; MessageBoxA

loc_65D5CB:
                cmp     CConnection_s_wsRecv, 0
                jnz     short loc_65D5F0
                mov     edx, ds:recv
                push    0               ; uType
                push    offset aModuleHookingE ; "Module Hooking Error"
                push    offset aGetprocaddre_0 ; "GetProcAddress(¥"recv¥") Failed."
                push    0               ; hWnd
                mov     CConnection_s_wsRecv, edx
                call    esi ; MessageBoxA

loc_65D5F0:
                call    CRagConnection__instanceR
                mov     ecx, eax
                call    sub_65CEB0
                mov     al, 1
                pop     esi
                retn
CConnection__Startup endp

どうでしょう?、少しはやる気が出てきませんか?

コードとしては特に難しい事をしている訳でもないので、先頭から順々に解釈していくことで、元のC++のソースコードを想像することも出来ます。筆者の頭の中では上記のコードを眺めていると、以下のようなコードが頭の中に見えてきます。慣れないうちは、アセンブリコードの行を処理ごとに色分けしてみるのも良いかもしれません。

typedef int (WSAAPI *tWS32_send)( SOCKET s, const char *buf,int len,int flags );
typedef int (WSAAPI *tWS32_recv)( SOCKET s, char *buf,int len,int flags );

WSADATA CConnection::m_s_wsaData;
tWS32_send CConnection::m_s_wsSend;
tWS32_recv CConnection::m_s_wsRecv;
HMODULE CConnection::m_s_wsmodule;

BOOL CConnection::Startup()
{
    if( WSAStartup( 0x0101,&m_s_wsaData) )
    {
        ErrorMsg( "Failed to load Winsock library!" );
        WSACleanup();
        return FALSE;
    }
// loc_65D56A

    m_s_wsmodule = LoadLibraryA( "ws2_32.dll" );

    m_s_wsSend = (tWS32_send)GetProcAddress( m_s_wsmodule, "send" );
    m_s_wsRecv = (tWS32_send)GetProcAddress( m_s_wsmodule, "recv" );

    if( !m_s_wsSend )
    {
        MessageBoxA( NULL,"GetProcAddress(¥"send¥") Failed.","Module Hooking Error",MB_OK);
    }
// loc_65D5CB
    if( !m_s_wsRecv )
    {
        MessageBoxA( NULL,"GetProcAddress(¥"recv¥") Failed.","Module Hooking Error",MB_OK);
    }
// loc_65D5F0

    CRagConnection::instanceR() -> sub_65CEB0();

    return TRUE;
}

call ds:WSAStartupで関数を呼び出し、
test eax,eaxで戻り値を評価、
jz short loc_65D56Aで戻り値がゼロ以外ならエラーメッセージの表示といった感じに読み続けていきます。
難しいと感じるのは全体を見て、膨大な量だと感じてしまうためで、1つ1つを見ると、実は単純な処理の羅列でしかありません。

ここまでくればsendとrecvのファンクションのアドレスが発覚したことになり、パケット受信処理をフックしてパケットを捕捉することにより、独自の処理を実装する事が可能です。
ただ、コードスクランブルが一部で実装されたことにより、そう単純には行かなかったりしますが、これはまた次回以降に続きます。

Ragnarok Online

Posted by redchat