トップ «前の日記(2008-09-01) 最新 次の日記(2008-09-03)» 編集

日々の破片

著作一覧

2008-09-02

_ メッセージを後付けの拡張ライブラリで変える

llfutureで、処理系のメッセージをちゃんとしてくれ、というのを聴いていて、思った。少なくともMSのコンパイラみたいに、日本語で出たほうがまだましだよな。

どういう手段が良いだろうか? 文字列を機械的に抽出してgettextのmo化しておいて、vsnprintfの呼び出しをフックしてそこで取り換えるってのはどうだろうか? rb_raiseとrb_compile_errorあたりを見ると、最終的にはstaticなerr_snprintfに行くし、そこでvsnprintfを呼び出している。

遅そうだけど、漏れは少なそうだし、遅いったって、どうせテスト時点でしか使わない……と考えると、拡張ライブラリでやるのが良さそうだ。コードの中から呼び出しを探してjmpテーブルを書き変えれば良いのだからどうにかなりそうだ。

で、とりあえず、フックできるかどうか試してみる。

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <windows.h>
int somefunc(char* p, int c)
{   // 置き換えのターゲット
    return printf(p, c);
}
int altfunc(char* p, int c)
{    // これでフックする
    char* alt = (char*)_alloca(strlen(p) + 32);
    int len = sprintf(alt, "***%s", p);
    if (*(alt + len - 1) == '\n') len--;
    strcpy(alt + len, "***\n");
    return altfunc(alt, c);
}
int main(int argc, char* argv[])
{
//    int (*fnc)(char*p, int c) = somefunc;
    unsigned char* org = (unsigned char*)somefunc; // 後の操作のためuchar_t*にする
//    int (*alt)(char*p, int c) = altfunc;
    unsigned char* alt = (unsigned char*)altfunc;
    int diff = org - alt;  // とりあえず、near_label jmp決め打ちでいいや
    DWORD old;
#if_DEBUG
    printf("%p: %02X %02X %02X %02X %02X\n", alt, *alt, *(alt + 1), *(alt + 2), *(alt + 3), *(alt + 4));
    printf("%p: %02X %02X %02X %02X %02X\n", org, *org, *(org + 1), *(org + 2), *(org + 3), *(org + 4));
#endif
    if (!VirtualProtect(org, 5, PAGE_EXECUTE_READWRITE, &old)
        || !VirtualProtect(alt, 5, PAGE_EXECUTE_READWRITE, &old))
    {
        printf("error %d\n", GetLastError());
        return 1;
    }
    else
    {
        unsigned char temp[5];
        memcpy(temp, org, 5);
        memcpy(org, alt, 5);
        *(DWORD*)(org + 1) -= diff;
        memcpy(alt, temp, 5);
        *(DWORD*)(alt + 1) += diff;
    }
    return somefunc("hello %d\n", 32);
}

で、デバッグモードでやってみる。

c:\home\test\Visual Studio 2008\Projects\TestHello\Debug>testhello
0100109B: E9 C0 03 00 00
010010FA: E9 01 03 00 00
***hello 32***

よっしゃ。できた。VirtualProtect APIがミソだな。ページ単位の設定だから2回呼ぶ必要はないはずだけど、逆にやり過ぎても大丈夫なようだ。

ではプロダクトモード

c:\home\test\Visual Studio 2008\Projects\TestHello\Release>testhello
hello 32

え? なぜだ? 死ぬならわかるが……

デバッガで逆アセンブルリストでステップを追う。でも読み込んだアドレスの内容がjmpテーブルっぽく無い。なぜだ?

int somefunc(char* p, int c)
{
    return printf(p, c);
00171000  jmp         dword ptr [__imp__printf (1720ACh)] 
--- ソース ファイルがありません。 ------------------------------------------------------------
00171006  int         3    

すげ。いきなりprintf呼ぶのか。というか、jmpテーブル無しで直接、関数のアドレス持ってるし。というか、ますます死なないのが不思議になる。

00171189  add         esp,8 
    }
    return somefunc("hello %d\n", 32);
0017118C  push        20h  
0017118E  push        offset string "hello %d\n" (172130h) 
00171193  call        dword ptr [__imp__printf (1720ACh)] 
}
00171199  mov         ecx,dword ptr [esp+20h] 
0017119D  add         esp,8 
001711A0  pop         edi  
001711A1  pop         esi  
001711A2  pop         ebp  
001711A3  pop         ebx  
001711A4  xor         ecx,esp 
001711A6  call        __security_check_cookie (1711AFh) 
001711AB  add         esp,0Ch 

2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|

ジェズイットを見習え