EXEを作成する

自己隔離は、多くの時間と労力を要する何かを始めるのに最適な時期です。それで、私はいつもやりたかったことをすることに決めました-私自身のコンパイラを書いてください。



これで彼はHelloWorldをコンパイルできるようになりましたが、この記事では、コンパイラの解析や内部構造ではなく、exeファイルのバイトごとのアセンブリなどの重要な部分について説明します。



開始



スポイラーが欲しいですか?プログラムは2048バイトになります。



通常、exeファイルの操作は、それらの構造を調査または変更することです。実行可能ファイル自体はコンパイラによって形成され、このプロセスは開発者にとって少し魔法のように見えます。



しかし今、私たちはそれを修正しようとします!



プログラムをビルドするには、HEXエディターが必要です(私は個人的にHxDを使用しました)。



まず、疑似コードを見てみましょう。



ソース
func MessageBoxA(u32 handle, PChar text, PChar caption, u32 type) i32 ['user32.dll']
func ExitProcess(u32 code) ['kernel32.dll']

func main()
{
	MessageBoxA(0, 'Hello World!', 'MyApp', 64)
	ExitProcess(0)
}




最初の2行は、WinAPIライブラリからインポートされた関数を示していますMessageBoxA関数は、テキストを含むダイアログボックスを表示し、ExitProcessはプログラムの終了についてシステムに通知します。

main関数は上記の関数を使用するため、個別に検討する意味はありません。



DOSヘッダー



まず、正しいDOSヘッダーを生成する必要があります。これはDOSプログラムのヘッダーであり、Windowsでのexeの起動に影響を与えることはありません。



私は多かれ少なかれ重要なフィールドに気づきました、残りはゼロで埋められます。



IMAGE_DOS_HEADER構造
Struct IMAGE_DOS_HEADER
{
     u16 e_magic	// 0x5A4D	"MZ"
     u16 e_cblp		// 0x0080	128
     u16 e_cp		// 0x0001	1
     u16 e_crlc
     u16 e_cparhdr	// 0x0004	4
     u16 e_minalloc	// 0x0010	16
     u16 e_maxalloc	// 0xFFFF	65535
     u16 e_ss
     u16 e_sp		// 0x0140	320
     u16 e_csum		
     u16 e_ip
     u16 e_cs
     u16 e_lfarlc	// 0x0040	64
     u16 e_ovno
     u16[4] e_res
     u16 e_oemid
     u16 e_oeminfo
     u16[10] e_res2
     u32 e_lfanew	// 0x0080	128
}




最も重要なことに、このヘッダーには、これが実行可能ファイルであることを意味するe_magicフィールドと、ファイルの先頭からのPEヘッダーのオフセットを示すe_lfanewが含まれます(このファイルでは、このオフセットは0x80 = 128バイトです)。



DOSヘッダー構造がわかったので、それをファイルに書き込みましょう。



(1)RAW DOSヘッダー(オフセット0x00000000)
4D 5A 80 00 01 00 00 00  04 00 10 00 FF FF 00 00
40 01 00 00 00 00 00 00  40 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 80 00 00 00










, , .



, (Offset) .



, 0x00000000, 64 (0x40 16- ), 0x00000040 ..

完了し、最初の64バイトが書き込まれます。さらに64を追加する必要があります。これは、いわゆるDOSスタブ(スタブ)です。DOSで起動する場合、プログラムがこのモードで実行するように設計されていないことをユーザーに通知する必要があります。



しかし、一般的に、これは行を出力してプログラムを終了する小さなDOSプログラムです。

スタブをファイルに書き込んで、さらに詳しく検討してみましょう。



(2)RAW DOSスタブ(オフセット0x00000040)
0E 1F BA 0E 00 B4 09 CD  21 B8 01 4C CD 21 54 68
69 73 20 70 72 6F 67 72  61 6D 20 63 61 6E 6E 6F
74 20 62 65 20 72 75 6E  20 69 6E 20 44 4F 53 20
6D 6F 64 65 2E 0D 0A 24  00 00 00 00 00 00 00 00






そして今は同じコードですが、分解された形式です



AsmDOSスタブ
0000	push cs			;  Code Segment(CS) (    )
0001	pop ds			;   Data Segment(DS) = CS
0002	mov dx, 0x0E	;     DS+DX,      $( ) 
0005	mov ah, 0x09	;   ( )
0007	int 0x21		;    0x21
0009	mov ax, 0x4C01	;   0x4C (  ) 
						;     0x01 ()
