2013-12-01 19:18:24Morris

[電腦攻防][作業1] shell code

  • Score:
    • 120 points.
  • Description:
    • Write two programs with the following properties:
      • On a host, called server host, write a program, called password_holder.c, that allows a user to input a character string.
      • After obtaining an input string from a user, password_holder.c opens a TCP/IP socket and listens on the socket to wait for any incoming request from a host, called client host.
      • When an incoming connection is established, password_holder.c sends the input string to the the client host as a password.
      • On a host, called client host, write a program, called mission_impossible.c, that connects to the above remote server to retrieve a password.
      • The compiled executable file of mission_impossible.c is called mission_impossible.exe.
      • After obtaining a password from the above server, mission_impossible.c asks its users to input a password.
      • If an input password is identical to the passwrod that mission_impossible.c retrieved from the above server, the program will execute a piece of code, called Mission Briefing Code (MBC) hereafter, that will display the following message ("Ethan Hunt, Run Now!") for 2 minutes and then delete the file and terminate itself.
      • If an erroneous password is input, the program will delete the file mission_impossible.c and terminate itself.
      • Initially, MBC must be stored in a global array and is encoded with your password. You can use any approach to encode MBC. When the input password is correct, your program will decode MBC and place it in the heap and then transfer your execution flow to the decoded MBC.

英文太差了,於是來點中文翻譯。
  • 首先,password_holder.c 是一個 server 端的程式,必須在執行時向開啟者請求輸入一個密碼,這個密碼將做為 client 端開啟程式的依據,得到輸入密碼後,等待 client 連上,隨後將這個密碼傳給 client 端的程式。server 端的程式功能到此為止。

  • 接著,mission_impossible.c 是一個 client 端的程式,必須在開啟時連向 server,接收由 server 傳來的密碼。比較難懂的地方在於 MBC 的存在,MBC 為一段 ASM code,並且以陣列形式的方式儲存在 mission_impossible.c 的全區變數,因為作業是這麼限制的。

  • 藉由 function pointer 將 MBC 的資料成為程式中的一部分執行 (不然一般而言,資料終究只是資料,不會可以執行的),而在 MBC 這段 ASM code 將要實做 1) 打印字串 2) 停止兩分鐘 3) 刪除指定檔案

  • 如何撰寫這個 ASM code ? 這麼問其實很難一言道盡,首先要理解這段 ASM code 與一般寫 ASM 有何不同,先去參照 http://www.vividmachines.com/shellcode/shellcode.html 的介紹。

    char code[] = "bytecode will go here!";
    int main(int argc, char **argv)
    {
      int (*func)();
      func = (int (*)()) code;
      (int)(*func)();
    }

    這段是核心的,*func 即是上面所述的 function pointer,首先將 data 段的資料位址強制轉換成的 function pointer 的型態,藉由 call function 跳轉到 data 上執行。

    與一般寫 ASM 作業有何不同 ? 這段 ASM code 沒有使用到 .data 段的宣告,一整片都是執行的 code,打印字串時,抓取位址的方式非常特別。

    舉個網址中例子

    _start:

    jmp short ender

    starter:

    xor eax, eax ;clean up the registers
    xor ebx, ebx
    xor edx, edx
    xor ecx, ecx

    mov al, 4 ;syscall write
    mov bl, 1 ;stdout is 1
    pop ecx ;get the address of the string from the stack
    mov dl, 5 ;length of the string
    int 0x80

    xor eax, eax
    mov al, 1 ;exit the shellcode
    xor ebx,ebx
    int 0x80

    ender:
    call starter ;put the address of the string on the stack
    db 'hello'

    可以看到,當使用 call starter 會將下一行的指令(剛好是這個字串"hello" 的位址)丟入 stack 之中,然後藉由 pop ecx 得到位址打印出來。

    但只是一種寫法,但是要當需要多個字串參數時,會一直在 ASM 後方增加新的字串,累計的時候就相當不方便,要自己算偏移多少值,對於增加或修改時都相當不方便,不過以 DEMO 導向設計的話,什麼都行!

  • 執行時常用的語法
  • 這次作業最大的疑惑,說好的 BOA 呢?其實它並沒有實做於這次的作業之中。
操作環境理解完,正是要說明 shell code 撰寫的時候了,但這次系統環境採用 Linux 而非 Windows,Windows 尚未研究它的各種麻煩,等到有人願意出來分享他的心得。

明年的這個時候,又有多少人修這門課查到這一篇呢?

「別給學弟妹考古題,會讓他們喪失自我學習的能力。」by 許富皓

細節的部分,等作業交了之後再說說吧。

特別注意到,刪除檔案的處理,通常透過的 system call 有兩種 unlink() 或者是
int execve(const char *filename, char *const argv[], char *const envp[]);

相信很多人仍不明白指標陣列,而將形式傳錯。


