加密与解密.溢出

缓冲区溢出

栈溢出

栈是什么,它里面是什么

  • 首先,栈是一种基本的数据结构,由编译器自动分配释放。第二,栈是一种先进后出的线性表。
  • 在程序中,栈用来保护重要的数据,主要来自于函数,并且于函数执行结束的时候被释放。同时栈也用来存放局部变量,函数返回地址。

栈实例

  • 通过代码观察堆栈的使用与释放,以及存储的内容

    // Stack.cpp : 定义控制台应用程序的入口点。
    // Windows XP SP3 + Visual Studio 2008 DeBug编译(MTd)
    
    #include "stdafx.h"
    
    void testDemo(char *Buf)
    {
    	char Demo[0x8];
    	memcpy(Demo,Buf,0x8);
    	return;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	char Buf[0x40];
    	memset(Buf,'\0',0x40);
    	testDemo(Buf);
    
    	return 0;
    }
    
  • 采用Debug编译是为了不要优化,防止代码原意被改变,同时更有利于观察堆栈,然后使用OD加载程序。

    1576134460678

  • 这是Main函数,当然,testDemo函数中也是这样开辟新的堆栈。

    1

    • 实际运行中,发现,开辟的栈空间相对实际存储的变量来说是一个很大的值,但是由于堆栈分配的低地址生长性,第一个申请的变量与返回地址之间只有8个字节。

    1576137329168

  • 将上面的例子简单修改,构造溢出。

    • 首先设置Release编译,并且修改堆栈保护的选项。

      1576140644479

    • 生成原来的例子,寻找Main函数,观察正常堆栈的构造。

      1576141235526

    • 构造程序、恶意数据,模拟溢出。

      2

