編寫Shellcode:尋找EIP/RIP

2021-02-14 看雪學院

本文為看雪論壇優秀文章

看雪論壇作者ID:sudozhange



當學習主動安全認證專家(OSCE)(連結:https://www.offensive-security.com/ctp-osce/)認證時,我花費了大量的時間用於研究如何編寫自定義shellcode。本文是我計劃要發布的一系列博文裡的第一篇,這個系列詳細介紹我在準備認證過程中所學習到的技術。

本文的重點是描述如何找到EIP/RIP以及找到後做什麼。OSCE專注於32位系統,作為繼續學習的一部分,我會研究並記錄下在64位系統上的方法。這超出了OSCE的需求,但這是我繼續學習過程的一部分。

 

在本文中,我不會介紹到所有的可能找到EIP和RIP的方法。我會介紹一些我熟悉並已經研究過的方法。如果你想找到更多的方法,我鼓勵你開始你自己的研究並開始做自己的學習總結。在這裡你看到的是我的學習總結,我希望它會在某些方面對你有幫助。

我知道這些主題多年來已經被多次提到,也沒有新的東西,但重點不是提出新的東西,而是在學習總結中提升自己,並且還可以幫助到剛開始學習的人。

從使用x86彙編(32位)指令定位EIP的方法開始。有兩種方法可以用,根據你可能會遇到的情況和限制,兩種方法可能都有用。兩種方法都可以找到EIP。我要介紹的第一種方法比第二種方法小一個字節。兩種方法都可以達到完全相同的目的,存儲EIP的值到EAX寄存器中。兩種方法都沒有空(0x00)字節。之所以使用一種而不是另一種,是因為您可能會遇到大小或字符限制。該方法最先由Aaron Adams在漏洞開發(Vulnerability Development)郵件列表中提出.他使用的這種方法利用x87浮點單元(FPU)寄存器來獲取EIP值。以下的基礎彙編代碼將EIP的值存儲到EAX寄存器中。

[SECTION .text]
 
BITS 32
 
global _start
 
_start:
    fldz
    fnstenv [esp-0x0C]
    pop eax
    add al, 0x07

執行fldz指令來激活FPU寄存器。該指令將常數值+0.0壓入FPU寄存器棧(ST(0))。在這個過程中,FPU寄存器被初始化,當前的EIP值被存儲到FPU指令指針偏移(FIP)寄存器中。圖 1描述了FPU寄存器的結構。該表拷貝自《Intel(r) 64 和 IA-32 架構軟體開發者手冊,卷1:基礎架構PDF》.
圖1:內存中保護模式x87 FPU狀態圖片(32位格式)下一條指令存儲FPU寄存器在特殊的地址。因此使用fnstenv指令將EIP的值存儲在便宜0x0C(12)的FIP寄存器中。ESP-0x0C的目標是固定的,所以FIP的值會存儲在當前ESP地址處。接下來,該值會從棧中彈出,並存儲到EAX寄存器中。由於在將EIP存儲到FPU的FIP寄存器和彈到EAX的這段時間已經執行了幾個字節的指令,所以需要調整該值來表示當前的EIP的值。用AL寄存器加0x7(7)完成此操作。AL寄存器被用來避免使用空字節。測試

nasm method1.asm -o method1.asm

char code[] = "\xD9\xEE\xD9\x74\x24\xF4\x58\x04\x07";
 
int main(int argc, char **argv)
{
   int (*func)();
   func = (int (*)()) code;
   (int)(*func)();
}

i686-w64-mingw32-gcc-win32 method1.c -o method1.exe -fno-stack-protector -no-pie -m32

運行最終的PE文件在你喜歡的調試器中,並看它是如何運行的。你需要將.data 節標記為可執行。你可以使用調試器來實現或者使用如LordPE這樣的工具。如果你不標記 .data 節為可執行,你會在執行你的shellcode時發生訪問違例。我需要在PE文件中搜索call eax指令,並設置斷點,第一個調用到EAX的會是shellcode。如果一切順利,EAX會在執行add al,7指令時指向EIP,如圖 2。
圖 2:EAX指向EIP感謝來自@TheColonial的建議,對EAX寄存器使用減法而不是add al指令可以再一些情況下避免錯誤,我添加了該替代方法。他正確的指出的問題,是通過對AL進行加法運算,如果加法導致了進位,EAX就會指向錯誤的地址。例如,如果AL寄存器包含大於或等於0xF9的任何數值,加上0x07後,進位的1會被丟棄掉。例如,如果EAX是0x001234F9,我們把0x07加到AL寄存器(0xF9)後,結果會是0x00123400而不是我們需要的0x00123500 。為了避免這個問題,並且避免空字節,可以讓EAX減去一個負值。基本的數學運算,減去一個負值等於加上它(的絕對值)。簡單卻有效。正確的代碼如下,還避開了空字節,並且只比之前大了1位元組:

[SECTION .text]
 
BITS 32
 
global _start
 
_start:
    fldz
    fnstenv [esp-0x0C]
    pop eax
    sub eax, -0x07

在圖 9中我們可以看出建議的代碼正常運行並且會比原來的代碼更加可靠。
圖 9 EAX指向EIP第二種方法僅比第一種方法長一個字節,也完成了將EIP的值存儲到EAX中的目標。我從 Phrack issue 62,Phile 7標題為History and Advances in Windows Shellcode中學到的這種方法。這篇文章由SK Chong所寫。為了獲得EIP這種方法使用了一系列的跳轉和調用,最終EIP的值被存儲在EAX中。SK Chong文章的原始文件已經找不到了,但是,我找到了其中的一些。在他的例子中,它使用了db項來硬編碼了那些跳轉和調用。我要提供的版本讀起來更容易並且以及更容易使用nasm彙編。

[SECTION .text]
 
BITS 32
 
global _start
 
_start:
    jmp label2
    label1:
        jmp getEIP
    label2:
        call label1
    getEIP:
        pop eax

上面的代碼執行了跳轉到label2,一個call指令調用了label1。當call被執行時,返回地址,即下一條指令的地址,被壓入棧中。執行,隨後跳轉到getEIP,會彈出返回地址到EAX。不要小題大作(No muss, no fuss.)。測試你可以使用測試方法 1的測試方法來測試方法2.當執行完,結果看起來如圖 3。
圖 3:EAX指向EIP查看是否32位架構中運行的方法在64位世界中是否運行是一個好的開始。如果只需做些微修改,為什麼還要重新造輪子?嘗試1


使用FPU指令可以運行,因為指令在64位下仍然生效。首先,找出未對32位模式運行下的指令進行修改或微小修改發生的情況。要做到這點,我使用x64dbg並使其執行notepad.exe,運行直到命中EntryPoing。使用Assemble指令,我手動替換了開始的一些指令位32-bit的指令。這樣做的結果如圖 4。這裡有一些需要注意。首先,67:出現在fnstenv指令前。根據Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B, 2C & 2D): Instruction Set Reference, A-Z 章節2.1.1,67h前綴是地址大小覆寫。這一定會出現的,因為我之前使用的是ESP寄存器而不是RSP。第二,我不能使用POP EAX指令,我需要使用POP RAX。