000c	int 0x21		;    0x21
000e	"This program cannot be run in DOS mode.\x0D\x0A$" ;  




これは次のように機能します。最初に、スタブはプログラムを開始できないことを示す行を出力し、次にコード1でプログラムを終了します。これは通常の終了(コード0)とは異なります。



スタブコードは(コンパイラごとに)わずかに異なる場合があります。gccとdelphiを比較しましたが、一般的な意味は同じです。



スタブラインが\ x0D \ x0D \ x0A $で終わるのも面白いです。この動作の理由として最も可能性が高いのは、c ++がデフォルトでファイルをテキストモードで開くためです。その結果、文字\ x0Aはシーケンス\ x0D \ x0Aに置き換えられます。その結果、3バイトが得られます。2バイトのキャリッジリターン(0x0D)は無意味であり、1はラインフィード(0x0A)です。バイナリモード(std :: ios :: binary)では、この置換は発生しません。



値の書き込みの正確さを確認するために、ImpExプラグインでFarを使用します。







NTヘッダー



128(0x80)バイトの後、NTヘッダー(IMAGE_NT_HEADERS64)に到達しました。これには、PEヘッダー(IMAGE_OPTIONAL_HEADER64)も含まれています。名前にもかかわらず、IMAGE_OPTIONAL_HEADER64が必要ですが、x64アーキテクチャとx86アーキテクチャでは異なります。



IMAGE_NT_HEADERS64構造
Struct IMAGE_NT_HEADERS64
{
	u32 Signature	// 0x4550 "PE"
	
	Struct IMAGE_FILE_HEADER 
	{
		u16 Machine	// 0x8664  x86-64
		u16 NumberOfSections	// 0x03     
		u32 TimeDateStamp		//   
		u32 PointerToSymbolTable
		u32 NumberOfSymbols
		u16 SizeOfOptionalHeader //  IMAGE_OPTIONAL_HEADER64 ()
		u16 Characteristics	// 0x2F 
	}
	
	Struct IMAGE_OPTIONAL_HEADER64
	{
		u16 Magic	// 0x020B      PE64
		u8 MajorLinkerVersion
		u8 MinorLinkerVersion
		u32 SizeOfCode
		u32 SizeOfInitializedData
		u32 SizeOfUninitializedData	
		u32 AddressOfEntryPoint	// 0x1000 
		u32 BaseOfCode	// 0x1000 
		u64 ImageBase	// 0x400000 
		u32 SectionAlignment	// 0x1000 (4096 )
		u32 FileAlignment	// 0x200
		u16 MajorOperatingSystemVersion	// 0x05	Windows XP
		u16 MinorOperatingSystemVersion	// 0x02	Windows XP
		u16 MajorImageVersion
		u16 MinorImageVersion
		u16 MajorSubsystemVersion	// 0x05	Windows XP
		u16 MinorSubsystemVersion	// 0x02	Windows XP
		u32 Win32VersionValue
		u32 SizeOfImage	// 0x4000
		u32 SizeOfHeaders // 0x200 (512 )
		u32 CheckSum
		u16 Subsystem	// 0x02 (GUI)  0x03 (Console)
		u16 DllCharacteristics
		u64 SizeOfStackReserve	// 0x100000
		u64 SizeOfStackCommit	// 0x1000
		u64 SizeOfHeapReserve	// 0x100000
		u64 SizeOfHeapCommit	// 0x1000
		u32 LoaderFlags
		u32 NumberOfRvaAndSizes // 0x16 
		
		Struct IMAGE_DATA_DIRECTORY [16] 
		{
			u32 VirtualAddress
			u32 Size
		}
	}
}




この構造に何が格納されているかを見てみましょう。



説明IMAGE_NT_HEADERS64
Signature — PE



IMAGE_FILE_HEADER x86 x64.



Machine — x64

NumberOfSections — ( )

TimeDateStamp —

SizeOfOptionalHeader — IMAGE_OPTIONAL_HEADER64, IMAGE_OPTIONAL_HEADER32.



Characteristics — , , (EXECUTABLE_IMAGE) 2 RAM (LARGE_ADDRESS_AWARE), ( ) (RELOCS_STRIPPED | LINE_NUMS_STRIPPED | LOCAL_SYMS_STRIPPED).



SizeOfCode — ( .text)

SizeOfInitializedData — ( .rodata)

