notifyのblog


  • Home

  • About

  • Tags

  • Categories

  • Archives

code-protect

Posted on 2021-03-03

代码保护技术

我写xmind上了无法展示就先贴着

代码保护技术

desc

反调试

调试器状态检测

winAPI

· Bool CheckRemoteDebuggerPresent()

· Bool IsDebuggerPresent()

PEB的BeingDebugged标志位

bool PebIsDebuggedApproach()
{
char result = 0;
__asm
{
// 进程的PEB地址放在fs:[30h]
mov eax, fs:[30h]
// 查询BeingDebugged标志位
mov al, BYTE PTR [eax + 2]
mov result, al
}
return result != 0;
}

PEB.NtGlobal

bool PebNtGlobalFlagsApproach()
{
int result = 0;
__asm {
// 进程的PEB
mov eax, fs:[30h]
// 控制堆操作函数的工作方式的标志位
mov eax, [eax + 68h]
// 操作系统会加上这些标志位FLG_HEAP_ENABLE_TAIL_CHECK,
// FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
// 它们的并集就是x70
//
and eax, 0x70
mov result, eax
}
return result != 0;
}

PEB.ProcessHeap.ForceFlags

bool HeapFlagsApproach()
{
int result = 0;
__asm
{
// 进程的PEB
mov eax, fs:[30h]
// 进程默认的堆
mov eax, [eax + 18h]
// 检查ForceFlag标志位
mov eax, [eax + 10h]
mov result, eax
}

return result != 0;
}

PEB.ProcessHeap.Flags

// PEB.ProcessHeap
mov eax, dword ptr [ebx+18h]
cmp dword ptr [eax+0ch] == 2

nt内核函数

· NtQueryInformationProcess

#include <stdio.h>
#include <windows.h>

typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation = 0,
ProcessDebugPort = 7,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessBreakOnTermination = 29
} PROCESSINFOCLASS;

// 声明一个函数指针。
typedef __kernel_entry NTSTATUS(WINAPI* NtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);
//ProcessDebugPort 可以用来查询进程的调试端口。如果进程被调试,那么返回的端口值会是-1,否则就是其他的值。
bool NtQueryInformationProcessApproach()
{
DWORD debugPort = 0;
// 由于这个是未公开的函数,因此需要使用LoadLibrary和GetProceAddress的方法获取调用地址
HMODULE hModule = LoadLibrary(TEXT(“ntdll.dll”));
NtQueryInformationProcess pNtQueryInformationProcess = (NtQueryInformationProcess)GetProcAddress(hModule, “NtQueryInformationProcess”);
if (pNtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &debugPort, sizeof(ProcessDebugPort), NULL)) {
printf(“[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n”); return false;
}
return debugPort == -1;
}

• 子主题 1

desc

· NtSetInformationThread

// 调试时有断点就会崩溃
void WINAPI StopDebegger()
{
HMODULE hModule = LoadLibrary(TEXT(“ntdll.dll”));
NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, “NtSetInformationThread”);
NtSetInformationThread(OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId()), (THREADINFOCLASS)0x11, 0, 0);
}

• 子主题 1

desc

触发异常法

LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* pei)
{
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)pei->ContextRecord->Eax);
pei->ContextRecord->Eip += 4;// 修改寄存器eip
pei->ContextRecord->Eax = 0;// 返回值
return EXCEPTION_CONTINUE_EXECUTION;// 告诉操作系统,继续执行进程剩余的指令
}
bool UnhandledExceptionFilterApproach(){
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
__asm{
xor eax, eax;
div eax;// 触发一个除零异常
}
return true;
}

·

desc

断点反调试

· software bpt 0xcc
rep scas ;

时间反调试

…

进程附加抢占

ptrace保护法

调试器端口检测

如23946

父进程检测

/proc/self/status 查看当前进程状态。输出信息中,TracerPid 是个用于判断程序是否被调试的标志,正常运行的程序其值为 0,若处于调试状态,其值为调试器的进程 ID

软件混淆

代码混淆

变量/类名混淆

ollvm控制流平坦化

二进制混淆

无用垃圾指令填充

常量数据进行编码或隐藏

虚拟执行(最难)

资源混淆

软件防篡改

加壳

加密壳

· VMProtect

· Themida

· WinLicense

· ASProtect

· Armadillo

· EXECryptor

· Themida

压缩壳

· UPX

sudo apt-get install upx-ucl

· ASPack

· NSPack

完整性校验

app Signature

资源保护

运行环境检测

虚拟机检测

vmware

desc

Root检测

desc

desc

hook检测

Hook 框架检测

· de.robv.android.xposed.installer

· de.robv.android.xposed.XposedBridge.main
de.robv.android.xposed.XposedBridge.handleHookedMethod

Java 方法的 Hook 检测

· AndFix

so 动态库的 GOT Hook 检测

so 动态库的 Inline Hook 检测

软件指纹

指发布时在软件特定区域写入特定“指纹”内容,以识别用户身份,防止软件未经授权而被分发

ARM指令 常识

Posted on 2021-03-01 | In ARM

arm

ARM属于RISC架构

ARM指令集是(32 )位宽,Thumb指令集是(16 )位宽的。

复位后,ARM处理器处于(SVC)模式,(ARM) 状态

ARM处理器总共( 37个)个寄存器,System模式下使用(17个)个寄存器,SVC模式下使用(18个)个寄存器。

ARM处理器中优先级别最高的异常为(RESET),( FIQ、IRQ)异常可以用来相应中断

ATPCS规定中,推荐子函数参数最大为(4) 个

ATPCS规定中,栈是(满减)

在用ARM汇编编程是,其寄存器有多个别名,通常PC是指(R15),LR是指( R14 ),SP是指(R13)

CPSR寄存器中反映处理器状态的位是(T位)

下面属于ARM子程序调用指令的是(BL)

ARM7属于(冯.诺依曼)结构,ARM9属于(哈佛)结构。

ARM7是(3)级流水线,ARM9是(5)级流水线。

ARM中可以访问状态寄存器的指令是(MRS),能够访问内存的指令是(LDR,非MOV)

异步串口中数据位可以是(5,6,7,8)

I2C协议中有几根线(2)

I2C协议中设备地址模式有(7位地址模式\10位地址模式)

S3C2410采用的是(ARM920T)核心

在串行异步通讯中,发送端串口的TxD要和接收端串口的(RxD)相连接

1.简述ARM发生异常时,ARM核心会自动做哪些事情?从异常返回时,我们要做哪些事情?

1) 当异常产生时, ARM core:

拷贝CPSR到SPSR_
设置适当的CPSR位:
改变处理器状态进入ARM状态
改变处理器模式进入相应的异常模式
设置中断禁止位禁止相应中断(如果需要)
保存返回地址到LR_
设置PC为相应的异常向量

2) 返回时,异常处理需要:

从SPSR_恢复CPSR
从LR_恢复PC
Note:这些操作只能在ARM态执行.

2.用ARM汇编指令写出实现64位加法和64位减法的代码段,使用的寄存器请自行分配。

假定低32位数存放在r0和r1里面,高32位数存放在r2和r3里面。
加法:
ADDS r0, r0, r1 //加S是因为要让这个操作影响标志位
ADC r2, r2, r3 //ADC是带进位的加法,如果上一条指令产生进位则一起加进来
减法:
SUBS r0, r0, r1 //加S是因为要让这个操作影响标志位
SBC r2, r2, r3 // SBC是带进位的减法指令

3.ARM处理器的模式: 各个发生异常时ARM处理器所处的模式

  • User :非特权模式,大部分任务执行在这种模式}
  • FIQ : 当一个高优先级(fast)}中断产生时将会进入这种模式
  • IRQ : 当一个低优先级(normal)中断产生时将会进入这种模式}
  • Supervisor}:当复位或软中断指令执行时将会进入这种模式
  • Abort :当存取异常时将会进入这种模式}
  • Undef :}当执行未定义指令时会进入这种模式
  • System :使用和User模式相同寄存器集的特权模式}
  • SVC:?

ARM处理器的异常:

  • Reset
  • Data Abort
  • FIQ
  • IRQ
  • Prefetch Abort
  • SWI
  • Undefined instruction

4. FIQ的什么特点使得它处理的速度比IRQ快?

1)FIQ优先级比IRQ高,不会被中断
2)FIQ有自己的专属寄存器:r8~r12,不用对通用寄存器入栈保护,可以加快速度
3)FIQ位于异常向量表的末尾0x1c,故无需跳转,可以在这里直接放置异常处理函数

5.什么指令可以放在中断向量表?

跳转指令,给PC赋值的指令
B,LDR,MOV

6. ARM处理器 中断向量表位于存储器的什么位置?

默认:0x0
也可以配置成:0Xffff0000

7.下列ARM指令将做什么?

a) LDRH r0,[r1,#6]
b) LDR r0, =0x999
a:将r1寄存器的值加上6,然后把以这个值为地址的内存单元里的值取半字(低16位)赋给r0
b:将立即数0x999赋给r0,注意这是一个伪指令

8. SWP指令的优势是什么?用来实现什么功能?

功能:在寄存器和存储器之间,由一次存储器读和一次存储器写组成的原子操作。完成一个字节或字的交换。
可以用来实现信号量

9. S3C2410支持几种引导方式(或者说是内存映射方式)?简述Nand引导方式S3C2410硬件做的事情。

1)nor flash启动方式。
2)nand flash启动方式。
从Nand flash启动时,S3C2410首先会执行固化在片上ROM中的一段小程序,这段程序负责将nand flash前2K的代码搬移到片上RAM,然后将PC指针指向0x0地址(注意这个时候片上RAM被映射到0x0的起始地址)

10.简述static和volatile关键字的含义和作用。

c语言中static关键字有两个作用,一是文件作用域,二是函数作用域。
文件作用域关键字static的作用是,以static申明的全局变量、函数不得被其他文件所引用
static另外一个用途是函数内部静态变量,只会被初始化一次,而且变量存储在全局数据段中而不是函数栈中,所以其生命期会一直持续到程序退出
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份

ARM指令集

ARM处理器的指令集可以分为跳转指令、数据处理指令、程序状态寄存器(PSR)处理指令、加载/存储指令、协处理器指令和异常产生指令6大指令。

winafl

Posted on 2021-03-01 | In FUZZ

WINAFL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
afl-fuzz
afl-fuzz [afl options] -- [instrumentation options] -- target_cmd_line
其中[afl options]常用的参数包括(这些参数由afl-fuzz.exe处理):
-i    input directory with test cases
-o  output directory for fuzzer findings
-D  directory with DynamoRIO binaries (drrun, drconfig)
-t   timeout for each run
-f   location read by the fuzzed program (stdin)
-x   optional fuzzer dictionary (see README) 字典文件
[instrumentation options]常用的参数包括:
-coverage_module – fuzzing对象程序会调用到的模块,设置要记录的模块,支持多个模块的记录
-target_module – fuzzing对象模块,也就是-target_offset所在的模块
-target_offset – fuzzing的偏移地址,也就是会被instrumentation的偏移
-target_method       – 只有有符号表的情况下才能用的方法,根据符号名去设置偏移
-fuzz_iterations – 再fuzzing对象程序重新启动一次内运行目标函数的最大迭代数
-debug – debug模式
target_cmd_line参数就是你要fuzzing对象的启动程序。


Winafl是一个开源的优秀半自动化fuzz测试工具,相比较其他的公开fuzz测试工具还是有很大的优势的
daɪnəməʊ RIO
DynamoRIO应该算是winafl的核心部分,它主要实现的是指令动态插桩,其实就是之前我提到的指令扩展,对函数输入输出进行一定的检查。关于DynamoRIO的原理以及介绍在网上有很多描述,这里不做过多介绍了。
 
用DynamoRIO测试过程
这里我们要用到的是DynamoRIO的ddrun.exe工具,
afl-fuzz.exe通过-i参数指向的目录读取用户提供的测试文件,并通过各种变形存储到out/.cur_input文件,供被fuzzing的应用程序调用打开,查看afl_fuzz.c可以发现其对测试用例都做过哪些变形。

-i   测试用例存放目录, 测试样本的输入目录
-o  fuzzing过程和fuzz结果的输出目录
-D  DynamoRIO所处的目录
-t   每次的运行时间
-f   被fuzz的进程要读取的文件
-x  可选fuzzer目录

使用说明
首先找出要fuzz的函数基于模块的地址偏移
要保证程序可以正常的跑在DynamoRIO下面,可以通过WinAFL的独立调试模式来测试这一点。独立调试模式不会使用fuzz部分(使用-debug选项)
要想正常运行,必须要保证afl-fuzz.exe和winafl.dll在同一目录下
afl-fuzz [afl options] -- [instrumentation options] -- target_cmd_line

-D选项是必须启用的,用于指定DynamoRIO所处的目录
-t 选项也是必须启用的,由于不同的选项导致的执行效率不同。所以-t的时间应该灵活设置。
默认是支持多线程的程序记录的。如果是单线程程序可以使用-covtype edge选项
instrumentation options
-covtype                  设置记录方式,为多线程和单线程程序所使用。bb/edge
-coverage_module  设置要记录的模块,支持多个模块的记录
-target_module       fuzz目标函数所处的模块,必须要设置-target_method或-target_offset
-target_method       只有有符号表的情况下才能用的方法,根据符号名去搞
-target_offset          要fuzz函数的相对模块头的偏移
-fuzz_iterations       目标函数的最大迭代次数
-nargs                     被fuzz的函数有几个参数?
-debug                   不会连接fuzzer部分,只会输出一个日志文件。包含加载的模块、打开的文件和输出报告。
-logdir                     只在-debug下可用,输出的log文件的位置
coverage_module可以有多个,target_module只有一个

.\afl-fuzz.exe -i in -o out -t 200000000+ -D .\ -- -coverage_module gdiplus.dll -fuzz_iterations 5000 -target_module test_gdiplus.exe -target_method main -nargs 2 -- test_gdiplus.exe not_kitty.bmp @@
其中 “@@” 表示待 fuzzing 的测试用例文件在 in 目录下
drrun.exe -pidfile childpid_38a0fc9cc4272ad6.txt -no_follow_children -c winafl.dll -coverage_module gdiplus.dll -coverage_module WindowsCodecs.dll -fuzz_iterations 5000 -target_module test_gdiplus.exe -target_method main -nargs 2 -fuzzer_id 38a0fc9cc4272ad6 -- test_gdiplus.exe out\.cur_input


Reverse

Posted on 2021-03-01

df

源码审计工具调研

Posted on 2021-03-01 | In source code-review

开始

​ C/C++语言的语法拥有其它语言所没有的灵活性,这种灵活性带来了代码效 率的提升,但相应也使得代码编写具有很大的随意性,另外 C/C++编译器不进 行强制类型检查,也不做任何边界检查,这就增加了代码中存在隐患的可能性。 如果能够在代码提交测试之前发现这些潜在的错误,就能够极大地减轻测试人员 的压力,减少软件项目的除错成本,可是传统的 C/C++编译器对此已经无能为力,这个任务只能由专用的代码检查工具完成。