圖 4:使用FPU尋找RIP,嘗試1運行該序列會部分成功(見圖5)。當加法執行時,RAX指向RIP之前的指令。這是必須使用67h地址大小前綴的原因。使用0x08替代0x07就能很容易的使之平衡。繼續,自己嘗試下,看看它是否有效。



圖 5:使用FPU指令尋找RIP,嘗試1的結果

編寫彙編


既然已經證明了使用FPU寄存器來獲取RIP的值是可行的。我的下一步是看看是否能夠寫一些彙編代碼,可以在調試器中手工輸入命令可以使之平衡。經過一些調整後,這是我想出的代碼:

SECTION .text]
 
BITS 64
 
global _start
 
_start:
    fldz
    fnstenv [rsp-0x0C]
    pop rax
    add rax, 0x07

當使用Nasm彙編時,上面指令中的代碼結果如圖 6。為了測試那些指令,我對用x64dbg打開的64位進程的第一條指令做二進位粘貼。如你所看到的,在add rax,0x07指令前有了新的48h前綴。根據Intel® 64 and IA-32 Architectures Software Developer’s Manual, Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D and 4 章節3.7.2.1,這是一個REX前綴,允許指令尋址8位寄存器。彙編器可能夠以這種方式丟到空字節。而且,由於當使用RSP而不是ESP時,對於fnstenv指令,地址大小前綴是非必須的,所以,RAX加上0x07位元組的結果是RAX包含了RIP的地址。

圖 6:使用FPU'指令尋找RIP,終結在開始測試該方法前,我想做些微小的修改。我想有必要將EAX替換成RAX。這種跳轉和調用和手工幹擾到調試器中有一些不同,所以我選擇先開始編寫一些彙編代碼。下面的代碼就是我選擇用來最開始嘗試的:

[SECTION .text]
 
BITS 64
 
global _start
 
