プログラム リバース: Visual Basic プログラム リバース分析

1. Visual Basic の概要:

1.百度百科事典の紹介:

Visual Basic (略して VB) は、Microsoft によって開発された一般的なオブジェクトベースのプログラミング言語であり、開発環境を支援するイベント駆動メカニズムを含む、構造化されたモジュール式のオブジェクト指向ビジュアル プログラミング言語です。Microsoft 独自の製品開発に使用できる言語です。

2. 個人的な概要:

ヒント: 現在、VB 言語は、VB を使用してグラフィカル インターフェイスを作成する Crackme の一部の検証アルゴリズムで使用されています。以下に記録されている知識ポイントは、基本的に Crack で VB を適用するためのものです。

  • VB 関数の呼び出しは stdcall 原則に従い、関数間には NOP 分離があり、さらに、VB プログラムは Windows API 関数を直接呼び出すのではなく、VB ライブラリ内の関数を呼び出します。以下の図でインポートされた関数はすべて MSVBVM50.dll からのものであることがわかります。
  •  VB ファイルは、MSVBVM60.dll という名前の VB 固有のエンジン (Microsoft Visual Basic Virtual Machine 6.0) を使用します。
  • 使用するコンパイル オプションに応じて、VB ファイルはネイティブ コード (N コード) と擬似コード (P コード) にコンパイルできます。前者はデバッガによって解析される IA-32 命令に適しており、後者は VB を使用するインタプリタ言語ですこのエンジンは仮想マシンを実装しており、命令 (バイトコード) を自己解析できます。したがって、VBの疑似コードを正確に解析したい場合は、VBエンジンを解析し、シミュレータを実装する必要があります。
    • ここでの N は自然なコンパイル ( Native ) を表します。
      • 自然にコンパイルされた VB プログラムは x86 アセンブリ コードを直接生成し、OD および IDA を使用して直接分析できます。
    • P は擬似コンパイルを表します。
      • ランタイムはインタープリターに依存して、疑似コードをアセンブリ コードに変換して実行します。
  • この言語は GUI グラフィカル インターフェイスの作成に適しています。VB プログラムは Windows オペレーティング システムのイベント駆動方式で動作するため、main() や Winmain() にはユーザー コード (デバッグしたいコード) はありません。プログラム内の各イベント処理にコードが存在します。

2. vb プログラムの EP 特性:

vb プログラムの EP は、push コマンドで始まりますが、ここで実際にプッシュされるのは、VBHeader 構造体を指すポインタであり、その後、call 命令によって MSVBVM60.dll の ThunRTMain が呼び出されます。これら 2 つの命令の目的は、ThunRTMain 関数を呼び出してさまざまな変数を初期化することです。

VBHeader 構造: (VBHeader)