​

llvm & clang

  • LLVM 是个很大很大的项目群,几乎把从编译到调试的各个构建环节都重新实现了一遍,目的:一是尽可能地模块化现有代码以方便在此基础上进行二次开发、一是提供比传统构建工具链更好的用户体验。clang是LLVM的子项目,是一款非常优秀的C++ 编译器,前端 clang + 后端 LLVM(后简称 LLVM/clang)就是一款可替代 GCC 的优秀编译
  • GCC 配套的标准库涉及 libstdc++ 和 libsupc++ 两个子库,前者是接口层(即,上层的封装),后者是实现层(即,底层的具体实现),对应到clang,则libc++(接口层)和 libc++abi(实现层)

->IR->x86,arm,ppc,mips,amd64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1:
2: char *test( int m, int n, char *p )
3:{
4: int result;
5: char *temp;
6: long nm;
7: int i, k, kk;
8: char name[11] = "Joe Jakeson";
9:
10: nm = n * m;
11: temp = p == "" ? "null" : p;
12: for( i = 0; i < m; I++ ) {
14: k++;
15: kk = i;
16: }
17:
18: if( k== 1 )
result = nm;
19: else if( kk > 0 )
result = 1;
20: else if( kk < 0 )
result = -1;
21:
22: if (m == result)
return temp;
23: else
return name;
24:}
  • 第 8 行向 name 数组赋值时丢掉了结尾的 nul 字符
  • 第 10 行的乘法精度会失准,即使考虑到 long 比 int 的字长更长,由 于符号位的原因仍然会造成精度失准
  • 第 11 行的比较有问题
  • 第 14 行的变量 k 没有初始化
  • 第 15 行的 kk 可能没有被初始化
  • 第 22 行的 result 也有可能 没有被初始化
  • 第 23 行返回的是一个局部对象的地址。

TscanCode

Description:

  • TscanCode是鹅厂静态分析团队开发的一款开源免费的C/C++静态分析工具。
  • 扫描C/C++代码不需要进行编译, 操作简单。
  • 速度快,平均扫描速度10W行/分钟。误报较多
  • 项目不再维护
  • 结果以图形化或者xml显示

Download :

github

测试集

samples/cpp

Usage:

1
./tscancode --xml func.cpp 2>result.xml

scan-build *****

description

  • scan-build 是一个命令行工具,它是一款静态分析器,是clang-tools的一款工具。
  • 需要编译项目,通过重写cc和cxx环境变量来改变你的编译环境,scan-build将编译器gcc设置为analyze-cc。analyze-cc作为一个伪编译器,转发命令行参数给gcc和clang来执行静态分析。不会分析没有参与编译的源码
  • 精准度非常之高。特别是逻辑上的漏洞很多都可以探查出来。
  • 项目构建完毕后,以html web页面显示代码缺陷报告

当一个项目在构建中,源文件在编译时同时也被静态分析器有序的检查着。当构建完成时,结构将会作为一个web网页的形式呈现给使用者。

insall

1
2
3
4
$ sudo apt-get install clang-8 clang-tools-8
$ la /usr/bin/ | grep scan-build
lrwxrwxrwx 1 root root 42 Dec 10 2019 scan-build-8 -> ../share/clang/scan-build-8/bin/scan-build
lrwxrwxrwx 1 root root 45 Dec 10 2019 scan-build-py-8 -> ../share/clang/scan-build-py-8/bin/scan-build

Usage

-h 显示scan-build的所有参数
-o html 报告文件的存放目录。默认将报告文件保存在/tmp目录里。
-k 增加一个继续运行的参数到具体的命令中
-v 冗余输出结果。可以选择2个或3个”-v”增加冗余度。
-V 当命令完成后,在浏览器中查看运行结果。

automake项目

1
scan-build ./configure

Cmake

1
2
mkdir build && cd build 
scan-build ./cmake ..

Make

1
scan-build --use-analyzer `which clang` make -j8

结果请到/tmp/scan-build-xxxxxxxxxxxxxxx/failures 查看。它是 html 的。

比如capstone项目的编译展示

1
2
3
4
5
6
git clone https://github.com/aquynh/capstone.git && cd capstone && git checkout next 
mkdir build && cd build
scan-build-8 --use-analyzer `which clang-8` ../cmake.sh
# 顺带安装下,后面课程需要用到
cd bindings/python
sudo make install

image-20200914233000688

Cppcheck***

官方网站

Description

CppCheck是一个C/C++代码缺陷静态检查工具。不同于C/C++编译器及其它分析工具,CppCheck只检查编译器检查不出来的bug,不检查语法错误。所谓静态代码检查就是使用一个工具检查我们写的代码是否安全和健壮,是否有隐藏的问题。

  • 检出多,有效检出少
  • 无须编译配置项目

Build

  • 安装windows包管理器 https://chocolatey.org/
  • choco install cppcheck

Debian:

  • sudo apt-get install cppcheck

Usage

Visual stdio

image-20200915131408641

1
$ cppcheck . -j 3 --enable=all --xml 2>result.xml

multi threads

1
cppcheck -j 4 <path>

Visual studio

在整个解决方案上运行 cppcheck:

1
$ cppcheck --project=<proj>.sln

在单个项目文件上运行 cppcheck:

1
$ cppcheck --project=<proj>.vcxproj

其他参数

1
2
3
4
5
$ cppcheck --enable=warning,performance <path>
# warning,performance 启用警告和性能消息:
# all 启用所有消息
# style 可以启用警告、性能、可移植性和样式信息
# information 启用信息消息

不确定消息: 默认情况下,如果确定,Cppcheck 只显示错误消息。如果使用 --inconclusive,当分析不确定时,也会写错误消息。

1
cppcheck --inconclusive <path>

输出格式:template

1
2
3
cppcheck --template=gcc gui/test.cpp
# vs, gcc 编译器输出风格
# "{file},{line},{severity},{id},{message}" 自定义

PC-lint

官网

Description

  • PC-lint for C/C++是由Gimpel软件公司于1985年开发的代码静态分析工具
  • 它能有效地发现程序语法错误、潜在的错误隐患、不合理的编程习惯等。
  • PC-lint是资格最老,最强力的代码检查工具
  • 收费软件, 有30天体验
  • 需要完整的项目环境,如头文件等,并且配置起来有一点点麻烦。
  • PC-Lint 则偏重于代码的逻辑分析,它能够发现代码中潜在的错误,比如数组访 问越界、内存泄漏、使用未初始化变量等

Usage

PC-Lint能够检查出很多语法错误和语法上正确的逻辑错误,PC-Lint为大部分错误消息都分配了一个错误号,编号小于1000的错误号是分配给C语言的,编号大于1000的错误号则用来说明C++的错误消息。

  1. 在doc/manual.pdf中给出了错误号

image-20200915150904327

以C语言为例,其中的
编号 001-199 指的是一般编译器也会产生的语法错误;
编号 200-299 是PC-Lint程序内部的错误,这类错误不会出现在代码中的;
编号 300-399 指的是由于内存限制等导致的系统致命错误。
编号 400-999 中出现的提示信息,是根据隐藏代码问题的可能性进行分类的:
编号 400-699 指的是被检查代码中很可能存在问题而产生的告警信息;
编号 700-899 中出现的信息,产生错误的可能性相比告警信息来说级别要低,但仍然可能是因为代码问题导致的问题。
编号 900-999 是可选信息,他们不会被默认检查,除非你在选项中指定检查他们。

  1. PC-Lint/FelexLint 提供了和许多编译器类似的告警级别设置选项
  • -wLevel,它的告警级别分为以下几个级别,缺省告警级别为 3 级:
  • -w0 不产生信息(除了遇到致命的错误)
  • -w1 只生成错误信息 – 没有告警信息和其它提示信息
  • -w2 只有错误和告警信息
  • -w3 生成错误、告警和其它提示信息(这是默认设置)
  • -w4 生成所有信息
  1. PC-Lint/FelexLint 还提供了用于处理函数库的头文件的告警级别设置选项

    -wlib(Level),这个选项不会影响处理 C/C++源代码模块的告警级别。
    它有和 -wLevel 相同的告警级别,缺省告警级别为 3 级:

    • -wlib(0) 不生成任何库信息
    • -wlib(1) 只生成错误信息(当处理库的源代码时)
    • -wlib(2) 生成错误和告警信息
    • -wlib(3) 生成错误、告警和其它信息(这是默认设置)
    • -wlib(4) 产生所有信息
  2. PC-Lint 的检查分很多种类,有:

    • 强类型检查
    • 变量值跟踪
    • 语义信息
    • 赋值顺序检查
    • 弱定义检查
    • 格式检查
    • 缩进检查
    • const 变量检查
    • volatile 变量检查等等。

    对每一种检查类型,PC-Lint 都有很多详细的选项,用以控制 PC-Lint 的检查效果。

    PC-Lint 的选项有 300 多种,这些选项可以放在注释中(以注释 的形式插入代码中)

    例如:

    ​ /*lint option1 option2 ... optional commentary */选项可以有多行

    ​ //lint option1 option2 ... optional commentary 选项仅为一行(适 用于 C++)

    选项间要以空格分开,lint 命令一定要小写,并且紧跟在/或//后面,不能有空格。

    如果选项由类似于操作符和操作数的部分组成,例如-esym(534, printf, scanf, operator new),其中最后一个选项是 operator new,那么在 operator 和 new 中间只能有一个空格。

    PC-Lint 的选项还可以放在宏定义中,当宏被展 开时选项才生效。

    例如:

    ​ #define DIVZERO(x) /*lint -save -e54 */ ((x) /0) /*lint -restore */ 允许除数为 0 而不告警

需要把发到邮箱的许可证eval-license.lic文件放到主程序pclp64路径

运行config/pclp_config来自动生成内容。

1
2
3
4
5
6
7
$ pip install regex pyyaml
$ python3 pclp_config.py --compiler=gcc --compiler-bin=`which gcc` --config-output-lnt-file=co-gcc.lnt --config-output-header-file=co-gcc.h --generate-compiler-config
$ python3 pclp_config.py --compiler=clang --compiler-bin=`which clang-8` --config-output-lnt-file=co-clang.lnt --config-output-header-file=co-clang.h --generate-compiler-config

#生成
-rw-rw-r-- 1 u16s u16s 17K Sep 15 17:58 co-gcc.h
-rw-rw-r-- 1 u16s u16s 2.0K Sep 15 17:58 co-gcc.lnt

适当添加 --compiler-options="--std=c++11"

1
2
$ pclp co.lnt source-files
$ pclp co.lnt -w1 +e900 source-files

windows visual studio

管理员启动 config\pclpvscfg.exe

1
.\gencfg.bat

大型项目:

On Linux or macOS this can be done with:

export IMPOSTER_LOG=.commands

and on Windows:

set IMPOSTER_LOG=.commands

编译 imposter

clang-8 config/imposter.c -o imposter

1
2
3
4
5
6
7
8
9
10
11
12
$ make -e CC=/path/to/imposter 
# 生成了 <proj>.commands
$ python3 pclp_config.py\
--compiler=gcc\
--imposter-file=<proj>.commands\
--config-output-lnt-file=project.lnt\
--generate-project-config\
#生成了hello.lnt

$ pclp64_linux co-gcc.lnt project.lnt 1>output.txt


#capstone项目太大, 内存不足炸了

![image-20200915190426990](/Users/notify/Library/Application Support/typora-user-images/image-20200915190426990.png)结果如下,不是很直观

image-20200915190052759

Coverity

Coverity概述

COVERITY是被Gartner、Forrester、 IDC和 VDC权威机构评定为最领先的应用安全测试解决方案(超大规模分布式静态代码检查工具)。世界上几乎所有超大型软件企业都在使用(包括谷歌,亚马逊,洛克希德马丁,空中客车,华为等)。

Coverity公司是由一流的斯坦福大学的科学家于2002年成立的,产品核心技术是1998年至2002年在斯坦福大学计算机系统实验室开发的,用于解决一个计算机科学领域最困难的问题,在2003年发布了第一个能够帮助Linux、FreeBSD等开源项目检测大量关键缺陷的系统。Coverity公司推出的综合开发测试平台,基于新一代的不做代码规则检查、只专注检测代码中的Bug静态分析技术,可以更好地帮助开发人员在写代码的时候就能发现并修复安全缺陷,缩短产品上市时间和降低风险。Coverity是唯一位列IDC前10名软件质量工具供应商的静态分析工具厂商,被VDC评为静态源代码分析领域的领导者。

Coverity支持的语言和

C/C++ C# Java JavaScript PHP Python ASP.NET Objective-C JSP Node.js Ruby

支持检测的主要缺陷类型列表:

  • API usage errors
  • Best practice coding errors
  • Build system issues
  • Buffer overflows
  • Class hierarchy inconsistencies
  • Code maintainability issues
  • Concurrent data access violations
  • Control flow issues
  • Cross-site scripting (XSS)
  • Cross-site request forgery (CSRF)
  • Deadlocks
  • Error handling issues
  • Hard-coded credentials
  • Incorrect expression
  • Insecure data handling
  • Integer handling issues
  • Integer overflows
  • Memory – corruptions
  • Memory – illegal accesses
  • Null pointer dereferences
  • Path manipulation
  • Performance inefficiencies
  • Program hangs
  • Race conditions
  • Resource leaks
  • Rule violations
  • Security best practices violations
  • Security misconfigurations
  • SQL Injection
  • Uninitialized members

使用https://scan.coverity.com对仓库代码进行coverity静态检查,如果检测到coverity issues会通过邮件通知相关patcher作者或者maintainer进行修改,但是邮件中无法看到issues的详细细节。需要登陆https://scan.coverity.com网站去查看详细细节,另外我们需要有方法在向社区提交修复patch之前检查修改后的代码是否能通过coverity检测。本文简述查看issue及对修复代码进行检测的方法。

有小伙伴说,用公司内部的coverity检查不就行了?不好意思,coverity是商业软件,公司内部使用的版本的和社区使用的版本大概率不同,另外2边检查范围和告警屏蔽配置的不同,导致最终检查出来的结果很可能有差异。

download

Usage

注册

image-20200910171309584

关联Github账户

在My Account -> Github中 关联自己的Github账户,

image-20200910171600312

去github检查相关项目访问权限image-20200910172815718

image-20200910171901007

添加项目

来到DashBoard->Add Projects

image-20200910171942696

开始创建目标分析项目(误将闭源源码泄漏与我无关)

image-20200910172341892

填好项目摘要信息

image-20200910174644153

注意选择缺陷报告访问权限,分别为

1: 公开项目摘要信息和缺陷(只读)

2: 只公开项目摘要信息

3: 需要维护人员授权才可访问项目摘要和缺陷