SizeOfUninitializedData — ( .bss)

BaseOfCode —

SectionAlignment —

FileAlignment —

SizeOfImage —

SizeOfHeaders — (IMAGE_DOS_HEADER, DOS Stub, IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER[IMAGE_FILE_HEADER.NumberOfSections]) FileAlignment

Subsystem — GUI Console

MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorSubsystemVersion, MinorSubsystemVersion — exe, . 5.2 Windows XP (x64).

SizeOfStackReserve — . 1 , 1. Rust , C++ .

SizeOfStackCommit — 4 . .

SizeOfHeapReserve — . 1 .

SizeOfHeapCommit — 4 . SizeOfStackCommit, .



IMAGE_DATA_DIRECTORY — . , , 16 . .



, , . :

Export(0) — . DLL. .



Import(1) — DLL. VirtualAddress = 0x3000 Size = 0xB8. , .



Resource(2) — (, , ..)

.



NTヘッダーが何で構成されているかを確認したので、他のヘッダーと同様に0x80でファイルに書き込みます。



(3)RAW NT-ヘッダー(オフセット0x00000080)
50 45 00 00 64 86 03 00  F4 70 E8 5E 00 00 00 00
00 00 00 00 F0 00 2F 00  0B 02 00 00 3D 00 00 00
13 00 00 00 00 00 00 00  00 10 00 00 00 10 00 00
00 00 40 00 00 00 00 00  00 10 00 00 00 02 00 00
05 00 02 00 00 00 00 00  05 00 02 00 00 00 00 00
00 40 00 00 00 02 00 00  00 00 00 00 02 00 00 00
00 00 10 00 00 00 00 00  00 10 00 00 00 00 00 00
00 00 10 00 00 00 00 00  00 10 00 00 00 00 00 00
00 00 00 00 10 00 00 00  00 00 00 00 00 00 00 00
00 30 00 00 B8 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00




その結果、この種のIMAGE_FILE_HEADER、IMAGE_OPTIONAL_HEADER64、およびIMAGE_DATA_DIRECTORYヘッダーが取得されます。















次に、IMAGE_SECTION_HEADER構造に従って、アプリケーションのすべてのセクションについて説明します。



IMAGE_SECTION_HEADER構造
Struct IMAGE_SECTION_HEADER
{
	i8[8] Name
	u32 VirtualSize
	u32 VirtualAddress
	u32 SizeOfRawData
	u32 PointerToRawData
	u32 PointerToRelocations
	u32 PointerToLinenumbers
	u16 NumberOfRelocations
	u16 NumberOfLinenumbers
	u32 Characteristics
}




IMAGE_SECTION_HEADERの説明
Name — 8 ,

VirtualSize —

VirtualAddress — SectionAlignment

SizeOfRawData — FileAlignment

PointerToRawData — FileAlignment

Characteristics — (, , , , .)



この場合、3つのセクションがあります。



仮想アドレス(VA)が最初からではなく1000から始まる理由はわかりませんが、私が検討したすべてのコンパイラがこれを行っています。その結果、SizeOfImageに書き込んだ1000 +3セクション* 1000(SectionAlignment)= 4000になります。これは、仮想メモリ内のプログラムの合計サイズです。おそらく、メモリ内のプログラムにスペースを割り当てるために使用されます。



 Name	| RAW Addr	| RAW Size	| VA	| VA Size | Attr
--------+---------------+---------------+-------+---------+--------
.text	| 200		| 200		| 1000	| 3D	  |   CER
.rdata	| 400		| 200		| 2000	| 13	  | I   R
.idata	| 600		| 200		| 3000	| B8	  | I   R


属性のデコード:



I-初期化されたデータ、初期化されたデータ

U-初期化されていないデータ、初期化されていないデータ

C-コード、実行可能なコードを含む

E-実行、実行を許可

R-コードの読み取り、

W-書き込みセクションからのデータの読み取り、セクションへのデータの書き込みを許可



.text(.code)-実行可能コード(プログラム自体)、CE属性を

格納します。.rdata(.rodata)-定数、文字列などの読み取り専用データを格納します。IR属性

.data-格納します。静的変数やグローバル変数など、読み取りと書き込みが可能なデータ。 IRW属性

.bss-静的変数やグローバル変数などの初期化されていないデータを格納します。さらに、このセクションは通常、RAWサイズがゼロでVAサイズがゼロ以外であるため、ファイル内のスペースを占有しません。