typedef struct
{
    char Signature[4];           //00H 四个字节的签名符号,和PEHEADER里的那个signature是类似性质的东西,VB文件都是"VB5!"
    WORD RtBuild;                   //04H 运行时创立的变量(类似编译的时间)
    BYTE LangDLL[14];         //06H 语言DLL文件的名字(如果是0x2A的话就代表是空或者是默认的)
    BYTE BakLangDLL[14];        //14H 备份DLL语言文件的名字(如果是0x7F的话就代表是空或者是默认的,改变这个值堆EXE文件的运行没有作用)
    WORD RtDLLVer;                  //22H 运行时DLL文件的版本
    DWORD LangID;                 //24H 语言的ID
    DWORD BakLangID;                //28H 备份语言的ID(只有当语言ID存在时它才存在)
    DWORD pSubMain;               //2CH RVA(实际研究下来是VA) sub main过程的地址指针(3.)(如果时00000000则代表这个EXE时从FORM窗体文件开始运行的)
    DWORD pProjInfo;              //30H VA 工程信息的地址指针,指向一个ProjectInfo_t结构(2.)
    DWORD fMDLIntObjs;         //34H ?详细见"MDL 内部组建的标志表"
    DWORD fMDLIntObjs2;          //36H ?详细见"MDL 内部组建的标志表"
    DWORD ThreadFlags;       //38H 线程的标志
    //* 标记的定义(ThreadFlags数值的含义)
    //+-------+----------------+--------------------------------------------------------+
    //| 值    | 名字           | 描述                                                
    //+-------+----------------+--------------------------------------------------------+
    //|  0x01 | ApartmentModel | 特别化的多线程使用一个分开的模型                        
    //|  0x02 | RequireLicense | 特别化需要进行认证(只对OCX)                             
    //|  0x04 | Unattended     | 特别化的没有GUI图形界面的元素需要初始化                       
    //|  0x08 | SingleThreaded | 特别化的静态区时单线程的                                                 
    //|  0x10 | Retained       | 特别化的将文件保存在内存中(只对Unattended)                    
    //+-------+----------------+--------------------------------------------------------+
    //ex: 如果是0x15就表示是一个既有多线程,内存常驻,并且没有GUI元素要初始化
    DWORD ThreadCount;                //3CH 线程个数
    WORD FrmCount;                    //41H 窗体个数
    WORD pExternalComponentCount;    //44H VA 外部引用个数例如WINSOCK组件的引用
    DWORD ThunkCount;                  //48H ?大概是内存对齐相关的东西
    DWORD GUITable;                  //4CH VA GUI元素表的地址指针(指向一个GUITable_t结构)
    DWORD pExternalComponentTable;        //50H VA 外部引用表的地址指针
//    DWORD pProjDep;                        // VA 工程的描述的地址指针(这个其实没有)
    DWORD pComRegData;                //54H VA COM注册数据的地址指针
    DWORD oProjExename;                //58H Offset 指向工程EXE名字的字符串
    DWORD oProjTitle;                        //5CH Offset 指向工程标题的字符串
    DWORD oHelpFile;                        //60H Offset 指向帮助文件的字符串
    DWORD oProjName;                        //64H Offset 指向工程名的字符串
}VBHeader_t;

 データ ウィンドウでプッシュ構造を追跡できます。

構造体の最初の 4 バイトはマジック ナンバー フィールド「VB5!」です。注目する必要があるのは、これがpSubMainVB プログラムへの本当の入り口であるということです。 

ヒント: この構造には C4 法と呼ばれる別の手法があり、この手法については次の 2 番目の例で詳しく紹介します。

例 1:

早期の情報探索:

1.EXE情報:

32 ビット、シェルなし、VB で書かれたプログラム

2. 実行します。

何かを入力するだけです:

主に、エラーが発生した後に表示されるメッセージに注目してください。

動的デバッグ:

1. キージャンプを見つける

まず、すべての参照テキスト文字列を検索します。

上に示したように、以前に実行したときに表示されたエラー メッセージを見つけて、ダブルクリックしてメッセージに従って確認してください。

赤い矢印に注目してください。どこからジャンプするのかが示されているので、それに従って見上げてください。

ここが検証のジャンプであり、次のステップはその検証アルゴリズムが何であるかを調べることです。

2. 検証アルゴリズムを分析します。

直接上にスワイプしてスタック フレームが構築される場所を見つけ、初期化関数をスキップして、分析に役立つ最初の関数呼び出しを見つけます。

スタックフレーム構築ポイント:

最初の操作: 入力文字列の長さを返します。

この関数を見つける方法は次のとおりです。

スタック フレームが構築されるポイントにブレークポイントを設定し、F8 ステップごとの動的調整と組み合わせます。ここで入力したユーザー名は「hahaha」です。

スタック フレーム構築ポイントから下に目を向けると、多数の __vbaObjSet 関数が表示されます。その機能は、オブジェクト変数をメモリ内のオブジェクトにポイントすることです。ここでは無視してかまいません。

下の図までデバッグしてみると、__vbaLenBstr関数はパラメータとして入力した名前を呼び出しており、この関数の機能は文字列の長さを返すことになっています。

