CTF

pwn tools for pwntools、peda、ROPgadget

Posted by 许大仙 on July 24, 2018

day1 for tools(pwntools,peda,ida)

一、pwntools

1.简介

pwntools是由Gallopsled开发的一款专用于CTF Exploit的Python库,包含了本地执行、远程连接读写、shellcode生成、ROP链的构建、ELF解析、符号泄漏等众多强大功能,可以说把exploit繁琐的过程变得简单起来。

2.安装

目前pwntools支持python2,如果需要python3,也有一个python3-pwntools。 Pwntools的主页在pwntools.com,Github项目地址为pwntools,可以下载到最新的源码用python进行安装 安装方法其实最简单的是使用pip,如下:

sudo pip install pwntools

虽然Pwntools大部分的功能都是纯python实现的可以直接使用,其还是依赖一些外部的python库例如capstone等,如果使用pip安装的话可以发现安装了不少依赖的库。而如果需要使用其ROP链的构建功能的话,则需要安装libcapstone这个非Python的库,不过需要注意的一点就是要保持python的capstone与这个libcapstone的版本一致否则可能出现API Version冲突的问题,在pip安装时可以指定其版本pip install capstone==3.0.4

3.大体构架

  • 导入模块:一般使用的话可以直接用from pwn import *将所有的模块导入到当前namespace。 这条语句还会帮你把os,sys等常用的系统库导入。

  • 常用模块及介绍

    • asm : 汇编与反汇编,支持x86/x64/arm/mips/powerpc等基本上所有的主流平台
    • dynelf : 用于远程符号泄漏,需要提供leak方法[暂时还未接触]
    • elf : 对elf文件进行操作
    • gdb : 配合gdb进行调试
    • memleak : 用于内存泄漏
    • shellcraft : shellcode的生成器
    • tubes : 包括tubes.sock, tubes.process, tubes.ssh, tubes.serialtube,分别适用于不同场景的PIPE[暂时还未使用]
    • utils : 一些实用的小功能,例如CRC计算,cyclic pattern等

4.分模块简介

汇编与反汇编

使用asm来进行汇编

>>> asm('nop')
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'

可以使用context来指定cpu类型以及操作系统[上述的arch指示了系统构架,cpu类型,比如arm构架是32位精简指令集RISC处理器构架]

>>> context.arch  = 'i386'  #即Intel 80386,i386通常被用来作为对Intel(英特尔)32位微处理器的统称
>>> context.os= 'linux' #操作系统
>>> context.endian= 'little' #小端
>>> context.word_size = 32 #字长

使用disasm进行反汇编

>>> print disasm('6a0258cd80ebf9'.decode('hex'))
   0:   6a 02   push   0x2
   2:   58  popeax
   3:   cd 80   int0x80
   5:   eb f9   jmp0x0

注意,asm需要binutils中的as工具[汇编器]辅助,如果是不同于本机平台的其他平台的汇编,例如在我的x86机器上进行mips[一种RISC处理器]的汇编就会出现as工具未找到的情况,这时候需要安装其他平台的cross-binutils。[包含其他处理器的汇编器]

shellcode生成器

使用shellcraft可以生成对应的架构的shellcode代码[产生的是汇编代码,如果要产生机器码则要再使用asm],直接使用链式调用的方法就可以得到

>>> print shellcraft.i386.nop().strip('\n')
nop
>>> print shellcraft.i386.linux.sh()
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
...

如上所示,如果需要在64位的Linux上执行/bin/sh就可以使用shellcraft.amd64.linux.sh(),配合asm函数就能够得到最终的pyaload了。 除了直接执行sh之外,还可以进行其它的一些常用操作例如提权、反向连接等等。

ELF文件操作

这个还是挺实用的,在进行elf文件逆向的时候,总是需要对各个符号的地址进行分析,elf模块提供了一种便捷的方法能够迅速的得到文件内函数的地址,plt位置以及got表的位置

>>> e = ELF('/bin/cat')
>>> print hex(e.address)  # 文件装载的基地址
0x400000
>>> print hex(e.symbols['write']) # 函数地址
0x401680
>>> print hex(e.got['write']) # GOT表的地址
0x60b070
>>> print hex(e.plt['write']) # PLT的地址
0x401680