构建项目

image-20200910173408953

先使用coverity二进制工具构建好项目并打包再上传即可开始分析

最下侧下载对应系统平台的二进制工具包

image-20200910195410865

  1. 下载
1
$ wget https://scan.coverity.com/download/linux64 --post-data "token=<token>&project=<ProjName>" -O coverity_tool.tgz
  1. 解压
1
$ tar -zxvf cov-analysis-linux64-2019.03.tar.gz
  1. 初始化编译器

查看所有编译器配置

1
cov-configure --list-configured-compilers json

gcc

1
cov-configure --comptype gcc --compiler `which gcc`

clang

1
2
3
$ rm -rf CMakeCache.txt CMakeFiles
$ cov-configure --comptype clangcxx --compiler `which clang++`
$ export CC=`which clang` && export CXX=`which clang++`
  1. 开始编译项目

cd到项目

采用cmake的项目构建框架

1
2
3
4
5
$ export CC=`which clang++` 可选
$ export CXX=`which clang++` 可选
$ cmake ..
# 对代码进行coverity清除和重编:
$ cov-build --dir cov-int bash -c 'make clean && make -j 8'

采用automake的项目构建框架

1
2
3
4
$ export CXXFLAGS="-O2 -fno-omit-frame-pointer -g"
$ CXX="clang++ $CXXFLAGS" CC="clang $CXXFLAGS" CCLD="clang++ $CXXFLAGS" ./configure
# 对代码进行coverity清除和重编:
$ cov-build --dir cov-int bash -c 'make clean && make -j 8'

或者

1
2
3
./configure
# 对代码进行coverity清除和重编:
$ CXX=/opt/local/bin/clang++ COVERITY_UNSUPPORTED=1 CXXFLAGS="-DNDEBUG -g3 -O2 -std=c++11" cov-build --dir cov-int bash -c 'make clean && make -j 8'

企业完整版才能有的如下功能

获取权限:

1
cov-analyze --dir coverity --all --wait-for-license
1
cov-analyze --dir coverity @@rule.txt

生成检测报告:

1
cov-format-errors --dir coverity --html-output coverity_html

检测报告web前端展示

1
cov-commit-defects --dir cov --host 127.0.0.1 --user x x x x x x x x --password xxxxx projname --stream STRAM-NAME

打包

1
tar czvf coverity.tgz cov-int

开始上传

1
2
3
4
5
6
curl --form token=<token> \
--form email=<email> \
--form file=@`pwd`/coverity.tgz \
--form version="<Version>" \
--form description="<Description>" \
https://scan.coverity.com/builds\?project\=<ProjName>

排除/添加扫描组件

image-20200911150915054

创建模型文件

https://scan.coverity.com/tune

image-20200911113248265

查看缺陷

image-20200911165935460

image-20200911171202562

1、空指针引用(Null pointer dereferences)
描述:程序调用值为null的指针的任何方法,会引发空指针异
可能的后果:程序Crash,exit, restart,执行未授权代码或命令
Checker:FORWARD_NULL

img

Checker: NULL_RETURNS

img

2、资源泄漏(Resource leaks)
描述:程序未释放资源,或程序错误地释放了资源
可能的后果:Dos攻击,敏感数据泄漏,资源消耗
Checker: RESOURCE_LEAK

img

3、内存破坏(Memory - corruptions)
描述:

  • 读写预期边界以外的内存缓冲区
  • 使用未初始化的变量
  • 函数/功能调用过程中使用了错误的参数取值
  • 重复使用释放后的内存

可能的后果:程序Crash,exit, restart,执行未授权代码或命令

Checker: OVERRUN

img

4、内存非法访问(Memory - illegal accesses)
描述:

  • 使用未初始化的变量

  • 使用释放后的资源(CPU、内存、Socket、文件等)

  • 函数返回堆栈变量的地址

可能的后果:程序Crash,exit, restart,资源消耗等

Checker: OVERRUN

img

Checker: USE_AFTER_FREE

img

Checker: RETURN_LOCAL

img

5、错误的表达式(Incorrect expression)
描述:使用错误的变量,不正确的类型转换
可能的后果:不符合预期的输出值,程序逻辑错误,运行时错误

Checker: COPY_PASTE_ERROR

img

6、未初始化变量(Uninitialized variables)
描述:变量使用前未初始化
可能的后果:程序逻辑不正确,产生错误的数据,程序Crash

Checker: UNINIT

img

Fortify

HP Fortify Source Code Analysis-Premium

Fortify更聚焦于产品代码的安全方面,被hp收购;

http://www8.hp.com/us/en/software-solutions/static-code-analysis-sast/

审计工具对比

检出数量: pclint >> cppcheck > TSC > coverity > scan-build

准确率: scan-build > coverity ≈ TSC >> cppcheck > pclint

综合评分:coverity > scan-build > TSC > cppcheck > pclint

Hello World

Posted on 2021-03-01

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
2
3
4
npm install hexo-cli -g
npm install hexo-asset-image
npm i hexo-permalink-pinyin --save
$ hexo new "My New Post"

More info: Writing

notifyのblog/node_modules/hexo-asset-image/index.js

themes/next/layout/_partials/pagination.swig add escape: false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ cat package.json 
{
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server"
},
"hexo": {
"version": "5.4.0"
},
"dependencies": {
"hexo": "^5.0.0",
"hexo-asset-image": "git+https://github.com/CodeFalling/hexo-asset-image.git",
"hexo-deployer-git": "^3.0.0",
"hexo-generator-archive": "^1.0.0",
"hexo-generator-category": "^1.0.0",
"hexo-generator-index": "^2.0.0",
"hexo-generator-tag": "^1.0.0",
"hexo-permalink-pinyin": "^1.1.0",
"hexo-renderer-ejs": "^1.0.0",
"hexo-renderer-marked": "^4.0.0",
"hexo-renderer-stylus": "^2.0.0",
"hexo-renderer-swig": "^1.1.0",
"hexo-server": "^2.0.0",
"hexo-theme-landscape": "^0.0.3"
}
}

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

libfuzz

Posted on 2020-04-11 | In FUZZ

libfuzzer

llvm、clang-3.5 是不行的,fuzz编译选项无法使用
多参考下 google libfuzzer 学会写测试代码,其中还有大量的测试字典 fuzzers/dict

libfuzzer build:


libfuzzer

  • 灵活:通过实现接口的方式使用,可以对任意函数进行fuzzing

  • 高效:在同一进程中进行fuzzing,无需大量fork()进程

  • 便捷:提供了API接口,便于定制化和集成

    但是没有dirty mode,需要编译时插桩

1
2
3
4
apt-get install -y make autoconf automake libtool pkg-config zlib1g-dev
git clone https://github.com/Dor1s/libfuzzer-workshop.git ~/libfuzzer
cd ~/libfuzzer/libFuzzer/ && ./Fuzzer/build.sh
当前目录生成一个 libFuzzer.a

usage


libFuzzer.a需要被静态链接到测试程序,并且会调用入口函数

fuzzer会跟踪哪些代码区域已经测试过,然后在输入数据的语料库上进行变异,来使代码覆盖率最大化。代码覆盖率的信息由 LLVM 的SanitizerCoverage 插桩提供。

1
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
编译一个cpp
#include <stdint.h>
#include <stddef.h>
#pragma comment(lib,"libFuzzer.a")

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
bool result = false;
if (size >= 3) {
result = data[0] == 'g' && data[1] == 'o' && data[2] == 'o' && data[3] == 'd';
}
return result;
}
// 代码覆盖率测试工具 -fprofile-arcs -ftest-coverage 用 afl-cov 才需要加,可不加
// -fsanitize-coverage=trace-pc-guard 提供代码覆盖率信息 生成 .sancov 文件才需要加
clang++ -g -std=c++11 -fsanitize=fuzzer,address -fprofile-arcs -ftest-coverage -fsanitize-coverage=trace-pc-guard demo.cxx ~/libfuzzer/libFuzzer/libFuzzer.a -o demo
code

效率感觉低了很多黄色框是检测是否错误读写,0x7fff8000是一个偏移,而不是数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//block_num是所有的block数量
uint64_t llvm_gcov_ctr[block_num];
//guard_block_num是特殊路径的block数量,也等于所有if数量+1,比如上面的 result = data[0] == 'g' && data[1] == 'o' && data[2] == 'o' && data[3] == 'd'; 有4个if, 那么就有6个
char __stop___sancov_guards[guard_block_num];
char __start___sancov_cntrs[guard_block_num];

