-
-
[原创]看雪.京东 2018CTF 第九题 PWN-羞耻player Writeup
-
2018-7-4 03:22 2824
-
后面有时间再修改更新下,里面可能有错误。
这是一个堆漏洞的利用题,c++编写。漏洞点有三处:一个逻辑错误导致的UAF:新申请的堆先赋值给了指针,再释放指针指向的旧堆,实际是释放了新申请的堆,释放与赋值的顺序颠倒出现了悬挂指针,UAF得以实现,但是由于PIE,不能单独直接利用;二是没有检查输入的表示大小的数为负数,导致heap overfolw;三是打印函数多打印一个字节,可以按字节泄露信息。
##程序分析
先checksec:
[*] '/usr/lib/x86_64-linux-gnu/libstdc++.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
程序运行情况如下:
# # # # #### ####### ####### # # # # # # # # # # ###### # #### # # # # ##### # # # ### # # ## # # # # # # # # ##### # # # # # # # # # # ####### # # # # # # ## # # # # # ## # # # # # # # #### # # # # # #### # ##### #### # # Welcome to KanXue CTF! Please enter your Recording Name? pediy 1. Add Clip 2. Edit Clip 3. Play Clip 4. Remove Clip >>> 1 Clip Adding 1. Video Clip 2. Audio Clip 3. Subtitle Clip 4. Metadata Clip >>>
程序模拟了音视频播放器,包括添加、修改、播放、删除包括视频、音频及其相关信息的功能,而且都是通过类完成编写,实际功能类都继承于同一个父类,父类定义大致如下:
class Clip{ public: virtual void clip_add(){ return;} virtual void clip_edit(){return;} virtual void clip_remove(){return;} virtual void clip_play(){return;} };
然后定义了4个子类Metadata
,SubClip
,AudioClip
和VideoClip
,vtable
在偏移203BD0
处。
漏洞点在VideoClip
的clip_edit
函数中:
if ( a1->frame_num > 0x400u ) a1->frame_num = 1024; pheap = (char *)operator new[]((unsigned int)a1->frame_num); if ( !pheap ) exit(1); a1->p_video_data = pheap; // uaf if ( a1->p_video_data ) operator delete[](a1->p_video_data); std::operator<<<std::char_traits<char>>(&std::cout, "Video Data : "); a1->frame_num = read(0, a1->p_video_data, (unsigned int)a1->frame_num);
信息泄露可以利用播放功能。VideoClip
和AudioClip
的clip_play
函数能打印经异或编码后的出信息:
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Playing video..."); std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>); for ( i = 0; a1->frame_num >= i; ++i ) std::operator<<<std::char_traits<char>>(&std::cout, (unsigned int)(char)(a1->p_video_data[i] ^ 0xCC)); return std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>); for ( i = 0; a1->length >= i; ++i ) std::ostream::operator<<(&std::cout, (unsigned __int8)(a1->p_audio_data[i] ^ 0x55)); return std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>);
在play
这里,也有问题,多打印了一个字节。
程序用到的数据结构如下:
00000000 struc_video struc ; (sizeof=0x50, align=0x8, mappedto_7) 00000000 p_method dq ? ; offset 00000008 Resolution dq ? 00000010 fps dd ? 00000014 frame_num dd ? 00000018 p_video_data dq ? ; offset 00000020 description db 48 dup(?) 00000050 struc_video ends 00000050 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 struc_audio struc ; (sizeof=0x48, align=0x8, mappedto_8) 00000000 p_method dq ? ; offset 00000008 bitrate dd ? 0000000C length dd ? 00000010 p_audio_data dq ? ; offset 00000018 description db 48 dup(?) 00000048 struc_audio ends 00000048 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 struc_subtitle struc ; (sizeof=0x18, align=0x8, mappedto_9) 00000000 p_method dq ? 00000008 lang dd ? 0000000C length dd ? 00000010 p_subtitle dq ? 00000018 struc_subtitle ends 00000018 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 struc_meta struc ; (sizeof=0x48, align=0x8, mappedto_10) 00000000 p_method dq ? 00000008 date db 32 dup(?) 00000028 owner db 32 dup(?) 00000048 struc_meta ends
其中的p_method
指向各自的vtable
。
另外程序开始还申请了256随机大小的堆并进行了随机释放。
利用思路
先跑空bins,然后利用fastbin进行堆地址的泄露,再利用small bin进行libc基址的泄露。
然后进行堆喷射将one_gadget
地址有效覆盖堆。
再结合SubClip
的clip_add
功能改写vtable
指针。SubClip
的clip_add
功能中,除了能读取负数的大小外,其还有复制已经存在的subtitle内容的功能。代码如下:
if ( a1->p_subtitle ) { len = 0; dest = 0LL; std::operator<<<std::char_traits<char>>(&std::cout, "Subtitle Length : "); if ( read(0, &len, 4uLL) <= 0 ) exit(1); if ( a1->length + len > 0x400 ) len = 1024 - a1->length; dest = (void *)operator new[](a1->length + len); if ( !dest ) exit(1); std::operator<<<std::char_traits<char>>(&std::cout, "Add Subtitle : "); memcpy(dest, (const void *)a1->p_subtitle, (unsigned int)a1->length); if ( read(0, (char *)dest + (unsigned int)a1->length, len) <= 0 ) exit(1); if ( a1->p_subtitle ) operator delete[]((void *)a1->p_subtitle); a1->p_subtitle = dest; } else { std::operator<<<std::char_traits<char>>(&std::cout, "Subtitle Language : "); if ( read(0, &a1->lang, 4uLL) <= 0 ) exit(1); std::operator<<<std::char_traits<char>>(&std::cout, "Subtitle Length : "); if ( read(0, &a1->length, 4uLL) <= 0 ) exit(1); if ( a1->length > 0x400u ) a1->length = 1024; a1->p_subtitle = operator new[]((unsigned int)a1->length); if ( !a1->p_subtitle ) exit(1); std::operator<<<std::char_traits<char>>(&std::cout, "Add Subtitle : "); if ( read(0, (void *)a1->p_subtitle, (unsigned int)a1->length) <= 0 ) exit(1); }
这两个功能配合就能使得堆溢出,从而覆写vtable
,最后get shell。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界