_start:
    jmp label2
    label1:
        jmp getEIP
    label2:
        call label1
    getEIP:
        pop rax

執行了使用彙編的代碼覆蓋的notepad的入口點的結果並可以在圖 7中看到。這種方法依舊沒有空字節,長度上僅僅10個字節。在64位架構上獲取了兩種方法後,我想看看是否有更好的方式。當搜索時,我想到了一篇由 Booze | Allen | Hamilton發表的文章,詳細的描述了由Rapid7 在Metasploit中實現的方法,該方法使用LEA指令來獲取RIP。經過一番嘗試,在使用Nasm命令行時,我想出了下面的代碼,可以復現Metasploit使用的方法:

[SECTION .text]
 
BITS 64
 
default rel
 
global _start
 
_start:
    lea rax, [_start]

當使用Nasm並使用一下標記彙編時,結果代碼沒有空字節,並僅僅7位元組長。-O0標記告訴Nasm不要執行優化。如果你沒有使用該標記,操作碼會包含空值。自己嘗試,並看看是如何功能做的:

nasm -O0 method3.asm -o method3.bin

圖 8:使用加載有效地址(LEA)指令尋找RIP,終結我已經詳細描述了幾種根據目標架構尋找EIP或RAX的方法。如果希望執行其他與shellcode交互的操作,動態獲取這些值是非常重要的。由於ASLR無法動態查找EIP/RIP,所以解碼,複製或者其他修改你的代碼的操作是不可能的。在之後的文章,我會繼續以此為基礎。本系列的最後一篇文章將會編寫一個完全自定義的shellcode,使用Intel x64彙編中的scatch編寫。感謝您花費時間閱讀我的博客!我希望你能夠從中學習並可能啟發你自己學習更多。https://blog.xenoscr.net/Finding-EIP/

看雪ID:sudozhange

https://bbs.pediy.com/user-703263.htm 

*本文由看雪翻譯小組 sudozhange 編譯,lipss校對。

好書推薦

