読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

ウィンドウをきっかけに Windows の内部の仕組みを探る (前半の補足)アセンブラでウィンドウを生成する方法

MS asm C++ Win32 windows hack プログラミング


前回の記事では以下の事を学んだ。

  • Windowsにおいてウィンドウを表示する機能は,Windows API という API によって提供されている。
  • Windows API の中で,特に user32.dll というファイルがウィンドウ処理を担当している。

(そして user32.dll の中味を逆アセンブルで解析するのはやめておこう,という事になった。)


ここではその記事の補足として,実際にアセンブリ言語のプログラムから user32.dll を呼び出し,ウィンドウを表示させてみよう。




ダイアログボックスやウィンドウの表示を

  1. インラインアセンブラ
  2. NASM
  3. MASM

の3つの方法で試してみる。使い方を詳しく書くので,アセンブラ初心者でも大丈夫。


(※他のサイトでは,よく「このソースをアセンブルして実行するとこうなります」のように書いてあるが,肝心のアセンブルの実行コマンドが書かれていなかったりする。これはやや不親切。)


(1)インラインアセンブラの方法

インラインアセンブラとは…

Visual C++ に付属のコンパイラでは,C言語のコード中に,機械語のソースコードを埋め込むことができる。
通常のCのソース中に

__asm{
	〜〜(ここにアセンブラを書く)
}

のように記述する。

参考:VC++でインラインアセンブラ
http://codezine.jp/article/detail/393?p=1


下記はインラインアセンブラのサンプル。(movtest.c)

asmブロック中で mov 命令を使い,i, j という変数にそれぞれ 1, 2 を代入している。

#include <stdio.h>

int main() {
    int i, j;
    __asm {
        mov i, 1
        mov j, 2
    }

    printf( "i = %d, j = %d \n", i, j );

    return 0;
}

コンパイルのために特別な事は何も必要ない。

	cl movtest.c

これで movtest.exe ができて,コマンドラインで実行すると i = 1, j = 2 が表示される。



では,インラインアセンブラでダイアログボックスを表示させてみよう。(w1.c)

#include <windows.h>

int main(int argc, char* argv[]) {

	HMODULE libhandle = LoadLibrary("user32.dll");
	typedef int (__stdcall *FUNCTION)(void);
	FUNCTION _proc = (FUNCTION)GetProcAddress(libhandle, "MessageBoxA");

	unsigned long _arg1, _arg2, _arg3, _arg4 ;
	unsigned long _ret;

	// 引数を設定
	_arg1 = (unsigned long)0;
	_arg2 = (unsigned long)"本文";
	_arg3 = (unsigned long)"タイトル";
	_arg4 = (unsigned long)0;

	// スタックに引数を追加(スタックなので逆順)
	// 呼び出し
	_asm {
		mov eax, _arg4
		push eax
		mov eax, _arg3
		push eax
		mov eax, _arg2
		push eax
		mov eax, _arg1
		push eax

		call _proc
		mov _ret, eax
	}
	FreeLibrary(libhandle);

	// 返り値
	printf("_ret = %d\n", _ret);

	return 0;
}

call命令で user32.dll から MessageBoxA 関数を呼び出している。

cl w1.c でコンパイル。実行するとボックスが出る。




(2)NASMを使う方法

NASM(Netwide Assembler)は親しみやすいアセンブラで,NASKのような互換言語を生んだ。

このアセンブリ言語を使ってダイアログボックスを表示させるにあたって,下記のサイトに従って進めてみる。

アセンブリ言語
http://maccyo.hp.infoseek.co.jp/assembler/assembly.html#003_1


まずNASMをダウンロード。
http://www.nasm.us/pub/nasm/releasebuilds/2.05.01/
nasm-2.05.01-win32.zipを取得。


次に,リンカ(ALINK.EXE)をダウンロード。
http://alink.sourceforge.net/download.html

  • win32.lib : Win32 Import library (win32.lib)
  • alink.exe : ALINK (Win32 version)

をそれぞれ取得。


nasm.exeと同じフォルダ上で,下記のコードを w2.asm として保存。

bits 32
extern MessageBoxA

section .text
global my_entry
my_entry:

push dword 0
push dword title
push dword string
push dword 0
call MessageBoxA

ret

section .data
title: db 'タイトル',0
string: db '本文',0