同样,也可以打开一个libc.so来解析其中system函数的位置:) 甚至可以修改一个ELF的代码

>>> e = ELF('/bin/cat')
>>> e.read(e.address+1, 3)  #读取地址e.address+1下的3个字节
'ELF'
>>> e.asm(e.address, 'ret')  #在指定地址下汇编
>>> e.save('/tmp/quiet-cat') #存储成新的文件
>>> disasm(file('/tmp/quiet-cat','rb').read(1))  #打开这个文件,返回基地址,在基地址处出去第一个字节
'   0:   c3            ret'

ELF模块在文档里好像还没有写的样子,不过可以从源码中看到一些可用的函数

  • asm(address, assembly) : 在指定地址进行汇编
  • bss(offset) : 返回bss段的位置,offset是偏移值
  • checksec() : 对elf进行一些安全保护检查,例如NX, PIE等。
  • disasm(address, n_bytes) : 在指定位置进行n_bytes个字节的反汇编
  • offset_to_vaddr(offset) : 将文件中的偏移offset转换成虚拟地址VMA
  • vaddr_to_offset(address) : 与上面的函数作用相反
  • read(address, count) : 在address(VMA)位置读取count个字节
  • write(address, data) : 在address(VMA)位置写入data
  • section(name) : dump出指定section的数据

ROP链生成器