//伪代码
int __cdecl LLVMFuzzerTestOneInput(const uint8_t *data, size_t size){
//到达block_n的条件分支就将cmp操作数记录下来
_sanitizer_cov_trace_const_cmp4(opa, opb) // -fsanitize=fuzzer
if(block_n_guard_true){
++_llvm_gcov_ctr[block_n];//-fprofile-arcs
}else{
#defined(FLAG(-fsanitize-coverage=trace-pc-guard))
_sanitizer_cov_trace_pc_guard(_start___sancov_guards[block_n])
++__stop___sancov_guards[block_n];
#elif defined(FLAG(-fsanitize=fuzzer))
++__start___sancov_cntrs[block_n];
#endif
++_llvm_gcov_ctr[block_n]; //-fprofile-arcs
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
ASAN:
//data是需要对齐的,8字节对齐. 下面的右移3,所以就是对齐8,如果右移6就是对齐64
//当传入 size = 4 时,那么
//0x7FFF8000+(data>>3)有如下内存值
// (Shadow bytes mask) 04 FA FA FA FA FA FA FA FA FA
//04就是只能访问4字节,必定==size,
检测原理:
//读取1字节,当访问data[4]时,
//编译器加代码判断
register size_t data_ptr = (size_t)(data + 4);
if(byte(0x7FFF8000+(data_ptr>>3)) && (data_ptr&7) >= byte(0x7FFF8000+(data_ptr>>3))){
_asan_report_load1(data_ptr);//error buffer-overflow
}

//读取2字节,当访问((uint16_t*)data)[2]时,那么就是如下代码:
register size_t data_ptr = (size_t)(data + 4);
if(byte(0x7FFF8000+(data_ptr>>3)) && (data_ptr&7) + 1 >= byte(0x7FFF8000+(data_ptr>>3))){
_asan_report_load2(data_ptr);//error buffer-overflow
}

//这就是检测原理了-fsanitize=address
//所以crash不一定是真的crash,可能是越界读取,这并不一定造成漏洞

libfuzzer根据_sanitizer_cov_trace_const_xxx函数和 __start___sancov_cntrs数组就可以很好的 构造出适合的测试数据来访问未探索的block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (size >= 3) {
//当size=3,访问了data[3],就直接crash
result = data[0] == 'g' && data[1] == 'o' && data[2] == 'o' && data[3] == 'd';
}

$ ./demo out_directory
//看下结果

==20205==ABORTING
MS: 1 EraseBytes-; base unit: 87967cad164ed1722d0cbd4778fbb298016acc07
0x67,0x6f,0x6f,
goo
artifact_prefix='./'; Test unit written to ./crash-3f95edc0399d06d4b84e7811dd79272c69c8ed3a
Base64: Z29v

那么简单的密码他能解决吗?便随手写了2个算法整个活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#pragma comment(lib,"libFuzzer.a")
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

#if 0
char data[] = { 10,13,6,28,12,4,21,57,49,12,61,49,1,27,9,125 };
//算法1
int checkinput(const char* a, int c) {
int i = 0;
if (!c) return 0;
uint8_t* enc = (uint8_t*)malloc(c);
memcpy(enc, a, c);
for (i = 0; i < c - 1; i++) {
enc[i] = enc[i] ^ enc[i + 1];
if (enc[i] != data[i]) {
free(enc);
return 0;
}
}
free(enc);
return c == sizeof(data);
}

#else
//算法2
int checkinput(const char* a, int c) {
int w, i, j, k;
char b[60] = { 20 ,20 ,20 ,05 ,20 ,20 ,20 ,20 ,00, 20, 05, 20, 05 ,20, 05 ,05 ,
20 ,00 ,20 ,05 ,05 ,20 ,20 ,05 ,20 ,20 ,00 ,20 ,05 ,20 ,20 ,20 ,
20 ,05 ,20 ,00 ,20 ,05 ,20 ,05 ,20 ,05 ,05 ,05 ,00 ,20 ,20 ,20 ,
05 ,20 ,20 ,20 ,20 };
int v18 = 0;
int v14 = 0;
for (i = 0; i <= c - 1; i++) {
if (i != (c - 1)) {
if (a[i] == '1')
v18--;
else if (a[i] == '2')
v18++;
else if (a[i] == '3')
v14--;
else if (a[i] == '4')
v14++;
else
return 0;
if (((v14 * 9 + v18) < 60) && ((v14 * 9 + v18) > -20))
if (v18 >= 0 && v14 >= 0 && b[v14 * 9 + v18] != 20)
return 0;
else
return 0;
}
}
return v18 == 7 && v14 == 5;
}

#endif

size_t ccount = 0;
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
ccount++;
if(checkinput(data, size)){
printf("Good boy. count:%zx\n", ccount);
int t=data[-1];// 产生leak错误 作为crash
return 0;
}
return 1;
}
1
2
3
mkdir crackme_out # fuzzer 程序可以有多个目录作为参数,此时 fuzzer 会递归遍历所有目录,把目录中的文件读入最为样本数据传给测试函数,同时会把那些可以产生新的的代码路径的样本保存到第一个目录里面。
./crackme -use_cmp=1 -dump_coverage=1 -print_final_stats=1 -max_len=15 ./crackme_out #不加-max_len费时几十倍

out

其他项目的编译方法

1
2
3
4
5
6
7
8
9
10
11
12
#使用-fsanitize-coverage = trace-cmp,编译器将在比较指令和switch语句周围插入额外的工具。
#使用-fsanitize-coverage = trace-div,编译器将检测整数除法指令(以捕获除法的正确参数)
#并使用 -fsanitize-coverage = trace-gep – LLVM GEP指令 (以捕获数组索引)。

#-fsanitize-coverage=[func,bb,edge] bb:basic block
#With -fsanitize-coverage=trace-bb the compiler will insert __sanitizer_cov_trace_basic_block(s32 *id) before every function, basic block, or edge (depending on the value of -fsanitize-coverage=[func,bb,edge]).

export FUZZ_CXXFLAGS="-O2 -fno-omit-frame-pointer -g -fsanitize=address \
-fsanitize-coverage=edge,indirect-calls,trace-cmp,trace-div,trace-gep,trace-pc-guard"

CXX="clang++ $FUZZ_CXXFLAGS" CC="clang $FUZZ_CXXFLAGS" \
CCLD="clang++ $FUZZ_CXXFLAGS" ./configure

coverage report

覆盖率就是整个fuzzer一趟测试触及的 basic-block 总个数。

文件./crackme.26107.sancov

1
2
# 转换成 .symcov
sancov-8 -symbolize ./crackme crackme.26107.sancov > crackme.26107.symcov

然后使用 coverage-report-server 解析这个文件。这里也有libfuzzer/lessons/08/coverage-report-server.py

1
2
3
4
$ curl http://llvm.org/svn/llvm-project/llvm/trunk/tools/sancov/coverage-report-server.py  -o ./coverage-report-server.py
$ python3 ~/libfuzzer/lessons/08/coverage-report-server.py --symcov crackme.26107.symcov --srcpath ./
Loading coverage...
Serving at 127.0.0.1:8001
cov

算法1:事实证明它还是不能解密如算法1的简单算法,更不说解循环xor :

1
2
enc[i+1] = enc[i] ^ enc[i + 1];
enc[0] = enc[0] ^ enc[c-1];

原因是,它依旧不能探查前后数据的关系,几乎不可能,仅仅对一些简单条件的路径能较好的探索,一般来说足够了

1
2
3
4
5
6
7
./demo -help=1 #可以看到很多fuzz选项
-use_cmp :根据trace guard的条件追踪来引导突变
-dump_coverage :可视化覆盖率
-max_len :测试数据最大长度
-max_total_time : 设置最长的运行时间, 单位是 秒, 这里是 300s , 也就是 5 分钟
-print_final_stats:执行完 fuzz 后 打印统计信息
#....待补充

移步【参考2】实战两个简单的CVE

Dictionary


就是输入的关键字合集,比如 png图片 就有 png 图片头。strcmp(phead,”xxxx”) 会直接将整个xxxx关键字替换进去,提高速度。就好比选择联想输入的候选词一样。

dictionary 文件-> google git or google afl

Dictionary 就是实现了这种思路。 libfuzzer 和 afl 使用的 dictionary 文件的语法是一样的, 所以可以直接拿 afl 里面的 dictionary 文件来给 libfuzzer 使用。

libfuzzer 官网

dict文件有用的只是由 "" 包裹的字串,libfuzzer 会用它们进行组合来生成样本。

1
./program -dict=./xxx.dict -max_total_time=300 -print_final_stats=1 input_dir

google AFL :

1
-x dir        - optional fuzzer dictionary (see README)

merge


可以使用 libfuzzer 把样本集进行精简。

1
2
mkdir output_min_dir
./program -merge=1 output_min_dir input_dir

output_min_dir: 精简后的样本集存放的位置
input_dir: 原始样本集存放的位置

参考&推荐:

  1. 基于Unicorn和LibFuzzer的模拟执行fuzzing
  2. fuzz实战之libfuzzer
  3. Dictionary
  4. libfuzzer-workshop
  5. fuzz总结
  6. [p1umer-2019/02/20/libfuzzer & LLVM 初探](https://p1umer.github.io/2019/02/20/libfuzzer & LLVM 初探/)

fuzz ImageMagick

Posted on 2020-04-10

前言

代码审计存在一定的局限性,一般都是静态的检测,对于复杂的算法来说,逻辑漏洞更加难以发觉,所以需要动态的代码执行技术来进行深层次的漏洞触发

fuzz 的种类

  • Generation Based :通过对目标协议或文件格式建模的方法,从零开始产生测试用例,没有先前的状态
  • Mutation Based :基于一些规则,从已有的数据样本或存在的状态变异而来
  • Evolutionary :包含了上述两种,同时会根据代码覆盖率的回馈进行变异。

AFL

模糊测试(Fuzzing)技术作为漏洞挖掘最有效的手段之一。对整体程序进行Fuzzing

AFL

1,Build:

1
2
3
4
5
6
git clone https://github.com/google/AFL.git ~/afl-src
cd ~/afl-src/qemu_mode
./build_qemu_support.sh
cd .. && make install
#开启崩溃转储,而不是交给崩溃处理程序
echo core >/proc/sys/kernel/core_pattern

2,Build test target

有两种

​ 一种是开源项目,那么直接在源码里面插桩,使用afl-clang和-clang++(其实是将编译器编译出来的汇编文件进行插桩,使用了~/afl-src/afl-as,将程序的每个block都加上回调,看下一篇AFL源码解读)

-fsanitize=address 就是开启AddressSanitizer (ASAN)内存检测工具 【参考2】
-g 产生符号,方便调试

1
2
3
4
clang -g -O1 -fsanitize=fuzzer                         mytarget.c # Builds the fuzz target w/o sanitizers
clang -g -O1 -fsanitize=fuzzer,address mytarget.c # Builds the fuzz target with ASAN
clang -g -O1 -fsanitize=fuzzer,signed-integer-overflow mytarget.c # Builds the fuzz target with a part of UBSAN
clang -g -O1 -fsanitize=fuzzer,memory mytarget.c # Builds the fuzz target with MSAN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export LLVM_CONFIG=`which llvm-config-3.5`

# 考虑加上 --disable-shared,静态链接上去,方便模糊测试库,不然afl是不会去分析的
# -fprofile-arcs -ftest-coverage 第9点要用,不然又要编译一次
#-fsanitize-coverage=func for function-level coverage (very fast).
#-fsanitize-coverage=bb for basic-block-level coverage (may add up to 30% extra slowdown).
#-fsanitize-coverage=edge for edge-level coverage (up to 40% slowdown).

./configure --disable-shared CC="afl-clang" CXX="afl-clang++" CFLAGS="-g -fsanitize=fuzzer,address -fprofile-arcs -ftest-coverage" LFLAGS="-static"
# 快速编译
./configure --disable-shared CC="afl-clang-fast" CXX="afl-clang-fast++" CFLAGS="-g -fsanitize=fuzzer,address -fprofile-arcs -ftest-coverage" LFLAGS="-static"

AFL_USE_ASAN=1 make

# 可以切换llvm版本
update-alternatives


#ASAN_OPTIONS, LSAN_OPTIONS, MSAN_OPTIONS or UBSAN_OPTIONS
#可以生成sancov覆盖率文件
ASAN_OPTIONS=coverage=1 ./magick

​ 另一种就是闭源程序,必须要使用QEMU 运行时动态插桩,但是效率低了。方法就是afl-fuzz命令后加上-q

1
2
#不在源程序插桩的编译,且想效率高的方法,使用clang, 比gcc好多了
CC=clang-3.5 CXX=clang++-3.5 LLVM_CONFIG=llvm-config-3.5 make

编译好的程序在 ImageMagick/utilities/.libs/magick
看下依赖情况

1
2
3
4
5
$ ldd ./magick
linux-vdso.so.1 => (0x00007fffaef6e000)
libMagickCore-7.Q16HDRI.so.7 => not found
libMagickWand-7.Q16HDRI.so.7 => not found
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f61ceded000)

把库lnk过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#当然了,上面我们用的--disable-shared, 所以不需要了
~/awork/ImageMagick/utilities/ [master*] ldd ./magick
linux-vdso.so.1 => (0x00007fffcc1cc000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f759aa3a000)
libgomp.so.1 => /usr/lib/x86_64-linux-gnu/libgomp.so.1 (0x00007f759a818000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f759a50f000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f759a2f2000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f759a0ea000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7599ee6000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7599cd0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7599906000)
/lib64/ld-linux-x86-64.so.2 (0x00007f759ac54000)

#否则
#设置库查找目录,不然ln过来还是找不到
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
ln -s ~/awork/ImageMagick/MagickCore/.libs/libMagickCore-7.Q16HDRI.so.7.0.0 libMagickCore-7.Q16HDRI.so.7
ln -s ~/awork/ImageMagick/MagickWand/.libs/libMagickWand-7.Q16HDRI.so.7.0.0 libMagickWand-7.Q16HDRI.so.7
$ ldd ./magick
linux-vdso.so.1 => (0x00007ffd2ad5d000)
libMagickCore-7.Q16HDRI.so.7 => ./libMagickCore-7.Q16HDRI.so.7 (0x00007f72dfe5d000)
libMagickWand-7.Q16HDRI.so.7 => ./libMagickWand-7.Q16HDRI.so.7 (0x00007f72df788000)

3,开源语料库(影响很大)

  • afl generated image test sets
  • fuzzer-test-suite
  • libav samples
  • ffmpeg samples
  • fuzzdata 【推荐】
  • moonshine

AFL给出的建议是最好小于1 KB,但其实可以根据自己测试的程序权衡,这在AFL文档的perf_tips.txt中有具体说明。

4,Extract

测试文件太多建议先筛选,不然太慢了

样本多样性 AFL-CMIN 移除执行相同代码的输入文件

  人工增加样本多样性的方法中,最简单且明显的就是搜集下载样本,放进输入文件夹。这个过程是对样本进行丰富的过程,它非常重要,但这个过程也常常引入样本的冗余,降低fuzz的效率。为了解决这个问题,需要从大量的样本中筛掉无用的样本。

cmin操作的是文件集合,输出的也是文件集合。

可能几千个文件和f一个文件来fuzz都是一样的结果,如果不进行cmin,非常低效。

cmin也是成功使用afl-fuzz中必不可少的一步。

1
2
3
4
5
6
7
#把所有图放一个文件夹samples里面
afl-cmin -i ~/fuzzdata/samples/ -o output_cmin -- ./magick convert @@ /dev/null

找了10分钟错误,看了afl-cmin代码好像是分配空间不足导致,不要怕用 -m none
Error: no instrumentation output detected (perhaps crash or timeout).

[+] Narrowed down to 65 files, saved in 'output_cmin'.

样本复杂度 AFL-TMIN 减小单个输入文件的大小

  基于字长+步长的形式,逐字节删除,然后通过插装反馈得出样本改变是否导致了程序运行路径发生了变化。若没有发生变化,可以认为删去的字节是冗余的,只用于一个指定的文件。为了使每一个test case达到表示与原始测试用例相同的代码路径所需的最小值,afl-tmin遍历test case的实际字节,逐步删除很小的数据块,直到删除任意字节都会影响到代码路径表示。

tmin操作的是单个文件,输出单个文件;

对于有效地fuzzing来说,这都是很重要的步骤,也是需要理解的重要概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# instrumented mode(默认)
afl-tmin -i input_file -o output_file -- /path/to/program [params] @@
# crash mode -x 直接把非返回0的导致程序非正常退出的都作为crash。
afl-tmin -x -i input_file -o output_file -- /path/to/program [params] @@
afl-tmin -x -i output_cmin -o output_tmain -- ./magick convert @@ xx.png


# afl-tmin 接受单个文件输入,如果语料库中文件数量特别多
mkdir -p tmin_output_cmin
#速度太慢
for i in output_cmin/*; do afl-tmin -i $i -o ./tmin_$i -- ./magick convert @@ xx2.png; done;
#建议使用下面的大佬的并发bash脚本
screen ~/afl-src/afl-ptmin 8 ./queue_cmin ./output_tmin/ "./magick convert @@ xx3.png"


#0字节文件考虑:加 -m none
[!] WARNING: Down to zero bytes - check the command line and mem limit!

5,Usage:

fuzz

1
2
3
4
5
6
7
8
#下载fuzz样本集
git clone https://github.com/MozillaSecurity/fuzzdata.git ~/fuzz/fuzzdata
#@@就是拿来代替-i目录的文件路径,相当于以命令行参数方式输入magick。
#-t 防止大型程序执行过慢,被放到 total hangs【无响应】
#-m 内存大小
#/dev/null 不要输出文件
afl-fuzz -t 300000 -m none -i ~/fuzz/fuzzdata/fuzzdata/samples/png -o output -- ./magick convert @@ /dev/null
#没插桩就加上-q, 使用qemu

白盒

执行一个单例, 测试程序的插桩

1
2
3
4
5
6
7
8
9
afl-showmap -m none -o ./output.tuples -- ./magick convert ./jj.png cjj,jpg

*] Executing './magick'...

-- Program output begins --
-- Program output ends --
[+] Captured 3848 tuples in './output.tuples'.

执行程序打印捕获的元组(tuples),衡量衡量程序覆盖情况(分支信息)

黑盒

就是使用QEMU模式,加上-Q

6,加速 SCREEN

还可以这样. 转自【参考4】

image-20200413024247769

多进程

-M 参数指定一个主Fuzzer(Master Fuzzer)
-S 参数指定多个从Fuzzer(Slave Fuzzer)

Master Fuzzer进行确定性测试(deterministic )即对输入文件进行一些特殊而非随机的的变异;
Slave Fuzzer 进行完全随机的变异。

我试了下,3000ms已经足够了

1
2
3
4
5
6
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -M master -- ./magick convert @@ xx3.png
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -S slave1 -- ./magick convert @@ xx3.png
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -S slave2 -- ./magick convert @@ xx3.png
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -S slave3 -- ./magick convert @@ xx3.png
...
分别ctrl+a+d 挂起状态,放入后台
1
2
3
4
5
6
7
8
9
10
11
#查看所有会话
screen -ls
#进入会话
screen -r session_name
#进入会话
screen -x session_name
#kill
screen -X -S 28006 quit
tmux # starts a new tmux session


1
2
3
4
5
6
在Session下,使用ctrl+a(C-a) 
ctrl-b c # new tab
ctrl-b 0 # switch to tab 0
ctrl-b d # detach
tmux a # re-attach to your previous session
ctrl-b ? # for help

这并不是一个很理想的解决方案,

  因为这样的条件下,运行前期master fuzzer的deterministic进程太慢,而slave随机产生新的样本后,master进程的deterministic变异进程总是来不及处理;而各slave进程重复概率大,需要的同步开销过大,导致afl-fuzz的处理速度并不是线性增长。

  将会在sync_dir文件夹建立master、slave1、slave2三个文件夹,三个 fuzzer各使用一个CPU。在fuzz过程中,各 fuzzer在空闲时可以读取其他文件夹中的新文件,然后对自己的queue文件进行同步和更新。

afl-whatsup 工具可以查看每个fuzzer的运行状态和总体运行概况,加上-s选项只显示概况,其中的数据都是所有fuzzer的总和。

1
afl-whatsup sync_dir

afl-gotcpu工具可以查看每个核心使用状态。

AFL

【参考1】cp“ 可以看到这里的-o指定的是一个同步目录,并行测试中所有的Fuzzer将相互协作,在找到新的代码路径时,相互传递新的测试用例,如下图中以Fuzzer0的角度来看,它查看其它fuzzer的语料库,并通过比较id来同步感兴趣的测试用例。

多系统

没有多系统先8学了

官方详解

大佬方案请看【参考1】最后

7,!!! When to stop and prune !!!

image-20200412161130767

  cycles done 随着周期数不断增大,其颜色也会由洋红色,逐步变为黄色、蓝色、绿色。当其变为绿色时,继续Fuzzing下去也很难有新的发现了。如上所示,fuzz程序发生异常,但是这个crash无论如何都不能触发漏洞,fuzz为什么会把它放过来?

  一旦master fuzzer完成了它的第一个周期,我们可以继续并停止我们的afl-fuzz实例。我们需要合并和最小化每个实例的队列queue,并重新启动fuzzing。当使用多个fuzzing实例运行时,AFL将在根目录的syncdir目录里,根据传给afl-fuzz的参数(fuzzer的名称),为每个fuzzer维护一个独立的、同步目录。每个单独的fuzzer syncdir目录都包含一个队列queue目录,其中包含AFL能够生成的所有导致新的代码路径被检测出来的测试用例。

我们需要合并每个fuzz实例的队列目录,但是因为其中会有很多重叠,我们应该尽量最小化这个新的语料库。

1
2
3
4
5
6
ls sync_dir
master slave1 slave2
mkdir queue_all
cp sync_dir/slave*/queue/* queue_all
cp sync_dir/master/queue/* queue_all
afl-cmin -m none -t 3000 -i queue_all -o queue_cmin_all -- ../magick convert @@ xx3.png

  一旦我们通过afl-cmin运行生成的队列,我们需要最小化每个结果文件,以使我们不在我们不需要的字节上浪费CPU周期。
  贴一个大佬写的写的小bash脚本,称为afl-ptmin,它将afl-tmin并行化到一定数量的进程中,并证明在最小化过程中显著地提升了速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

cores=$1
inputdir=$2
outputdir=$3
pids=""
total=`ls $inputdir | wc -l`

for k in `seq 1 $cores $total`
do
for i in `seq 0 $(expr $cores - 1)`
do
file=`ls -Sr $inputdir | sed $(expr $i + $k)"q;d"`
echo $file
afl-tmin -m none -t 3000 -i $inputdir/$file -o $outputdir/$file -- $4 &
done
wait
done


chmod +x ~/afl-src/afl-ptmin
mkdir -m 777 queue
screen ~/afl-src/afl-ptmin 8 ./queue_cmin_all ./queue/ "./magick convert @@ xx3.png"

  即使有并行化,这个过程仍然需要一段时间。完成后,从sync_dirs目录中各个fuzzer目录下删除以前的队列queue目录(/syncdirs/fuzzer1/queue/) ,然后复制**/sync_dirs/queue/**文件夹以替换旧的队列文件夹。

1
2
3
4
5
6
rm -rf sync_dir/master/queue 
rm -rf sync_dir/slave1/queue
rm -rf sync_dir/slave2/queue
cp -r queue/ sync_dir/master/queue
cp -r queue/ sync_dir/slave1/queue
cp -r queue/ sync_dir/slave2/queue

使用最新的最小化队列queue,我们可以在之前离开的地方继续fuzzing。

1
2
3
4
screen afl-fuzz -m none -t 3000 -i- -o sync_dir/ -M master -- magick convert @@ xx1.png
screen afl-fuzz -m none -t 3000 -i- -o sync_dir/ -S slave1 -- magick convert @@ xx2.png
screen afl-fuzz -m none -t 3000 -i- -o sync_dir/ -S slave2 -- magick convert @@ xx3.png

一个 -i- , 这告诉AFL只使用syncdir中的queue/目录作为该fuzzer的种子目录,然后从那里重新启动。

8,crashesdump 处理测试结果

分类

1. crash exploration mode 变异

可以快速地产生很多和输入crash相关、但稍有些不同的crashes,从而判断漏洞是否足以利用。

1
afl-fuzz -m none -C -i poc -o peruvian-were-rabbit_out -- ~/src/LuPng/a.out @@ out.png

2.triage_crashes 信号量判断

11代表了SIGSEGV信号,有可能是因为缓冲区溢出导致进程引用了无效的内存;
06代表了SIGABRT信号,可能是执行了abort\assert函数或double free导致;
….后面再收集

1
~/afl-src/experimental/crash_triage/triage_crashes.sh fuzz_out program @@ 2>&1 | grep SIGNAL

3:crashwalk

#build

1
2
3
4
5
apt-get install gdb
mkdir ~/tools && mkdir ~/tools/go
git clone https://github.com/jfoote/exploitable.git ~/tools/
export GOPATH="~/tools/go" && export CW_EXPLOITABLE="~/tools/exploitable/exploitable/exploitable.py"
go get -u github.com/bnagy/crashwalk/cmd/...

#use
crashwalk支持AFL/Manual两种模式。

#Manual Mode

1
~/tools/go/bin/cwtriage -root syncdir/master/crashes/ -match id -~/program @@

#AFL Mode 读取crashes/README.txt文件获得目标的执行命令

1
~/tools/go/bin/cwtriage -root syncdir -afl

4:afl-collect

项目地址: https://github.com/rc0r/afl-utils

它是afl-utils套件中的一个工具,同样也是基于exploitable来检查crashes的可利用性。
它可以自动删除无效的crash样本、删除重复样本以及自动化样本分类

1
2
3
4
5
6
7
git clone https://github.com/rc0r/afl-utils
cd afl-utils
python3 setup.py install

看输出提示,注意设置
echo "source /usr/lib/python3.5/site-packages/exploitable-1.32_rcor-py3.5.egg/exploitable/exploitable.py" >> ~/.gdbinit

1
afl-collect -j 8 -d crashes.db -e gdb_script ./sync_dir ./collection_dir --  /path/to/target --target-opts

还有其他很多功能多留意,后面我再来补充

afl-collect
afl-cron
afl-minimize
afl-multicore
afl-multikill
afl-stats
afl-sync
afl-vcrash

5:手动看异常 (AddressSanitizer )(ASAN)

我们上面编译测试程序编译就开了ASAN,可以很好的分析展示出错误

1
2
3
4
5
6
7
8
./magick convert crash/xxxxxxx output

ERROR: AddressSanitizer: [内存漏洞分析]
--buffer-overflow [stack and heap]
--SEGV [Null point reference]
ERROR: LeakSanitizer: [内存泄漏分析]
--Direct leak
--Indirect leak

9,code coverage 代码覆盖率

  由于不能量化在二进制文件中执行可用的代码路径的程度,你会丢失很多信息。通过确定你没有到达代码库的哪些部分,你可以更好地调整你的测试用例种子,以便于达到更高的测试完整度。

  • ​ 工具之一是GCOV,它随gcc一起发布,所以不需要再单独安装,和afl-gcc插桩编译的原理一样,gcc编译时生成插桩的程序,用于在执行时生成代码覆盖率信息。
  • ​ 另外一个工具是LCOV,它是GCOV的图形前端,可以收集多个源文件的gcov数据,并创建包含使用覆盖率信息注释的源代码HTML页面。
  • ​ 最后一个工具是afl-cov,也是一个python脚本,可以快速帮助我们调用前面两个工具处理来自afl-fuzz测试用例的代码覆盖率结果。
1
2
3
$ apt-get install lcov
$ git clone https://github.com/mrash/afl-cov.git ~/afl-cov
$ ~/afl-cov/afl-cov -V

一,CFLAGS中添加"-fprofile-arcs"和"-ftest-coverage"选项,
--prefix指定一个新的目录。

1
2
3
4
5
6
cd ~/xxx/build/ 
make clean
./configure --prefix=./build-cov CC="clang" CXX="clang++" CFLAGS="-fprofile-arcs -ftest-coverage -g -fsanitize=address" --disable-shared
AFL_USE_ASAN=1 make

#上面加了-fprofile-arcs -ftest-coverage参数了,所以不需要

二,有了新程序,afl-cov可以将在给定输入的二进制程序中采用的代码路径与文件系统上的代码库链接起来。执行afl-cov。当afl-fuzz停止时,afl-cov将退出

1
2
3
4
5
6
7
8
9
10
11
12
#AFL_FILE和afl中的”@@”类似
#-d选项指定afl-fuzz输出目录;
#—live用于处理一个还在实时更新的AFL目录
#LD_LIBRARY_PATH 指定程序的库文件搜索路径
#–enable-branch-coverage用于开启边缘覆盖率(分支覆盖率)统计
#-c用于指定源码目录;
#-e选项用来设置要执行的程序和参数

screen ~/afl-cov/afl-cov -d ~/sync_dir/ --live --coverage-cmd --code-dir program/ -e "magick convert AFL_FILE /dev/null"

#需要链接库时
screen ~/afl-cov/afl-cov -d ~/sync_dir --live --enable-branch-coverage -c . -e "cat AFL_FILE | LD_LIBRARY_PATH=./build-cov/lib magick convert AFL_FILE /dev/null"

  完成后,afl-cov在sync_dir目录下的名为cov的目录中生成报告信息。 其中包括可以在Web浏览器中轻松查看的HTML文件,详细说明命中了哪些函数和哪行代码,以及未命中的函数和代码行。

当然使用afl去fuzz chrome, 如v8引擎的js编译基本可以放弃了,因为就简单的关键字function ,那要产生多少err才能通过啊。几乎不太可能去生成有效的js语法,会卡在语法parser那里。

参考&推荐:

  1. AFL漏洞挖掘技术漫谈(一):用AFL开始你的第一次Fuzzing

  2. Fuzzing-ImageMagick (魔鬼!不到2天光爆破一个程序就十几个cve !)

  3. fuzz

  4. AFL-FUZZ暴力效率流实践 大佬对fuzz进行了改造,值得深究嗷

  5. SanitizerCoverage clang的官方文档,讲了本节很多要了解的编译参数

这些项目必学一下,afl官方停止更新了

winafl、afl-go、WinAFL、afl-cov、kafl、android-afl

afl衍生品

下面计划学习libFuzzer

sdk-adb-qemu

Posted on 2020-03-15

SDK

emulator

${ANDROID_SDK_HOME}/emulator

这个qemu是avd模拟器简化的**${ANDROID_SDK_HOME}/emulator/qemu/darwin-x86_64//qemu-system-x86_64** 不使用

1
2
emulator -list-avds
emulator -avd Pixel_3_API_29 -writable-system -no-snapstorage

avd在$HOME/.android/avd

adb

${ANDROID_SDK_HOME}/platform-tools/adb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$adb start-server //启动adb
$adb kill-server
#-l 锁定该程序
#-r 重新安装该程序,保存数据
#-s 安装在SD卡内,而不是设备内部存储
$adb install -r [apk文件]
#日志
$adb logcat | grep "jc"
#radio 输出通信系统的log
#system 输出系统组件的log
#events 输出event的log
#main 输出java层的log
$adb logcat -b radio
#强杀
$adb shell am force-stop <packagename>
#获取设备的ID和序列号
$adb get-product
$adb root
$adb disable-verity
$adb shell getprop ro.product.cpu.abi
$adb shell 'mount -o rw,remount -t auto /system'
$adb forward tcp:23496 tcp:23496
$adb reboot bootloader
#通过adb启动应用程序页面
$adb shell am start -D -n com.example.how_debug/com.example.how_debug.MainActivity
$adb push "C:\Program Files\IDA 7.0\dbgsrv\android_x64_server" /data/local/ax64
$adb shell 'chmod +x /data/local/*'
$adb shell 'cd /data/local/ && ./ax64'

fastboot

1
fastboot oem unlock

other

mount

1
mount -o rw,remount -t auto /system

Jeb条件

1
2
android:debuggable="true" 
ro.debuggable=1

install MagiskHide Props Config and then:

1
2
3
4
adb shell //adb进入命令行模式
su //切换至超级用户
magisk resetprop ro.debuggable 1 //设置debuggable
stop;start; //一定要通过该方式重启

Android手机系统的 ro.debuggable 这一配置位于 /default.prop 文件中,而 /default.prop 又来源于手机每次启动时 boot.img 中 ramdisk 的挂载,所以想要直接通过修改 /default.prop 是不可行的,但是系统文件是只读的,改了也没用。网上流传较广的是改 boot.img

qemu

https://www.cnblogs.com/yinzhengjie/p/7846595.html

1
2
3
brew install qemu
brew install libvirt
brew services start libvirt

1.创建磁盘映像

1
$qemu-img create -f qcow2 ~/qemu/images/ubuntu-desktop-18.04.qcow2 10G

2.启动附加了Ubuntu ISO的QEMU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# -accel hvf 
qemu-system-x86_64 \
-cpu host -smp 3 -enable-kvm -machine accel=hvf -m 2048 \
-net user,hostfwd=tcp::2222-:22 -net nic \
-usb -device usb-tablet \
-display default,show-cursor=on \
-vga virtio \
-cdrom ~/Parallels/Parallels\ iso/ubuntu-18.04.4-desktop-amd64.iso \
-drive file=/Users/notify/qemu/images/ubuntu-desktop-18.04.qcow2,if=virtio,index=0,format=qcow2,media=disk \
-boot order=dc



#-net user,hostfwd=tcp::4444-:5555

选项解释:
# cpu 内存
-enable-kvm 是使用kvm内核,不用qemu的内核,开启虚拟机加速;
-accel hvf
-m 1024是给客户机分配1024MB内存;
-smp 1 是给客户机分配1个CPU;
# 磁盘
-boot order=dc是指定系统的启动顺序为光驱(d: CD-ROM)、硬盘(c: hard Disk);
-hda 是分配给客户机的IDE硬盘(即前面准备的镜像文件);
-boot order=dc
-fda : (file) ;
-fdb : (file) ; 使用指定文件(file)作为软盘镜像,
-hda :(file);
-hdb :(file);
-hdc :(file);
-hdd :(file); 使用指定file作为镜像硬盘镜像;file为/dev/fd0表示使用物理;
-cdrom (默认占用hdc接口)是分配客户机的光驱。将file指定为/dev/cdrom可以直接使用物理光驱
-drive:( option[,option[,option[,.....]]])定义一个新的硬盘设备,可用子选项有很多。
  file=/path/to/somefile:定义映像文件路径;
  if=interface:指定硬盘设备所连接接口类型,即控制器类型,如
  ide,scsi,sd,mtd,floppy,pflash及virtio等;
  index=index:设定同一种控制器类型中不同设备的索引号,即标识号;
  media=media:定义截止类型为硬(disk)盘还是光盘(cdrom);
  snapshot=snapshot:指定当前硬盘设备是否支持快照功能,on或off;
  cache=cache:定义如何使用物理机缓存来访问块数据,其可用值有
  none,writeback,unsafe和writethrough四个;
  format=format:指定映像文件格式,具体格式可参见qemu-img命令;
-boot:([order=drives][,once=drives][,menu=on|off])定义启动设备的引导次序,每种设备使用一个字符表示,不同的架构所支持的设备及其表示字符不尽相同,在x86PC架构上,a,b表示软驱,c表示第一块硬盘,d表示第一个光驱设备,n-p表示网络适配器,默认为硬盘设备。(案例:-boot order=dc,once=d,其中order表示启动顺序,意思是先找第一个光驱设备,再去找第一块硬盘启动,而once则表示第一次启动时需要找光驱设备。)
# 其他硬件
# 显示
-nographic : 默认情况下,qemu使用SDL来显示VGA输出;而此新选项用于禁止图形接口,此时,qemu类似一个简单的命令行程序,其仿真串口设备将被重定向到控制台;
ctrl a h :
C-a h print this help
C-a x exit emulator
C-a s save disk data back to file (if -snapshot)
C-a t toggle console timestamps
C-a b send break (magic sysrq)
C-a c switch between console and monitor
C-a C-a sends C-a

-monitor stdio:表示在标准输入输出显示monitor界面(-nographic)
  Ctrl-a,c:在console和monitor之间切换
  Ctrl-a,h:显示帮助信息。
-curses:禁止图形接口,并使用curses/ncurses作为交互接口;
-alt-grab:使用Ctrl+Alt+Shift组合键释放鼠标;
-ctrl-grab:使用右Ctrl键释放鼠标;
-sdl:启用SDL(Simple Directmedia Layer)它是用C语言编写的开源免费的多媒体程序库,它提供了一个简单的接口用于实现操作系统硬件平台的图形显示,声音,输入设备等等。目前来讲,SDL库已经被广泛应用于各个操作系统。换句话说,SDL是是能够让你的虚拟机直接在启动时在当前主机上就直接打开一个图形界面。他要求你操作系统本身要支持SDL。
-spice:(option[,option[,......]])启用splice远程桌面协议,其有许多子选项,具体请参考qemu-kvm的手册;
-vga type : 指定要仿真的VGA接口类型,常见的有:
cirrus : Cirrus Logic GD5446显示卡;
std:带有Bochs VBI扩展的标准VGA显示卡;
vmware:VMWare SVGA-II兼容的显示适配器;
qxl:QXL半虚拟化显示卡,于VGA兼容,在Guest中安装qxl驱动后能以很好的方式工作,在使用spice协议时推荐使用此类型;
none:禁用VGA卡;
-vnc:(display[,option][,option[,......]])默认情况下,qemu使用SDL显示VGA输出,使用VNC选项,可以让qemu监听在VNC上,并将VGA输出重定向至VNC会话,使用此选项时,必须使用-k选项指定键盘布局类型,其有许多子选项,具体请参考qemu-kvm的手册。VNC(Virtual Network Computing,应用层协议)基于RFB(远程帧缓冲)协议,来控制另外一台计算机系统,它是图像化的桌面分享系统,VNC克服了SDL只能在图形界面中运行的功能。
[options]:
    password:连接是需要验证密码,设置密码通过monitor接口使用change;
    reverse:“反向”连接至某出于监听状态的vncview上;
    
example vnc的display指定方法:
-vnc [ip]:[port],password # :1 表示是监听在主机的2500+N端口上!换句话说,就是监听在5901端口。
> help change
> change vnc password
-vnc unix:[/path/to/socket_file] # 这是基于套接字进行连接,这种方式只能局限于在本机工作,无法向第一种基于IP的方式进行连接可以让其他主机来连接本地的服务。
-vnc none # 表示在启动虚拟机时不启动VNC功能,将来在需要时可以在手动启动起来。
> change vnc :2


# usb
-usb :开启USB总线
-device usb-tablet GuestOS为Windows时,tablet用于实现鼠标定位。

# internet
-net:(tap[,valn=n][,name=name][,fd=h][,ifname=name][,script=file][,downscript=dfile])通过物理机的TAP网络接口连接至VLAN n中,使用script=file指定的脚本(默认为/etc/qemu-ifup)来配置当前网络接口,并使用downscript=file指定的脚本(默认为/etc/qemu-ifdown)来撤销接口配置,使用script=no和downscript=no可分别用来禁止直行脚本;
-net:(user[,option][,option][,....])在用户模式配置网络栈,其不依赖于管理权限,有效选项有:
vlan=n :连接至vlan n,默认n=0;
name=name:指定接口的显示名称,常用于监控模式中;
net=addr[/mask]:设定GuestOS可见的IP网络,掩码可选,默认为10.0.2.0/8;
host=addr:指定GuestOS中看到的物理机的IP地址,默认为指定网络中的第二个,即x.x.x.2;
dhcpstart=addr:指定DHCP服务地址池中16个地址的起始IP,默认为第16个至第31个,即x.x.x.16-x.x.x.31;
dns=addr:指定GuestOS可见的dns服务器地址,默认为GuestOS网络中的第三个地址,即x.x.x.3;
tftp=dir:激活内置的tftp服务器,并使用指定的dir作为tftp服务器的默认根目录;
bootfile=file:BOOTP文件名称,用于实现网络引导GuestOS,如:"qemu -hda yinzhengjie_linux.img -boot n -net user,tftp=/tftpserver/pub,bootfile=/pxelinux.0"


扩展小知识:KVM的虚拟网络模型

1
2
3
4
I386平台专用选项;
  -no-acpi:禁用ACPI功能,GuestOS与ACPI出现兼容问题时使用此选项;
  -balloon none:禁用balloon设备;
  -balloon virtio[,addr=addr]:启用virti balloon设备;

3启动系统

1
删掉cdrom

qemu配置网卡方法:

不使用sdk的qemu(命令不适用)

Brew install qemu

如需要使用

1
2
$export PATH=$ANDROID_SDK_HOME/emulator/qemu/darwin-x86_64:$PATH
$export DYLD_LIBRARY_PATH=$ANDROID_SDK_HOME/emulator/lib64/qt/lib:$DYLD_LIBRARY_PATH

我之前进行过相关的测试,qemu这一块的网络连接的相关方法分享一下,配置起来稍微麻烦一点,需要创建虚拟网卡;
如果在本地测试可以通端口映射的方式进行;方法如下:

方法一:创建虚拟网卡

(注意本地机器上创建的虚拟网卡和qemu上的配置的地址网段的关系)

一、需要安装的工具软件:

1
2
$apt-get install bridge-utils        # 虚拟网桥工具
$apt-get install uml-utilities # UML(User-mode linux)工具

二、配置虚拟网卡的命令:

1
2
3
4
$sudo ifconfig tap0 down
$sudo tunctl -d tap0
$sudo tunctl -b -u $USER -t tap0
$sudo ifconfig tap0 192.168.10.1 promisc up

三、qemu启动时需要添加相关参数,启动命令如下:

1
2
3
4
5

$sudo qemu-system-x86_64 -enable-kvm -m 1024 -smp 2 -hda qemu-test/vdisk.img -cdrom android-x86.iso -net nic,vlan=0 -net tap,vlan=0,ifname=tap0,script=no,downscript=no
#【note 起来之后在qemu中通过命令行设置ip地址】
#然后在宿主机上使用adb connect x.x.x.x 连接qemu中的android系统进行相关的操作;

方法二、在启动qemu的时候直接进行端口映射:

1
2
$sudo qemu-system-x86_64 -enable-kvm -m 1024 -smp 2  -hda ./vdisk.img -cdrom $android_x86_64.iso -redir tcp:5555::5555
$adb shell

libvirt

1
2
3
brew install qemu
brew install libvirt
brew services start libvirt
1
2
3
4
#只是创建
virsh define ~/qemu/images/ubuntu.xml
#创建后,虚拟机立即执行,成为活动主机
virsh creat ~/qemu/images/ubuntu.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<domain type='qemu' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name>ubuntu</name>
<uuid>2005CB24-522A-4485-9B9A-E60A61D9F8CF</uuid>
<memory unit='GB'>2</memory>
<cpu mode='custom'>
<model>Westmere</model>
</cpu>
<vcpu>2</vcpu>
<features>
<acpi/>
<apic/>
</features>
<os>
<type arch='x86_64' machine='q35'>hvm</type>
<bootmenu enable='yes'/>
</os>
<clock offset='localtime'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<pm>
<suspend-to-mem enabled='no'/>
<suspend-to-disk enabled='no'/>
</pm>
<devices>
<emulator>/usr/local/bin/qemu-system-x86_64</emulator>
<controller type='usb' model='ehci'/>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/Users/notify/qemu/images/ubuntu-desktop-18.04.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<disk type='file' device='cdrom'>
<source file='/Users/notify/Parallels/Parallels iso/ubuntu-18.04.4-desktop-amd64.iso'/>
<target dev='sdb' bus='sata'/>
</disk>
<console type='pty'>
<target type='serial'/>
</console>
<input type='tablet' bus='usb'/>
<input type='keyboard' bus='usb'/>
<graphics type='vnc' port='5900' listen='127.0.0.1' passwd='10010' keymap='en-us'/>
<video>
<model type='virtio' vram='16384'/>
</video>
</devices>
<seclabel type='none'/>
<qemu:commandline>
<qemu:arg value='-machine'/>
<qemu:arg value='type=q35,accel=hvf'/>
<qemu:arg value='-netdev'/>
<qemu:arg value='user,id=n1,hostfwd=tcp::2222-:22'/>
<qemu:arg value='-device'/>
<qemu:arg value='virtio-net-pci,netdev=n1,bus=pcie.0,addr=0x19'/>
</qemu:commandline>
</domain>

Ctrl+Alt+Del按钮重新启动计算机,然后快速按Esc进入启动菜单。

c m d Mean
virsh list 列表
virsh start ubuntu 启动虚拟机
virsh suspend x 暂停虚拟机
virsh resume x 启动暂停的虚拟机
virsh shutdown x 正常关闭虚拟机
virsh destroy x 强制关闭虚拟机
virsh dominfo x 显示虚拟机的基本信息
virsh vncdisplay controller 查看该虚拟机的端口地址
virsh domname 2 显示id号为2的虚拟机名
virsh domid x 显示虚拟机id号
virsh domuuid x 显示虚拟机的uuid
virsh domstate x 显示虚拟机的当前状态
virsh dumpxml x 显示虚拟机的当前配置文件
virsh setmem x 512000 给不活动虚拟机设置内存大小
virsh edit x 编辑配置文件(一般是在刚定义完虚拟机之后

(可能和定义虚拟机时的配置不同,因为当虚拟机启动时,需要给虚拟机分配id号、uuid、vnc端口号等等)

ptmalloc

Posted on 2020-03-11
1
2
3
4
5
6
7
8
172.16.9.52   9W8K#J7v  22/8888  5032/6032
172.16.9.53 9Cr3ReEc 22/8888 5033/6033
student37
ATE5GtBW

ssh ctf@172.16.9.52 -p 9W8K#J7v
ssh ctf@172.16.9.53 -p 9Cr3ReEc
https://172.20.1.1

Heap

  • • dlmalloc – General purpose allocator
  • • ptmalloc2 – glibc
  • • jemalloc - FreeBSD and firefox
  • • tcmalloc – Google
  • • libumem - Solaris

ptmalloc

Ubuntu16:

  • libc6_2.23-0ubuntu11.2_amd64
  • libc6_2.23-0ubuntu11.2_i386

Ubuntu18:

  • libc6_2.27-3ubuntu1.3_amd64
  • libc6_2.27-3ubuntu1.3_i386

32

1
无

64

chunksize = 0x180 则malloc( ( 0x180 - 24, 0x180 - 8 ] )

堆块块(Chunk), 其头部保存着自身的大小等信息, 在这之后即 是用户数据。一共有 3 种类型, 它们都使用同一种数 据结构(malloc_chunk)定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct malloc_chunk {
/* Size of previous chunk (if free). */
INTERNAL_SIZE_T prev_size;
/* Size in bytes, including overhead. */
INTERNAL_SIZE_T size;
/* double links -- used only if free. */
struct malloc_chunk* fd;
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger
size. */
/* double links -- used only if free. */
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
};