URW.idata属性-他のライブラリからインポートされた関数を含むセクション。IR属性



重要なポイントは、セクションが互いに続く必要があるということです。さらに、ファイルとメモリの両方にあります。少なくとも順序を任意に変更すると、プログラムの実行が停止しました。



プログラムに含まれるセクションがわかったので、それらをファイルに書き込みます。ここでオフセットは8で終了し、記録はファイルの中央から開始されます。



(4)RAWセクション(オフセット0x00000188)
                         2E 74 65 78 74 00 00 00
3D 00 00 00 00 10 00 00  00 02 00 00 00 02 00 00
00 00 00 00 00 00 00 00  00 00 00 00 20 00 00 60
2E 72 64 61 74 61 00 00  13 00 00 00 00 20 00 00
00 02 00 00 00 04 00 00  00 00 00 00 00 00 00 00
00 00 00 00 40 00 00 40  2E 69 64 61 74 61 00 00
B8 00 00 00 00 30 00 00  00 02 00 00 00 06 00 00
00 00 00 00 00 00 00 00  00 00 00 00 40 00 00 40






次のエントリアドレスは、PE-HeaderのSizeOfHeadersフィールドに対応する00000200になります。セクションをもう1つ追加し、これがプラス40バイトの場合、ヘッダーは512(0x200)バイトに収まらず、FileAlignmentで整列された512 + 40 = 552バイト、つまり1024(0x400)バイトを使用する必要があります。そして、0x228(552)からアドレス0x400まで残っているものはすべて、何かで埋める必要があります。もちろん、ゼロで埋める方がよいでしょう。



Farでセクションのブロックがどのように見えるかを見てみましょう。







次に、セクション自体をファイルに書き込みますが、微妙な違いが1つあります。



SizeOfHeadersの例からわかるように、ヘッダーを書き込んで次のセクションに進むことはできません。見出しを記録するには、すべての見出しが一緒にかかる時間を知る必要があるためです。そのため、必要なスペースを事前に計算するか、空の(ゼロ)値を書き込んでから、すべてのヘッダーを書き込んだ後、実際のサイズを返して書き留める必要があります。



したがって、プログラムはいくつかのパスでコンパイルされます。たとえば、.rdataセクションは.textセクションの後にありますが、.textセクションが0x1000(SectionAlignment)バイトを超えて大きくなると、範囲のアドレス0x2000を占めるため、.rdata内の変数の仮想アドレスを見つけることができません。したがって、.rdataセクションは0x2000ではなく、0x3000に配置されます。そして、戻って、.rdataの前にある.textセクションのすべての変数のアドレスを再計算する必要があります。



しかし、この場合、私はすでにすべてを計算しているので、すぐにコードのブロックを書き留めます。



。テキストセクション



Asmセグメント.text
0000	push rbp
0001	mov rbp, rsp
0004	sub rsp, 0x20
0008	mov rcx, 0x0
000F	mov rdx, 0x402000
0016	mov r8, 0x40200D
001D	mov r9, 0x40
0024	call QWORD PTR [rip + 0x203E]
002A	mov rcx, 0x0
0031	call QWORD PTR [rip + 0x2061]
0037	add rsp, 0x20
003B	pop rbp
003C	ret




特にこのプログラムでは、最後の3行とまったく同じように、最初の3行は必要ありません。

プログラムは2番目の呼び出し関数で終了するため、最後の3つは実行されません。



しかし、これがメイン関数ではなくサブ関数である場合は、この方法で実行する必要があるとしましょう。



ただし、この場合の最初の3つは必須ではありませんが、望ましいものです。たとえば、MessageBoxAではなくprintfを使用した場合、これらの行がないとエラーが発生します。



64ビットMSDNシステムの呼び出し規則に従って、最初の4つのパラメーターはレジスタRCX、RDX、R8、R9に渡されます。それらがそこに収まり、たとえば浮動小数点数ではない場合。そして残りはスタックを通過します。



理論的には、関数に2つの引数を渡す場合、それらをレジスタに渡し、スタック内の2つの場所を予約して、必要に応じて関数がレジスタをスタックにプッシュできるようにする必要があります。また、これらのレジスタが元の状態で返されることを期待するべきではありません。



したがって、printf関数の問題は、引数を1つだけ渡すと、スタック内の4つの場所すべてが上書きされることですが、引数の数だけ1つだけ上書きする必要があるようです。



