<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/binary/7452

            66章 系統調用(syscall-s)


            眾所周知,所有運行的進程在操作系統里面分為兩類:一類擁有訪問全部硬件設備的權限(內核空間)而另一類無法直接訪問硬件設備(用戶空間)。

            操作系統內核和驅動程序通常是屬于第一類的。

            而應用程序通常是屬于第二類的。

            舉個例子,Linux kernel運行于內核空間,而Glibc運行于用戶空間。

            這種分離對與操作系統的安全性是至關重要的:它最重要的一點是,不給任何進程有破壞到其它進程甚至是系統內核的機會。另一方面,一個錯誤的驅動或系統內核錯誤都會造成系統崩潰或者藍屏。

            保護模式下的x86處理器允許使用4個保護等級(ring)。但Linux和Windows兩個操作系統都只使用了兩個:ring0(內核空間)和ring3(用戶空間)。

            系統調用(syscall-s)是兩個運行空間的連接點。可以說,這是提供給應用程序主要的API。

            在Windows NT,系統調用表存在于SSDT。

            通過系統調用實現shellcode在計算機病毒作者之間非常流行。因為很難確定所需函數在系統庫里面的地址,但系統調用很容易確定。然而,由于系統調用屬于比較底層的API,所以需要編寫更多的代碼。最后值得一提的是,在不同的操作系統版本里面,系統調用號是有可能不同的。

            66.1 Linux


            在Linux系統中,系統調用通常使用int 0x80中斷進行調用。通過EAX寄存器傳遞調用號,再通過其它寄存器傳遞所需參數。

            Listing 66.1: A simple example of the usage of two syscalls

            section .text
            global _start
            _start:
                mov edx,len ; buf len
                mov ecx,msg ; buf
                mov ebx,1   ; file descriptor. stdout is 1
                mov eax,4   ; syscall number. sys_write is 4
                int 0x80
                mov eax,1   ; syscall number. sys_exit is 4
                int 0x80
            section .data
            msg db 'Hello, world!',0xa
            len equ $ - msg
            

            編譯:

            nasm -f elf32 1.s
            ld 1.o
            

            Linux所有的系統調用在這里可以查看:http://go.yurichev.com/17319

            在Linux中可以使用strace(71章)對系統調用進行跟蹤或者攔截。

            66.2 Windows


            Windows系統使用int 0x2e中斷或x86下特有的指令SYSENTER調用用系統調用服務。

            Windows所有的系統調用在這里可以查看:http://go.yurichev.com/17320

            擴展閱讀:“Windows Syscall Shellcode” by Piotr Bania

            67章 Linux


            67.1 位置無關代碼


            在分析Linux共享庫的時候(.so)的時候,可能會經常看到類似下面的代碼:

            Listing 67.1: libc-2.17.so x86

            .text:0012D5E3 __x86_get_pc_thunk_bx proc near ; CODE XREF: sub_17350+3
            .text:0012D5E3 ; sub_173CC+4 ...
            .text:0012D5E3     mov ebx, [esp+0]
            .text:0012D5E6     retn
            .text:0012D5E6 __x86_get_pc_thunk_bx endp
            ...
            .text:000576C0 sub_576C0 proc near ; CODE XREF: tmpfile+73
            ...
            .text:000576C0     push ebp
            .text:000576C1     mov ecx, large gs:0
            .text:000576C8     push edi
            .text:000576C9     push esi
            .text:000576CA     push ebx
            .text:000576CB     call __x86_get_pc_thunk_bx
            .text:000576D0     add ebx, 157930h
            .text:000576D6     sub esp, 9Ch
            ...
            .text:000579F0     lea eax, (a__gen_tempname - 1AF000h)[ebx] ; "__gen_tempname"
            .text:000579F6     mov [esp+0ACh+var_A0], eax
            .text:000579FA     lea eax, (a__SysdepsPosix - 1AF000h)[ebx] ; "../sysdeps/posix/tempname.c"
            .text:00057A00     mov [esp+0ACh+var_A8], eax
            .text:00057A04     lea eax, (aInvalidKindIn_ - 1AF000h)[ebx] ; "! \"invalid KIND in __gen_tempname\""
            .text:00057A0A     mov [esp+0ACh+var_A4], 14Ah
            .text:00057A12     mov [esp+0ACh+var_AC], eax
            .text:00057A15     call __assert_fail
            

            在每個函數開始處,所有指向字符串的指針都需要通過EBX和一些常量值來修正地址。這就是所謂的PIC(位置無關代碼),它的目的是讓這段代碼即使隨機地放在內存中某個位置都能正確地執行。這也是為什么不能使用絕對地址的原因。

            PIC(位置無關代碼)對于早期的操作系統和現在那些沒有虛擬內存支持的嵌入式系統來說至關重要(所有進程都放在同一個連續的內存塊)。此外,它還用于*NIX系統的共享庫。這樣共享庫只需要加載一次到內存之后就可以讓所有需要的進程使用,而且這些進程可以把同一個共享庫映射到各自不同的內存地址上。這也是為什么共享庫不使用絕對地址也能夠正常地工作。

            讓我們做一個簡單的實驗:

            #!c
            #include <stdio.h>
            int global_variable=123;
            int f1(int var)
            {
                int rt=global_variable+var;
                printf ("returning %d\n", rt);
                return rt;
            };
            

            用GCC 4.7.3編譯它并用IDA查看.so文件的反匯編代碼:

            #!bash
            gcc -fPIC -shared -O3 -o 1.so 1.c
            

            .text:00000440 public __x86_get_pc_thunk_bx
            .text:00000440 __x86_get_pc_thunk_bx proc near ; CODE XREF: _init_proc+4
            .text:00000440 ; deregister_tm_clones+4 ...
            .text:00000440     mov ebx, [esp+0]
            .text:00000443     retn
            .text:00000443 __x86_get_pc_thunk_bx endp
            .text:00000570 public f1
            .text:00000570 f1 proc near
            .text:00000570
            .text:00000570 var_1C = dword ptr -1Ch
            .text:00000570 var_18 = dword ptr -18h
            .text:00000570 var_14 = dword ptr -14h
            .text:00000570 var_8 = dword ptr -8
            .text:00000570 var_4 = dword ptr -4
            .text:00000570 arg_0 = dword ptr 4
            .text:00000570
            .text:00000570     sub esp, 1Ch
            .text:00000573     mov [esp+1Ch+var_8], ebx
            .text:00000577     call __x86_get_pc_thunk_bx
            .text:0000057C     add ebx, 1A84h
            .text:00000582     mov [esp+1Ch+var_4], esi
            .text:00000586     mov eax, ds:(global_variable_ptr - 2000h)[ebx]
            .text:0000058C     mov esi, [eax]
            .text:0000058E     lea eax, (aReturningD - 2000h)[ebx] ; "returning %d\n"
            .text:00000594     add esi, [esp+1Ch+arg_0]
            .text:00000598     mov [esp+1Ch+var_18], eax
            .text:0000059C     mov [esp+1Ch+var_1C], 1
            .text:000005A3     mov [esp+1Ch+var_14], esi
            .text:000005A7     call ___printf_chk
            .text:000005AC     mov eax, esi
            .text:000005AE     mov ebx, [esp+1Ch+var_8]
            .text:000005B2     mov esi, [esp+1Ch+var_4]
            .text:000005B6     add esp, 1Ch
            .text:000005B9     retn
            .text:000005B9 f1 endp
            

            如上所示:每個函數執行時都會矯正“returning %d\n”和global_variable的地址。__x86_get_pc_thunk_bx()函數通過EBX返回一個指向自身的指針(返回的是0x57C)。這是一種獲取程序計數器(EIP)的簡單方法。0x1A84常量是這個函數開始處到(Global Offset Table Procedure Linkage Table(GOT PLT))它們之間的距離差。IDA會把這些偏移處理成更容易理解后再顯示出來,所以實際上的代碼是:

            .text:00000577 call __x86_get_pc_thunk_bx
            .text:0000057C add ebx, 1A84h
            .text:00000582 mov [esp+1Ch+var_4], esi
            .text:00000586 mov eax, [ebx-0Ch]
            .text:0000058C mov esi, [eax]
            .text:0000058E lea eax, [ebx-1A30h]
            

            這里的EBX指向了GOT PLT section。當計算global_variable(存儲在GOT)的地址時須減去0x0C偏移量。當計算"returning %d\n"字符串的地址時須減去0x1A30偏移量。

            順便說一下,AMD64的指令支持使用RIP用于相對尋址,這使得它可以產生出更簡潔的PIC代碼。

            讓我們用相同的GCC編譯器編譯相同的C代碼,但使用x64平臺。

            IDA會簡化了反匯編代碼,造成我們無法看到使用RIP相對尋址的細節,所以我在這里使用了objdump來查看反匯編代碼:

            0000000000000720 <f1>:
            720: 48 8b 05 b9 08 20 00    mov rax,QWORD PTR [rip+0x2008b9] # 200fe0 <_DYNAMIC+0x1d0>
            727: 53                      push rbx
            728: 89                      fb mov ebx,edi
            72a: 48 8d 35 20 00 00 00    lea rsi,[rip+0x20] #751 <_fini+0x9>
            731: bf 01 00 00 00          mov edi,0x1
            736: 03 18                   add ebx,DWORD PTR [rax]
            738: 31 c0                   xor eax,eax
            73a: 89 da                   mov edx,ebx
            73c: e8 df fe ff ff          call 620 <[email protected]>
            741: 89 d8                   mov eax,ebx
            743: 5b                      pop rbx
            744: c3                      ret
            

            0x2008b9是0x720處指令地址到global_variable地址的差,0x20是0x72a處指令地址到"returning %d\n"字符串地址的差。

            你可能會看到,頻繁重新計算地址會導致執行效率變差(雖然在x64會更好)。所以如果你比較關心性能的話最好還是使用靜態鏈接。

            67.1.1 Windows

            Windows的DLL并沒有使用PIC機制。如果Windows加載器需加載DLL到另外一個基地址,它需要把DLL在內存中的“重定位段”(在固定的位置)里所有地址都調整為正確的。這意味著多個Windows進程不能在不同進程內存塊的不同地址共享一份DLL,因為每個實例加載在內存后只固定在這些地址工作。

            67.2 LD_PRELOAD hack in Linux


            Linux允許讓我們自己的動態鏈接庫加載在其它動態鏈接庫之前,甚至是系統庫(如 libc.so.6)。

            反過來想,也就是允許我們用自己寫的函數去“代替”系統庫的函數。舉個例子,我們可以很容易地攔截掉time(),read(),write()等等這些函數。

            來瞧瞧我們是如何愚弄uptime這個程序的。我們知道,該程序顯示計算機已經工作了多長時間。借助strace的幫助可以看到,該程序通過/proc/uptime文件獲取到計算機的工作時長。

            #!c
            $ strace uptime
            ...
            open("/proc/uptime", O_RDONLY) = 3
            lseek(3, 0, SEEK_SET) = 0
            read(3, "416166.86 414629.38\n", 2047) = 20
            ...
            

            /proc/uptime并不是存放在磁盤的真實文件。而是由Linux Kernel產生的一個虛擬的文件。它有兩個數值:

            #!bash
            $ cat /proc/uptime
            416690.91 415152.03
            

            我們可以用wikipedia來看一下它的含義:

            第一個數值是系統運行總時長,第二個數值是系統空閑的時間。都以秒為單位表示。
            

            我們來寫一個含open(),read(),close()函數的動態鏈接庫。

            首先,我們的open()函數會比較一下文件名是不是我們所想要打開的,如果是,則將文件描述符記錄下來。然后,read()函數會判斷如果我們調用的是不是我們所保存的文件描述符,如果是則代替它輸出,否則調用libc.so.6里面原來的函數。最后,close()函數會關閉我們所保存的文件描述符。

            在這里我們借助了dlopen()和dlsym()函數來確定原先在libc.so.6的函數的地址,因為我們需要控制“真實”的函數。

            題外話,如果我們的程序想劫持strcmp()函數來監控每個字符串的比較,則需要我們自己實現一個strcmp()函數而不能用原先的函數。

            #!c
            #include <stdio.h>
            #include <stdarg.h>
            #include <stdlib.h>
            #include <stdbool.h>
            #include <unistd.h>
            #include <dlfcn.h>
            #include <string.h>
            
            void *libc_handle = NULL;
            int (*open_ptr)(const char *, int) = NULL;
            int (*close_ptr)(int) = NULL;
            ssize_t (*read_ptr)(int, void*, size_t) = NULL;
            bool inited = false;
            
            _Noreturn void die (const char * fmt, ...)
            {
                va_list va;
                va_start (va, fmt);
                vprintf (fmt, va);
                exit(0);
            };
            
            static void find_original_functions ()
            {
                if (inited)
                    return;
                libc_handle = dlopen ("libc.so.6", RTLD_LAZY);
                if (libc_handle==NULL)
                    die ("can't open libc.so.6\n");
                open_ptr = dlsym (libc_handle, "open");
                if (open_ptr==NULL)
                    die ("can't find open()\n");
                close_ptr = dlsym (libc_handle, "close");
                if (close_ptr==NULL)
                    die ("can't find close()\n");
                read_ptr = dlsym (libc_handle, "read");
                if (read_ptr==NULL)
                    die ("can't find read()\n");
                inited = true;
            }
            
            static int opened_fd=0;
            
            int open(const char *pathname, int flags)
            {
                find_original_functions();
                int fd=(*open_ptr)(pathname, flags);
                if (strcmp(pathname, "/proc/uptime")==0)
                    opened_fd=fd; // that's our file! record its file descriptor
                else
                    opened_fd=0;
                return fd;
            };
            
            int close(int fd)
            {
                find_original_functions();
                if (fd==opened_fd)
                    opened_fd=0; // the file is not opened anymore
                return (*close_ptr)(fd);
            };
            
            ssize_t read(int fd, void *buf, size_t count)
            {
                find_original_functions();
                if (opened_fd!=0 && fd==opened_fd)
                {
                    // that's our file!
                    return snprintf (buf, count, "%d %d", 0x7fffffff, 0x7fffffff)+1;
                };
                // not our file, go to real read() function
                return (*read_ptr)(fd, buf, count);
            };
            

            把它編譯成動態鏈接庫:

            gcc -fpic -shared -Wall -o fool_uptime.so fool_uptime.c -ldl
            

            運行uptime,并讓它在加載其它庫之前加載我們的庫:

            LD_PRELOAD=`pwd`/fool_uptime.so uptime
            

            可以看到:

            01:23:02 up 24855 days, 3:14, 3 users, load average: 0.00, 0.01, 0.05
            

            如果LD_PRELOAD環境變量一直指向我們的動態鏈接庫文件名,其它程序在啟動的時候也會加載我們的動態鏈接庫。

            更多的例子請看:

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线