[2013/12/11 Update]
  • 題目模糊不清的狀況下,我們有新的理解方式。

    解密 function 應該寫在 MBC 裡頭,然後整個 MBC 可以收 server 傳過來,因此 server 傳到 client 是一段經過加密 MBC。而 client 要預先分配 global 一段給 MBC。而 MBC 也有一段 header,如果解密 function 運形於 MBC 後段,header 需加入跳轉的 offset。

    題目也許是故意這樣設計的,發揮創意去寫吧。

  • 首先,來看一下 Linux system call table

    %eaxNameSource %ebx%ecx%edx%esx%edi
    1sys_exitkernel/exit.c int----
    2sys_forkarch/i386/kernel/process.c struct pt_regs----
    3sys_readfs/read_write.c unsigned intchar *size_t--
    4sys_writefs/read_write.c unsigned intconst char *size_t--
    5sys_openfs/open.c const char *intint--
    6sys_closefs/open.c unsigned int----
    7sys_waitpidkernel/exit.c pid_tunsigned int *int--
    8sys_creatfs/open.c const char *int---
    9sys_linkfs/namei.c const char *const char *---
    10sys_unlinkfs/namei.c const char *----
    11sys_execvearch/i386/kernel/process.c struct pt_regs----

    162sys_nanosleepkernel/sched.c struct timespec *struct timespec *---

    實作時,請確認好丟入的暫存器所對應的參數(不管是值還是址)。

  • 如何使用 system call,根據素材 http://mike820324.blogspot.tw/search/label/shell%20code ,這個網址相當重要,其中要運用到的式 nanosleep() 和 execve()

    int nanosleep(const struct timespec *req, struct timespec *rem);
    ....
    struct timespec {
                   time_t tv_sec;        /* seconds */
                   long   tv_nsec;       /* nanoseconds */
               };

    這個函數處理時,傳遞的是指標,而記憶體配置如上圖, time_t tv_sec 記憶體位址較低,而 long tv_nsec 記憶體為址是高的 (ANSI C 的 struct 語法都是如此配置記憶體)。

    如果我們採用將變數放到 stack 方式時,先丟入 nanoseconds 變數的值 (push #val),再丟入 seconds 的值。整個 timespec 占有 8 bytes 記憶體大小,分別占據 4 bytes,放入結束後,由於 esp (stack pointer) 會指到這個 timespec 記憶體位置,因此把 esp 移入到 nanosleep() 指定暫存器中,接著呼叫 int 80 運行即可。

    int execve(const char *filename, char *const argv[],
                      char *const envp[]);


    這個函數稍微複雜了點,通常我們會使用到 main(int argc, char *argv[]),來讀入這個程式的相關參數,但是 execve() 裡頭沒有設定 int argc,那它是怎麼處理的 ? 其實也不難想像,由於給定的是參數指標陣列,因此在最後一個指標一定指向於 null (zero)。

    但是明白這些後,很多人又把字串結尾的 '\0' 和 null 搞混了。


            char *argv[]={"/bin/rm","./test",NULL};
            execve(argv[0],argv,NULL);

    舉一個助教給的參數範例,宣告字串
    '/bin/rm', 0x00, '/test', 0x00
    其中 0x00 即為 '\0' 的字串結尾的十六進制。

    假使採用
    直接打在 asm code 中,db '/bin/rm', 0x00 ... 類似的方法。

    由於這個函數要求指標陣列,仍需要將每個字串的 offset 收集成一個連續記憶體的陣列,這又是新的難關了,要怎麼拿到連續記憶體並且將每個字串的 offset 放入?如果沒辦法使用 malloc(),丟入 stack 是一個好辦法,顛倒丟入 offset 最後再將 esp 取出,便可拿到連續記憶體的指標陣列的起始位址。

  • 複習一下,組合語言

    • 在 ANSI C 操作時,參數的 push 順序,是由右邊到左邊。假如呼叫 foo(a, b, c)
      依序 push c, push b, push a

    • stack 是從高記憶體位址往低位址填
      因此
      如果要將字串塞到 stack 中,必須將字串顛倒 push 進去。同理利用 stack 的指標陣列使用。

  • 得知了 ANSI C 的 function 中,也可以呼叫 function 來完成開啟 stack 上可執行程式的指令,這樣就不必在編譯時期加入參數設定。

  • 番外篇:
    由 server 傳送加密過的 MBC code,而解密 function 放在起頭位置。
    重點難題 - 如何對自己做解密?// 解密 function 沒有被加密,但是真正運行的 code 被加密。
    也就是這段 MBC 會對自己的一部分 code 進行解密動作。
    初步設計是這個樣子的:

    • 一開始 MBC 需要得到一個字串位址當作參數。
      接著進行解密動作,跳轉到解密 function,跳轉時利用 call 得到後段的記憶體位址,使用 for loop 運行解密動作 (將每一個 byte 著手 xor 操作 ... 等)。

    • 檢查 MBC 裡頭的 check sum 欄位,然後跳轉到指定的指令流上。
      // check sum 欄位在 server 那端加入。

  • 無法提供程式代碼。

xem phim online 2017-10-03 14:50:29

我在摸索我存在的意義