先简单回顾一下ROP的原理,由于NX开启[栈保护机制,NX]不能在栈上执行shellcode,我们可以在栈上布置一系列的返回地址与参数,这样可以进行多次的函数调用,通过函数尾部的ret语句控制程序的流程,而用程序中的一些pop/ret的代码块(称之为gadget)来平衡堆栈。其完成的事情无非就是放上/bin/sh,覆盖程序中某个函数的GOT为system的,然后ret到那个函数的plt就可以触发system(‘/bin/sh’)。由于是利用ret指令的exploit,所以叫Return-Oriented Programming。(如果没有开启ASLR,可以直接使用ret2libc技术

这种技术的难点自然就是如何在栈上布置返回地址以及函数参数了。而ROP模块的作用,就是自动地寻找程序里的gadget,自动在栈上部署对应的参数。

elf = ELF('ropasaurusrex')
rop = ROP(elf)
rop.read(0, elf.bss(0x80))
rop.dump()
# ['0x0000:0x80482fc (read)',
#  '0x0004:   0xdeadbeef',
#  '0x0008:  0x0',
#  '0x000c:0x80496a8']
str(rop)
# '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'

使用ROP(elf)来产生一个rop的对象,这时rop链还是空的,需要在其中添加函数。

因为ROP对象实现了__getattr__的功能,可以直接通过func call的形式来添加函数,rop.read(0, elf.bss(0x80))实际相当于rop.call(‘read’, (0, elf.bss(0x80)))。 通过多次添加函数调用,最后使用str[转换成字符串]将整个rop chain dump出来就可以了。

  • call(resolvable, arguments=()) : 添加一个调用,resolvable可以是一个符号,也可以是一个int型地址,注意后面的参数必须是元组否则会报错,即使只有一个参数也要写成元组的形式(在后面加上一个逗号)
  • chain() : 返回当前的字节序列,即payload
  • dump() : 直观地展示出当前的rop chain
  • raw() : 在rop chain中加上一个整数或字符串
  • search(move=0, regs=None, order=’size’) : 按特定条件搜索gadget。
  • unresolve(value) : 给出一个地址,反解析出符号

其他

对于整数的pack与数据的unpack,可以使用 p32/p64: 打包一个整数,分别打包为32或64位的字符串.

u32/u64:解包一个字符串,得到字符串对应整数 [有些不可打印字符的相互转换,或机器码写入时常用] p对应pack,打包,u对应unpack,解包: payload = p32(0xdeadbeef) # pack 32 bits number

另外,在utils工具中比较常用的就是可以使用cyclic pattern来找到return address的位置,这个功能在gbd peda中也是有的

数据输出方面,如果需要输出一些信息,最好使用pwntools自带的,因为和pwntools本来的格式吻合,用法:

some_str = "hello, world"
log.info(some_str)

其中的info代表是log等级,也可以使用其他log等级。

样例

from pwn import *
context(arch = 'i386', os = 'linux')

r = remote('exploitme.example.com', 31337)
# EXPLOIT CODE GOES HERE
r.send(asm(shellcraft.sh()))
r.interactive()

基本上仿造这个格式就可以写exp[即exploit]了。

from pwn import *

用来导入pwntools模块

context(arch = ‘i386’, os = ‘linux’)

设置目标机的信息:

context是pwntools用来设置环境的功能。在很多时候,由于二进制文件的情况不同,我们可能需要进行一些环境设置才能够正常运行exp,比如有一些需要进行汇编,但是32的汇编和64的汇编不同,如果不设置context会导致一些问题。

一般来说我们设置context只需要简单的一句话,比如context(os=’linux’, arch=’amd64’, log_level=’debug’)

这句话的意思是:

  1. os设置系统为linux系统,在完成ctf题目的时候,大多数pwn题目的系统都是linux

  2. arch设置架构为amd64,可以简单的认为设置为64位的模式,对应的32位模式是’i386’

  3. log_level设置日志输出的等级为debug,这句话在调试的时候一般会设置,这样pwntools会将完整的io过程都打印下来,使得调试更加方便,可以避免在完成CTF题目时出现一些和IO相关的错误。

r = remote(‘exploitme.example.com’, 31337)

用来建立一个远程连接,url或者ip作为地址,然后指明端口,这里也可以仅仅使用本地文件,调试时方便:

r = process(“./test”)

test即为文件名,这使得改变远程和本地十分方便.

asm(shellcraft.sh())

asm()函数接收一个字符串作为参数,得到汇编码的机器代码。 比如:

>>> asm('mov eax, 0')
'\xb8\x00\x00\x00\x00'

shellcraft.sh()则是执行/bin/sh的shellcode了

注:shellcraft模块是shellcode的模块,包含一些生成shellcode的函数。 其中的子模块声明架构,比如shellcraft.arm 是ARM架构的,shellcraft.amd64是AMD64架构,shellcraft.i386是Intel 80386架构的,以及有一个shellcraft.common是所有架构通用的。

r.send()

将shellcode发送到远程连接

r.interactive()

将控制权交给用户,这样就可以使用打开的shell了

二、peda

peda是Python Exploit Development Assistance for GDB[增强GDB的功能,比如在调试时自动显示寄存器信息,栈等] 极其不建议用pip安装,据说高版本的gdb不匹配pip安装的peda。[即使安装了,用pip list查看到了peda的包,gdb还是不识别peda的命令] git虽然慢,但是为了正常使用还是用git吧:

$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinit 

常用命令:

  • aslr – 显示/设定GDB的ASLR(地址空间配置随机加载)设置[aslr on 就是栈随机化开启]

  • checksec – 检查二进制文件的各种安全选项[包括NX,金丝雀等]

  • dumpargs – 函数将要被调用时,显示将要被传入函数的所有参数(默认会在反汇编代码下方自动显示)

  • dumprop – 在给定内存范围中Dump出所有ROP gadgets

  • elfheader – Get headers information from debugged ELF file

  • elfsymbol – 获取non-debugging symbol信息(plt表中的情况)

  • lookup – Search for all addresses/references to addresses which belong to a memory range

  • patch – Patch memory start at an address with string/hexstring/int

  • pattern – 生成字符串模板 写入内存 用于定位溢出点

  • pshow – Show various PEDA options and other settings

  • pset – Set various PEDA options and other settings

  • readelf – 获取elf头信息

  • ropgadget – Get common ROP gadgets of binary or library

  • ropsearch – Search for ROP gadgets in memory

  • searchmem find – 在内存中查找字符串,支持正则表达式
  • shellcode – 生成shellcode

    比如shellcdeo generate x86/linux exec

  • vmmap – 可以用来查看栈、bss段是否可以执行

  • xormem – XOR a memory region with a key
  1. https://pwntoolsdocinzh-cn.readthedocs.io/en/master/about.html)

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。

三、linux安装 ROPgadget及官方用法

git clone https://github.com/JonathanSalwan/ROPgadget
cd ROPgadget
sudo python setup.py install

Install


If you want to use ROPgadget, you have to install Capstone first.

For the Capstone’s installation on nix machine:

$ pip install capstone 或者 sudo pip install capstone

已安装过会提示:Requirement already satisfied: capstone in ./.local/lib/python2.7/site-packages (3.0.5)

Capstone supports multi-platforms (windows, ios, android, cygwin…). For the cross-compilation, please refer to the https://github.com/aquynh/capstone/blob/master/COMPILE.TXT file.

After Capstone is installed, ROPgadget can be used as a standalone tool:

$ ROPgadget.py

Or installed into the Python site-packages library, and executed from $PATH.

$ python setup.py install $ ROPgadget

Or installed from PyPi

$ pip install ropgadget $ ROPgadget

Usage


usage: ROPgadget.py [-h] [-v] [-c] [--binary <binary>] [--opcode <opcodes>]
				    [--string <string>] [--memstr <string>] [--depth <nbyte>]
				    [--only <key>] [--filter <key>] [--range <start-end>]
				    [--badbytes <byte>] [--rawArch <arch>] [--rawMode <mode>]
				    [--re <re>] [--offset <hexaddr>] [--ropchain] [--thumb]
				    [--console] [--norop] [--nojop] [--nosys] [--multibr]
				    [--all] [--dump]

optional arguments:
	    -h, --help   show this help message and exit
	    -v, --versionDisplay the ROPgadget's version
	    -c, --checkUpdateChecks if a new version is available
	    --binary <binary>Specify a binary filename to analyze
	    --opcode <opcodes>   Search opcode in executable segment
	    --string <string>Search string in readable segment  #!!!!
	    --memstr <string>Search each byte in all readable segment   #!!!!
	    --depth <nbyte>  Depth for search engine (default 10)
	    --only <key> Only show specific instructions  #!!!用“xxx | yyy”显示多个满足要求的
	    --filter <key>   Suppress specific instructions
	    --range <start-end>  Search between two addresses (0x...-0x...)
	    --badbytes <byte>Rejects specific bytes in the gadget's address
	    --rawArch <arch> Specify an arch for a raw file
	    --rawMode <mode> Specify a mode for a raw file
	    --re <re>Regular expression
	    --offset <hexaddr>   Specify an offset for gadget addresses
	    --ropchain   Enable the ROP chain generation
	    --thumb  Use the thumb mode for the search engine (ARM only)
	    --consoleUse an interactive console for search engine
	    --norop  Disable ROP search engine
	    --nojop  Disable JOP search engine
	    --callPreceded   Only show gadgets which are call-preceded (x86 only)
	    --nosys  Disable SYS search engine
	    --multibrEnable multiple branch gadgets
	    --allDisables the removal of duplicate gadgets
	    --dump   Outputs the gadget bytes

ROPgadget示例

~$ ROPgadget –binary test.out –only “pop ret”
Gadgets information
============================================================
0x00000000004008ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008b0 : pop r14 ; pop r15 ; ret
0x00000000004008b2 : pop r15 ; ret
0x00000000004008ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400700 : pop rbp ; ret
0x00000000004008b3 : pop rdi ; ret
0x00000000004008b1 : pop rsi ; pop r15 ; ret
0x00000000004008ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400601 : ret
0x0000000000400682 : ret 0x2009

跳转到0x00000000004008b3地址,就可以设置第一个函数参数,再ret到要去的函数。

阅读一下链接

一、一步一步学ROP之linux_x86篇

笔记:

gcc -fno-stack-protector -z execstack -o level1 level1.c

这个命令编译程序。-fno-stack-protector和-z execstack这两个参数会分别关掉DEP[堆栈不可执行]和Stack Protector[栈保护]

gdb调试目标程序,确定溢出点

正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp[exploit]的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响buf在内存中的位置,虽然我们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当我们直接执行./level1的时候,buf的位置会固定在别的地址上

最简单的方法就是开启core dump这个功能。

开启core dump

注意:第二行是设置产生core文件的存储路径,可以自行定义。

开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

gdb core文件

  • ./level1运行,段错误:产生core文件
  • gdb 二进制文件 core文件
  • 确定buffer首地址[shellcode要放置的位置,用于ret返回用]

因为溢出点是140个字节,再加上4个字节的ret地址,我们可以计算出buffer的地址为$esp-144。通过gdb的命令 “x/10s $esp-144”,我们可以得到buf的地址为0xbffff290

OK,现在溢出点,shellcode和返回值地址都有了,可以开始写exp了。

pwntools这个工具,因为它可以非常方便的做到本地调试和远程攻击的转换。本地测试成功后只需要简单的修改一条语句就可以马上进行远程攻击。

本地调试/远程攻击

最终本地测试代码如下:

最终code

执行exp:

执行

接下来我们把这个目标程序作为一个服务绑定到服务器的某个端口上,这里我们可以使用socat这个工具来完成,命令如下:

socat TCP4-LISTEN:10001,fork EXEC: ./level1

随后这个程序的IO就被重定向到10001这个端口上了,并且可以使用 nc 127.0.0.1 10001来访问我们的目标程序服务了。

因为现在目标程序是跑在socat的环境中,exp脚本除了要把p = process(‘./level1’)换成p = remote(‘127.0.0.1’,10001) 之外,ret的地址还会发生改变。解决方法还是采用生成core dump的方案,然后用gdb调试core文件获取返回地址。然后我们就可以使用exp进行远程溢出啦

即在socat环境下,gdb level1 /../../core

四、CheckSec工具

检测结果中的各种安全机制解析

1.RELRO

relro 是一种用于加强对 binary 数据段的保护的技术。relro 分为 partial relro 和 full relro

(1)Partial RELRO

现在gcc 默认编译就是 partial relro(很久以前可能需要加上选项 gcc -Wl,-z,relro)

  • some sections(.init_array .fini_array .jcr .dynamic .got) are marked as read-only after they have been initialized by the dynamic loader
  • non-PLT GOT is read-only (.got)
  • GOT is still writeable (.got.plt)

(2)Full RELRO

  • 拥有 Partial RELRO 的所有特性
  • lazy resolution 是被禁止的,所有导入的符号都在 startup time 被解析
  • bonus: the entire GOT is also (re)mapped as read-only or the .got.plt section is completely initialized with the final addresses of the target functions (Merge .got and .got.plt to one section .got). Moreover,since lazy resolution[延迟绑定] is not enabled, the GOT[1] and GOT[2] entries are not initialized.
  • GOT[0] is a the address of the module’s DYNAMIC section. GOT[1] is the virtual load address of the link_map, GOT[2] is the address for the runtime resolver function
  • 编译时需要加上选项 gcc -Wl,-z,relro,-z,now

其中-z 参数是把-z 后面的 keyword 传给linker ld

ld manual 中关于now 选项的解释 When generating an executable or shared library, mark it to tell the dynamic linker to resolve all symbols when the program is started, or when the shared library is linked to using dlopen, instead of deferring function call resolution to the point when the function is first called.

ld manual 中关于relro选项的解释 Create an ELF “PT_ GNU_ RELRO” segment header in the object.

(3)官方文档解释

Partial RELRO

Partial RELRO is the default setting in GCC, and nearly all binaries you will see have at least partial RELRO.

From an attackers point-of-view, partial RELRO makes almost no difference, other than it forces the GOT to come before the BSS in memory, eliminating the risk of a buffer overflows on a global variable overwriting GOT entries.

Full RELRO

Full RELRO makes the entire GOT read-only which removes the ability to perform a “GOT overwrite” attack, where the GOT address of a function is overwritten with the location of another function or a ROP gadget an attacker wants to run.

Full RELRO is not a default compiler setting as it can greatly increase program startup time since all symbols must be resolved before the program is started. In large programs with thousands of symbols that need to be linked, this could cause a noticable delay in startup time.

2.Fortify

fortify 技术是 gcc 在编译源码的时候会判断程序哪些buffer会存在可能的溢出,在 buffer 大小已知的情况下,GCC 会把 strcpy,memcpy 这类函数自动替换成相应的 __strcpy_chk(dst, src, dstlen)等函数[减少栈溢出漏洞]。GCC 在碰到以下四种情况的时候会采取不同的行为。

FORTIFY_SOURCE provides buffer overflow checks for the following functions:

memcpy, mempcpy, memmove, memset, strcpy, stpcpy, strncpy, strcat, strncat, sprintf, vsprintf, snprintf, vsnprintf, gets.

char buf[5]; //buf = malloc(5)也一样

/* 1) Known correct.
  No runtime checking is needed, memcpy/strcpy
  functions are called (or their equivalents inline).  */
memcpy (buf, foo, 5);
strcpy (buf, "abcd");

/* 2) Not known if correct, but checkable at runtime.
  The compiler knows the number of bytes remaining in object,
  but doesn't know the length of the actual copy that will happen.
  Alternative functions __memcpy_chk or __strcpy_chk are used in
  this case that check whether buffer overflow happened.  If buffer
  overflow is detected, __chk_fail () is called (the normal action
  is to abort () the application, perhaps by writing some message
  to stderr.  */
memcpy (buf, foo, n);
strcpy (buf, bar);

/* 3) Known incorrect.
  The compiler can detect buffer overflows at compile
  time.  It issues warnings and calls the checking alternatives
  at runtime.  */
memcpy (buf, foo, 6);
strcpy (buf, "abcde");

/* 4) 不知道 buf 的 size,就不会 check  */
memcpy (p, q, n);
strcpy (p, q);

gcc 中 -D_ FORTIFY_ SOURCE=2是默认开启的,但是只有开启 O2或以上优化的时候,这个选项才会被真正激活。

如果指定-D_ FORTIFY_ SOURCE=1,那同样也要开启 O1或以上优化,这个选项才会被真正激活。

可以使用-U_ FORTIFY_ SOURCE 或者-D_FORTIFY_SOURCE=0来禁用。

如果开启了-D_ FORTIFY_ SOURCE=2, 那么调用__ printf_ chk函数的时候会检查 format string 中是否存在%n,如果存在%n 而且 format string 是在一个可写的 segment 中的(不是在 read-only 内存段中),那么程序会报错并终止。如果是开启-D_FORTIFY_SOURCE=1,那么就不会报错。

➜  test_fortify ./fortify
aaa%n
*** %n in writable segment detected ***
aaa[1]   23468 abort    ./fortify

3.Canary

要检测对函数栈的破坏,需要修改函数栈的组织,在缓冲区和控制信息(如 EBP 等)间插入一个 canary word。这样,当缓冲区被溢出时,在返回地址被覆盖之前 canary word 会首先被覆盖。通过检查 canary word 的值是否被修改,就可以判断是否发生了溢出攻击

常见的 canary word:

Terminator canaries

由于绝大多数的溢出漏洞都是由那些不做数组越界检查的 C 字符串处理函数引起的,而这些字符串都是以 NULL 作为终结字符的。选择 NULL, CR, LF 这样的字符作为 canary word 就成了很自然的事情。例如,若 canary word 为 0x000aff0d,为了使溢出不被检测到,攻击者需要在溢出字符串中包含 0x000aff0d 并精确计算 canaries 的位置,使 canaries 看上去没有被改变。然而,0x000aff0d 中的 0x00 会使 strcpy() 结束复制从而防止返回地址被覆盖。而 0x0a 会使 gets() 结束读取。插入的 terminator canaries 给攻击者制造了很大的麻烦。

Random canaries

这种 canaries 是随机产生的。并且这样的随机数通常不能被攻击者读取。这种随机数在程序初始化时产生,然后保存在一个未被隐射到虚拟地址空间的内存页中。这样当攻击者试图通过指针访问保存随机数的内存时就会引发 segment fault。但是由于这个随机数的副本最终会作为 canary word 被保存在函数栈中,攻击者仍有可能通过函数栈获得 canary word 的值。

Random XOR canaries

这种 canaries 是由一个随机数和函数栈中的所有控制信息、返回地址通过异或运算得到这样,函数栈中的 canaries 或者任何控制信息、返回地址被修改就都能被检测到了

如何绕过 canary

  • 不覆盖 canary,只覆盖全局变量
  • 覆盖 canary,但是通过覆盖 canary 地址往上的函数参数、calling function 的栈帧等内容在函数 ret 之前 getshell
  • 改写__stack_chk_fail或者 exit的 got 表项,在 check canary 失败之后会调用这两个 libc 函数
  • 通过某种方式泄露出栈上的 canary 值,然后覆盖[格式化字符串漏洞]
  • 如果程序在 canary 被覆盖之后不会崩溃,而是重新运行,那么可以考虑每次覆盖 canary 的一个字节,一个字节一个字节的猜测 canary 的值[爆破]

五、NX

栈不可执行。NX由 CPU 提供支持,在页表中有Non-executable flag, 来限制一块内存区域不可执行。

绕过方法

mprotect():修改指定内存区域的执行权限

参考链接:软件常用安全防护手段 checksec 总结

六、补充

python脚本在linux环境下运行

  • 直接使用python xxxx.py执行。其中python可以写成python的绝对路径。使用which python进行查询。
  • 或在py脚本的头部(第一行)写上#!/usr/bin/python2.7,这个地方使用python的绝对路径[可用which python查看]。此后该脚本路径下就可使用./xxx.py直接执行了

System与execev的区别:

system =fork+execve+waitpid,是在单独的进程中执行命令,完了还会回到你的程序中【像是当前父进程中断了,先在子进程中运行你的程序/命令,等待子进程运行完成才继续运行父进程中system之后的代码[子进程还是复制了父进程的栈等内存]】。而exec函数是直接在你的进程中执行新的程序,新的程序会把你的程序覆盖,除非调用出错,否则你再也回不到exec后面的代码,就是说你的程序就变成了exec调用的那个程序了。

Gdb 中查看内存的命令:

x/<n/f/u> <addr> n、f、u是可选的参数。

  • n是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。

  • f 表示显示的格式。

	> x 按十六进制格式、d 按十进制格式、u 按十六进制格式无符号整型、o 按八进制格式、t 按二进制格式、a 按十六进制格式、c 按字符格式、f 按浮点数格式……显示变量。
	
	> 如果地址所指的是**字符串,那么格式可以是s**,如果地址是**指令地址,那么格式可以是i**。
- u 表示从当前地址往后请求的字节数,**如果不指定的话,GDB默认是4个bytes**。
	
	> u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字 节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
-  < addr >表示一个内存地址。

**例如:**
命令:x/3uh 0x54320 
表示从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十六进制显示。

execl()系统调用

execl()其中后缀”l”代表list也就是参数列表的意思,第一参数path字符指针所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:argv[0],argv[1]… 最后一个参数须用空指针NULL作结束。


参考链接:

  1. http://brieflyx.me/2015/python-module/pwntools-intro/
  2. https://blog.csdn.net/qq_29343201/article/details/51337025
  3. https://blog.csdn.net/smalosnail/article/details/53149426
  4. pwntools 官方文档
  5. 一步一步学ROP之linux_x64篇 或https://blog.csdn.net/zsj2102/article/details/78560300
  6. 一步一步学ROP之gadgets和2free篇 – 蒸米

其他

1.开启NX

linux下开启了NX的以及windows下开启了DEP的程序堆栈是不可执行的

2.数据执行保护(Data Execution Prevention, DEP)安全机制

让攻击者注入的Shellcode无法执行

3.ASLR

ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置

4.linux _64与linux _86的区别

linux_64与linux_86的区别主要有两点:

  • 首先是内存地址的范围由32位变成了64位。但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常
  • 其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上。

由于参数不能直接溢出放在栈中,而是要存储在寄存器,那么就需要搜索有用的指令,用于设置参数比如pop %edi,mov %xxx,%esi等。

这时候ROPgadgets开源工具就派上用场了,它可以用于搜索你所需要的rop链。官方的说法是:This tool lets you search your gadgets on your binaries to facilitate your ROP exploitation.【就好像objdump -d test.out grep pop 这样,但是ROPgadgets能搜索的节更多,用途更广更便捷】