このアイデアは実際には非常に単純です。呼び出し関数の機能を観察し、独自の入力とスタック ウィンドウ内の変更に基づいて呼び出し関数を見つけます。__vbaLenBstr 関数は、戻り値が eax に格納されることを示しています。

__vbaLenBstr 関数の動作を分析します: (402415~402427)

  • 40241B で、eax は返された文字列の長さを保存し、それを edi に保存します。
  • 40241D で、name の値を ecx に保存します。
  • 402420 これを見て少し不思議に思いました. imul 命令は 2 つの命令番号の乗算ですが、なぜここにパラメータが 3 つあるのでしょうか? ここでは動的調整を選択して結果を確認します. この命令を完了すると、 の値が
    edi は 8EDE2 つまり 6*0x17CFB です。
  • 402427では、OFレジスタの判定、つまりオーバーフローかどうかを判定しています。したがって、入力するユーザー名は長すぎてはなりません

次の部分では、文字列の最初の値を取り出して edi の値を追加し、この値を 10 進数に変換して保存します。

__rtcAnsiValueBstr は以前にも登場しました。これは、文字の ASCII コードの 16 進値を取得して eax に保存し (eax はこのコードを直接保存します)、それを edx に入れて edi の値に加算します。ここでは、オーバーフローがあるかどうかも確認し、操作後の値をスタックにプッシュして保存します。

0x8EDE2+0x=8EE4A

次に、次の関数 __vbaStrI4: 16 進数を 10 進数に変換します。

16 進数 8EE4A は 10 進数 585290 に変換され、文字列をコピーするパラメーターとして __vbaStrMove を呼び出します。

最後の部分は 402523 にあります。

この関数は理解するのが簡単で、「AKA-」と ECX レジスタの値 (以前に取得した 585290) を結合し、最後に strcmp 比較を実行します。

この画像は比較関数内です。スタックにプッシュされた 2 つのパラメータを確認してください。1 つはアルゴリズム演算後のシーケンス番号、もう 1 つは自分で入力したシーケンス番号です。strcmp がどのように比較されるかを気にする必要はなく、Ctrl+F9 を押すだけです。以下のコードは、strcmp の値に基づいて、ジャンプ出力シーケンス番号が成功したかどうかを比較して判定します。

登録マシンの書き込み:

1. アルゴリズム全体のロジックを確認します。
  • ユーザー名文字列を受け入れます。
  • ユーザー名の長さを取得します。
  • 長さは 0x17CFB 倍され、オーバーフローした場合はオーバーフローを処理する関数にジャンプします。
  • 乗算の結果は、ユーザー名文字列の最初の文字の ASCII コード値に加算されます。
  • 結果を10進数に変換
  • 計算結果に「AKA-」を接続すると、最終的なシーケンス番号が得られます。
2. 登録機:
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
int main()
{
        char str1[10] = "";
        int len,seriali;

        puts("Please Input Your Name:");
        scanf("%s", str1);
        len = strlen(str1);
        len = 0x17CFB * len;
        seriali = len + str1[0];

        printf("AKA-%d",seriali);

        return 0;
}

やれ:

例 2:

早期の情報探索:

1.EXE情報:

32 ビット、シェルなし、VB で書かれたプログラム

2. 実行します。

プログラムをダブルクリックすると、Neg ウィンドウがポップアップ表示されます。メイン ウィンドウが表示されるまでに長い時間がかかります。

何かを入力するだけです:

主に、エラーが発生した後に表示されるメッセージに注目してください。

前提知識:

ヒント: このプログラムを分析するために使用されるいくつかのナレッジ ポイントをここに示しますが、一部のナレッジ ポイントはここではあまり詳しく書かれません。いくつかのナレッジ ポイントを整理するには、Baidu または私のブログに移動してください。

1.タイマー:

時間制御関数は、プログラム インターフェイスに現在時刻を表示したり、毎回イベントをトリガーしたりするなど、Windows アプリケーションでよく使用されます。Visual Basic のタイマー (時間) コントローラーは、この問題を具体的に解決するコントロールです。Timer コントロールでは Name プロパティと Enabled プロパティを使用できますが、最も重要なのは Interval プロパティです。Interval 属性は、クロック イベント間の間隔をミリ秒単位で、0 ~ 65535 の範囲の値で決定します。したがって、最大時間間隔は、1 分をわずかに超える 65 秒を超えることはできません。Interval プロパティが 1000 に設定されている場合、Timer イベントが 1 秒ごとにトリガーされることを意味します。構文形式: Timer.Interval = X、X は特定の時間間隔を表します。

TImer オブジェクトは、コールバック メソッド、コールバックに渡されるオプションの状態オブジェクト、コールバックが最初に呼び出されるまでの遅延、および 2 つのコールバック呼び出し間の時間間隔を定義する TimerCallback デリゲートを指定します。

timer = new Timer(
	callback: new TimerCallback(TimerTask),
	state: timerState,
	dueTime: 1000,
	period: 2000);
2. アセンブリ内の浮動小数点レジスタについて:

Notion – メモ、タスク、Wiki、データベース用のオールインワン ワークスペース。毎日の仕事アプリを 1 つに統合する新しいツール。あなたとあなたのチームのためのオールインワンのワークスペースですicon-default.png?t=N7T8https://reveone.notion.site/470788e8c8634c4a99183a4fb374fdd0?pvs=4

Neg ポップアップを削除します。

方法 1: タイマーを直接検索する

まず、データ フレーム内で文字列「Timer」を直接検索できます。

OD でプログラムを開き、データ ウィンドウを右クリックして、[検索] → [バイナリ文字列] → [タイマーの入力] を選択します。

タイマーを見つけた後、後ろに値の文字列「0x1B58」があることがわかります (リトルエンディアンのストレージに注意してください)。この値はタイマーを呼び出した後の遅延時間です。10 進数の 7000 に変換されます。タイマーの単位はミリ秒です。これは、この Neg ウィンドウの滞留時間は 7 秒であることを意味します。これはデータ領域に存在するため、滞留時間を (1 ミリ秒に) 直接変更できます。

リトル エンディアン ストレージの問題にはまだ注意する必要があります。ここで入力する値は「10 00」です。ここで追加する知識ポイント: プログラムを変更した後、それを新しいプログラムとして保存する方法:

コマンド ウィンドウで右クリック → [実行可能ファイルにコピー] を選択 → [すべての変更] をクリックします。

次に、タブがポップアップ表示されます。

[コピー] を選択すると、ファイル ペインがポップアップ表示されます。

ウィンドウ内で右クリックし、「ファイルを保存」を選択すると、「名前を付けてファイルを保存」ウィンドウが表示されます。

新しいプログラムとして保存した後、ダブルクリックして実行すると、Neg ウィンドウが点滅し、シリアル番号ウィンドウに直接ジャンプすることがわかります。

実はここで時間を0秒に変更してみたのですが、実行してみるとプログラムは常にNegウィンドウのままになってしまうことが分かりました、Timerについてはあまり詳しくないので、値が0のときは推測するしかありません、それは常に残ることを意味します。

方法2:4C法

このメソッドについて理解する最初のことは、4C メソッドが何であるかです。ここでの 4C は、実際には VBHeader 構造体のオフセット 4C にあるメンバーを指しますGUITable。このメンバーはポインターでもあり、その機能は Form GuI 記述テーブルを指すことです。 。平たく言えば、このポインタはフォーム パラメータ データ ブロックを指し、そこでウィンドウが表示される順序が表示されます。この機能を使用して、個人的な希望に応じて各ウィンドウが表示される順序を調整できます。

GUITable 構造の主要メンバーの定義を分析できます。

Signature DWORD //00H.必须是50000000
FomID TGUID //04,可能是以GUID方式命名的formID
Index BYTE //24H 窗体的序号
Flag1 BYTE //28H 第一个窗体的启动标志,可能是90 也可能是10
AGUIDescriptionTable DWORD //48H指针指向以“FFCC…“开始的FormGUI表
Flag3 Dword //4CH.意义不明

