linux動態注入(含視訊演示)

Editor發表於2018-09-26

如果純粹用文字來描述什麼是動態注入,可能還是不太容易理解,所以本篇文章從如下一段程式碼開始:


// who.c

#include <stdio.h>

#include <unistd.h>

int main()

{

while (1) {

printf("who are you ?\n");

sleep(2);

}

return 0;

}

這段程式碼本身沒有什麼意思,執行"gcc who.c -o who -g -Wall"完成編譯並啟動who程式,每隔2s會向終端列印一句"who are you ?",但有意思的是,可能會出現一種"詭異"的現象,比如螢幕上突然冒出一句"it's me ~"。


1.  為什麼可以出現這種“詭異”的現象?


通過反彙編who程式可以看出,程式最終會進入while(1)迴圈體,重複執行"bf24064000"、"e8c5feffff"、"bd0x000000"、"e8ebfeffff"、"ebea"這5條機器指令:


        linux動態注入(含視訊演示)


如果who程式執行到某條指令(比如"e8c5feffff")時暫停了,並且在恢復執行之前,0x400586處的指令塊被替換成列印"it's me ~"的程式碼,那麼who程式下次執行,自然就會列印"it's me ~"。

     

linux動態注入(含視訊演示)



2.  怎麼獲取列印"it's me ~"的機器碼?


  • 32位系統,編譯如下程式碼並在反彙編結果中提取:


// isme32.c

int main()

{

__asm__(

"jmp forward\n\t"

"backward:popl %esi\n\t"

"movl $4, %eax\n\t"

"movl $2, %ebx\n\t"

"movl %esi, %ecx\n\t"

"movl $12, %edx\n\t"

"int $0x80\n\t"

"int3\n\t"

"forward:call backward\n\t"

".string \"it's me ~\\n\""

);

return 0;

}

  • 64位系統,編譯如下程式碼並在反彙編結果中提取:

// isme64.c

int main()

{

__asm__(

"jmp forward\n\t"

"backward:popq %rsi\n\t"

"movq $1, %rax\n\t"

"movq $2, %rdi\n\t"

"movq $12, %rdx\n\t"

"syscall\n\t"

"int3\n\t"

"forward:call backward\n\t"

".string \"it's me ~\\n\""

);

return 0;

}

本篇文章的實驗環境是64位ubuntu系統,所以執行"gcc isme64.c -oisme64 -g -Wall"生成isme64可執行檔案,並通過反彙編isme64檔案提取機器碼:


        linux動態注入(含視訊演示)

        

用藍色、綠色標記出來的16進位制內容,即為列印"it's me ~"的機器碼,但有2點需要說明:


  • 綠色部分其實是"it's me ~\n\0"這串字元的ascii碼值,也被objdump解釋成彙編指令了,這是反彙編工具很難避免的一個問題,用gdb/disassemble反彙編的結果也是一樣的,因為它們選擇的都是線性掃描演算法,遞迴下降演算法效果會好一些,但仍然不能保證得到精確的結果;

  • C程式實現列印"it's me ~",寫一條printf("it's me ~")語句不就可以了麼,為什麼要寫成這種"奇奇怪怪"的樣子?因為如果用C語法來實現的話,"it's me ~"這個字串會被編譯器安排到isme64程式的.data段,就沒辦法隨著指令塊一起注入到目標程式中,而且這段程式碼中取"it's me ~"地址的技巧,對於彙編初學者是很有意思的,程式碼不長,建議在大腦裡執行一遍,並找到其中的奧妙。

         

3.  怎麼實現注入?


本篇文章僅僅用於學習目的,注入的場景和方法都比較簡單,思路在文章第1節已經介紹過了,就是"暫停被注入程式 -> 替換即將執行的指令塊 -> 通知被注入程式繼續執行",由於有些場合還需要保證注入操作的隱蔽性,所以以下程式還在注入指令塊完整執行後,將被注入程式恢復到了注入前的狀態:


 (理解這段程式碼,至少需要學習ptrace()系統呼叫的作用,如果有核心基礎,也可以更深入的學習一下ptrace()的內部原理,另外,CODE巨集對應的內容,即為利用文章第2節描述的方法,提取的機器碼)


// inject.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/ptrace.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <sys/user.h>

#include <errno.h>

// 注入指令塊(列印"it's me ~")

#ifdef ENV_I386

#define CODE \

"\xeb\x15\x5e\xb8\x04\x00\x00\x00" \

"\xbb\x02\x00\x00\x00\x89\xf1\xba" \

"\x0c\x00\x00\x00\xcd\x80\xcc\xe8" \

