ollydbg で次の数行のアセンブリを見つけました。
MOV ECX,DWORD PTR DS:[xxxxxxxx] ; xxxxxxxx is an address
MOV EDX,DWORD PTR DS:[ECX]
MOV EAX,DWORD PTR DS:[EDX+116]
CALL EAX
誰かステップスルーして、ここで何が起きているのか教えてくれませんか?
これは、構造体に格納された関数ポインターの呼び出しです。
この最初の行は、 address に格納されているポインターを取得しますDS:xxxxxxxx
。角括弧は、Cと同様にアドレスの逆参照*
を示します。メモリからの値がポインタとして使用されようとしています。それはレジスターに入れられecx
ます。
MOV ECX,DWORD PTR DS:[xxxxxxxx] ; xxxxxxxx is an address
2 行目は、上記で取得したポインターを逆参照します。その値 fromecx
は、逆参照されるアドレスとして使用されるようになりました。メモリ内で見つかった値は別のポインターです。この 2 番目のポインターは、edx
レジスターに配置されます。
MOV EDX,DWORD PTR DS:[ECX]
3 行目もメモリを逆参照します。今回は、上記で取得したポインタから 0x116 バイトずれたアドレスにアクセスが発生します。これは 4 で割り切れないため、この関数ポインターは C++ vtable からのものではないようです。メモリから取得した値は、今度はレジスタに格納されますeax
。
MOV EAX,DWORD PTR DS:[EDX+116]
最後に、 が指す関数eax
が実行されます。これは、関数ポインタを介して関数を呼び出すだけです。関数は引数を取りませんが、回答の改訂について質問がありPUSH
ます。このスニペットに先行する指示はありますか? それらは関数の引数になります。クエスチョン マークは、この関数が値を返す可能性があることを示しています。
CALL EAX
全体として、コード スニペットは、プラグイン ライブラリから OllyDbg への拡張関数の呼び出しのように見えます。OllyDbg ABIstruct
は、いくつかの関数ポインターを含むさまざまな を指定します。関数ポインターの配列もありますが、保持されたポインターに到達するための二重の間接参照 (偶数倍でアライメントされていないオフセットも) があるため、これは関数ポインターの配列または C++ ではなく、そうでedx
はないと思います。struct
クラスの vtable。
つまり、関数ポインタを含むxxxxxxxx
へのポインタへのポインタです。struct
OllyDbg ソース ファイル PlugIn.h には、いくつかの候補struct
定義があります。次に例を示します。
typedef struct t_sorted { // Descriptor of sorted table
char name[MAX_PATH]; // Name of table, as appears in error
int n; // Actual number of entries
int nmax; // Maximal number of entries
int selected; // Index of selected entry or -1
ulong seladdr; // Base address of selected entry
int itemsize; // Size of single entry
ulong version; // Unique version of table
void *data; // Entries, sorted by address
SORTFUNC *sortfunc; // Function which sorts data or NULL
DESTFUNC *destfunc; // Destructor function or NULL
int sort; // Sorting criterium (column)
int sorted; // Whether indexes are sorted
int *index; // Indexes, sorted by criterium
int suppresserr; // Suppress multiple overflow errors
} t_sorted;
これらの例は許可されており、asm スニペットは関数ポインターのポインターをNULL
チェックしません。NULL
したがって、DRAWFUNC
fromt_table
またはSPECFUNC
of である必要がありt_dump
ます。
ヘッダー ファイルを含む小さなプロジェクトを作成し、 と を使用printf()
しoffsetof()
て、それらのいずれかが 0x116 のオフセットにあるかどうかを判断できます。
そうでなければ、OllyDbg の内部はこのスタイルで書かれていると思います。そのため、OllyDbg 内でさまざまな目的に使用されるプライベートstruct
な定義 (Plugin.h ファイルで公開されていない) が存在する可能性があります。
付け加えておきたいのですが、OllyDbg のソースが利用できないのは残念だと思います。そこに含まれる静的にリンクされた逆アセンブラは、ある種の ?GPL ライセンスの下にあるという印象を受けましたが、OllyDbg のソースを入手することができませんでした。
アドレス xxxxxxx から 32 ビットの数値を取得し、ECX レジスタに入れます。次に、この値をアドレスとして使用し、値を読み取って EDX レジスタに入れ、最後にこの数値に 116 を加えて、そのアドレスの値を EAX に読み取ります。次に、現在 EAX に保持されているアドレスでコードの実行を開始します。そのコードが戻りオペコードに遭遇すると、call 命令の後に実行が続行されます。
これはかなり基本的な組み立てです。あなたがデバッガーでやっているのと、割り当ての期限がいつなのか不思議に思います;-)
コンパイラが MSVC であるというコメントを考えると、それが仮想メソッド呼び出しであると 99% 確信しています。
MOV ECX,DWORD PTR DS:[xxxxxxxx]
クラス インスタンスへのポインタは、グローバル変数から ECX にロードされます。(注: デフォルトの __thiscall 呼び出し規約では、ECX を使用してインスタンス ポインター (別名thisポインター) を渡します)。
MOV EDX,DWORD PTR DS:[ECX]
vftable (仮想関数テーブル) ポインターは通常、クラス レイアウトの最初の項目です。ここで、ポインタが EDX にロードされます。
MOV EAX,DWORD PTR DS:[EDX+116]
テーブルのオフセット 116 (0x74) にあるメソッド ポインターが EAX に読み込まれます。各ポインターは 4 バイトであるため、これはクラスの 30 番目の仮想メソッド (116/4 + 1) です。
CALL EAX
メソッドが呼び出されます。
元の C++ では、次のようになります。
g_pObject1->method30();
仮想メソッドを含む MSVC の C++ クラスの実装について詳しくは、こちらの記事を参照してください。
私が ASM (1997) をやってからしばらく経ちましたが、それでも私は i386 ASM しかやっていませんでした。
残念ながら、これらの 4 行のコードではあまりわかりません。ほとんどの場合、CPU レジスタにデータをロードして関数を呼び出すだけです。
具体的には、データまたはポインターがそのアドレスから CX レジスターにロードされているようです。次に、その値が CX から DX にコピーされます。したがって、DX にある CX のポインターの値があります。次に、DX の値に 116 のオフセットを加えた値が AX レジスタ (アキュムレータ?) にコピーされます。
次に、AX にコピーされたそのアドレスにある関数が実行されます。