具体的な操作は以下の通りです。

分析のためにプログラムを IDA に投入します。これまでのノートの分析によると、EP の最初の命令が VBHeader 構造を指すポインターをプッシュすることがわかります。データ ウィンドウに進みます。

GUITable メンバーを検索します。

引き続き 0x406868 にジャンプします。

ウィンドウを見ると2つのブロックに分かれているのが分かりますが、各ブロックのサイズはGUITable構造体のサイズ(0x50)です

矢印のところがウィンドウの表示順序です ここでは2つのウィンドウの表示順序を変更し、シリアル番号ウィンドウを前に配置しています シリアル番号ウィンドウが実行されるとプログラムが終了しますこれは Neg メッセージ ウィンドウですが、表示されません。

変更したプログラムを保存します。変更したプログラムに AfKayAs.banNeg という名前を付けました。実行後は、Neg ウィンドウがポップアップ表示されなくなることがわかります。

動的トーン分析アルゴリズム:

1. キージャンプを見つけます。

これは以前と同じ古いルーチンであり、採用された方法は参照文字列を見つけることです。

追跡文字列「You Get Wrong」をダブルクリックし、間違った文字列にジャンプする矢印に従ってキー判定コードを見つけます (もちろん、正しい文字列を直接追跡することもできます。両方とも問題ありません)。

2. 検証アルゴリズムを分析します。

重要な判定ポイントを確認したら、ブレークポイントを設定し、スタックフレームが構築されている場所に直接移動してブレークポイントを設定します。F9 が直接実行されます。ユーザー名に「hahaha」、シリアル番号に「123456」を入力します。これらの操作のスクリーンショットを 1 つずつ撮るつもりはありません。これらの内容には、以前のクラック分析で詳しく説明した、シリアル番号生成アルゴリズムに関する主要な命令のいくつかを見つける方法が含まれます (主に私が怠け者であるため、次のことを確認してください)前の質問の学習ノートの 2 番目のクラックに説明があります)。シリアル番号生成アルゴリズムの主なプロセスは次のとおりです。

1 つ目はユーザー名の長さを取得することです

次の 3 つの命令は、名前の長さを edi に入力し、次に名前を ecx に格納することであり、3 番目の命令は、名前の長さに 0x15B38 を乗算して edi に格納することです。

おそらく edi=name*0x15B38+最初の文字の ASCII コードの長さです。

次に、計算された値を 10 進数に変換します。計算した値は 533432 です。

計算値を浮動小数点型に変換し、2.0を加算して浮動小数点形式で表現します。

次に、前の数値を ST0 に入力し、3 を掛けてから 2 を引き、1600300 となります。

最後に、1600300 からマイナス 15 を引いて、最終的なシーケンス番号 "" を取得します。

確認してください:

登録マシンの書き込み:

1. アルゴリズム全体のロジックを確認します。

このアルゴリズムの検証プロセスは浮動小数点数に役立ちますが、解析にのみ影響します。基本的なロジックは、名前の最初の文字を取得するという前のクラックと似ています。検証プロセスは次のとおりです:

  • 名前の長さを取得し、固定値 (0x15B38) で乗算します。
  • 名前の最初の文字の ASCII コード値に 2 を加えたもの
  • 次に、3 マイナス 2 を掛けます。
  • 最後に 15 を加算すると、その値がシリアル番号になります。
2. 登録機:
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
int main()
{
	char str1[10] = "";
	int len,seriali;

	puts("Please Input Your Name:");
	scanf("%s", str1);
	len = strlen(str1);
	len = 0x15B38 * len;
	seriali = (len + str1[0] + 2) * 3 - 2 + 15;

	printf("%d",seriali);

	return 0;
}

やれ:

おすすめ

転載: blog.csdn.net/weixin_46175201/article/details/133340945