"\xe6\xff\xff\xff\x69\x74\x27\x73" \

"\x20\x6d\x65\x20\x7e\x0a\x00"

#define REG_IP  regs.eip

#else   // X_64

#define CODE \

"\xeb\x19\x5e\x48\xc7\xc0\x01\x00" \

"\x00\x00\x48\xc7\xc7\x02\x00\x00" \

"\x00\x48\xc7\xc2\x0c\x00\x00\x00" \

"\x0f\x05\xcc\xe8\xe2\xff\xff\xff" \

"\x69\x74\x27\x73\x20\x6d\x65\x20" \

"\x7e\x0a\x00"

#define REG_IP  regs.rip

#endif

#define CODE_SIZE (sizeof(CODE)-1)

/* 往pid程式的addr地址處寫資料 */

void putdata(pid_t pid, unsigned long addr, void *vptr, int len)

{

int count = 0;

long word;

while (count < len)

{

memcpy(&word, vptr+count, sizeof(word));

word = ptrace(PTRACE_POKEDATA, pid, addr+count, word);

count += sizeof(word);

if (errno != 0)

printf("putdata failed: %p\n", (void *)(addr+count));

}

}

/* 讀pid程式addr地址處的資料 */

void getdata(pid_t pid, unsigned long addr, void *vptr, int len)

{

int i = 0, count = 0;

long word;

unsigned long *ptr = (unsigned long*)vptr;

while (count < len)

{

word = ptrace(PTRACE_PEEKDATA, pid, addr+count, NULL);

count += sizeof(word);

ptr[i++]  = word;

if (errno != 0)

printf("getdata failed: %p\n", (void *)(addr+count));

}

}

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

{

pid_t pid;

struct user_regs_struct regs;

char backup[CODE_SIZE+1];

if (argc != 2) {

printf("Usage: %s {pid}\n", argv[0]);

return -1;

}

// 通過啟動引數,獲取被注入程式號,並attach

pid = atoi(argv[1]);

ptrace(PTRACE_ATTACH, pid, NULL, NULL);  // SIGTRAP

if (errno != 0)

printf("attach failed: %s\n", strerror(errno));

wait(NULL);  // 收到回覆訊號,保證後續過程在attach完成的情況下執行

// 獲取被注入程式當前暫存器值

ptrace(PTRACE_GETREGS, pid, NULL, &regs);

// 備份*ip暫存器指向的指令塊

getdata(pid, REG_IP, backup, CODE_SIZE);

// 替換為CODE指令塊

putdata(pid, REG_IP, CODE, CODE_SIZE);

// 恢復被注入程式的執行

ptrace(PTRACE_CONT, pid, NULL, NULL);    // SIGCONT

// 注入程式最後一條指令為int3,此為即為等待注入指令塊執行完畢

wait(NULL);

// 等待使用者輸入,方便檢視結果

printf("continue to execute the orginal process, press any key ..\n");

getchar();

// 將修改的指令塊恢復為備份內容

putdata(pid, REG_IP, backup, CODE_SIZE);

// 恢復被注入程式暫存器值

ptrace(PTRACE_SETREGS, pid, NULL, &regs);

// detach,被注入程式恢復正常執行

ptrace(PTRACE_DETACH, pid, NULL, NULL);

return 0;

}


4.  視訊演示


         a. 執行gcc inject.c -oinject -g -Wall,編譯生成注入程式


         b. 檢視who程式號N,並執行sudo inject N,進行動態注入操作


         c. 觀察who程式執行視窗是否列印了"it's me ~"


         d. 回到inject程式執行視窗,按任意鍵結束inject程式,並恢復who程式到注入前的狀態,繼續不停的列印"who are you ?"


視訊連結:https://v.youku.com/v_show/id_XMzgzMTM1NTU1Ng==.html?spm=a2h0k.11417342.soresults.dposter



5.  參考

         ptrace實現動態注入:http://www.cnblogs.com/r1ng0/p/9585356.html

         gdb的工作原理:https://blog.csdn.net/u012658346/article/details/51159971




原文作者:xinpoo

原文連結:https://bbs.pediy.com/thread-246948.htm

轉載請註明,轉自看雪論壇



看雪閱讀推薦:


1、[原創]小白文——好玩不貴的無線遙控器克隆指南


2、[翻譯]核心模式Rootkits:檔案刪除保護


3、[翻譯]國外2018最新區塊鏈教程英文版,大膽翻譯,助力論壇『區塊鏈安全』開設第四棒!


4、[原創]看雪安全峰會—《從WPA2四次握手看KRACK金鑰重灌攻擊》


5、[原創]2018看雪CTF第十五題WP



相關文章