2013年10月17日 星期四

一種特別的參數傳遞技巧:直接嵌入立即資料於程式區段

相信有認真看過敝人曾經發過的幾篇呼叫慣例系列文章,應該已經很了解傳遞參數給函式背後
的基本原理,C 語言主要常用的不外乎就是 stdcall、cdecl 與 fastcall,在 x86-64 的環境裡面
也曾經提到過全部統一用 fastcall,可是參數在傳遞的過程中,讀者有沒有想過,是否能夠將
資料直接嵌入在程式區段中,而不需要宣告資料區段呢?這就是今天的主題,敝人將示範一種
特別的技巧,讓資料直接混合在程式區段內傳遞給一條簡單的 Windows API 作為示範。


今天要討論的示範程式很簡單,使用 MessageBox API 顯示一個小小的對話方塊
用 C 語言寫很簡單,原始碼如下:

#include <windows.h>

static TCHAR g_szText[] = _T("Hi! I'm the fasm program");

int APIENTRY WinMain(HINSTANCE,HINSTANCE,LPSTR,INT)
{
    MessageBox(
      HWND_DESKTOP,     // A handle to the owner windows of the message box
      g_szText,         // The message to be displayed.
      GetCommandLine(), // The dialog box title.
      MB_OK);           // The contents and behavior of the dialog box.
    return 0
 

這個程式執行結果如下:


現在把上面那段小小的 Win32 程式用 FASM 重寫,原始碼如下:

format PE GUI 4.0
entry start

include 'win32a.inc'

section '.text' code readable executable

start:
        push ebp         ; store old value of ebp
        mov  ebp,esp     ; create a stack frame
        push ebx esi edi ; store volatile registers

        invoke GetCommandLine

        push   MB_OK
        push   eax
        push   g_szText
        push   HWND_DESKTOP
        call   [MessageBox]

        pop  edi esi ebx
        leave
        invoke ExitProcess, 0

section '.data' data readable writeable

g_szText db "Hi! I'm the fasm program!"

section '.idata' import data readable writeable

library kernel,'kernel32.dll',                  \
        user,'user32.dll'

import kernel,                                  \
       GetCommandLine,'GetCommandLineA',        \
       ExitProcess, 'ExitProcess'

import user,                                    \
       MessageBox,'MessageBoxA'                  

讀者可以看到用 FASM 寫也是很簡單就可以完成一樣的功能,執行結果當然就跟剛剛上面
的圖一樣,只是還要自己手動寫 .idata 區段引入想要用的 API,讀者在這邊可以注意到吾
人改用了一條指令 leave,它其實跟另外兩條指令的組合等價:


傳統常用的寫法      新式寫法
mov esp,ebp           leave
pop  ebp

 
這是在函式的閉幕碼內常常出現的樣版,mov esp, ebp 可以先破壞函式內的區域變數堆疊
然後在恢復原本的 ebp,後來 x86 組合語言乾脆弄一條指令 leave,本身可讀性也必較好
新式的 Compiler 應該都會採用 leave,敝人自己在寫 FASM 也已經改用 leave。

然後讓我們來想想一個問題,有沒有可能直接把原本需要放在 .data 區段內的 g_szText
位址上的資料直接混合在 .text 區段內,也就是直接嵌入在程式區段裡面,要討論這個
問題之前,先回想一下 x86 組合語言指令 call 有甚麼特性。

還記得以前呼叫慣例系列文章有講過,call 不僅會把下一段返回後要執行的程式位址推入
堆疊中,它還有跳躍的功能,所以讀者發現了沒有,call 指令不光只是用在呼叫函式這種
正規用途 ( 也就是被呼叫函式內有要 ret 搭配 ),還可以拿來用於非正規用途,拿它投機取巧
把資料直接嵌入在程式區段,吾人下面先貼出不同處給讀者看看:

        push  MB_OK
        push  eax
        call  @F
        db    "Hi! I'm the fasm program!",00h
     @@:
        push  HWND_DESKTOP
        call  [MessageBox]

什麼!!資料竟然跟程式碼混合在一塊,這就是 call 指令的另一種妙用,少數用 call 指令而沒
有搭配 ret 的情況,因為 call 把下一段位址先推入堆疊啦,然後巧妙跨過資料而直接跳躍到
push HWND_DESKTOP,所以這種技巧不會讓 CPU 執行程式的時候誤判,不過顯然除錯器
Ollydbg 也知道這種技巧,當在下把編譯後的執行檔放入 Ollydbg 反組譯後出現下圖:


讀者點圖放大看可以發現,Ollydbg 很聰明的發現了吾人使用了嵌入式的資料直接
跟程式區段混合在一起,本篇主要就是介紹這種技巧給讀者知道參數的傳遞能夠用
call 指令巧妙的將資料嵌入在程式區段中,對於一些短的顯示字串或是常數,也許
直接用此技巧嵌入在程式碼區段中會比較方便,也不用去花腦袋思考變數命名的問題

不過這種技巧在 Ollydbg 內遇到那段 call 指令記得要用 step into,別用 step over,因為
CPU 在執行程式碼遇到 call 基本上就是認為要跳進某個函式,所以你得要用 into,假如
你用 over,那就會發現程式一路往下走就跑完了,因為程式永遠遇不到 ret 指令。

後記:

基於類似的理由,個人就對 FASM 提供的匿名標籤功能充滿敬意,它讓我不用在去思考
label 的名稱到底要怎麼令,反正 @F 就會往下找最近的 @@,而 @B 就會往回找最近的
@@,這個功能實在很方便,但是 FASM 要提供這種功能所實現的程式碼可就不簡單了。
對組譯器技術有興趣的讀者,可以專研 FASM Assembler 的原始碼,該組譯器是完全用純
x86 Assembly 完成,要閱讀它的程式碼基本上難度很高,我的過來經驗是最好要能閱讀
用 C 寫成的 NASM Assembler 的原始碼在去看 FASM Assembler 的原始碼,因為該作者
用組語大概已經達到神人境界,程式碼裡面會用到很多只有組合語言才能表達的特殊技巧
而且沒有對應的 C 概念可以表達,本文的示範就是其中一例,讀者在 C 語言中就很難辦到
組合語言這種可以偷機取巧用 call 指令指把資料混合在程式區段。當然有過 DOS 低階編程
的 LKK 們或許就覺得沒啥新鮮,在 DOS 中還可以直接操作節區,直接讓 ds 就等於當前的
cs 呢,在 DOS 下要怎麼搞都可以。

1 則留言:

  1. LuckyClub Casino Site | Live dealer casino and a world class
    LuckyClub Casino is one of the leading gambling portals that have been operating since 2006. It's the first of many online casino sites that cater to Asian 카지노사이트luckclub

    回覆刪除