したがって、プログラムがおかしな動作をしたくない場合は、関数に少なくとも1つの引数を渡すと、常に少なくとも8バイト* 4引数= 32(0x20)バイトを予約します。



関数呼び出しを含むコードのブロックを検討してください



MessageBoxA(0, 'Hello World!', 'MyApp', 64)
ExitProcess(0)


まず、引数を渡します



。rcx= 0 rdx =

メモリ内の文字列の絶対アドレスImageBase + Sections ["。Rdata"]。VirtualAddress +セクションの先頭からの文字列のオフセット、文字列はバイト0に読み取られます

r8 =前の

r9 = 64(0x40)と同様MB_ICONINFORMATION 、情報アイコン



次に、MessageBoxA関数の呼び出しがありますが、これではすべてがそれほど単純ではありません。重要なのは、コンパイラが可能な限り短いコマンドを使用しようとすることです。命令サイズが小さいほど、そのような命令はそれぞれプロセッサのキャッシュに収まり、キャッシュミス、過負荷が少なくなり、プログラムの速度が速くなります。コマンドとプロセッサの内部動作の詳細については、Intel64およびIA-32アーキテクチャソフトウェア開発者マニュアルを参照してください。



フルアドレスで関数を呼び出すこともできますが、少なくとも(1つのopcode +8アドレス= 9バイト)かかり、相対アドレスを使用すると、callコマンドは6バイトしかかかりません。



この魔法を詳しく見てみましょう。rip+ 0x203Eは、オフセットで指定されたアドレスでの関数呼び出しにすぎません。



少し先を見て、必要なオフセットのアドレスを見つけました。 MessageBoxAの場合は0x3068で、ExitProcessの場合は0x3098です。



魔法を科学に変える時が来ました。 opcodeがプロセッサにヒットするたびに、その長さを計算し、現在の命令アドレス(RIP)に追加します。したがって、命令内でRIPを使用する場合、このアドレスは現在の命令の終了/次の命令の開始を示します。

最初の呼び出しの場合、オフセットは呼び出しコマンドの終了を示します。これは002Aです。メモリ内でこのアドレスはオフセットセクション["。Text"]にあることを忘れないでください。VirtualAddress、つまり 0x1000。したがって、コールのRIPは102Aになります。MessageBoxAに必要なアドレスは0x3068です。0x3068-0x102A = 0x203Eを検討してください2番目のアドレスの場合、すべてが0x1000 + 0x0037 = 0x1037、0x3098-0x1037 = 0x2061と同じです。



アセンブラコマンドで見たのはこれらのオフセットです。



0024	call QWORD PTR [rip + 0x203E]
002A	mov rcx, 0x0
0031	call QWORD PTR [rip + 0x2061]
0037	add rsp, 0x20


アドレス0x400にゼロを追加して、ファイルに.textセクションを書き込みましょう。



(5)RAW .textセクション(オフセット0x00000200-0x00000400)
55 48 89 E5 48 83 EC 20  48 C7 C1 00 00 00 00 48
C7 C2 00 20 40 00 49 C7  C0 0D 20 40 00 49 C7 C1
40 00 00 00 FF 15 3E 20  00 00 48 C7 C1 00 00 00
00 FF 15 61 20 00 00 48  83 C4 20 5D C3 00 00 00
........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00


4 . FileAlignment. 0x000003F0, 0x00000400, . 1024 , ! .




.Rdataセクション



これはおそらく最も単純なセクションです。ここに2行入れて、512バイトにゼロを追加します。



.rdata
0400	"Hello World!\0"
040D	"MyApp\0"




(6)RAW .rdataセクション(オフセット0x00000400-0x00000600)
48 65 6C 6C 6F 20 57 6F  72 6C 64 21 00 4D 79 41
70 70 00 00 00 00 00 00  00 00 00 00 00 00 00 00
........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00




.Idataセクション



さて、これが最後のセクションで、ライブラリからインポートされた関数について説明しています。



私たちを待っている最初のものは、新しい構造IMAGE_IMPORT_DESCRIPTORです



IMAGE_IMPORT_DESCRIPTOR構造
Struct IMAGE_IMPORT_DESCRIPTOR
{
	u32 OriginalFirstThunk (INT)
	u32 TimeDateStamp
	u32 ForwarderChain
	u32 Name
	u32 FirstThunk (IAT)
}




説明IMAGE_IMPORT_DESCRIPTOR
OriginalFirstThunk — , Import Name Table (INT)

Name — ,