my_entryと書いてある部分がエントリポイント(プログラム実行開始地点)になっている。


これをアセンブルする。(コンパイル言語Cの場合と異なり,コンパイルするとは言わない。)

	nasm w2.asm -fwin32

すると,objファイルができる。これは生のマシン語ファイルだ。



次に,このオブジェクトファイルを外部ライブラリとリンクして,実行可能にする。

alink.exeと同じフォルダに w2.asm と WIN32.LIB を移動して,コマンドプロンプトから

	alink w2.obj WIN32.LIB -oPE -entry my_entry

と打ち込む。

w2.exeが出力され,実行すると,先ほどと同様のメッセージボックスが表示される。


先ほどと違う点は,このプログラムはGUIだがアセンブラだけでコーディングして作った,ということ。

少し感慨深いのでは。


(3)MASMを使う方法

前項では,確かにアセンブラのみでコーディングした。
しかし途中でWIN32.LIBというライブラリファイルが出てきた。


ここではあくまで user32.dll の機能を確かめたいのだが,余計なライブラリファイルを介さずにプログラムを作り上げることは可能だろうか。

MASMを使う事で可能になる。



MASM(Macro Assembler)は,マイクロソフトがMS-DOS用に開発したアセンブラで,NASMよりもやや高機能。
NASMとの完全な互換性はない。


もし Visual Studio 2005 を利用しているのなら,下記サイトからMASM8.0を入手できる。

Microsoft Macro Assembler 8.0 (MASM) Package (x86)
http://www.microsoft.com/downloads/details.aspx?familyid=7A1C9DA0-0510-44A2-B042-7EF370530C64&displaylang=en


Visual Studio 2008 がインストールされている場合は,かわりに下記サイトからMASM32というフリーソフトを落とす。

MASM32
http://www.masm32.com/

downloadページから適当なミラーサイトを選ぶ。インストールには多少時間がかかる。



MASMの入門サイトとしては,下記の情報源がすばらしく役に立つ。

Win32 MASM プログラミング入門
http://www7.plala.or.jp/keny01/asm/win32/


Iczelion's Win32 Assembly Tutorials の翻訳
http://www.interq.or.jp/chubu/r6/masm32/masm006.html
(CPUの .386 という記述は適宜 .586 に読み替える。)


MASMの中核になるのは ml.exe である。

ml.exe と同じフォルダ(masm32\bin)に下記のコードを保存。(w3.asm)

.586
.model flat, stdcall

NULL            EQU     0
MessageBoxA     proto :dword, :dword, :dword, :dword
ExitProcess     proto :dword

.data
TITLE1          DB 'タイトル', 0
MESSAGE         DB '本文', 0

.code
WinMainCRTStartup   proc
    invoke MessageBoxA, NULL, offset MESSAGE, offset TITLE1, 0
    invoke ExitProcess, 0
    ret
WinMainCRTStartup   endp
end

NASMの構文よりも多少複雑で,invokeなどの命令が使われている。

このソースコードを ml.exe でアセンブルする。使い方は

	ml /coff /c /Cx w3.asm

とする。

w3.objが出来上がるが,Visual C++に付属のリンカ link.exe を使ってリンクしよう。

	link /SUBSYSTEM:WINDOWS w3.obj kernel32.lib user32.lib

w3.exeができる。

これで,user32.dll と直接結びついた形でメッセージボックスを表示させる事ができた。


ml.exe の使い方は,下記サイトが参考になった。

MASMでexe作成
http://oshiete1.goo.ne.jp/qa4375462.html

Tutorial 2: MessageBox
http://www.interq.or.jp/chubu/r6/masm32/tute/tute002_Jp.html


ところで,ここまでメッセージボックスばっかりだったので,ウィンドウを作ってみよう。


コードは少し長くなる。
http://www.interq.or.jp/chubu/r6/masm32/tute/tute003_Jp.html の内容を少し変えた物をコメント付きで下記に掲載する。(w4.asm)

.586 
.model flat,stdcall 
option casemap:none 
include    \masm32\include\windows.inc 
include    \masm32\include\user32.inc 
includelib \masm32\lib\user32.lib                    ; Windows API を呼び出し 
include    \masm32\include\kernel32.inc 
includelib \masm32\lib\kernel32.lib 

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

