こんばんは!
皆さん、お久しぶりです!
私は無事、希望していた高校に合格し、やっと落ち着いてきたところです。
そして今日から夏休み。これから1ヶ月、勉強しつつこちらでも遊んでいけたらと思っています。
最近の興味:リバースエンジニアリング
最近、リバースエンジニアリングにハマっています。
具体的には、IDA ProやGhidraなどを使って、静的解析をちょくちょく試しています。
中学2年生の頃にもGhidraを少し触ってみたことがあったのですが、当時は「何をどこからどう勉強すればいいのか」が全く分からず、結局放置してしまいました。
でも今は、AIの発達もあり、無料でも相当優秀なAI教師が身近にいますよね。
そのおかげで、最近またGhidraなどに触れて解析を再開してみると、パズルのように問題が解けていく感覚があり、とても楽しく感じるようになりました。
将来やってみたいこと
この経験を通じて、将来やってみたい仕事が明確になってきました。
それは――リバースエンジニアリングの仕事です。
とはいえ、リバースエンジニアリングにも色々な役職がありますよね。
- ゼロデイ脆弱性の発見
- マルウェア・ランサムウェアの挙動解析
- バイナリ解析全般
私は、そのすべてに興味があります。
ですから、高校3年間で2,000〜3,000時間の学習時間を確保し、しっかりスキルを身につけたいと考えています。
将来の目標
以下が、私の今考えているキャリアプランです:
-
GMO Flatt SecurityまたはGMOサイバーセキュリティ byイエラエに就職
→ 実務を通じて技術力を磨く -
英語の勉強と並行してスキルを高め、FFRIセキュリティに転職
→ より高度なマルウェア解析技術とゼロデイ発見スキルを習得 -
最終的に、CrowdStrikeにフルリモートで就職
→ 世界レベルの環境でリバースエンジニアとして働く
もちろん、こんなに上手くいくとは限りませんが(笑)、
目指す価値のある夢だと思っています。
本題!
今回の記事では、IDA Proについてを取り扱っています。
具体的には、exeファイルのデコンパイルやディスアセンブルについてを解説します!
基本的なつかいかた
IDA Proはインストールされている体で話をします。私は毎回ida.exeを開くのは面倒なので、レジストリ登録して右クリックで簡単に開けるようにしてます。んで、IDA Proで逆アセンブル処理などが終わったら、Produce File>Create C Fileに行って、疑似C言語ファイルを保存します。大体C言語で大まかな動作フローが分かったらそれでいいです。後、まだ何なのかよくわからない関数などが出てきたらmapファイルなども保存して見てください。私の今回の目的は、とあるexeファイルから、暗号化されたファイルを復号化するためのキーを特定することです。私の場合は、自己展開型のexeファイルだったため、tempフォルダを覗いて、完全コピーしました。私はまだ使ったことはないのですが、x64dbgなどの動的解析ツールを使って、CreateFileやWriteFileとかのAPI呼び出しにブレークポイントを設定して監視するという方法もあります。そういうのが難しいならProcess Monitorを使ってどこにファイルを作成したかなどを知ることができます。
逆コンパイル結果の解析
まずは、元のexeファイルのメタデータを確認します。確認方法はプロパティ>詳細に行けば分かります。大きな手掛かりがありました。元のファイル名が載っていました。ここでの明言は避けますが(ほとんどは利用規約的にリバースエンジニアリングは禁止されているので)、この元のファイル名が後に重要な手掛かりになります。そして、言語の部分がニュートラル言語となっていました。これは、言語の設定をしていなかったときに設定されます。これは、RustやGoなどのマルウェア、ほかには自己展開型(以降SFXと呼ぶ)の実行ファイルに良く見られます。今回の線では、SFXの実行ファイルだと仮説を立てて進めてみます。
では次に、WindowsのAPIであるCreateFileや、WriteFile、CreateDirectory、tempなどを検索してみます。見事にヒットしました。sfxと見て間違いなさそうです。
exeファイルの疑似C言語ファイルを見てみます。そのコードのごく一部に、このような記述がありました。
v5 = sub_14000AAA0(&NewDirectory, L"{TEMP}\\onefile_{PID}_{TIME}", 4096);
v6 = sub_14000A4B0("NUITKA_ONEFILE_PARENT");
これは、NUITKAという、PyInstallerと良く混同するコンパイラのことです。PyInstallerはpythonのエンジンを使ったままそれをexeに包み込んで使用するラッパーで、nuitkaはpythonコードをC/C++のネイティブコードに変換して、一切pythonエンジンを使わずexeで動作するsfx型のコンパイラのことです。このコードを見る限り、このtempフォルダの最初がonefileというディレクトリにプログラムの中身があるようです。
逆アセンブル結果の解析
一応exeのアセンブリ見てはみたんですがmovとか低レベルすぎてちんぷんかんぷんでした。ですが、AIに取り込んでみると、一部動作ロジックの推測はできました。いやぁ自分も早くx86アーキテクチャのアセンブリ習得したいところですね(笑) 先にアセンブリを見るのではなく、まずは逆コンパイルをして、そのコードを確認してから、大雑把に全体を見て、最適化や難読化によって読み取れない部分があるならそこをアセンブリで細かく確認すると良さそうです。でも、C言語とは違って、関数も変数も何なくてメモリアドレスやレジスタ名で処理を追う必要があるため、可読性が死ぬほど低いのととにかく量が多いのでやる気が失せてしまいますね(笑)
Tempに保存されたファイルの解析
Tempに保存されていたファイルから復号キーが保存されているであろうファイルを探ります。さっき発見したメタデータに含まれていた元のファイル名と良く似た、こちらもまたメタデータがニュートラル言語になっているdllファイルを見つけました。おそらくこのdllファイルに復号キーが保存されているのでしょう。
dllファイルのディスアセンブル
次はdllファイルのディスアセンブルを行いたいと思います。行った後の結果がこちらです。
decryptやcrypto、aesとかで検索をかけたら、このようなものがヒットしました。
.data:000000018045E320 dq offset aCryptoCipherRa ; "Crypto.Cipher._raw_aes"
Crypto.Cipher._raw_aesでgoogleに検索かけてみると、pycryptodomeというpythonのライブラリがヒットしました。AES暗号の復号化をするうえで重要な関数だそうです。ここのアドレスからクロスリファレンスを参照てみます。が、何もヒットしません。もしかすると直接このアドレスから関数を呼び出しているのではなく、このアドレスが沢山保存されているテーブルから間接的にアクセスしている可能性があります。ということで、即値検索を行っていきます。
即値検索
IDA Proでは、Alt+Iで即値検索のウィンドウが開きます。さっきのポインタのアドレスをここに入力します。さっきの例だと18045E320
です。ida側にhex形式だと指定しなければならないため、このアドレスの先頭に、0xを追加します。
0x18045E320
これでsearchしてみます。残念ながらヒットしませんでした。推測が間違っているようです。もしかするとアドレスの動的計算をしている可能性があります。その線で考えていきましょう。
解析手法の変更
今までは、アドレスから直接関数を参照しているものを見つけようとしていました。(クロスリファレンス)
でも、この動的計算によってキーの保存されたアドレスが参照されていると話は変わってきます。このキーが参照される瞬間を捉える必要があります。そこで今使えるのが、
PyBytes_FromStringAndSizeです。
これは何なのかというと、このNuiktaによってC言語に変換されたプログラムを、Pythonで受け渡しができるような形式に変換するためのPythonのAPIです。具体例を言うと、C言語で書かれたAESの復号処理を行うプログラムを、そのままPythonに渡して動かそうとしても言語が違うため処理不能です。でも、このPython/C APIを使うことによって、C言語のプログラムをPythonで動かせる形に変換してくれるのです。このAPIの一部の関数が、先ほどのPyBytes_FromStringAndSizeです。だから、このAESの復号処理をするときにこのAPIは必ず使われます。よって、この関数を特定し、これを参照しているアドレスを発見出来たら、それが復号キーの保存されている可能性が高いのです。では具体的な手順に移ります。
Python APIの関数特定
まずは、IDA Proで先ほどのdllを開いた状態で、Shift+F3をFunctionsタブを開きます。そこでCtrl+Fを押して、さっきのAPI関数、PyBytes_FromStringAndSize
でフィルターをかけて検索してみます。ヒットしませんでした。ここでももしかすると動的リンクで関数呼び出しをしているのかもしれません。簡単に言うと、
dllのインポートテーブルからPython本体(Python310.dll)にアドレスを参照してもらって、そこからcallしている可能性があります。
そのため、関数リスト(Functions)には検索をしてもヒットしませんでした。では、どうすればいいのか.
それは、Importsから検索をしてみることです。そうすることで、このインポートテーブルにある目的のPython310.dllにアドレスを参照するものを見つけることができます。
ImportsからAPIの関数検索
Importsタブの方からPyObject_GetAttrString
を検索してみます。この関数はさっきのものとは違い、AESで復号化をするとき、指定した名前のメソッドを取り出すために使われる関数です。具体例でいうと、AES.newというのをC言語で処理できるように変換するようなものです。Nuitkaでコンパイルされたプログラムなら確実と言っていいほど使われてます。すると、400件ほど沢山ヒットしました。成功です。でも400件は多すぎますね(笑)
絞り込む方法があるのかまでは知らないので、骨が折れますが、一つひとつ確認していきましょう。
呼び出し元の検証
ここまで済んだらあとは脳筋で見ていくだけです。 まずはヒットしたものをクリックしてコードにジャンプします。そうすると、大抵はこういう感じになっているはずです。
.text:0000000180009AFB lea rdx, aSpecFromModule ; "_spec_from_module"
.text:0000000180009B02 mov rcx, rax
.text:0000000180009B05 call cs:PyObject_GetAttrString
しっかりとPyObject_GetAttrString
がcallされているのを確認できますね。
そして、ここで重要なのがこのcallされている命令の、直前にある引数です。この例でいうとrcxが第一引数、rdxが第二引数です。私の場合、このaSpecFromModule
の部分が、aNew
のものを探します。理由を詳しく解説するとだいぶ長くなるので割愛しますが、簡単に説明すると、このNew
というのは、Cryptodomeを使うとAES.new
という形で、復号オブジェクトを生成するときに使われる、絶対に変わることのない関数名です。なぜこれを調べるのかというと、ここには私の探している、復号キーや、初期化ベクトルなどが絶対に入っているキーワードだからです。他にもdecrypt
とかだったら復号処理を行うときに使われるキーワードの可能性があるため、それで検索してみてもいいかもしれません。でも、今回は一番確実かと思われるNewで探しています。でももちろん、newという単語は一般的に良く知られていて、使われている単語なのでほかの無関係の関数とかもヒットするかもしれません。現に10個ほどヒットしました。
別に、他のものを解析するときも同じような考え方でいいです。たとえばネットワーク通信している箇所を見つけたい場合は、pythonだったらsocket
ライブラリを使うだろうな、と仮説を立てて、socket
やconnect
などで検索をかけてみます。それでsocket
ライブラリ特有の関数とかが使われていることが判明したら仮説は正解です。後はそのsokcet
ライブラリ特有のキーワードを調べて、自分の欲しい処理に含まれているキーワードを検索していったら辿り着きます。今回の場合は復号処理が行われているであろうと思い、decryptやcrypto、aesと検索をしてみたら
Crypto.Cipher._raw_aes
というものがヒットしたので、これをWeb検索してみたらCryptodome
のライブラリの関数だったと判明しました。あとはこのCryptodome
で復号処理をする前準備(AESオブジェクトの作成)のときに使われるAES.new
の、new
という単語を選びました。AES
で検索をかけると別の関数でも沢山使われていて今調べているものとは全く違うものまで大量にヒットするからです。でも、このAES
は、何のライブラリを使っているかを調べるときにはだいぶ有効なので、使い分けが大切です。だから、このAESオブジェクトの作成の時に使われるAES.new
のnew
だったらほかの関数がヒットしたりということは起こりません。それとちなみに補足を言うと、変数名とか(今の解析している対象物だとdecryption_key
やiv
など)から直接探そうとすると、ほっとんど無理です。何故かというとこういうのはコンパイルして、変数の名前が完全に変わってしまうからです。それをデコンパイラとかディスアセンブラがいいように解釈して、v3
とか、v57
みたいな具合で変数を設定しているので、何か特別なことがない限りここからの特定はだいぶ厳しいです。実際に、もしもdecryption_keyという形の変数を探そうとした場合、途方もない労力と時間がかかります。汚染解析と言って、とにかく他の関数とかロジックを全て一つずつ調べて、このdecryption_keyの中身だろうなと予想して、発見する方法です。C言語のデコンパイル結果でこのようなものをたまに見ますよね。
v1 = v2
これが汚染解析の一部です。v1をv2に代入するみたいに書かれてますよね。こうやって広がっていくので汚染解析と呼ばれています。このようなプログラムを全て網羅して、全体を見て解析する必要があります。正攻法である前方解析と比べると100倍労力と時間がかかります。
ちょっと長く言いすぎてしまいました。ってことで、次のステップに移りましょう。
ということで、分かりましたね?第二引数であるrdxの後の文字列は、aNew
でないといけないということが。理由は先ほどのとおりです。大事だから詳しく書きました。リバースエンジニアリングをするうえで、仮説を立てることはとっても大切なことです。(AIからそのことを学びました)
これができないとほとんどリバースエンジニアリングはできないでしょう。目的のものを探し出すためには、その目的のもののちょっと特殊なキーワードを頭に入れておかないと、静的解析をするのは困難です。動的解析の話は別ですよ?まあそれでもこの考えはどっちの解析手法でも同じですが。ある程度身に付けておかないと自分で解析するのは難しくなります。
ということで、話を整理するためもう一度、ImportsでPyObject_GetAttrString
を検索してみると、一つヒットしました。クリックしてコードにジャンプしてみます。.idataセクションに辿り着きました。ここで、PyObject_GetAttrStringにカーソルを置いた状態でXキーを押してクロスリファレンスを確認します。約400個のアドレスで呼び出されているようです。AIに生成してもらった検索ツールを使って、このクロスリファレンスに出てきたアドレスの周囲10アドレス以内にnew
というキーワードがあるのかを調べてもらいました。結論から言うとヒットしませんでした。これも失敗。次の方法に移ります。
PyBytes_FromStringAndSizeの調査
説明だけして実際に調査はしていませんでした。こりゃ失敬失敬。ということで、実際に調査してみます。
Importsタブで、PyBytes_FromStringAndSize
を検索してみます。ヒットしませんでした。これもダメか、と思いつつ、入力されたテキストを消していくと、一つだけヒットしました。それが、
PyBytes_FromString
です。これはさっきの関数とほぼ同等のものです。そして、ジャンプしてクロスリファレンスを参照してみました。二件、.rdata
と.pdata
にヒットしました。.rdataに行ってみてみました。すると、こんなアセンブリになっていました。
.text:000000018040D584 mov [rsp+10C8h+var_58], rax
.text:000000018040D58C mov rbp, rdx
.text:000000018040D58F mov [rsp+10C8h+var_1088], r9b
.text:000000018040D594 mov r15, rcx
.text:000000018040D597 mov edx, 2Eh ; '.' ; Ch
.text:000000018040D59C mov rcx, rbp ; Str
.text:000000018040D59F mov r13, r8
.text:000000018040D5A2 call strrchr
.text:000000018040D5A7 mov r14, rax
.text:000000018040D5AA mov r12, rbp
.text:000000018040D5AD test rax, rax
.text:000000018040D5B0 jz short loc_18040D5B6
.text:000000018040D5B2 lea r12, [rax+1]
.text:000000018040D5B6
.text:000000018040D5B6 loc_18040D5B6: ; CODE XREF: sub_18040D560+50↑j
.text:000000018040D5B6 xor esi, esi
.text:000000018040D5B8 mov rcx, r12
.text:000000018040D5BB test r14, r14
.text:000000018040D5BE mov eax, esi
.text:000000018040D5C0 cmovnz rax, rbp
.text:000000018040D5C4 mov [rsp+10C8h+var_1080], rax
.text:000000018040D5C9 call cs:PyBytes_FromString
これは私の期待しているものではありませんでした。私は、
lea rcx, [キーが格納されたアドレス]
call cs:PyBytes_FromString
こんなふうになっていて、このキーが格納されたアドレスに直接復号キーが入っているものかと思っていました。でも、この例では、rcxに、r12というレジスタが入っています。これは何を意味するのかというと、第一引数がr12という名前の、rcxとは違う中身がずっと保持される不揮発性のレジスタのことです。アセンブリを追っていくと、PyBytes_FromStringに渡すアドレスをr12から受け取っていて、そのr12はどこからやってきたのかと調べていくと、lea r12, [rax+1] ここではr12に入っている中身が、rax+1にあるアドレスのものに書き換えられていることが分かります。このraxはどこからきたのかを調べると、
mov r12, rbp
このようにrbpの中身がr12にコピーされているということがわかります。rbpはどこからやってきたんでしょう。
call strrchr
これを見ると、strrchrという関数がcallされていることが分かります。web検索をしてみると、C言語の関数だそうです。どういうものかというと、 文字strの中から文字cがあるかどうか探索する。
対象の文字が見つかった場合、最後に現れた文字cのポインタを返す。
というものです。簡単に言うと、文字列を後ろから検索して、対象の文字が最後に出てきた場所のアドレスを返す関数です。
この対象の文字が何なのかを調べるためにこのstrrchrがcallされる前の命令を見てみます。
mov edx, 2Eh ; '.' ; Ch
これを見てみると、edx(第二引数)に2Ehというものを入れているようです。2Ehはasciiコード表では、「.」だそうです。これを見てください。
.text:000000018040D59C mov rcx, rbp ; Str
strrchrのrcx(第一引数)にはrbpの値をセットしていることが分かります。このrbpは何なんでしょうか?
.text:000000018040D58C mov rbp, rdx
この関数の最初のところで、第二引数として渡されたrdxを、rbpにコピーしているようです。
よって、これはファイル名から拡張子を取得するためのコードだと分かります。これは私の探し求めているものではありませんでした。これは失敗ということで、手段を変えてみます。そもそも何故これは失敗してしまったのでしょうか?目的のものがヒットしなかったということは、実行時に動的にアドレス解決を行っている可能性が非常に高いです。x64dbgを使ってブレークポイントを置いたら一番効率的に復号キーを取得することができると思いますが、今回は静的解析だけで復号キーを特定したいため、この手段は封印します。というかそもそもまだx64dbgを使ったことがないので、今はとりあえずIDA Proだけを使って本気で解析してみます。静的解析のスキルも上がるし悪いことでも何でもありません。これは一種の手段です。
ちなみにこれから調べていく関数で動的解析が必須になってしまう場面がやってきます(笑)
GetProcAddressの監視
さっきのようにインポートテーブルから検索してもヒットしないのはなぜかというと、さっきも説明した通り動的にアドレス解決を行っている可能性が高いです。もう一つの可能性は、そもそもその関数は使われていないという仮説です。でも、今回のような場合ではこの関数は確実に使っているものと推測できます。(PyBytes_FromString
はC言語をPythonで処理するときに必ず必要な関数だから)
なので、インポートテーブルなしに外部の関数のアドレスを取得する代表的な方法が、このGetProcAddress
というものです。これを使えば動的にアドレス解決を行うことができます。ということで、GetProcAddress
というWindowsのAPIをImportsタブから探してみます。これは、kernel32からヒットするはずです。無事ヒットしました。
次に、それをクリックしてクロスリファレンスを確認します。すると、10個くらいヒットしました。一つ一つ確認していくと、このようなアセンブリがありました。
.text:000000018040D814 lea rdx, [rsp+10C8h+ProcName] ; lpProcName
.text:000000018040D81C mov rcx, r12 ; hModule
.text:000000018040D81F call cs:GetProcAddress
見た感じ、動的解析するしかなさそうです(笑)
普通の人はここでx64dbgでブレークポイントを置いて使うんですが、私の場合は、x64dbgが実行できない環境にいる前提で話を進めます。まあ別にここでx64dbgを使えば、復号キーのありかはわかります。でも、今回は動的解析はできないので、他の調べ方を模索してみます。
解析方法の洗い出し
私は超重要な根本のことを忘れていました。AES-256のキーは必ず、32バイトであることを。初期化ベクトルも同様16バイトであることを。私が何を言いたいのかというと、PyBytes_FromStringAndSizeのような関数を呼び出すときには必ず、このバイト数を引数として渡す必要があります。話は簡単です。ただ、16や32のような数字を関数で引数としてロードされている箇所をアセンブリで検索するような簡単なことです。ということで、やっていきます。
バイト列からの探索
IDA ProのSearch > Sequence of bytesで、検索を行っていきます。普通にアセンブリでテキスト検索するのでも、まあ駄目なわけではないんですが、若干の無関係なもの(ノイズ)が出てくるため、バイト列から検索するのが最も正確なのでそれをお勧めします。
mov edx, 20h
を探します。この20hは16進数表記になっていて、10進数に変換すると32です。さっきのAES-256のキーは必ず32バイトと言いましたね。これを使うことによってedxレジスタに32という数字が入ってきたということです。ただ、このままだとバイト列検索ができないため、バイトシーケンスの型に直して検索をかけます。
BA 20 00 00 00
こうすることで検索ができるようになります。そうすると、二件ヒットしました。
Address Function Instruction
.text:0000000180167244 sub_1801666F0 mov edx, 20h ; ' '
.text:00000001803DEE44 sub_1803DED20 mov edx, 20h ; ' '
この二つのコードにそれぞれジャンプしてみます。
そうすると、このようなものが現れました。
.text:0000000180167244 mov edx, 20h ; ' '
.text:0000000180167249 jmp loc_1801675A1
ビンゴです。この命令が終わった後のコードフローを確認してみると、jmp loc_1801675A1 というジャンプ先があります。実際に飛んでみてください。すると、
.text:000000018016754D mov [rsp+0F0h+var_C0], rcx ; __int64
.text:0000000180167552 mov [rsp+0F0h+var_C8], rcx ; __int64
.text:0000000180167557 mov [rsp+0F0h+var_D0], rcx ; __int64
.text:000000018016755C lea rcx, sub_180165A30 ; int
.text:0000000180167563 call sub_1803DDDB0
.text:0000000180167568 mov rdx, cs:qword_180473708
.text:000000018016756F mov r8, rax
.text:0000000180167572 mov rcx, r14
.text:0000000180167575 mov rbx, rax
.text:0000000180167578 call cs:PyObject_SetItem
.text:000000018016757E sub qword ptr [rbx], 1
.text:0000000180167582 mov dword ptr [rbp+57h+arg_8], eax
.text:0000000180167585 jnz short loc_180167594
.text:0000000180167587 mov rdx, [rbx+8]
.text:000000018016758B mov rcx, rbx
.text:000000018016758E call qword ptr [rdx+30h]
.text:0000000180167591 mov eax, dword ptr [rbp+57h+arg_8]
.text:0000000180167594
.text:0000000180167594 loc_180167594: ; CODE XREF: sub_1801666F0+E95↑j
.text:0000000180167594 test eax, eax
.text:0000000180167596 jz loc_180167647
.text:000000018016759C mov edx, 6Eh ; 'n'
.text:00000001801675A1
.text:00000001801675A1 loc_1801675A1: ; CODE XREF: sub_1801666F0+B59↑j
.text:00000001801675A1 ; sub_1801666F0+BE7↑j ...
.text:00000001801675A1 mov rax, [rdi+58h]
.text:00000001801675A5 mov rbx, [rdi+68h]
.text:00000001801675A9 mov qword ptr [rbp+57h+var_70], rax
.text:00000001801675AD mov rax, [rdi+60h]
.text:00000001801675B1 mov qword ptr [rbp+57h+var_70+8], rax
.text:00000001801675B5 xor eax, eax
.text:00000001801675B7 mov [rdi+58h], rax
.text:00000001801675BB mov [rdi+60h], rax
.text:00000001801675BF mov [rdi+68h], rax
.text:00000001801675C3 mov [rbp+57h+var_60], rbx
.text:00000001801675C7 test rbx, rbx
.text:00000001801675CA jnz short loc_1801675F3
.text:00000001801675CC mov rcx, r12
.text:00000001801675CF call sub_180411220
.text:00000001801675D4 mov rcx, [rbp+57h+var_60]
.text:00000001801675D8 mov rbx, rax
.text:00000001801675DB test rcx, rcx
のようなアセンブリがありました。この中でとっても重要なものはこの二つです。
.text:00000001801675CC mov rcx, r12
.text:00000001801675CF call sub_180411220
何故これが重要なのかというと、復号キーが入っているRCXレジスタに、r12の内容をコピーするという処理と、この呼び出されたsub_180411220という関数は、さっきの復元キーがセットされたRCXレジスタと、その前にEDXにセットされたバイトの長さを使って次の処理をしようとしているのです。そして、このr12の内容を調べるため、これよりも上の方にあるアセンブリを見てみます。
.text:000000018016719E mov r12, rax
.text:00000001801671A1 mov rax, [rdi+18h]
.text:00000001801671A5 mov [rdi+18h], r12
.text:00000001801671A9 test rax, rax
.text:00000001801671AC jz short loc_1801671B3
.text:00000001801671AE mov [r12+18h], rax
このようなアセンブリを発見しました。r12レジスタにraxレジスタに入ってるものをコピーしているようです。これが、このsub_180411220で使われるrcxレジスタの中身です。
結論
結論から言うと、静的解析では「ここまでが限界」です。私は今までバカほど時間を費やして静的解析をしてきましたが、raxは何なのかを調べようとするには、動的解析を使うしかほかなる方法はもうありません。GetProcAddressによってアドレスが動的解決されているのです。そのため、実際にブレークポイントを置いて解析するしか方法はありません。また近日中に動的解析編をやるかもしれません(笑)
本当に不定期なので気が乗ったらやります!ご清聴ありがとうございました!!