FirstThunk — , Import Address Table (IAT)



まず、2つのインポートされたライブラリを追加する必要があります。想起:



func MessageBoxA(u32 handle, PChar text, PChar caption, u32 type) i32 ['user32.dll']
func ExitProcess(u32 code) ['kernel32.dll']


(7)RAW IMAGE_IMPORT_DESCRIPTOR(オフセット0x00000600)
58 30 00 00 00 00 00 00  00 00 00 00 3C 30 00 00
68 30 00 00 88 30 00 00  00 00 00 00 00 00 00 00
48 30 00 00 98 30 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00




2つのライブラリを使用し、それらのリストを終了したと言います。最後の構造はゼロで埋められます。



 INT	| Time	 | Forward  | Name   | IAT
--------+--------+----------+--------+--------
0x3058	| 0x0    | 0x0      | 0x303C | 0x3068
0x3088	| 0x0    | 0x0      | 0x3048 | 0x3098
0x0000	| 0x0    | 0x0      | 0x0000 | 0x0000


次に、ライブラリ自体の名前を追加しましょう。



ライブラリ名
063	"user32.dll\0"
0648	"kernel32.dll\0"




(8)RAWライブラリ名(オフセット0x0000063C)
                                     75 73 65 72
33 32 2E 64 6C 6C 00 00  6B 65 72 6E 65 6C 33 32
2E 64 6C 6C 00 00 00 00




次に、user32ライブラリについて説明します。



(9)RAW user32.dll(オフセット0x00000658)
                         78 30 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  78 30 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 4D 65 73 73 61 67 
65 42 6F 78 41 00 00 00




最初のライブラリの[名前]フィールドは、少し高く見ると0x303Cを指しています。アドレス0x063Cに、ライブラリ "user32.dll \ 0"があることがわかります。



ヒント、.idataセクションはファイルオフセット0x0600およびメモリオフセット0x3000に対応することに注意してください。最初のライブラリの場合、INTは3058です。これは、ファイル内でオフセット0x0658になることを意味します。このアドレスには、エントリ0x3078と2番目のゼロがあります。リストの終わりを意味します。 3078は0x0678を参照します。これはRAW文字列です



"00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00"



最初の2バイトは重要ではなく、ゼロに等しくなります。そして、関数の名前がゼロで終わる行があります。つまり、「\ 0 \ 0MessageBoxA \ 0」として表すことができます。



この場合、IATはIATテーブルと同様の構造を参照しますが、プログラムの起動時に関数アドレスのみがロードされます。たとえば、メモリ内の最初のエントリ0x3068は、ファイル内で0x0668以外の値になります。プログラムコードの呼び出し呼び出しを通じて参照するシステムによってロードされるMessageBoxA関数のアドレスがあります。



そしてパズルの最後のピース、kernel32。また、SectionAlignmentにゼロを追加することを忘れないでください。



(10)RAW kernel32.dll(オフセット0x00000688-0x00000800)
                         A8 30 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  A8 30 00 00 00 00 00 00 
00 00 00 00 00 00 00 00  00 00 45 78 69 74 50 72 
6F 63 65 73 73 00 00 00  00 00 00 00 00 00 00 00 
........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00






Farがインポートした関数を正しく識別できたことを確認し







ます。すべてが順調だったので、ファイルを実行する準備が整いました。

ドラムロール…



最終







おめでとうございます、やりました!



ファイルは2KB =ヘッダー512バイト+512バイトの3セクションを占有します。



数値512(0x200)は、プログラムのヘッダーで指定したFileAlignmentにすぎません。



さらに:

もう少し深く行きたい場合は、「HelloWorld!」という碑文を置き換えることができます。プログラムコード(セクション.text)の行のアドレスを変更することを忘れないでください。メモリ内のアドレスは0x00402000ですが、ファイルには逆バイト順00 20 40 00が含まれます。



または、クエストはもう少し複雑です。コードに別のMessageBox呼び出しを追加します。これを行うには、前の呼び出しをコピーして、その中の相対アドレス(0x3068-RIP)を再計算する必要があります。



結論



記事はかなりしわくちゃになっていることが判明しました。もちろん、見出し、プログラム、インポートテーブルの3つの部分で構成されます。



誰かが彼らのexeをコンパイルした場合、私の仕事は無駄ではありませんでした。



すぐに同様の方法でELFファイルを作成することを考えていますが、そのような記事は興味深いでしょうか?)



リンク:






All Articles