堆溢出

  • 首先使用一个简单的程序来认识一下堆的结构。有这么几点需要注意:

    • 堆分配算法,与操作系统版本,编译器版本,编译选项,Build类型都有关系。
    • 调试器加载的堆管理与常态堆管理策略有差异
    • 无论申请的空间是多大的,分配的内存块都是8字节为一块。
  • 首先简单的开辟堆然后申请空间。

    // Heap.cpp : 定义控制台应用程序的入口点。
    // Windows XP SP3 + Visual Studio 2008 Release编译(MD)
    
    #include "stdafx.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HLOCAL h1,h2,h3,h4,h5,h6;
    	HANDLE hp;
    	hp = HeapCreate(0,0x1000,0x10000);
    	_tprintf(TEXT("Create Heap End!\r\n"));
    	__asm int 3;
    	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x3);
    	h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x5);
    	h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x6);
    	h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x8);
    	h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x13);
    	h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x18);
    
    	HeapFree(hp,0,h1); //free to freelist[2]  
    	HeapFree(hp,0,h3); //free to freelist[2]  
    	HeapFree(hp,0,h5); //free to freelist[4] 
    	HeapFree(hp,0,h4);
    	return 0;
    }
    
  • 接下来,我们在int 3的时候选择附加调试器,就可以看到常规堆块的分布方式。

    1576209264023

    默认实时调试器切换

    • OD中有设置可以直接切换实时调试器。

      1576224811482

    • x64 dbg也一样的操作

      1576224921462

    • 然后在报错的时候选择“否”,就可以使用实时调试器调试程序了

      3

  • 首先是没有分配堆空间的时候,只有一个默认堆首,以及最后的双向链表。

    1576209831439

  • 然后我们开始申请堆空间,可以看到申请的大小是堆首部的前四个字节指示,而且大小为我们申请的空间加上块首部大小,最后拓展到0x8这个堆空间单位大小的大小。

    1576224298087

  • 然后是Free释放堆空间,可以看到,修改的不是对应的堆中的数据,而是堆句柄中的Freelist数组,新的指针对指向刚刚释放的空间。

    1576225240840

  • 接下来试图构造利用。

    // Heap.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    
    HANDLE MyProcessHandle = GetCurrentProcess();
    TCHAR DemoFilePath[] = TEXT("./Heap.data");
    // 打印成功的语句,也就是被Attach的部分,也可以是通过覆盖函数返回地址来完成目标
    void AttachFunction()
    {
    	LPVOID Waiting_For_Changed_Data = VirtualAllocEx(MyProcessHandle,NULL,0x100,MEM_COMMIT,PAGE_READWRITE);
    	strcpy((char*)Waiting_For_Changed_Data,"Yeah!! You Win!!\r\n");
    	printf("%s",Waiting_For_Changed_Data);
    	ExitProcess(0);
    	return;
    }
    //
    char* __stdcall GetStrAddr(LPVOID OrgStr,char *SubStr)
    {
    	if (OrgStr == NULL || SubStr == NULL) return NULL;
    
    	char *ret = strstr((char*)OrgStr,SubStr);
    	if (ret != NULL) return ret;
    	char* temp = (char*)OrgStr;
    	do 
    	{ 
    		do { ++temp; } while (*temp != '\0');
    		do { ++temp; } while (*temp == '\0');
    		ret = strstr(temp,SubStr);
    	} while (!ret);
    	return ret;
    }
    // 主逻辑函数,同时也是存在堆溢出的函数
    void MainFunction(LPVOID UserID,LPVOID PassWD)
    {
    	// 创建堆
    	HANDLE Handle_Of_Heap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE,0,0);
    	// 申请两次堆空间
    	HLOCAL Writ_For_Overflowed = HeapAlloc(Handle_Of_Heap,HEAP_ZERO_MEMORY,0x10);
    	HLOCAL For_Placeholder_One = HeapAlloc(Handle_Of_Heap,HEAP_ZERO_MEMORY,0x10);
    	// 释放第二个堆空间,构造溢出条件
    	HeapFree(Handle_Of_Heap,0,For_Placeholder_One);
    	// strlen()
    	int argvlen = -1;
    	char *i = (char *)UserID;
    	do { ++argvlen; ++i; } while (*i != '/');
    	// 溢出覆盖下一个的堆空间的两个指针。
    	memcpy(Writ_For_Overflowed,UserID,argvlen);
    	// 申请堆空间,返回为第二个堆空间的地址,溢出覆盖的是下一个分配堆的地址
    	HLOCAL For_Placeholder_Twe = HeapAlloc(Handle_Of_Heap,HEAP_ZERO_MEMORY,0x10);
    	// 返回需要的返回地址,接下来返回时修改为函数AttachFunction()的地址
    	HLOCAL Write_Data = HeapAlloc(Handle_Of_Heap,NULL,0x10);
    	memcpy(Write_Data,PassWD,0x4);
    	return;
    }
    // 读取文件,分割文件,同时保证函数不被优化,接下来交给主要函数
    int _tmain(int argc, _TCHAR* argv[])
    {
    	_tprintf(TEXT("Please construct the data in \"Heap.data\" file!\r\n"));
    	_tprintf(TEXT("eg:\r\n"));
    	_tprintf(TEXT("    <name>UserID</name>\r\n"));
    	_tprintf(TEXT("    <pass>PassWD</pass>\r\n"));
    	// 确保函数不会被优化
    	if (!&AttachFunction || !&MainFunction) return 0;
    	// 读取文件并且处理文件,分割为两个指针。
    	HANDLE DemoFile = CreateFile(DemoFilePath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    	if (DemoFile == INVALID_HANDLE_VALUE) { printf("Open Error!"); return 0; }
    	LARGE_INTEGER FileSize;
    	if (!GetFileSizeEx(DemoFile, &FileSize)) { printf("Get File Size Error!"); return 0; }
    	LPVOID DataBuffer = VirtualAllocEx(MyProcessHandle,NULL,FileSize.QuadPart,MEM_COMMIT,PAGE_READWRITE);
    	DWORD NumberOfBytesToRead;
    	if (!ReadFile(DemoFile, DataBuffer, FileSize.QuadPart, &NumberOfBytesToRead, NULL)) { printf("Read File Error!"); return 0; }
    	CloseHandle(DemoFile);
    	char* UserID = GetStrAddr(DataBuffer,"<name>")+0x6;
    	char* PassWD = GetStrAddr(DataBuffer,"<pass>")+0x6;
    	if (UserID==NULL||PassWD==NULL) return 0;
    	MainFunction(UserID,PassWD);
    	printf("You Failed !!\r\n");
    	ExitProcess(0);
    	return 0;
    }
    
  • 演示
    4

整形溢出

存储溢出

  • 简单说就是各种存储单位的存储空间大小不一致,导致转换或者存储超大的数的时候出现溢出。
  • 比如说Short Int(16位)和Int(32位)当他们相互转换的时候就会存在安全问题。

1576582609034

运算溢出

  • 同样的问题出现在超长的大数以及反常理的输入。

1576582678666

符号问题

  • 无符号整数和有符号整数之间的存储存在一个bit位的区别,这不仅存在存储和运算上出现错误和漏洞,同时会出现问题——在数据拷贝时设置的数据大小。

Use-After-Free

  • 漏洞的成因是悬空指针的问题

直接看代码会很清楚

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

#include "stdafx.h"

class FakeSever
{
public:
	FakeSever();
	~FakeSever();
	void Print();
protected:
private:
	char *m_String_Hello;
};
FakeSever::FakeSever()
{
	DWORD Time;
	__asm
	{
		RDTSC;
		mov Time,EAX;
	}
	m_String_Hello = (char *)VirtualAlloc(NULL,0x100,MEM_COMMIT,PAGE_READWRITE);
	sprintf(m_String_Hello,"%s%X","HelloWorld,NEW SELF ID IS:",Time);
	_tprintf(TEXT("[ New :此对象的String为:%s]\r\n"),m_String_Hello);
	return;
}
FakeSever::~FakeSever()
{
	_tprintf(TEXT("[Free :此对象的String为:%s]\r\n"),m_String_Hello);
	return;
}
void FakeSever::Print()
{
	_tprintf(TEXT("%s\r\n"),this->m_String_Hello);
	return;
}
int _tmain(int argc, _TCHAR* argv[])
{
	int InputKey = 0;
	int countex = 0;
	int freeID = 0;
	FakeSever *Fake[3];
	_tprintf(TEXT("请输入以下指令方便实现程序:(最多同时实现三个对象)\r\n"));
	_tprintf(TEXT("    1:新建一个FakeSever对象,该对象具有Print方法,其私有属性根据当前时间生成。\r\n"));
	_tprintf(TEXT("    2:释放一个FakeSever对象。\r\n"));
	_tprintf(TEXT("    3:执行一个FakeSever对象的Print方法。\r\n"));
	_tprintf(TEXT("    0:结束程序并退出。\r\n"));
	while(TRUE)
	{
		_tprintf(TEXT("[ in > ] Scanf: "));
		scanf("%d",&InputKey);
		switch (InputKey)
		{
		case 0:
			ExitProcess(0);
			break;
		case 1:
			{
				_tprintf(TEXT("[out > ] 新建的对象为[对象ID:%d]...\r\n"),countex);
				Fake[++countex] = new FakeSever();
				break;
			}
		case 2:
			{
				_tprintf(TEXT("[MSG ! ] 请输入要释放的对象ID...\r\n"));
				_tprintf(TEXT("[ in > ] Input ID: "));
				scanf("%d",&freeID);
				free(Fake[freeID]);
				break;
			}
		case 3:
			{
				_tprintf(TEXT("[MSG ! ] 请输入要执行的对象ID...\r\n"));
				_tprintf(TEXT("[ in > ] Input ID: "));
				scanf("%d",&freeID);
				Fake[freeID]->Print();
				break;
			}
		default:
			_tprintf(TEXT("[ERROR!] 输入有误...\r\n"));
			break;
		}
	}
	return 0;
}

结果

1576585882395

posted @ 2020-06-28 16:49  yOmicron  阅读(211)  评论(0)    收藏  举报