July 25, 2006

Runlib32 - Linux 的 Rundll32.exe 實做

rundll.exe (Win16 / Windows 3.x) 與 rundll32.exe (Win32) 是 Microsoft Windows 提供一個特別的機制,允許直接找出 .DLL 或 .EXE 中 function entry,並給定參數後作呼叫,使用方法如下:
    Rundll32.exe  DllFileName  FuncName
    
如果我們設計了一個 MyDLL.dll,並在其中定義了一個 MyFunc 的函式,於是透過以下指令即可呼叫該函式的實做部份:
    Rundll32.exe  MyDLL.dll MyFunc
    
而不需要額外的應用程式去呼叫,這也讓我們想到,如果某個套裝軟體安裝後,在某個 .dll 提供了惡意的 function,是不是有機會以 shell 去觸發 RunDll32.exe 去執行惡意行為呢?是的,的確有這樣的方式,也有許多變形。這裡不探討作壞事的技術細節 (小弟金盆洗手很久了 :P),詳細 Rundll 的應用方式可參考 [Using Rundll],熟悉 Win32 底層技術的開發者,切換到 UNIX 系統時,不免會有些疑惑:是否有類似 Rundll32.exe 的工具去驗證 shared objects 呢?

[izik] 創作了 [runlib] 這個軟體專案,允許在 Linux 上施行類似 Win32 Rundll32.exe 的行為,並提供更強大的功能,這裡展示 "Hello World" 的 Runlib 版本:
Runlib32$ ./runlib -v -x printf-out libc.so.6,puts \""Hello World"\"
------------------------------------------------------------
puts[<0xb7ed8610>]@libc.so.6[]
------------------------------------------------------------

	* Stack Generated (1 parameters, 4 bytes)
	-----------------------------------------

	Generated Assembly
	------------------
		 * pushl $0xbfce7c9a
		 * call 0xb7ed8610

	Streams Buffers
	---------------
		 * Standart Output (STDOUT) : 15 bytes
		 * Standart Error  (STDERR) : 0 bytes

	Function Result
	---------------
		 * Pointer: No
		 * Value: 12

Runlib32$ cat printf-out 
Hello World
首先,我們試著去尋找 glibc (libc.so.6,數字 6 表示 ABI version) 中 Standard C Library 的 puts 函式進入點,產生 x86 stack push / call 的動作,執行的結果導入 stdout,並獲得一個 Function result,我們將過程所產生的 streams buffers 印出即得到 "Hello World",[runlib] 提供的文件 RUNLIB-HOWTO 介紹更複雜的使用方式,可作為參考。

稍早在「深入淺出 Hello World」系列演講提到 function / system call invocation 的細節,[runlib] 就是一個很好的示範,整個程式最核心的部份就是 src/lib.c,而呼叫 syscall 的程式碼片段如下:
        /*
         * Manually pushing the function arguments to the stack
         */
 
        if (ptr->stack) {
                for (j = 0; j < ptr->stack->stack_items; j++) {
                        __asm__ __volatile__ (\
                                "pushl %0 \n" \
                                : /* no output */ \
                                : "r" (ptr->stack->stack[j]) \
                                : "%eax" \
                                );
                }
        }
 
        /*
         * Make the CALL!
         */
        ret = (unsigned long) ptr->fcn_handler();
 
        /*
         * Be polite, let's clean the stack afterward
         */
        if (ptr->stack) {
                ptr->stack->stack_items *= sizeof(long);
                __asm__ __volatile__ (\                       
                        "addl %0, %%esp \n" \
                        : /* no output */ \
                        : "r" (ptr->stack->stack_items) \
                        : "%esp" \
                        );
                ptr->stack->stack_items /= sizeof(long);
        }

        s_errno = errno;
        signal(SIGSEGV, SIG_DFL);
首先我們必須處理 x86 stack 中 function parameter 的順序與返回狀態的議題,上述兩個 inline assembly 片段則負責這個工作,至於 ptr->fcn_handler,可參考載入 shared object 並查詢 symbol 的部份:
/*
 * lib_load, load library and resolv the symbol
 * * ptr, given library pointer
 */
int lib_load(libptr *ptr) {
        /*
         * Open handler to library and resolv the symbol/function
         */
        ptr->lib_handler = __lib_dlaction(dlopen(ptr->lib_name, RTLD_LAZY));
        ptr->fcn_handler = __lib_dlaction(dlsym(ptr->lib_handler, ptr->fcn_name));                  
        if (!ptr->lib_handler || !ptr->fcn_handler) {
                return 0;
        }
[runlib] 也特別處理了 Stream I/O 的部份,也給我們許多新的啟發,比方說實做一個 ELF sandbox,對 Unit test 也派得上用場,特定的 Test case 則可驗證單獨 function,搭配 PIE (Position-Independent Execution) 或許可創造更多變化。
由 jserv 發表於 July 25, 2006 10:37 PM
迴響