具体来说, 用于管理的堆块主要分为 3 种类型。

Allocated Chunk: 已分配块

image-20201216143511628

Free Chunk: 被释放块

image-20201216143528038

Top Chunk: 顶块

位于所有块之后, 保存着 未分配的所有内存, 与已分配块使用相同的域。

glibc 使用 size 域的最低 3 位来 存储一些其它信息。相关的掩码信息定义如下:

1
2
3
#define PREV_INUSE 0x1  //如果此位为 0 则表示上一块为被释放的块, 这个时候此块的 PREV_SIZE 域保存的是上一块的地 址以便在 free 此块时能够找到上一块的地址并进行 合并操作。
#define IS_MMAPPED 0x2 //第 2 位表示此块是否由 mmap 分配, 如果 此位为 0 则此块是由 top chunk 分裂得来, 否则是由 mmap 单独分配而来。
#define NON_MAIN_ARENA 0x4 //第 3 位表示此块是否不属于 main_arena, 在之后会提到main_arena是主线程用于保存堆状态的结构, 如果此位为 0 则表示此块是在 主线程中分配的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pwndbg> arena   <- main_arena
{
mutex = 0,
flags = 1,
fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
top = 0x6020a0,
last_remainder = 0x0,
bins = {0x7ffff7dd1b78 <main_arena+88> = arena.top, 0x7ffff7dd1b78 <main_arena+88>, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b98 <main_arena+120>, 0x7ffff7dd1b98 <main_arena+120>, 0x7ffff7dd1ba8 <main_arena+136>, 0x7ffff7dd1ba8 <main_arena+136>, 0x7ffff7dd1bb8 <main_arena+152>, 0x7ffff7dd1bb8 <main_arena+152>, 0x7ffff7dd1bc8 <main_arena+168>, 0x7ffff7dd1bc8 <main_arena+168>, 0x7ffff7dd1bd8 <main_arena+184>, 0x7ffff7dd1bd8 <main_arena+184>, 0x7ffff7dd1be8 <main_arena+200>, 0x7ffff7dd1be8 <main_arena+200>, 0x7ffff7dd1bf8 <main_arena+216>, },
binmap = {0, 0, 0, 0},
next = 0x7ffff7dd1b20 <main_arena>,
next_free = 0x0,
attached_threads = 1,
system_mem = 135168,
max_system_mem = 135168
}