.DATA                                                ; 初期化済みデータ 
ClassName db "SimpleWinClass",0                      ; ウィンドウクラス名 
AppName db "ウィンドウのタイトル",0                  ; ウィンドウ名 

.DATA?                                               ; 初期化しないデータ 
hInstance HINSTANCE ?                                ; インスタンスハンドル 
CommandLine LPSTR ? 

.CODE                                                ; コード開始 
start: 

invoke GetModuleHandle, NULL                         ; インスタンスハンドル取得 
mov hInstance,eax 

invoke GetCommandLine                                ; コマンドライン取得 
mov CommandLine,eax 

invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT        ; 下で定義したメイン関数へ 
invoke ExitProcess, eax                              ; 終了コード付きで終了 

                                                     ; メイン関数
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
    LOCAL wc:WNDCLASSEX 
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 

    mov   wc.cbSize,SIZEOF WNDCLASSEX                ; ウィンドウ情報を設定 
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInstance 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_WINDOW+1 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, addr wc                  ; ウィンドウを登録 
    invoke CreateWindowEx,NULL,\ 
                ADDR ClassName,\ 
                ADDR AppName,\ 
                WS_OVERLAPPEDWINDOW,\ 
                CW_USEDEFAULT,\ 
                CW_USEDEFAULT,\ 
                CW_USEDEFAULT,\ 
                CW_USEDEFAULT,\ 
                NULL,\ 
                NULL,\ 
                hInst,\ 
                NULL 
    mov   hwnd,eax 
    invoke ShowWindow, hwnd,CmdShow                   ; 表示 
    invoke UpdateWindow, hwnd                         ; 表示領域を更新 

    .WHILE TRUE                                       ; メッセージループ 
                invoke GetMessage, ADDR msg,NULL,0,0 
                .BREAK .IF (!eax) 
                invoke TranslateMessage, ADDR msg 
                invoke DispatchMessage, ADDR msg 
   .ENDW 
    mov     eax,msg.wParam                            ; 終了コード 
    ret 
WinMain endp 

                                                      ; ウィンドウプロシージャ
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    .IF uMsg==WM_DESTROY                              ; 閉じるメッセージの場合 
        invoke PostQuitMessage,NULL                   ; 破棄へ 
    .ELSE 
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam  ; デフォルト処理 
        ret 
    .ENDIF 
    xor eax,eax 
    ret 
WndProc endp 

end start

前記事でも解説した CreateWindowEx 関数がここに出てきた。


プログラムの構造としては,C言語のウィンドウプロシージャのコードをそのままアセンブラに移植したような感じだ。(実際には逆だが)


先ほどと同じ手順でアセンブル&リンクする。

D:\masm32\bin>ml /coff /c /Cx w4.asm
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: w4.asm

D:\masm32\bin>Link /SUBSYSTEM:WINDOWS w4.obj user32.lib
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


D:\masm32\bin>w4.exe



アセンブラだけで作ったウィンドウが表示された。


まとめ

  1. VC++のインラインアセンブラ
  2. NASM
  3. MASM

の3つの手段で,アセンブリ言語により user32.dll の機能を呼び出し,ダイアログボックスやウィンドウを表示させた。


通常,ウィンドウを生成させる際には .NET / MFC / Windows SDK (windows.h) などなどあらゆるラッパーを使う。

しかし,実はそんなもの使わなくても,コアになるDLLさえあればよいのだ,という事がわかる。



今回の内容は,「ウィンドウをきっかけに Windows の内部の仕組みを探る (前半)フレームワークからDLLまでのスケール」の補足記事であり,後半の記事にスムーズにつなげるためのものだった。


シリーズのテーマは,「ウィンドウを生成するための機械語は?」「ウィンドウの正体は?」というもの。

今回は「機械語によって user32.dll を呼び出せる」という事が実際に体験できたので,次は,user32.dll そしてOSが具体的にどのような処理を行なっているのかを考える。


後半記事へ続く。

関連する記事:

メモリの中身を読んでみよう (プロセスをダンプ+解析する方法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081019/1224341559


自作のC言語プログラムから,BIOS設定(CMOS)を読み書きする方法 (の調査ログ) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20120104/p1


メモリ・CPUなどハードウェアの構成情報を,バッチで取得しよう (WSH/JScriptでWMIを使う方法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20100906/p1