相關焦點

  • 如何編寫shellcode查找EIP & RIP
    我在學習OSCE認證時,花費了大量時間研究如何編寫自定義shellcode。這是我打算發布的一系列博客文章中的第一篇,其中將詳細介紹我在此過程中學到的一些技術。這篇博客文章的重點將是描述如何找到EIP / RIP,以及找到它可以做什麼。OSCE將精力集中在32位系統上,作為我繼續學習的一部分,我將研究和記錄適用於64位系統的方法。
  • 利用miasm解析shellcode(一)
    本文轉載自【微信公眾號:MicroPest,ID:gh_696c36c5382b】前面介紹了一些不同類型的逆向方法過程,這裡再介紹個shellcode的解析;在freebuf上看了利用miasm解析shellcode的文,覺得非常有意思,官網的推薦是這樣的:「miasm 是一個可以做動態及靜態分析的框架,支持很多處理器之外,也可以 load Windows
  • 編寫Windows Kernel Shellcode
    最近有個想法,直接利用內核代碼執行權限,來寫文件,於是就抄起了VS,開始寫shellcode,開始以為和R3下面寫shellcode一樣簡單。新建個驅動的項目,按照下面修改項目的屬性,然後動態獲取API,再調API完成自己的功能。項目屬性配置好以後,開始寫shellcode,計算要使用的內核API的名稱hash,為了方便計算,我寫了一個簡單的MFC程序。
  • shellcode編寫指南
    編寫shellcode3.1 c++庫文件配合內聯彙編先來針對指定系統的shellcode的編寫,指定系統的,我們首先通過LoadLibraryA函數導入相應的dll文件,獲得一個dll句柄,在把這個dll句柄當作參數傳入GetProcAddress 搜索查找指定函數,返回該函數的地址,然後通過函數的地址來調用函數,用c++代碼內聯彙編實現
  • 簡單shellcode學習
    引言之前遇到沒開啟NX保護的時候,都是直接用pwtools庫裡的shellcode一把梭,也不太懂shellcode代碼具體做了些什麼,遇到了幾道不能一把梭的題目
  • 一步步學寫Windows下的Shellcode
    為什麼會問這個問題,前段時間在做win下的Exploit,但是都是使用大佬寫的shellcode,無法實現個人的一些需求。而網絡上編寫shellcode的教程大多是關於Linux的,加之順帶學習PE文件結構,所以打算寫一篇關於Windows 下shellcode的編寫,為要編寫Shellcdoe的讀者提供一些參考。
  • CTF必備技能丨Linux Pwn入門教程——ShellCode
    首先我們把演示程序~/Openctf 2016-tyro_shellcode1/tyro_shellcode1複製到32位的Docker環境中並開啟調試器進行調試分析。我們通過著名的shell-storm.org的ShellCode資料庫shell-storm.org/shellcode/找到了一段符合條件的ShellCode。
  • 萌新逆向學習筆記——CreateRemoteThread注入Shellcode
    結合上面兩點就產生了本篇文章的主旨內容Shellcode。有人說Shellcode僅限於Linux和unix,而經百度似乎也有很多種說法。個人理解是一段被注入後可獨立運行,依賴性少或完全沒依賴的代碼。為了撇開爭論,暫且保持書上所說的那樣,稱之為Shellcode。說的直白一點,其實就是把參數,要運行的函數地址全部放到結構體裡,最後以一段用彙編編寫的Shellcode運行起來。
  • 工具Pe2shc (Pe to Shellcode) 的源碼級分析
    shellcode一直是無文件執行的主題,我們經常要生成shellcode,但用msf生成的一般免殺效果不盡人意。如何能讓提取shellcode變得簡單,是大家的共同想法。    有人提出了「將PE直接轉為shellcode」,因為編寫PE是相對簡單得多。在Git上有人給出了這麼個程序,直接可以將PE/ELF轉化為shellcode,我們來看看。
  • Battleye 系列翻譯之shellcode更新
    BattlEye shellcode updates(點擊閱讀原文查看連結) 隨著時間的推移,反外掛策略也在發生著變化,功能在不斷的變化,以圖最大化產品的效率。我在一年前曾經在我的博客上完成過一份完整的關於BE的shellcode的總結,今天這篇文章只是在上次所說的shellcode的基礎上,看看哪些地方發生了改變。
  • 乾貨 | Linux基本技能—Shell精簡教程
    sh:sh由Steve Bourne開發,是Bourne Shell的縮寫,sh 是Unix標準默認的shell。ash:ash shell是由Kenneth Almquist編寫的,Linux中佔用系統資源最少的一個小shell,它只包含24個內部命令,因而使用起來很不方便。
  • shell腳本極簡教程
    Linux使用它作為默認的shell是因為它有諸如以下的特色:sh:sh 由Steve Bourne開發,是Bourne Shell的縮寫,sh 是Unix 標準默認的shell。ash:ash shell 是由Kenneth Almquist編寫的,Linux中佔用系統資源最少的一個小shell,它只包含24個內部命令,因而使用起來很不方便。
  • 編寫Linux Shell腳本的最佳實踐
    畢竟shell腳本這個東西不算是正經的程式語言,他更像是一個工具,用來雜糅不同的程序供我們調用。因此很多人在寫的時候也是想到哪裡寫到哪裡,基本上都像是一段超長的main函數,不忍直視。同時,由於歷史原因,shell有很多不同的版本,而且也有很多有相同功能的命令需要我們進行取捨,以至於代碼的規範很難統一。
  • 《Linux基礎》第5講 shell編程 (一)
    ashash shell 是由Kenneth Almquist編寫的,Linux中佔用系統資源最少的一個小shell,它只包含24個內部命令,因而使用起來很不方便。csh 是Linux比較大的內核,它由以William Joy為代表的共計47位作者編成,共有52個內部命令。
  • php 不用字母,數字和下劃線寫 shell
    師傅的文章 《一些不包含數字和字母的 webshell》https://www.leavesongs.com/penetration/webshell-without-alphanum.html還有這個師傅的 《記一次拿webshell踩過的坑(如何用PHP編寫一個不包含數字和字母的後門
  • 正確的使用python調用shell的姿勢
    python天生的優勢,用它來開發一些devops的自動化作業是非常方便的,當然在linux上,一般我們用shell就能寫一些簡單的自動化腳本,但如果自動化作業複雜的話,使用shell腳本就很難搞定了,一方面shell腳本量變大就會比較難以工程化,維護和閱讀,另外一個重要的原因是shell不具備正經程式語言所具備的豐富的一些類庫,比如說map類型必須得bash版本4.x以上才有,或者有序
  • shell是什麼?
    bash shell執行shell一、shell是一個命令解釋器,是人與作業系統之間的橋梁。人與作業系統怎麼關聯?通過shell。人通過shell向作業系統發出操作指令。二、shell腳本:shell命令的集合。從上而下的執行。三、常見的shell:1、bourne shell.2、C shell.3、bourne-Again shell:bash是一個為gun項目編寫的unix shell,bash是大多數linux系統、MAC、windows默認的shell。