fastbinsY = {0x602020, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
pwndbg> bins
fastbins
0x20: 0x602020 —▸ 0x602000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

Bins:

Fast Bins:

  • 单向链表

  • 每个元素是一 条单向链表的头部

  • 且同一条链表中块的大小相同。 主要保存大小 32 至 128 字节的块 (0x20 - 0x80). malloc(0-0x78)

  • 特点是当 free 时 不取消下一块的 PREV_INUSE 位, 也不检查是否能 够进行合并操作

  • Fast bins 的取用机 制是 LIFO (Last In First Out) , 即后释放的块将先被利用。(从fastbin头开始存和取)修改 fd

  • Free 1 2 3 M 3 2 1

  • 链无数量限制

Unsorted Bins

从头开始插入, 从尾部解链分配

之后的 Small Bins 与 Large Bins 也是同样的机制。

1
2
3
4
5
6
7
8
 bins = {0x602120, 0x602000, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b88 <main_arena+104>,...}

pwndbg> x/4gx 0x602120 first
0x602120: 0x0000000000000000 0x0000000000000091
0x602130: 0x0000000000602000 next 0x00007ffff7dd1b78 prev
pwndbg> x/4gx 0x602000 last
0x602000: 0x0000000000000000 0x0000000000000091
0x602010: 0x00007ffff7dd1b78 next 0x0000000000602120 prev

Small Bins:

  • 每个元素是一 条双向循环链表的头部
  • 且同一条链表中块的大小相同。
  • 主要保存大小 32 至 1024 字节的块。
  • 由于是 双向链表, Small Bins 的取用机制是 FIFO (First In First Out) , 即先释放的块会先被利用,
  • Free 1 2 3 M 1 2 3

Large Bins

  • 每个元素是一条 双向循环链表的头部
  • 同一条链表中块的大小不一 定相同
  • 按照从大到小的顺序排列
  • 每个 bin 保存一定 大小范围的块, 主要保存大小 1024 字节以上的块。

malloc流程

#假设malloc一个0x80的块)

malloc hook指针

在第一次执行malloc函数时, 系统会使用brk系 统调用向操作系统扩展程序的数据区, 此时 glibc 将 初始化top chunk与main_arena, 取得132KB的空间。 如果之后所有的 malloc 操作都可以满足, 即最后总 是能在此 132KB 中找到合适的内存块返回, 则不再 使用系统调用与内核交互。这段时间, 程序的堆内存 由 glibc 管理。glibc 首先将其加上 8 字节的额外开销,再对齐32字节。

  • 检查 Fast bins 如果请求大小满足 Fast bins, 则在对应的 bin 中 寻找是否有相同大小的块, 如果有则直接将其取出 返回给程序, 同时更新 fast bins 中对应 bin 存储的链 表头指针

  • 检查 Small bins 如果块大小符合 Small bins, 则在对应大小的 Small bin 中寻找是否有合适的块, 如果有则直接返 回, 同时更新 Small bins 中对应 bin 的链表中该块的 上一块和下一块的指针。

  • 处理 Unsorted bin 如果之前没能返回恰好符合的块, 则开始处理 Unsorted bin 中的块(这里有一个例外, 如果 Unsorted bin 中只有一个块且这个块是 last_remainder, 而且大 小足够, 则优先使用此块, 分裂后将前一块返回给 用户, 剩下的一块作为新的 last_remainder 再次放入 Unsorted bin.[7])

  1. a) 逐个迭代Unsorted bin中的块, 如果发现块的 大小正好是需要的大小, 则迭代过程中止, 直接返 回此块;否则将此块放入到对应的 Small bin 或者 large bin 中, 这也是整个 glibc 堆管理中唯一会将块 放入 Small bins 与 large bins 中的代码。
  2. b) 迭代过程直到 Unsorted bin 中没有块或超过 最大迭代次数(10000)为止。
  3. c) 随后开始在 Small bins 与 large bins 中寻找最 合适的块(指大于请求大小的最小块), 如果能够找到, 则分裂后将前一块返回给用户, 剩下的块放入 Unsorted bin 中。
  4. d) 如果没能找到, 则回到开头, 继续迭代过程, 直到 Unsorted bin 空为止 2.2.4 使用顶块 如果之前的操作都没能找到合适的块, 将分裂 Top chunk 返回给用户, 若 Top chunk 的大小仍然不 足, 则再次执行 malloc_consolidate 函数清除 Fast bins, 若Fast bins已空, 只能使用sysmalloc函数借助 系统调用拓展空间。

free流程

  1. Free 函数是 glibc 的释放接口, 将之前分配得到 的用户内存指针作为参数, glibc 会释放这一块空间。 主要的功能由_int_free 函数实现。
  2. 首先, 进行一系列的检查, 包括内存地址对齐, PREV_INUSE 位是否正确等等, 能够发现一些破坏 与 double free 的问题。
  3. 如果块大小满足 Fast bins, 则不取消下一块的 PREV_INUSE 位, 也不设置下一块的 prev_size 域, 直接将该块放入对应的 Fast bin 中, 且不进行相邻块 的合并操作。
  4. 检查被 free 内存块的前一块(这里的前一块指连 续内存中的上一块, 通过 prev_size 域来寻找), 如果 未使用, 则合并这 2 块, 将前一块从其 bin 中移除之 后再检查其后一块, 如果发现是 Top chunk, 则最后将 合并到 Top chunk 中, 不放入任何 bin; 如果不是 Top chunk 且未使用, 则再合并这 2 块, 将后一块从其 bin 中移除, 并且将合并过的大块放入 Unsorted bin 中。

heap伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
void* find_from_fastbin(int idx){
bin = &arena->fastbin[idx] - 0x10;
chunk* res = bin->fd;
bin->fd = res->fd;
// 高版本有fastbin检查
vassert(res->size == idx*16+0x20);
// 但这段代码没 有检查内存地址的对齐
return res;
}
// LIFO
void mv_to_fastbin(void* chunk, int idx){
void* fd = arena->fastbin[idx]->fd;
arena->fastbin[idx]->fd = chunk;
chunk->fd = fd;
}

// 处理 Small Bins 的 代码如下所示:
void* find_from_smallbin(int idx){
// 大小相等
small_bin = &arena->bins[idx] - 16;
chunk* victim = small_bin->bk;
if (victim == 0){/* initialization check */
malloc_consolidate(av);
return
}
bck = victim->bk;
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = small_bin;
return victim;
}

void* find_from_small_large(size_t size, int idx){
// 满足最小大小
if (chunk = find_it()){
vassert(chunk->size > size);
relloc(chunk, size);
return chunk
}
return null;
}

// FIFO double link insert
void mv_to_bins(void* chunk){
//这也是整个 glibc 堆管理中唯一会将块 放入 Small bins 与 large bins 中的代码。
fd = bins[chunk->size/0x10]->fd;
bk = bins[chunk->size/0x10]->bk;
bins[chunk->size/0x10]->fd = chunk;
chunk->fd = fd;
chunk->bk = bk;
fd->bk = chunk;
bk->fd = chunk;
}
// double link delete
void unlink(void* chunk){
vassert(fd->bk == chunk && bk->fd == chunk, "unlink error");//早期无此检查
bk, fd = chunk->bk, chunk->fd;
bk->fd = fd;
fd->bk = bk;
}

void unlink_auto(void* chunk){
if(is_fastbin(chunk)){
/*遍历fastbin一条链将其删除*/
。。。。。
}else{
unlink(chunk)
}
}

void* __libc_malloc(size_t need_size){
chunksize = ((need_size-1)&~0b1111) + 0x20;
int idx = chunksize/0x10 - 2;

if(fastbin[idx/*0-6*/]有空闲块 && chunksize>=0x20 && chunksize<=0x80) //chunksize 0x20-0x80
return find_from_fastbin(idx)


if(bins[idx + 2 /*2-2+61*/]有空闲块 && chunksize>=0x20 && chunksize<=0x3f0) //chunksize 0x20 - 0x3f0
return find_from_smallbin(idx + 2)


// 剩下的一块作为新的 last_remainder 再次放入 Unsorted bin
if(bins[1]->fd != &bins[1]-0x10 &&
bins[1]->fd-> == &bins[1]-0x10 && // 如果 Unsorted bin 中只有一个块且这个块是 last_remainder,
&& last_remainder.size >= chunksize) // 而且大小足够, 则优先使用此块, 分裂后将前一块返回给用户,
{
if(last_remainder.size == chunksize) { // 大小足够且相等
last_remainder = nullptr;
unlink_auto(last_remainder);
return last_remainder;
}
else{ // last_remainder需要分裂
if(last_remainder->size - chunksize >= 32){ //满足最小分裂要求
// 分裂last_remainder
unlink_auto(last_remainder);
void new_last_remainder = chunk + chunksize;
new_last_remainder->size = last_remainder->size - chunksize;
// 返回last_remainder
void* res = last_remainder;
last_remainder = new_last_remainder; // 保存分裂的块
//再次放入Unsorted bin
bins[1]->fd = new_last_remainder;
return res;
}
// 不满足
}
}

// 从unsorted bins
int count = 0;
for(curr = bins[1], curr != &bins[1] - 0x10 && count<=10000; curr = curr->fd, count++){
if(curr->size == chunksize){
//没有采用unlink,注意的是这里无unkink检查,这就很好
bck = curr->bk;
...
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
return curr;
}else{//没找到
// 将此块放入到对应的 Small bin 或者 large bin 中,
// 这也是整个 glibc 堆管理中唯一会将块 放入 Small bins 与 large bins 中的代码。
unlink(curr);
// 放入Small bins or large bins
mv_to_bins(curr);
// 从Small bins 与 large bins查找
void* ret = find_from_small_large(chunksize, idx);
if(ret) return ret;
}
}

// 从 topchunk分裂一个
if(chunksize<=topchunk->size)
return topchunk;
else{ // topchunk大小不够
// 清除 Fast bins, 若 Fast bins 已空, 只能使用 sysmalloc 函数借助 系统调用拓展空间。
void* ret = malloc_consolidate(fastbins);
if (ret){
return ret
}else{// 若 Fast bins 已空 , 或者释放了还是不够
return sys_brk()
}
}
}

// -------------------------------- free ---------------------------------
void* global_prev_free_fastbin = nullptr;
void __libc_free(void* ptr){
void* chunk = ptr - 0x10;
void* fd_chunk = chunk + chunk->size;
vassert(chunk&0b1111==0, "");//对齐检查
vassert(fd_chunk->prev_inuse == true, "");// 下一块的 prev_inuse 检查
vassert(bins[1]->fd->bk == &bins[1]-0x10, "unsorted bin 指向自身检查");
if(chunk->size > 0x20 && chunk->size<=0x80){ // fastbins
//这种释放相当于直接把该chunk引用放到了fastbins,啥也不做,只是做下检查和链表链接
vassert(global_prev_free_fastbin!=chunk, "double free");
global_prev_free_fastbin = chunk;
mv_to_fastbin(chunk, chunk->size/0x10 - 2); // 直接放入fastbin
return;
}else{
fd_chunk->prev_size = chunk->size; // // 设置下一块 prev_size = size;
fd_chunk->prev_inuse = false; // 设置下一块 prev_inuse 为false;
if (chunk->prev_inuse == false){ // 前一块是释放了的
void* bk_chunk = chunk - chunk->prev_size;
vassert(bk_chunk->size == chunk->prev_size, "");
// 合并相邻的前一块
// 将上一块从其所在bins移除掉
unlink(bk_chunk);
// 上一块的 bk_chunk->size += chunk->size
// 更新下一chunk 的 prev_size = bk_chunk->size
merge(bk_chunk, chunk, fd_chunk);
}

void* fd_fd_chunk = fd_chunk + fd_chunk->size;
if( fd_fd_chunk->prev_inuse == false){ // chunk的下一块为释放的块
if(fd_chunk == top_chunk)//发现是 Top chunk
merge(fd_chunk, top_chunk)
else{
// 合并相邻的下一块
// 将下一块从其所在bins移除掉
unlink(fd_chunk);
// 当前chunk->size += fd_chunk->size
// 更新下下一chunk 的 prev_size = bk_chunk->size
merge(chunk, fd_chunk, fd_fd_chunk);
}
}
}
}

// -------------------------------- relloc ---------------------------------
void* __libc_relloc(void* ptr, size_t new_size){
void* chunk = ptr - 0x10;
chunksize = ((need_size-1)&~0b1111) + 0x20;
void *fd_chunk = chunk + chunk->size;
if(chunksize < chunk->size){ // 缩容
if(chunk->size - chunksize >=32 ){// 分裂出的部分如果达到 块的最小大小(32 字节)
chunk->size = chunksize;
_int_free(chunk + chunk->size + 0x10);
return chunk;
}else{
goto new_copy;
}

}else{ // 扩容
if(fd_chunk == top_chunk){ // 已分配的块后一块是 Top chunk
top_chunk_size = top_chunk->size - (chunksize - chunk->size);
top_chunk = chunk + new_size;
top_chunk->size = top_chunk_size;
top_chunk->prev_inuse = true;
return chunk;
}else{
void* fd_fd_chunk = fd_chunk + fd_chunk->size;
if(fd_fd_chunk->prev_inuse == false && // chunk的下一块为释放的块
(fd_chunk->size + chunk->size) - chunksize >=32 ){ // 且大小满足
unlink(fd_chunk);
_int_free(fd_chunk);
}
else if(fd_fd_chunk->prev_inuse == false && // chunk的下一块为释放的块
(fd_chunk->size + chunk->size) == chunksize){ // 且大小相等
unlink(fd_chunk);
}//否则
goto new_copy;
}
}
//impossible
return;
new_copy:
void* res = malloc(new_size);
memcpy(res, ptr, chunk->size - 0x10);
free(ptr);
return res;
}





unlink Attack

image-20201218114127335
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// ----------------------- unlink ---------------------------

/*
fakefd->bk == fake_chunk
fakebk->fd == fake_chunk
{
*ptr == chunk
fakefd = ptr - 0x18
fakebk = ptr - 0x10
}

fakefd->bk = fakebk
fakebk->fd = fakefd;
{
导致 *(ptr - 0x18 + 0x18) = ptr - 0x10
导致 *(ptr - 0x10 + 0x10) = ptr - 0x18
也就是 *ptr = ptr - 0x18
}

*/

Fastbin_attack

1
2
3



The House of Prime glibc [2.3.5 - 2.19)

此方法是利用 glibc 在判断 Fast Bins 的下标时的 一个疏忽来破坏main_arena中的数据, 在早期的glibc 2.3.5 版本中, main_arena 结构与现在有所不同:

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_state {
/* Serialize access. */
mutex_t mutex;
// Should we have padding to move the mutex to its own cache line?
#if THREAD_STATS
/* Statistics for locking. Only used if THREAD_STATS is defined. */
long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif
/* The maximum chunk size to be eligible for fastbin */
INTERNAL_SIZE_T max_fast; /* low 2 bits used as flags */
/* Fastbins */
mfastbinptr fastbins[NFASTBINS]; ...

而 glibc 在根据块大小取得 Fast bins 数组下标时 使用的宏如下面所示:

#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2)

若此时的 sz 为 8, 则最后得到的下标为 (8 >> 3) – 2 = –1, 这就意味着若溢出时将下一块的 sz 修改为 8,则会造成 fastbins 数组的上一个域即 max_fast 变量遭到破坏。

但在glibc 2.19版本中, main_arena的结构已经 发生了变化, 虽然 fastbin_index 宏仍然存在漏洞, 但 在 fastbins 数组的前面不再是 max_fast 变量, 此变量 被移出结构体作为全局变量 global_max_fast 使用, 这样一来, 通过破坏前一个域来进行利用的方法已 不可行。

The House of Mind. [当前流行的 glibc 中已不再可行]

此方法在当前流行的 glibc 中已不再可行, glibc 在 free 时都需要对 Unsorted bin 进行检查, 确认其中 的第一个块的后向指针是否指向自身, 如此一来若 攻击者将 Unsorted bin 指向希望控制的内存区域, 则 会导致检查失败而无法利用。

The House of Force 【需要进行heap泄露】

此方法通过溢出Top chunk的size域来欺骗glibc, 使得攻击者 malloc 一个很大的数目(负有符号整数) 时能够使用 Top chunk 来进行分配, 此时 Top chunk 的位置会加上这个大数, 造成整数溢出, 结果是 Top chunk 能够被转移到堆之前的内存地址(如程序的.bss 段, .data 段, GOT 表等), 下次再执行 malloc 时, 就会 返回转移之后的地址, 攻击者即能够控制其他的内 存数据。

32为例子

  • 攻击者溢出 Top chunk 的上一块, 将 Topchunk 的大小修改为 0xffffffff。

  • 攻击者需要能够控制下一次 malloc 时传入的

  • 大小, 此时传入 0xffffffec, 经过处理得到的大小是 0xfffffff0, glibc 的大小使用的是 size_t 类型存储, 这 是一种与机器字长相等的无符号整形。故此时 glibc 认为 0xffffffff > 0xfffffff0, 即 Top chunk 的空间足够, 于是使用 Top chunk 进行分配。假设 Top chunk 之前 的位置是 0x804a010 分配后, Top chunk 的地址将处 于 0x804a010 + 0xfffffff0 = 0x804a000。

下次 malloc 时, 攻击者就能够取得 0x804a000 这块内存, 进一步修改就能够干扰程序的运行。

此方法的缺点在于, 会受到之后提到的 ASLR 技术的影响, 如果攻击者需要修改指定位置的内存, 他需要知道当前 Top chunk 的位置以构造合适的 malloc 大小来取得对应的内存。而 ASLR 开启时, glibc 的堆内存地址将会是随机的, 如果想要利用此 项技术, 则需要进行信息泄露。

The House of Lore 【可行】

glibc 2.19也可使用,需绕过

此方法希望通过破坏已经放入 Small bins 中块 的 bk 指针来达到取得任意地址的目的

在早期的 glibc 2.3.5 版本的_int_malloc 中, 处理 Small Bins 的 代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//参考上面的 find_from_smallbin
bin = small_bin[idx] - 0x10

// 能够 chunk->bk = fake_chunk
malloc(nb){
// fake_bck = fake_chunk->bk
// 导致 bck->fd = bin;
// 导致 bin->bk = fake_bck
}
malloc(nb){



}

即如果请求的大小符合 Small Bins, 则在对应的 bin 中寻找是否有空闲的块, 如果有就直接从 bin 的 链表中解链并返回给用户。

基于上面的机制, 当一个块存在于 Small Bin 的 第一个块(这里的第一块指的是在取 Small Bin 块时 选择的第一块, 即 Small Bin 的 bk 指针指向的那一块) 时, 通过溢出修改其 bk 指针指向某个地址 ptr, 当下 一次 malloc 对应大小的块时, 就有 bck = victim->bk= ptr, 且 bin->bk = bck = ptr, 这样以来就成功地将这 个 bin 的第一块指向了 ptr, 下次再 malloc 对应大小 就能够返回 ptr+16 的位置了, 这样攻击者再对取回 的块进行写入就能控制 ptr+16 的内存内容。

尽管在 glibc 2.19 版本的代码中, 已经加入了对 应的防御机制, 通过检查 bin 中第二块的 bk (我感觉是fd) 指针是 否指向第一块, 可以发现对 Small bins 的破坏。但攻 击者如果同时伪造了 bin 中的前 2 块(smallbin->bk->bk->fd == smallbin->bk), 就可以绕过这 个检查。故此种利用方式仍然可以进行。

The House of Spirit [fastbins attack]

此方法通过堆的Fast bin机制来辅助栈溢出的利 用, 一般的栈溢出漏洞的利用都希望能够覆盖函数 的返回地址以控制 EIP 来劫持控制流, 但若栈溢出 的长度无法覆盖返回地址, 但是能够覆盖到栈上的 一个即将被 free 的堆指针, 此时可以将这个指针改 写为栈上的地址并在相应位置构造一个 Fast bin 块的 元数据。接着在 free 操作时, 这个栈上的“堆块”即 被投入 Fast Bin 中, 下一次 malloc 对应的大小时, 由 于 Fast Bin 的机制为先进后出, 故上次 free 的栈上的 “堆块”能够被优先返回给用户, 再次写入时就可能 造成返回地址的改写, 亦可进行信息泄漏。

此方法的缺点与The House of Force方法类似, 需要对栈地址进行泄漏, 否则无法正确覆盖需要释 放的堆指针, 且在构造数据时, 需要满足对齐的要 求, 存在很多的限制。

现代保护手段NX ALSR PIE RELRO

  • 不可执行位 NX Bit
  • 地址空间布局随机化 ASLR
  • 位置无关可执行文件 PIE
  • 重定位只读 RELRO
    1. 部分 RELRO: 在程序装入后, 将其中一些段 (如.dynamic)标记为只读, 防止程序的一些重定位信 息被修改。
    2. 完全 RELRO: 在部分 RELRO 的基础上, 在 程序装入时, 直接解析完所有符号并填入对应的值, 此时所有的 GOT 表项都已初始化, 且不装入 link_map 与_dl_runtime_resolve 的地址(二者都是程 序动态装载的重要结构和函数)。

堆的攻击手段

  1. 现代利用手段
  • 现代解链操作 UNLINK 攻击当前的glibc对unlink宏进行了 加固, 传统的 unlink 利用方法已不再可行。为了绕过 当前的 glibc 对 unlink 做的检查, 需要在内存中找到 指向构造的伪块的指针
  • 攻击 Fast bins
  • 释放后使用漏洞的利用
  • 两次释放漏洞的利用 double-free
  • 改写 Realloc Hook
  • 利用单字节溢出漏洞
  • 扩展被释放块
  • 扩展已分配块
  • 收缩被释放块
  • 攻击 Unsorted Bin
  • 重分配函数的利用

double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
free(a)
// fast -> a -> null
free(b)
// fast -> b -> a -> null
free(a)
// fast -> a -> b -> a -> b
d = malloc() // = a
// fast -> b -> a -> b
e = malloc() // = b
// fast -> a -> b
change(e) {//就等于change b
e->fd = free_got;
f = malloc() // = a
// fast -> b -> free_got
malloc()
change(malloc())

}

__realloc_hook [fastbin attack]

__realloc_hook 在程序启动时有一个非 0 的初 始化值, 在 64 位系统中, 这个地址将以 7f 开头

攻击者能够通过刻意的内存地址错位来进行 Fast Bin Attack, 从而达到改写__realloc_hook 的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x7ffff7dd3798 <_IO_stdfile_0_lock+8>:  0x0000000000000000      0x0000000000000000
0x7ffff7dd37a8 <__free_hook>: 0x0000000000000000 0x0000000000000000



0x7ffff7dd1ae8 <_IO_wide_data_0+296>: 0x0000000000000000 0x00007ffff7dd0260
0x7ffff7dd1af8: 0x0000000000000000 0x00007ffff7a92ea0
0x7ffff7dd1b08 <__realloc_hook>: 0x00007ffff7a92a70 0x0000000000000000
0x7ffff7dd1b18: 0x0000000000000000 0x0000000100000000
0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b38 <main_arena+24>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b48 <main_arena+40>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b58 <main_arena+56>: 0x0000000000000000 0x0000000000000000

__realloc_hook = main_arena - 0x20
__free_hook = main_arena - 0x1c88


// 0x7fxxxxxxxx
// chunk = 7fpointer - 8 + 5. size = 0x7f
freed_fastchunk->fd = &__realloc_hook - 8 + 5

Off-by-one 单字节溢出漏洞

通过溢出下一个块的size域,攻击者能够在堆中创造出重叠的内存块

扩展被释放块

若溢出块的下一块为被释放的块且处于Unsorted Bin 中, 则可以通过溢出一个字节来将其大 小扩大, 下次取得此块时就意味着其后的块将被覆 盖而造成进一步的溢出。

image-20201218181759112

扩展已分配块

若溢出块的下一块为使用中的块, 则需要合理 控制溢出的字节, 使其被释放时的合并操作能够顺 利进行, 例如直接加上下一块的大小使其完全被覆 盖。下一次分配对应大小时, 即可取得已经被扩大的 块, 并造成进一步溢出。

image-20201218181813210

收缩被释放块

此情况针对溢出的字节只能为 0 的情况, 此时

可以将下一个被释放块的大小缩小, 如此一来在之 后分裂此块时将无法正确更新后一块的 prev_size 域,导致释放时出现重叠的堆块。

image-20201218183231401

B2为fasten

The House of Einherjar

此方法也是针对溢出字节只能为 0 的情况, 利 用的思路是不将溢出块下一块的大小缩小, 而是仅 仅更新后一块的 prev_size 域, 使其在被释放时能够 找到之前一个合法的被释放块并与其合并, 造成堆 块的重叠

image-20201218183211613
1
2
3
4
5
C->prev_size = 0x200
LOWb(C->size) = 0 // prev_inuse = false
free(C);
malloc(0x300-8) // = A+B+C

成功后的利用方式

malloc包含已释放块

如果是fastbin那就简单

malloc包含已分配块

攻击 Unsorted Bin [可le a k]

当 Unsorted Bin 中的块恰好为分配的块大小时

则会直接从 Unsorted Bin 中移除 返回给用户。若不是, 则将其投入对应的 Bin 之中。

无论结果如何, 其最终都将从 Unsorted Bin 中移除, 而这里移除使用的方法并不是 2.3 节中释放操作使 用的 unlink 宏, 而是如下代码:

1
2
3
4
bck = victim->bk; 
...
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

不同于 unlink 宏, 这里对 Unsorted Bin 的链表完 整性并没有做检查, 导致如果能够可以修改某一个 处于 Unsorted Bin 中的块的 bk 指针, 则可以造成一 个任意内存写入, 可以将 bk + 16 的位置改写为 Unsorted Bin 的地址, 但 Unsorted Bin 的 bk 指针将被 破坏, 下一次再使用 Unsorted Bin 时, 将可能由于 bck 的位置不可写而造成程序崩溃。

Leak: 如果last->bk = ptr, 则malloc后, ptr->fd = unsorted_bin, 可以直接泄漏ptr->fd, 且无检查

2.29 tcache 【无le a k】

tcache全称thread local caching,是glibc2.26后新加入的一种缓存机制(在Ubuntu 18及之后的版本中应用),提升了不少性能,但是与此同时也大大牺牲了安全性,在ctf-wiki中介绍tcache的标题便是tcache makes heap exploitation easy again,与fastbin非常相似但优先级高于fastbin,且相对于fastbin来说少了很多检查,所以更加便于进行漏洞利用。

  • 可直接double free

tcache 和 fastbin在free中并没有被清除inuse标志

  1. tcache机制的主体是tcache_perthread_struct结构体,其中包含单链表tcache_entry

  2. 单链表tcache_entry,也即tcache Bin的默认最大数量是64,在64位程序中申请的最小chunk size为32,之后以16字节依次递增,所以size大小范围是0x20-0x410,也就是说我们必须要malloc size≤0x408的chunk

  3. 每一个单链表tcache Bin中默认允许存放的chunk块最大数量是7

  4. 在申请chunk块时,如果tcache Bin中有符合要求的chunk,则直接返回;

    如果在fastbin中有符合要求的chunk,则先将对应fastbin中其他chunk加入相应的tcache Bin中,直到达到tcache Bin的数量上限,然后返回符合符合要求的chunk;

    如果在smallbin中有符合要求的chunk,则与fastbin相似,先将双链表中的其他剩余chunk加入到tcache中,再进行返回

  5. 在释放chunk块时,如果chunk size符合tcache Bin要求且相应的tcache Bin没有装满,则直接加入相应的tcache Bin

  6. 与fastbin相似,在tcache Bin中的chunk不会进行合并,因为它们的pre_inuse位会置成1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
	idx 0   bytes 0..24 (64-bit) or 0..12 (32-bit) 16
idx 1 bytes 25..40 or 13..20 17
idx 2 bytes 41..56 or 21..28


typedef struct tcache_entry
{
struct tcache_entry *next; //默认允许存放的chunk块最大数量是 7
} tcache_entry;

typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS]; //数组counts用于存放每个bins中的chunk数量
struct tcache_entry *entries[TCACHE_MAX_BINS/*64*/]; //数组entries用于放置64个bins
} tcache_perthread_struct;


typedef struct tcache_entry
{
//指向chunk当中的用户内存区,第一个变量是一个指针,指向tcache的下一个chunk,
//就是单链表访问
struct tcache_entry *next;
/* 这个字段是用来检测双重free释放的 */
struct tcache_perthread_struct *key;
} tcache_entry;

tcache机制允许将空闲的chunk以链表的形式缓存在线程各自tcache的bin中。

下一次malloc时可以优先在tcache中寻找符合的chunk并提取出来。

他缺少充分的安全检查,如果有机会构造内部chunk数据结构的特殊字段,我们可以有机会获得任意想要的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static void
+tcache_put (mchunkptr chunk, size_t tc_idx)
+{
+ tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
+ assert (tc_idx < TCACHE_MAX_BINS);
+ e->next = tcache->entries[tc_idx];
+ tcache->entries[tc_idx] = e;
+ ++(tcache->counts[tc_idx]);
+}

+static void *
+tcache_get (size_t tc_idx)
+{
+ tcache_entry *e = tcache->entries[tc_idx];
+ assert (tc_idx < TCACHE_MAX_BINS);
+ assert (tcache->entries[tc_idx] > 0);
+ tcache->entries[tc_idx] = e->next;
+ --(tcache->counts[tc_idx]);
+ return (void *) e;
+}

#if USE_TCACHE
//检查bytes合法性,并获取请求的标准chunk大小
checked_request2size (bytes, tbytes);
//获得tcache对应请求大小bin的索引
size_t tc_idx = csize2tidx (tbytes);
//如果没有初始化tcache,那么我们初始化tcache
MAYBE_INIT_TCACHE ();

DIAG_PUSH_NEEDS_COMMENT;
//检查tcache给定索引是否有chunk满足条件,有就直接取出来
//索引大小不能超过index,并且tcache存在,并且tcache对应索引有块,初始化时不为0
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);//获取tcache
}
DIAG_POP_NEEDS_COMMENT;
#endif


file:///Users/notify/ctf-wiki/pwn/linux/glibc-heap/tcache_attack-zh/

file:///Users/notify/ctf-wiki/site/pwn/linux/glibc-heap/tcache_attack-zh/index.html

References

• http://phrack.org/issues/58/4.html

• http://acez.re/ctf-writeup-hitcon-ctf-2014-stkof-or-modern-heap-overflow/

Glibc Adventures: The Forgotten Chunks,

• https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/

12

11 posts
4 categories
6 tags
GitHub E-Mail
Links
  • driverxdwのblog
© 2021 notify
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4