之前用laravel的时候,对于laravel自带的ORM,也就是Eloquent,一直都有一点很不爽,那就是所有的字段对应过来的属性全部都是字符串,这一点真的是让人很不爽,本来我就对弱类型感觉很蛋疼,觉得很容易出事,结果这地方还非要弄个和心里期望类型不一致。

当然,这个问题,如果硬要做也是可以通过getter/setter来做,但麻烦不说,指不定哪地方laravel又会没按照getter/setter来取,那就只能呵呵了……

然后,最近在laravel上看文档时,突然发现laravel5新加了一个casts特性,通过这个特性,我们相当于是显式指定了属性的类型,laravel在内部存取属性的时候,会自动帮我们把类型转换做好,这样我们就不用担心数据类型和心里预期不一致了,可以放心大胆的用===做比较了。

然后查了下laravel源代码,发现类型转换是下面这段:

/**
 * Cast an attribute to a native PHP type.
 *
 * @param  string  $key
 * @param  mixed   $value
 * @return mixed
 */
protected function castAttribute($key, $value)
{
    if (is_null($value)) return $value;

    switch ($this->getCastType($key))
    {
        case 'int':
        case 'integer':
            return (int) $value;
        case 'real':
        case 'float':
        case 'double':
            return (float) $value;
        case 'string':
            return (string) $value;
        case 'bool':
        case 'boolean':
            return (bool) $value;
        case 'object':
            return json_decode($value);
        case 'array':
        case 'json':
            return json_decode($value, true);
        case 'collection':
            return $this->newCollection(json_decode($value, true));
        default:
            return $value;
    }
}

通过这段我们也可以很清楚的发现支持指定了哪些类型。

然后本来以为这样就完事了,结果没想到今天突然发现了一个神奇的现象,到现在也没能想明白是怎么回事。我本地在没有加上casts的情况下,取出来的数据竟然自动识别了整数类型,然后刚感叹完竟然能自动识别了,放服务器上跑了下,立马就又打回原形了。本来还以为是laravel版本不同,结果看了下composer.lock,发现完全一致,都是v5.0.27,于是就惊呆了。然后又查了一下php版本,两个确实还不一样,本地是5.5.17,服务器是5.5.9-1ubuntu4.9,但不太能想象这会是PHP版本导致的不同,毕竟要知道数据类型必须是从数据库中查询出来。

在思前想后没想明白的情况下,又试了一下,如果我在本地,给一个被自动识别出是int的字段赋值为一个字符串,还是不会自动转为int,于是觉得还是显式用casts来指定类型的好,这样不管怎么操作,最后都保证了数据类型确定,然后返回的json中也不至于出现一下是字符串,一下是数字了。

总之,这个特性简直不能更喜欢,现在再剩下的类型蛋疼的地方就是输入的参数了,也不知道会不会哪天也能有好的解决方案~~~

说句实在话,这次ACTF在开始之前,我真的很担心,很怕这次的结果不好,进不了决赛,毕竟想进决赛想了很久了。然后比赛刚开始的几个小时,看到放出来的几个题,我更是觉得这次要玩完,真的是怎么也没想到,最后竟然能拿到第一,连我自己都不敢相信。

这次的比赛,真的是不禁让人想吐槽shenmegui,无论是从比赛题目还是管理上,无疑都是整个XCTF联赛中最烂的一次。虽然比赛开始前,lym就跟我们预告不要对这次比赛报太大的希望,结果真是果不其然,甚至是比想象中还要惨。比赛一上来先是登录不了,整个比赛推迟半小时开始,本来说第一天晚上补,结果到时间竟然把服务器关了,说忘了,然后比赛过程中更是各种人为错误一大堆,然后最最神奇的是,竟然有道web题说因为管理员失误导致难度降低,做出来的队回收了积分(虽然好像回收了对我们队有利,从不会做web飘过),然后hint完全乱放,都是在有队做出来之后放,这明显不公平吧……

然后再来说下比赛题目,签到题就给人与众不同的感觉,应该是见过分值最高的签到题了吧,然后也确实是比其它签到题难想,但是签到题的flag让人看着就感觉不好了,竟然直接表明这是一场Misc CTF,还好不是Web CTF,不然真的就要死无全尸了!然后这次题目真是各种神奇啊,各种信号题,然后还有什么邮箱提交解答方法的人工审核题(果断到最后也没人做),结果,就这种奇葩题目,竟然除了人工审核题和一道有前置条件的题,每题都有人过,我也是醉了。

这次能拿第一,关键还是4G那道信号题了,全场唯一过,虽然做出来的队友各种吐槽这题不难啊,还表示应该有队做到最后一步了,然后最后一步又不难,怎么会做不出,但终究还是靠着这题拿到了第一。开场也是靠信号题开的场了,本来是毫无突破口,结果队友突然就跟打了鸡血似的,把那道我看都不想看的信号题给做出来了,然后又没多久,另一个队友又把4G给过了,一下子就跑到了前面,然后我就在那个Secret FS上各种纠结,各种猜题意,直到大概7点才好不容易确定题意,然后蛋疼了半天过了第一关,发现后面个数增多后,之前的想法完全没法完,然后想出了不错的解法后,几乎整个程序推倒重写,然后各种bug,结果中途还被老板拉着扯公司的破事,简直头大。然后果断导致没能在第一天结束前搞定,于是第一天结束前爬上第一挂一个晚上的愿望就这么破灭了(因为以为只有这样才有可能在第一的位置多挂几个小时)。总而言之,第一天我是打了一圈酱油,上午各种无事可做,然后一天也没能做出一题,签到除外(话说这还是我第一次过签到)。

然后晚上真是一晚上没睡好,各种失眠,第二天早早侯着开场,然后稍微再调了下后就过掉了FS,成功爬到第一的位置,为此我还专门截了图,因为当时是估计难得再能有第二次。然后再第一天结束后,队友也是发现了Cube Game的点,我再当时又无事可做的情况下过去帮忙,倒腾了会后成功搞定,继续扩大优势。接着发现Dice Game放出后没多久就被各种队过掉,于是果断过去看,看完感觉果然很水,暴力找下随机种子就好,虽然还被Python3的问题坑了,已经各种蛋疼的小坑,最终还是在N个小时的折腾后搞定(不过刚竟然听说,这题有问题,乱发东西就可以轻松随便水过,也是醉了)。然后又是各种蛋疼的啥也不会,直到晚上换口味的猪头提示越放越多才终于搞定。

然后之后就再也没能过题了,于是乎,其实直到比赛结束前最后一秒,都还在各种担心会不会被人绝杀,因为到最后我们基本已经弃疗,脑洞题也是实在脑洞不出来,而看后面紧跟着的队,差的是感觉很可能会被做出来的题,真的就是十分担心,毕竟那个时候心态已经变了,看到了拿第一的希望,不愿意就这样失去这个绝佳的机会(大神们各种不在!!!)。还好最后是上天保佑,没有被绝杀。

这次比赛应该说是hctf那场之后,最闲的一场比赛了,反正各种不会,会的又都过掉了,除了蛋疼就只能蛋疼。

这次比赛,相比于之前的比赛,最让我感到欣慰的是,终于不再是我和某队友两人过题,变成了三人过,虽然由于几道题最后一步是我来做了,所以flag还是由我交了,但是毕竟谁做了多少大家心里都清楚,这次的比赛才更有团队的感觉。

然后,不得不说的是,web题还是一如既往的一道也没能搞定,真的就是哭出来啊!

从只是为了进决赛不得不过来挣扎一下,到现在拿到第一,真的就是说不出的感觉,可以说,如果不是因为怕出不来线,我这场很可能就不会来做了,后天和大后天就要考算法和概率论,半个学期没听课,真的是不知道怎么去救,然后还有什么英语期中作文要写,感觉自己都要疯了。

就先说这么多吧,然后至于WriteUp,投稿了360安全播报,给team弄点外快,但愿别被大神喷就好。不过看到群里说放的早了也是有点无奈,通知也没说不能放啊,于是写完了就直接投了,从没弄过不清楚,也就SCTF那次赶早发过一次Code500的WriteUp(但那次官方给了时间点),再就都是悠哉悠哉慢慢写了。然后过几天考试考完了,可能会在博客里补点详细的解题过程,做个笔记。

不得不说,这道题真的感觉很坑,浪费了很多时间也无济于事,只因为完全没往正确的方向走,真的就是方向选对了就超水,也就是导致这题分值如此之低的原因,然而方向不对根本察觉不过来。

题目给了一个加密的字符串和一个public key,但是对于RSA而言,我们知道,其实公钥和私钥完全就是在有哪些人应该知道这一点理解上的一个概念而已,其实完全是可以反过来的,于是我们想会不会是用这个公钥来解密这个字符串,甚至怀疑过会不会是别的加密而不是RSA,也就是这样,我们在错误的路上彻底的回不来了。直到最后比赛结束后,被指点了一下之后才总算是知道了这题怎么做。

其实这题做不出的关键还是在于,我们的心中一直是本能的记住,RSA是不可以由公钥推算私钥的,这是RSA的基础。然而,其实有些情况下,是可以有效攻击RSA的,就比如这题,这题公钥和我们平常见到的公钥完全不像,特别大,与n位数差不多了,虽然比赛的时候就觉得很奇怪,但也没多想,就是因为这一点,导致这题RSA可以被攻击了,推算出私钥,至于具体的攻击代码,github上随便一搜就能找到了,这里用Wiener攻击可以破掉。

首先我们用openssl来解析说给公钥:

openssl rsa -in warmup-c6aa398e4f3e72bc2ea2742ae528ed79.pub -pubin -text

得到结果如下:

Modulus (2050 bit):
    03:67:19:8d:6b:56:14:e9:58:13:ad:d8:f2:2a:47:
    17:bc:72:be:1e:ab:d9:33:d1:b8:69:44:fd:b7:5b:
    8e:d2:30:be:62:d7:d1:b6:9d:22:20:95:c1:28:c8:
    6f:82:01:2e:cb:11:61:91:fd:9d:01:8a:6d:02:f8:
    4d:b2:7b:c5:1a:21:30:7d:c8:6f:4b:f7:71:c6:91:
    c1:43:e5:ab:e5:49:b5:bd:2d:6e:b1:a2:1f:d6:27:
    0e:7e:1b:48:fe:06:11:fb:b2:e1:b0:b3:52:4e:6f:
    4d:e8:b4:e4:a3:45:da:44:a1:3d:e8:25:b7:26:08:
    db:6c:7c:4a:40:b7:82:66:e6:c8:7b:bf:de:f6:b4:
    83:81:d4:9c:45:07:a5:8b:cd:47:b7:6d:64:b4:59:
    08:b1:58:bd:7e:bc:4d:ac:b0:b1:cf:d6:c2:c1:95:
    74:f4:0e:b2:ef:d0:e9:e1:0d:c7:00:5c:ad:39:bc:
    af:52:b9:ea:c3:87:33:68:d6:90:31:c5:e7:24:68:
    4a:44:f0:68:ef:d1:d3:dc:09:6d:9b:5d:64:11:e5:
    8b:de:e4:3e:46:b9:9a:0d:04:94:b9:db:28:19:5a:
    f9:01:af:f1:30:d4:a6:e2:03:da:d0:8d:a5:7f:a7:
    e4:02:62:a5:ba:db:2a:32:3e:da:28:b4:46:96:ab:
    30:5d
Exponent:
    00:f3:95:9d:97:8e:02:eb:9f:06:de:f3:f3:35:d8:
    f8:af:d7:60:99:51:dd:ac:60:b7:14:b6:c2:2a:f0:
    fa:91:2f:21:0b:34:20:6b:d2:4a:96:01:c7:8d:f4:
    a0:27:5f:10:7f:d3:ab:55:2d:95:05:7e:b9:34:e7:
    1b:dd:cd:70:45:c2:4b:18:58:7b:8c:8f:cf:5a:dd:
    4c:5d:83:f0:c7:7c:94:dc:9c:50:cb:e4:38:e2:b6:
    7b:af:d3:16:33:b6:aa:f1:78:1d:90:c3:ad:6f:03:
    d0:37:b3:32:18:01:b2:35:46:d4:83:e6:7e:26:06:
    7f:7b:22:34:7d:db:c0:c2:d5:92:ce:81:4c:bf:5d:
    fc:cc:14:14:37:f1:4e:0b:39:90:f8:80:61:e5:f0:
    ba:e5:f0:1e:3f:a7:0d:b0:e9:60:5e:7c:fd:57:5e:
    9c:81:ef:ee:c5:29:c3:3f:d9:03:7a:20:fd:8a:cd:
    51:3a:c9:63:77:68:31:3e:63:f9:83:8a:e3:51:1c:
    dd:0a:9a:2b:51:6f:21:48:c8:d4:75:a3:60:a0:63:
    59:44:97:39:ee:cd:25:1a:bb:42:b0:14:57:3e:43:
    9f:2f:a4:57:35:57:b2:56:99:ff:c1:1e:63:1c:e8:
    ee:97:5a:86:e7:e2:72:bc:f5:f7:6a:93:45:03:48:
    fe:3f
writing RSA key
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAQEDZxmNa1YU6VgTrdjyKkcX
vHK+HqvZM9G4aUT9t1uO0jC+YtfRtp0iIJXBKMhvggEuyxFhkf2dAYptAvhNsnvF
GiEwfchvS/dxxpHBQ+Wr5Um1vS1usaIf1icOfhtI/gYR+7LhsLNSTm9N6LTko0Xa
RKE96CW3JgjbbHxKQLeCZubIe7/e9rSDgdScRQeli81Ht21ktFkIsVi9frxNrLCx
z9bCwZV09A6y79Dp4Q3HAFytObyvUrnqw4czaNaQMcXnJGhKRPBo79HT3Altm11k
EeWL3uQ+RrmaDQSUudsoGVr5Aa/xMNSm4gPa0I2lf6fkAmKlutsqMj7aKLRGlqsw
XQKCAQEA85Wdl44C658G3vPzNdj4r9dgmVHdrGC3FLbCKvD6kS8hCzQga9JKlgHH
jfSgJ18Qf9OrVS2VBX65NOcb3c1wRcJLGFh7jI/PWt1MXYPwx3yU3JxQy+Q44rZ7
r9MWM7aq8XgdkMOtbwPQN7MyGAGyNUbUg+Z+JgZ/eyI0fdvAwtWSzoFMv138zBQU
N/FOCzmQ+IBh5fC65fAeP6cNsOlgXnz9V16cge/uxSnDP9kDeiD9is1ROsljd2gx
PmP5g4rjURzdCporUW8hSMjUdaNgoGNZRJc57s0lGrtCsBRXPkOfL6RXNVeyVpn/
wR5jHOjul1qG5+JyvPX3apNFA0j+Pw==
-----END PUBLIC KEY-----

得到n和e之后,用Wiener攻击,我们可以得到私钥d为:

4221909016509078129201801236879446760697885220928506696150646938237440992746683409881141451831939190609743447676525325543963362353923989076199470515758399

然后接下来就是简单用c ^ d % n来解密了,最后,写的计算脚本如下:

#!/usr/bin/env python
#encoding:utf-8

import Wiener

c = 0x1e04304936215de8e21965cfca9c245b1a8f38339875d36779c0f123c475bc24d5eef50e7d9ff5830e80c62e8083ec55f27456c80b0ab26546b9aeb8af30e82b650690a2ed7ea407dcd094ab9c9d3d25a93b2140dcebae1814610302896e67f3ae37d108cd029fae6362ea7ac1168974c1a747ec9173799e1107e7a56d783660418ebdf6898d7037cea25867093216c2c702ef3eef71f694a6063f5f0f1179c8a2afe9898ae8dec5bb393cdffa3a52a297cd96d1ea602309ecf47cd009829b44ed3100cf6194510c53c25ca7435f60ce5f4f614cdd2c63756093b848a70aade002d6bc8f316c9e5503f32d39a56193d1d92b697b48f5aa43417631846824b5e86

n_str = '''
    03:67:19:8d:6b:56:14:e9:58:13:ad:d8:f2:2a:47:
    17:bc:72:be:1e:ab:d9:33:d1:b8:69:44:fd:b7:5b:
    8e:d2:30:be:62:d7:d1:b6:9d:22:20:95:c1:28:c8:
    6f:82:01:2e:cb:11:61:91:fd:9d:01:8a:6d:02:f8:
    4d:b2:7b:c5:1a:21:30:7d:c8:6f:4b:f7:71:c6:91:
    c1:43:e5:ab:e5:49:b5:bd:2d:6e:b1:a2:1f:d6:27:
    0e:7e:1b:48:fe:06:11:fb:b2:e1:b0:b3:52:4e:6f:
    4d:e8:b4:e4:a3:45:da:44:a1:3d:e8:25:b7:26:08:
    db:6c:7c:4a:40:b7:82:66:e6:c8:7b:bf:de:f6:b4:
    83:81:d4:9c:45:07:a5:8b:cd:47:b7:6d:64:b4:59:
    08:b1:58:bd:7e:bc:4d:ac:b0:b1:cf:d6:c2:c1:95:
    74:f4:0e:b2:ef:d0:e9:e1:0d:c7:00:5c:ad:39:bc:
    af:52:b9:ea:c3:87:33:68:d6:90:31:c5:e7:24:68:
    4a:44:f0:68:ef:d1:d3:dc:09:6d:9b:5d:64:11:e5:
    8b:de:e4:3e:46:b9:9a:0d:04:94:b9:db:28:19:5a:
    f9:01:af:f1:30:d4:a6:e2:03:da:d0:8d:a5:7f:a7:
    e4:02:62:a5:ba:db:2a:32:3e:da:28:b4:46:96:ab:
    30:5d
'''

e_str = '''
    00:f3:95:9d:97:8e:02:eb:9f:06:de:f3:f3:35:d8:
    f8:af:d7:60:99:51:dd:ac:60:b7:14:b6:c2:2a:f0:
    fa:91:2f:21:0b:34:20:6b:d2:4a:96:01:c7:8d:f4:
    a0:27:5f:10:7f:d3:ab:55:2d:95:05:7e:b9:34:e7:
    1b:dd:cd:70:45:c2:4b:18:58:7b:8c:8f:cf:5a:dd:
    4c:5d:83:f0:c7:7c:94:dc:9c:50:cb:e4:38:e2:b6:
    7b:af:d3:16:33:b6:aa:f1:78:1d:90:c3:ad:6f:03:
    d0:37:b3:32:18:01:b2:35:46:d4:83:e6:7e:26:06:
    7f:7b:22:34:7d:db:c0:c2:d5:92:ce:81:4c:bf:5d:
    fc:cc:14:14:37:f1:4e:0b:39:90:f8:80:61:e5:f0:
    ba:e5:f0:1e:3f:a7:0d:b0:e9:60:5e:7c:fd:57:5e:
    9c:81:ef:ee:c5:29:c3:3f:d9:03:7a:20:fd:8a:cd:
    51:3a:c9:63:77:68:31:3e:63:f9:83:8a:e3:51:1c:
    dd:0a:9a:2b:51:6f:21:48:c8:d4:75:a3:60:a0:63:
    59:44:97:39:ee:cd:25:1a:bb:42:b0:14:57:3e:43:
    9f:2f:a4:57:35:57:b2:56:99:ff:c1:1e:63:1c:e8:
    ee:97:5a:86:e7:e2:72:bc:f5:f7:6a:93:45:03:48:
    fe:3f
'''

n = int(n_str.replace('n', '').replace(' ', '').replace(':', ''), 16)
e = int(e_str.replace('n', '').replace(' ', '').replace(':', ''), 16)

attacker = Wiener.Wiener(n, e)
attacker.crack()

decrypted = pow(c, attacker.d, n)
print hex(decrypted)[2:-1].decode('hex')

这题就这样轻松的解决了,但这题也真的是给了我们不小的教训,有时候思维定势真的很恐怖,其实稍微换个思路就会是柳暗花明。

看到这题的题目确实是让我有点不敢相信,各种以为是自己理解错了题目的意思,一开始真的很难相信,程序可以这么玩……

我们先看看题目描述:

Different people see different me.
But I am always myself.
202.112.26.114:12321
Make the output of your program exactly the same as your source code.
All 5 correct required to get this flag
$python2 --version
Python 2.7.6
$python3 --version
Python 3.4.0
$gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ruby --version
ruby 1.9.3p484 (2013-11-22 revision 43786) [x86_64-linux]
$perl --version
This is perl 5, version 18, subversion 2 (v5.18.2) built for x86_64-linux-gnu-thread-multi

想要一个程序同时支持python2、python3、c、ruby、perl感觉就已经够难的了,竟然还要求每个程序的输出都是自身!

然后这题还有个简化版本,只要支持其中任何三种语言就好,好吧,不得不说这看起来一点也不简单,对于某些想要自己写的队友而言……

当然,这种题其实果断应该是Google大法好,最后队友在网上找到了这个,同时支持C、Ruby、Python、Perl、Brainfuck,于是果断手动将Brainfuck去掉(其实本来就有个不带Brainfuck的版本),然后交上去试了下,过了4个,但是python3过不了,然后拿python3跑下了,发现是语法问题,“print a%b,”这句在py3里显然是过不了的,加上括号改成形式的结局就是最后会多一个空行,此时py2、py3都是一样了。

于是问题来了,怎么解决这个空行的问题呢?

我当时的想法是,首先我们发现def printf那里是单独为python写的一段代码,于是乎我们就可以在这里做文章了,它整个代码的关键是第七行最后那一个printf的调用,而这里为python定义的printf也只是在那里调用了,于是,果断在这里截取字符串和参数,使得在最后的换行不输出,从而弥补print本身带来的换行,于是这题就这么愉快的过掉了,最后得到的程序在此,这里是用的那个本来就有个不带Brainfuck的版本改的,因为会短点,改完后一共420个字符

然后后来又听说了一个更好想的想法,也是对自己当时根本没往这里想醉了:既然print会多输出一个换行,那么我们用stdout的write不就好了么!

感觉这题就是很好的说明了Google的重要性,两题一共400分,要是自己写,只怕写到比赛结束也不一定能写出来了。

这几次比赛下来,真的是深刻的让我感受到了自己逆向水平的不足,这次的0ctf也更是好不容易在第二天下午才发现这题怎么做,才总算没有在逆向题上无功而返。

这道题说实在的,确实是挺简单的,不过很麻烦倒是真的,唯独的一个问题就是不能用Hex-rays来看,反编译得到的代码是有问题的。

signed __int64 __usercall handle@<rax>(__int64 a1@<rbp>)
{
  __int64 *src; // rsi@1
  __int64 *dst; // rdi@1
  signed __int64 i; // rcx@1

  *(_DWORD *)(a1 - 4) = accept(3, 0LL, 0LL);
  recv(*(_DWORD *)(a1 - 4), save_flag, 0x1000uLL, 0);
  close(*(_DWORD *)(a1 - 4));
  src = qword_E0B00A0;
  dst = qword_E0AF0A0;
  for ( i = 0x200LL; i; --i )
  {
    *dst = *src;
    ++src;
    ++dst;
  }
  return 0xE0AF8A0LL;
}

处理部分反编译代码如上,我们会发现非常奇怪的是,这里做了一个字符串固定字符串的复制之后接返回了,什么事都没做啊,遇到这种情况当然得看看汇编代码,但不知道自己眼睛怎么长的,最开始一直没有看到问题……

然后,我还尝试通过strings往回找flag相关部分,发现确实是有输出flag的地方,可完全不知道从哪里跳过来的,然后发现了一段乱七八糟的代码片段,每个片段都很短,如下:

.text:000000000DEAD1E4 jmp     short loc_DEAD1E8
.text:000000000DEAD1E6 ; ---------------------------------------------------------------------------
.text:000000000DEAD1E6 jp      short loc_DEAD181
.text:000000000DEAD1E8
.text:000000000DEAD1E8 loc_DEAD1E8:                            ; CODE XREF: .text:000000000DEAD1E4
.text:000000000DEAD1E8 pop     rax
.text:000000000DEAD1E9 retn

看起来似乎000000000DEAD1E6那里有2个无意义的字节,问题肯定就在这些代码片段里了,然后还发现,程序复制的那个字符串里中间有些部分看起来是一些指向这些代码片段的指针,但还是没有什么解题的想法。

最后,某次突然在这段汇编代码的最后部分发现了问题:

.text:000000000DEAD40E mov     eax, offset save_flag
.text:000000000DEAD413 mov     rdi, rax
.text:000000000DEAD416 mov     eax, offset unk_E0B20C0
.text:000000000DEAD41B mov     rsi, rax
.text:000000000DEAD41E mov     eax, (offset qword_E0AF0A0+800h)
.text:000000000DEAD423 mov     rsp, rax
.text:000000000DEAD426 retn
.text:000000000DEAD426 handle end

仔细一看,这里000000000DEAD423这一行竟然把rsp给改了,我勒个去,这是直接换栈了,那这下就啥都说的通了,它复制的那个字符串里面都是他提前设定好的地址,然后那些代码片段最后就通过ret来返回到特定的地方,一句话说就是以ROP的方式在运行程序,只能说感觉我也是醉了。

知道方法后,做起来也是麻烦,没有什么好的办法,只能一点点的手动把代码扒出来。

首先,先是把那个字符串中看起来是需要的部分拎出来:

arr = [233492980, 8, 233493105, 1384820248253759637, 233492771, 233492717, 233492996, 233493095, 233492728, 233492739, 233492717, 233493114, 233493006, 233492728, 233492972, 51966, 233492801, 233492717, 233492996, 233493124, 233492728, 233492717, 233493114, 233493006, 233492728, 233492972, 48879, 233492781, 233492717, 233492996, 233493124, 233492728, 233493192, 1, 233493134, 13337, 233492964, 0, 233492972, 0, 233492988, 472, 233492891, 233492717, 233493143, 233493182, 233492728, 233492717, 233493172, 233493006, 233492728, 233492972, 1, 233492851, 233492717, 233492996, 233493182, 233492728, 233492717, 233493172, 233493006, 233492728, 233492972, 1, 233492988, 104, 233492906, 233492717, 233493201, 233493006, 233492728, 233492717, 233493085, 233493026, 233492728, 233492801, 233492717, 233492996, 233493211, 233492728, 233492717, 233493085, 233493006, 233492728, 233492717, 233493085, 233493026, 233492728, 233492801, 233492717, 233492996, 233493095, 233492728, 233492717, 233493143, 233493006, 233492728, 233492881, 233492717, 233492996, 233493153, 233492728, 233492717, 233493143, 233493006, 233492728, 233492972, 0, 233492988, 18446744073709551072L, 233492906, 233492717, 233493201, 233493006, 233492728, 233492717, 233493114, 233493026, 233492728, 233492988, 32, 233492906, 233492988, 18446744073709550648L, 233492951, 233493308] #, 233493423, 14950972352827054927L, 13643839583502281931L]

然后一点点的得到地址与代码的对应关系表:

d = {}
d[0x000000000DEAD1F4]      = 'pop     rcx'
d[0x000000000DEAD271]      = 'pop     r9'
d[0x000000000DEAD123]      = 'mov     rax, [rdi]'
d[0x000000000DEAD0ED]      = 'add     rsi, 8'
d[0x000000000DEAD204]      = 'mov     [rsi], rax'
d[0x000000000DEAD267]      = 'mov     r8, [rsi]'
d[0x000000000DEAD0F8]      = 'sub     rsi, 8'
d[0x000000000DEAD103]      = 'add     rdi, 8'
d[0xdead27a]    =   'mov     [rsi], r9'
d[0xdead20e]    =   'mov     rax, [rsi]'
d[0xdead1ec]    =   'pop     rbx'
d[0xdead141]    =   'imul    rax, rbx'
d[0xdead284]    =   'mov     r9, [rsi]'
d[0xdead12d]    =   'add     rax, rbx'
d[0xdead2c8]    =   'pop     r12'
d[0xdead28e]    =   'pop     r10'
d[0xdead1e4]    =   'pop     rax'
d[0xdead1fc]    =   'pop     rdx'
d[0xdead19b]    =   'cmp     rax, rbxnjnz     out1nadd     rsp, rdxnout1:'
d[0xdead297]    =   'mov     [rsi], r10'
d[0xdead2be]    =   'mov     r11, [rsi]'
d[0xdead2b4]    =   'mov     [rsi], r11'
d[0xdead173]    =   'and     rax, rbx'
d[0xdead1aa]    =   'cmp     rax, rbxnjz     out2nadd     rsp, rdxnout2:'
d[0xdead2d1]    =   'mov     [rsi], r12'
d[0xdead25d]    =   'mov     [rsi], r8'
d[0xdead222]    =   'mov     rbx, [rsi]'
d[0xdead2db]    =   'mov     r12, [rsi]'
d[0xdead191]    =   'shr     rax, 1'
d[0xdead2a1]    =   'mov     r10, [rsi]'
d[0xdead1d7]    =   'loop    success, out3nsuccess:nadd     rsp, rdxnout3:'
d[0xdead33c]    =   'get flag'

[d[one] for one in arr if one > 0xdead000 and one < 0xdeaf000]

然后按这个表替换之后得到代码如下:

pop     rcx
pop     r9
mov     rax, [rdi]
add     rsi, 8
mov     [rsi], rax
mov     r8, [rsi]
sub     rsi, 8
add     rdi, 8
...
mov     [rsi], r9
mov     rbx, [rsi]
sub     rsi, 8
pop     rdx
cmp     rax, rbx
jz next3
add     rsp, rdx
next3:
pop     rdx
loop    success1, fail2
add     rsp, rdx
get flag

我们发现里面从来没有做过任何修改栈上值的操作,以及唯一对rsp的修改就是add rsp,也就是相当于把代码跳转回去,就是实现循环的作用了。

于是乎,为了看的更清楚,我们给每个语句编上地址,使我们可以知道每次add rsp是跳转到哪里,以及每次pop出来的值是多少:

[8, 1384820248253759637, 51966, 48879, 1, 13337, 0, 0, 472, 1, 1, 104, 0, 18446744073709551072L, 32, 18446744073709550648L]

i = 0
for one in s:
    if one.find('pop') != -1:
        print one.replace('pop  ', 'mov_s') + ', ' + hex(t[i])
        i += 1
    else:
        print one

我们这里会将pop替换成mov_s指令,当做mov指令看就好,加个_s是为了区分下而已:

dst         = 0xE0AF0A0
src         = 0xE0B00A0
buffer      = 0xE0B20C0
save_flag   = input

rdi = save_flag
rsi = buffer

0x008:  mov_s   rcx, 0x8
0x018:  mov_s   r9, 0x1337deadbeef0095
0x028:  mov     rax, [rdi]
0x030:  add     rsi, 8
0x038:  mov     [rsi], rax
0x040:  mov     r8, [rsi]
0x048:  sub     rsi, 8
0x050:  add     rdi, 8
0x058:  add     rsi, 8
0x060:  mov     [rsi], r9
0x068:  mov     rax, [rsi]
0x070:  sub     rsi, 8
//
0x078:  mov_s   rbx, 0xcafe
0x088:  imul    rax, rbx
0x090:  add     rsi, 8
0x098:  mov     [rsi], rax
0x0a0:  mov     r9, [rsi]
0x0a8:  sub     rsi, 8
0x0b0:  add     rsi, 8
0x0b8:  mov     [rsi], r9
0x0c0:  mov     rax, [rsi]
0x0c8:  sub     rsi, 8
0x0d0:  mov_s   rbx, 0xbeef
0x0e0:  add     rax, rbx
0x0e8:  add     rsi, 8
0x0f0:  mov     [rsi], rax
0x0f8:  mov     r9, [rsi]
0x100:  sub     rsi, 8
0x108:  mov_s   r12, 0x1
0x118:  mov_s   r10, 0x3419
0x128:  mov_s   rax, 0x0
0x138:  mov_s   rbx, 0x0
0x148:  mov_s   rdx, 0x1d8
0x158:  cmp     rax, rbx
jnz     out1
add     rsp, rdx
out1:
0x160:  add     rsi, 8
0x168:  mov     [rsi], r10
0x170:  mov     r11, [rsi]
0x178:  sub     rsi, 8
0x180:  add     rsi, 8
0x188:  mov     [rsi], r11
0x190:  mov     rax, [rsi]
0x198:  sub     rsi, 8
0x1a0:  mov_s   rbx, 0x1
0x1b0:  and     rax, rbx
0x1b8:  add     rsi, 8
0x1c0:  mov     [rsi], rax
0x1c8:  mov     r11, [rsi]
0x1d0:  sub     rsi, 8
0x1d8:  add     rsi, 8
0x1e0:  mov     [rsi], r11
0x1e8:  mov     rax, [rsi]
0x1f0:  sub     rsi, 8
0x1f8:  mov_s   rbx, 0x1
0x208:  mov_s   rdx, 0x68
0x218:  cmp     rax, rbx
jz     out2
add     rsp, rdx
out2:
0x220:  add     rsi, 8
0x228:  mov     [rsi], r12
0x230:  mov     rax, [rsi]
0x238:  sub     rsi, 8
0x240:  add     rsi, 8
0x248:  mov     [rsi], r8
0x250:  mov     rbx, [rsi]
0x258:  sub     rsi, 8
0x260:  imul    rax, rbx
0x268:  add     rsi, 8
0x270:  mov     [rsi], rax
0x278:  mov     r12, [rsi]
0x280:  sub     rsi, 8
0x288:  add     rsi, 8
0x290:  mov     [rsi], r8
0x298:  mov     rax, [rsi]
0x2a0:  sub     rsi, 8
0x2a8:  add     rsi, 8
0x2b0:  mov     [rsi], r8
0x2b8:  mov     rbx, [rsi]
0x2c0:  sub     rsi, 8
0x2c8:  imul    rax, rbx
0x2d0:  add     rsi, 8
0x2d8:  mov     [rsi], rax
0x2e0:  mov     r8, [rsi]
0x2e8:  sub     rsi, 8
0x2f0:  add     rsi, 8
0x2f8:  mov     [rsi], r10
0x300:  mov     rax, [rsi]
0x308:  sub     rsi, 8
0x310:  shr     rax, 1
0x318:  add     rsi, 8
0x320:  mov     [rsi], rax
0x328:  mov     r10, [rsi]
0x330:  sub     rsi, 8
0x338:  add     rsi, 8
0x340:  mov     [rsi], r10
0x348:  mov     rax, [rsi]
0x350:  sub     rsi, 8
0x358:  mov_s   rbx, 0x0
0x368:  mov_s   rdx, 0xfffffffffffffde0L
0x378:  cmp     rax, rbx
jz     out2
add     rsp, rdx
out2:
0x380:  add     rsi, 8
0x388:  mov     [rsi], r12
0x390:  mov     rax, [rsi]
0x398:  sub     rsi, 8
0x3a0:  add     rsi, 8
0x3a8:  mov     [rsi], r9
0x3b0:  mov     rbx, [rsi]
0x3b8:  sub     rsi, 8
0x3c0:  mov_s   rdx, 0x20
0x3d0:  cmp     rax, rbx
jz     out2
add     rsp, rdx
out2:
0x3d8:  mov_s   rdx, 0xfffffffffffffc38L
0x3e8:  loop    success, out3
success:
add     rsp, rdx
out3:
0x3f0:  get flag

然后这里看跳转的规则是,用那句add esp前面的标号,加上这句指令给rsp增加的值,得到的地址,就是下一条指令了。

于是乎,慢慢看下这段程序,会发现就是一个快速幂(PS:这里最后几句代码可能对应的有点问题,但不太影响整体逻辑的理解),就是求输入8个字节(作为一个长整数看)的0x3419次方,与一个目标值比较,然后会这样比较9轮(好吧,我承认这一部分是动态调出来了,看出快速幂后就没管这段程序了),而目标值的初始值是0x1337DEADBEEF0095(初始值不算一轮),下一轮的值为前一轮的值乘以0xcafe后加0xbeef。

果断计算出每轮目标值,然后用WolframAlpha算出应该的输入(比如第一轮的输入),最后得flag的程序如下:

import zio

# aim = 0x1337DEADBEEF0095
# for i in xrange(9):
#     aim = aim * 0xcafe + 0xbeef
#     print aim % (2 ** 64)
#
# WolframAlpha

ans = [15397851891508532645,
5882479468299745861,
6203462900357968133,
11756062250229433221,
6010465035347615365,
10697597399494305925,
14617956564689458309,
9036388475871214725,
17260895165766855813]

payload = ''
for i in ans:
    payload += zio.l64(i)

io = zio.zio(('127.0.0.1', 0x3419))
io.write(payload)

这题150分搞成这样,也是有点无语了,知道方法后,最后的一点苦力工作当时都不想干了,都是交给队友看的了~~~

这题放的比较迟,题目描述(Try SAT if you fail in GRE :P)也是直接,虽然没敢看GRE,但是看着描述想来这题是GRE的简化版了的样子。

这题同样是直接给出了Server端的程序,不过不得不提一下的是,似乎是题放出来过了好久才附上的,我也是醉了,难怪刚开始看这题完全不明所以。

这题首先有个sha1的检测,反正暴力就好,后面才开始关卡,我们直接看关键部分程序:

def SATexam(k):
	var = range(1, k + 1) * 3
	random.shuffle(var)
	for i in xrange(k // 2):
		v = random.choice(var)
		var[v] = -var[v]
	return var

def SATjudge(sat, answer):
	k = len(sat) // 3
	assign = [0] * k

	assert(len(sat) == k * 3)
	assert(len(answer) * 8 >= k) # at least k bits

	i, j = 0, 0
	for i in xrange(k):
		assign[i] = (ord(answer[i // 8]) >> (i % 8)) & 1

	for i in xrange(k):
		r = 0
		for j in xrange(3): # neither SAT-1 nor SAT-2, it's SAT-3 :)
			v = sat[i * 3 + j]
			r |= (v < 0) ^ (assign[abs(v) - 1] == 1)
		if not r:
			print 'ERR: %d %s' % (i, str(sat[i * 3: (i + 1) * 3]))
			return False

	return True

其中SATexam产生输入,就是1-n重复三遍,随机排序,然后选取部分取相反数,不过看起来取相反数的数量不是很大,然后SATjudge用来检查结果是否正确,可以认为是要我们给出一个01串,满足某个性质即可。

简单点说就是,把3n个数,3个一组分成n组后,要求每组的数至少有一个满足:这个数是负数 xor 这个数对应的01串中那一位为1。反正感觉条件很蛋疼,不太好描述就是了……

很显然,如果输入没有负数,我们直接给出一个全1串,显然是满足条件的,于是我们以这个为初始状态,将其中一些1改成0就好,不过时间复杂度简直没法算,并且想来题目对输入中负数个数的限制应该也是对这题这样调整的效率很有影响的。不过按照之前的经验看,这种题暴力应该是都可以算出来的,于是果断对输入从前往后扫,如果发现,某一位不满足了,就尝试调整结果的01串就好,然后还准备了各种调整时的优化策略,虽然到最后都还没用上就轻松过了。

比赛时的程序如下:

#!/usr/bin/env python
#encoding:utf-8

import string
import hashlib
import itertools
import zlib
import struct

import zio

ROUNDS = 50
# n = 0
# cnt = []
# data = []
# adj_left = ''
# ans = []
# adj_right = ''

def link(i, j, sign):
    adj_left[i].append(j)
    adj_right[j].append((i, sign))
    cnt[i] += sign ^ ans[j]

def update_right(right):
    ans[right] = not ans[right]
    for i, s in adj_right[right]:
        if s ^ ans[right]:
            cnt[i] += 1
        else:
            cnt[i] -= 1

def adjust_right(left, right):
    update_right(right)
    for i, s in adj_right[right]:
        if left == i:
            continue
        if not adjust_left(right, i):
            update_right(right)
            return False
    return True

def adjust_left(right, left):
    if cnt[left] > 0:
        return True
    for i in adj_left[left]:
        if right == i:
            continue
        if adjust_right(left, i):
            return True
    return False

def solve():
    global n
    global cnt
    global data
    global adj_left
    global ans
    global adj_right

    n = len(data) / 3
    cnt = [0] * n
    adj_left = [[] for i in xrange(n)]
    ans = [True] * n
    adj_right = [[] for i in xrange(n)]

    for i in xrange(n * 3):
        link(i / 3, abs(data[i]) - 1, data[i] < 0)

    for i in xrange(n):
        if not adjust_left(-1, i):
            print 'Error!!!'

    result = ''
    for i in xrange(0, n, 8):
        s = ans[i:i + 8][::-1]
        s = ''.join([str(int(j)) for j in s])
        result += chr(int(s, 2))
    result = zlib.compress(result)
    print len(result), io.write(struct.pack('>I', len(result)) + result)

def challenge():
    start = io.readline().strip('n')
    print start
    # all_chars = [chr(i) for i in xrange(256)]
    for pad in itertools.combinations(string.printable, 20 - len(start)):
        if hashlib.sha1(start + ''.join(pad)).digest().endswith('xFFxFFxFF'):
            io.write(start + ''.join(pad))
            print zio.HEX(start + ''.join(pad))
            break


TARGET = ('202.112.26.111', 23333)
# TARGET = ('127.0.0.1', 8888)

io = zio.zio(TARGET, print_read=False, print_write=False, timeout=10000000)
challenge()
for i in xrange(1, ROUNDS + 1):
    print '=' * 10 + 'Level ' + str(i) + '=' * 10
    io.read_until('Enjoy your SAT:n')
    data_len = struct.unpack('>I', io.read(4))[0]
    data = zlib.decompress(io.read(data_len)).split(',')
    data = [int(i) for i in data]
    io.read_until_timeout()
    print len(data)
    solve()
print 'Win! Get flag:'
# s = ''
# while True:
    # s += io.read(1)
    # print s
io.read_until('scholarship:n')
print io.read_until_timeout(60)

当时先是本地跑的,很轻松的就过了,结果跑线上的时候真是跑到哭出来,要传的数据太大,然后传输起来各种超时,最后把超时加到N大之后终于可以跑过了,可是在最后一组算完之后收flag却总是收不到,然后以各种姿势一次次的试,最后终于有一次一个字符一个字符的收的时候收到了,真是哭出来啊。本来还担心过计算太慢,结果真是我想太多了,计算比这网络传输快了不知道多少个数量级了都……

0ctf都过去这么久了,想来网上writeup已然到处都是,不过在这里还是简单记录一下自己比赛当时的解题过程吧……

首先这题给出了加解密的程序:

def encrypt(plaintext, key):
    ciphertext = ""
    for i in xrange(len(plaintext)):
        p = ord(plaintext[i]) - ord('a')
        k = ord(key[i % len(key)]) - ord('a')
        c = (tr[k][p] + i**7) % 26
        ciphertext += chr(c + ord('a'))
    return ciphertext

def decrypt(ciphertext, key):
    plaintext = ""
    for i in xrange(len(ciphertext)):
        c = ord(ciphertext[i]) - ord('a')
        k = ord(key[i % len(key)]) - ord('a')
        p = tr[k][(c - i**7) % 26]
        plaintext += chr(p + ord('a'))
    return plaintext

初看程序会觉得有种好神奇的感觉的说,竟然用了同一个矩阵同时做加解密的置换,只能说矩阵是特殊设计好的了,本来还以为加解密的key值可能不同,观察一下矩阵却发现其实加解密的key应该是一样的,因为矩阵每一行逆过来都是自身。不过,这都是废话,下面开始正式解题:

首先,i**7很明显是多余的部分,于是上来就将i**7从密文中减掉,这样密文每个字母就和明文字母一一对应了,此时,整个程序其实就和维吉尼亚密码很相似了,唯一的区别就是维吉尼亚密码中采取的是偏移量不同的恺撒密码,而这里采取的是26种置换(也就是表中对应的26行)。

于是乎,果断祭出之前破维吉尼亚密码的程序,破维吉尼亚密码的时候,首先是对重合指数进行分析,确定密钥长度,这一步显然对于这题的加密算法也是适用的。然后确定密钥长度后,就是尝试用每一个密钥解密,统计解密出来的字母频率与标准频率的差值,显然我们这里也可以这样,只是需要把解密过程替换下,将凯撒加密替换成这里的置换表。

本来这样很轻松就解决了这题的,结果可曾想,竟然在改程序的时候犯了超二的错,结果一直过不掉,甚至开始怀疑他这里故意破坏了频率:

如上图所示,它这个程序本身是调用的Caesar的encipher来做的解密,由于凯撒加密和解密的区别只是密钥而已,所以他这里是可以的,也正是这样,他下面得到密钥的时候就要用26来减,也就是注释的那句,结果刚开始我忘记改这里了,于是乎就真的就是满满的都是泪……

这题不得不说挺水的,浪费这么多时间不得不说是个悲剧,最后给出比赛时改的代码

今天突然想到纷印好像一直都还没有对服务器的返回做gzip压缩,服务器访问速度最近又一直被各种诟病,于是决定倒腾一下,毕竟这个对服务器访问速度影响还是挺大的。

首先,在网上查怎么开启gzip,方法真是形形色色,有php开的,有node开的,也有apache或者nginx开的,稍微选了一下,便决定在nginx里面做,感觉又不容易出错,还免得各个部分的代码都得改~~~

综合了一下网上查到的资料,最后得到的配置如下:

gzip on;
gzip_disable "msie6";
gzip_min_length 1k;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript text/x-component image/bmp;

其中gzip_disable是把对IE6不开启gzip压缩(兼容性不好),虽说现在本来就不会支持IE6了,但强迫症,还是感觉禁了舒坦点;

gzip_min_length是设置了会开启gzip压缩的文件大小最小值,太小了压缩似乎容易越压越大;

gzip_vary和http头有关系,加个vary头,给代理服务器用的,有的浏览器支持压缩,有的不支持,所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩;

gzip_proxied是根据某些请求和应答来决定是否在对代理请求的应答启用压缩;

gzip_comp_level是设置压缩的级别,从1-10,越大压缩率越高,也自然对CPU的消耗越大,按个人喜好设置就好;

gzip_buffers设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流,16 8k代表按照原始数据大小以8k为单位的16倍申请内存;

gzip_http_version是设置了会开启gzip压缩的HTTP请求的最低版本号,默认1.1,需要注意的是做反向代理的时候,代理服务器与真是主机之间的通讯是采取的HTTP 1.0,所以如果配置的不好可能反向代理的时候就没有压缩了;

gzip_types是设置需要进行gzip压缩的文件的类型,按照MIME来设置,无论是否设置,都会对text/html进行压缩,然后这里没有对大部分图片进行压缩,因为图片一般本身都是已经压缩了的,再做gzip意义不大(在google的图片优化建议中也看到有提到几个图片压缩软件),当然,BMP是个例外,不过想来网站上也基本不会出现BMP的。

配置好压缩后,测了一下,果然效果显著,有个1.2M的pdfjs直接给压到了265K,简直爽。然后又拿百度的页面优化建议跑了下,结果发现静态内容缓存也木有,于是乎,果断趁热打铁,又找了下nginx开启静态文件缓存,最后得到配置如下:

location ~ .(ico|gif|bmp|jpg|jpeg|png|swf|mp3|flv)$ {
    expires 1d;
}

location ~ .(js|css)$ {
    expires 1h;
}

这样把图片视频神马的和js、css给分开设置缓存时长,因为感觉js、css还是设置的时间短点的好,毕竟还是很有可能会改动的,尤其是我们这边用的AngularJS,好像显示的各个元素都是js动态生成的……

不过缓存配置比较不爽的是,似乎得在每一个server配置里面单独配,不能在server外面统一配,也不知道是不是我打开方式不对,不过好在只有前端要配,后端不需要,这样要改的量直接少了一半……

这就是今天下午配服务器的成果了,至少现在看起来效果还是不错的,感觉网站的访问速度确实是快了不少,以后看来弄服务器的时候,上来先得把这些东西配好了。

由于某场比赛的原因,导致了BCTF延迟了一周,于是乎成功和0CTF连着了,连着两周的比赛,真的就是比到人都虚脱了,外加这段时间本来就忙,于是成功体会到了忙成狗的感觉,不过好在现在一切都结束了,虽然结果不尽如人意,但也还算可以接受。

先说说BCTF,BCTF的题从一开始就给人一种蛋疼的感觉,上来就是一个450分的exploit(zhongguancun),连别的选择都木有,然后一直到结束,放出来的题满满的都是reverse、exploit和web,就连crypto的题都是同时打上了reverse的标签,虽说我挺喜欢逆向溢出的题,但BCTF的题目难度却是让我一点也高兴不起来,溢出从头到尾真的感觉就是一题都做不出来,zhongguancun那题也是自己犯二,一直算错长度,所以根本就没找到溢出点,虽说找到溢出点也真的不一定能够会做,逆向的题更是各种不会,逆向水平本来就渣,要不是最后好不容易找到个裸算法的题(redundant_code)慢慢看,不然就要一无所获了。

不过BCTF的时候,队友的状况真是让我十分无语,一个个的都啥动静木有,然后尤其是做web的,sqli_engine那个注入玩了不知道多久也没玩出来,到头来竟然是我这边实在做不下去,跑过去玩了下给玩出来的,这简直就是让人情何以堪,我们队从开始比到现在,第一个成功的注入竟然是我弄的……

也许是由于要面向国际队伍,这次BCTF的题真的就是把难度整个的提了上去,我们队则简直就是成了被针对的那种,溢出逆向都是稍微会一点的人做不出来的,而web题则是超水,但我们队却是完全不会做web,于是结果不言而喻。

再说0CTF,由于BCTF的悲剧,0CTF也算是憋着一口气,并且0CTF有线下赛,我们已经等了很久了,一直都想去现场赛感受一下,这次机会一定不能错过。不过可能是由于BCTF已经比得有点心力交瘁,还没有完全恢复,这次比赛说句实在话,就我个人而言比赛状况也不是很好。

首先比赛刚开始的时候,插入了一个腾讯的笔试,于是前几个小时更是浑浑噩噩的过去了。然后下午也明显不在状态,一个简单exploit都不知道在想什么纠结了很久,第一天我基本可以说是没有什么收获,基本都是靠队友在拿分,名次也是很不容乐观,好在第二天总算是勉强找到了一点状态,才慢慢的开始A题,看着名次一点点的往上爬,直到最后拿到决赛的入场卷。

这次比赛的整个过程中,不得不说,感觉整个队的比赛氛围至少正常多了,可以看到相互的讨论,各种冒泡,不管有没有做出题,至少可以发现大家都是努力在想方法,这种感觉真的很好。

不过这次比赛最让我郁闷的应该是那个exploit400(freenote)了,明明都已经想到了怎么堆溢出进行dword shoot,却由于把权限记错了,记成了exploit300那题的权限,以为GOT表是不能写的了,于是就完全不知道怎么做了,想想就觉得可惜。

但既然过都过去了,已经改变不了什么,反正也算是进了线下赛,也不算太不满意,只是接下来的比赛大概需要更认真点,一定要拿到最终决赛的入场卷。

至于这两场比赛的WriteUp,只有抽空慢慢写了,最近确实是太忙了,怎么也没想到一开学竟然会忙成这样,为了这两场比赛,也是耽误了不少东西,总归是要慢慢补上了的……

想来已经一个多月没有写些什么了,这段时间各种忙,现在才算好不容易把事都一个个的结了,不过说句实在话,忙也由忙的好处,至少这段时间不至于整天想到那件让我崩溃的事了……

今天节奏稍微慢下来点,就把最近遇到的问题稍微捋一捋,也算是给最近的一个交待了……

由于新产品的开发,由PHP换到了node.js,所以两套代码一起跑是确定的了,又想用同一个域名,于是准备换nginx转发,鉴于之前从没玩过nginx,就只有默默的跑去查nginx的配置了。

不过查了一下,不得不说,nginx的配置看起来挺简洁的,多个域名以及路径的配置非常简洁,稍微纠结了一下的就是使用php cgi的配置,因为既然用nginx了,PHP又和nginx是放在同一台机器上的,就没必要用Apache了,直接用nginx连php cgi就好了。晚上nginx连php cgi的文章太乱,各种乱七八糟的参数配了一堆,最后总结+实验起来就用了下面这点:

location /v1 {
    try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
    #try_files $uri =404;
    #fastcgi_split_path_info ^(.+.php)(/.+)$;
    fastcgi_pass unix:/dev/shm/php5-fpm.sock;
    include fastcgi_params;
}

因为PHP用的laravel的框架,所有请求都要rewrite到index.php上,第一个location用来做rewrite,第二个location就是用来将所有对PHP文件的请求转给php cgi执行,这里由于是一台机器,用的php-fpm,直接就使用了文件socket,然后放在了/dev/shm(这个目录在内存里),这样应该会比使用网络socket要快。

然后第二个location中配置的try_files是为了防止Nginx文件类型错误解析漏洞,不过我在php.ini中设置了cgi.fix_pathinfo=0,于是这里就注释了,至于那句fastcgi_split_path_info暂时也不是很确定是不是有意义,就先注释了留在那里了,什么时候有空仔细研究nginx的配置了再说。

本来以为nginx的切换会一帆风顺,可是没想到,把Apache换成nginx之后,自己测试已经没有问题了,但是上线没多久打印店就相继表示连不上后台了,本来一看是只有部分打印店表示有问题,就觉得肯定不是由于切换了nginx的原因,但是看着情况有点不对,就还是赶快换回了Apache,结果一换回去就好了,于是就傻眼了,怎么想也想不出来会是哪里出了问题。最后周五去办公室后,据说好像是只有使用XP的出了问题,就开了个XP的虚拟机,连nginx的服务器做测试,结果https就跪了,可是用http就没问题,然后fiddler检查给的错误是handshake挂了,这也难怪无论是在nginx的access还是error日志中都查不到请求的记录,只有用tcpdump抓包能看到有包。在多方改动无果的情况下,便愈发的觉得是https的加密算法的问题,就企图去找Apache的https加密算法的配置,结果尴尬的没找到……最后只好直接改nginx的加密算法测试,没想到一改就成功了,果然是服务器配置的加密算法和XP支持的加密算法完全不同惹的祸么……但是就无语了,只能说都是之前找到的ssl配置不靠谱惹的祸,不过最后总算是完美解决了。

结果最让人没想到的是,就在当天晚上准备再换成nginx之前,有家店连Apache的API时连不上了,报了和我们测试时连nginx挂彩时一样的错,然后直接换成连改好后的nginx就好了,当时就想,这几个月都没遇到过的问题,竟然恰好就在我们配nginx时发现并解决之后出现了,岂止幸运。

然后最近终于还是下决心买了个域名,之前一直舍不得+选择恐惧综合症,所以虽然想买个域名但却一直没有能下手。几番斟酌,最终选了q2zy这个域名,选这个域名是为了某些含义,却又不至于太明显,毕竟有些事不想让人知道,却又希望能于我有特殊意义。

买了域名,自然少不了各种配置,首先就是去QQ邮箱那边弄了个域名邮箱,然后就是博客等各种页面的域名分配了。

本来买国际域名最大的好处就是不用强制去备案,但是由于博客是架着SAE上的,也就不可避免的多了些蛋疼。SAE绑定域名如果不备案就只能绑海外线路,流量按国内线路的两倍算,不过这倒无所谓,反正云豆对于我而言就是多的根本用不完,可是蛋疼的是速度,海外线路的速度相比于国内线路,不解释的慢了不少,于是能不走海外线路的资源自然就得尽量不走海外线路,不过本来博客的各种静态资源都已经是完全用了七牛的CDN,没有什么可担心的(PS:CDN配置得重新按照这个写的改下)。但是,后台部分却没有开启加速,因为用的插件默认不开,所以没敢开,怕出什么问题,这下就蛋疼了,不过还好WordPress可以把后台和前台的地址设的不一样,虽说似乎这个功能本来目的不是我这个,不过无所谓了,能用就行,于是毫不犹豫的将前台页面设为刚买的域名,后台域名设成SAE的二级域名,然后此时后台的所有资源都是用的SAE的二级域名。不过这样也是有硬伤的,因为前后台域名不一样,故而cookie不公用,即使后台登录了,前台也拿不到登录信息。

接下来就是要将SAE二级域名过来的访问301重定向到自己的域名上,但是需要注意的是,不能所有请求都重定向,不然前后台域名设置不一样就白设了,于是最终得到的配置如下:

- rewrite: if ( in_header["host"] ~ "geekjayvic.sinaapp.com" && path == "/" ) goto "?%{QUERY_STRING} [L,QSA,R=301]"
- rewrite: if ( !is_file() && !is_dir() && in_header["host"] ~ "geekjayvic.sinaapp.com" && path ~ "^(.*)$" ) goto "$1 [L,QSA,R=301]"
- rewrite: if ( in_header["host"] ~ "blog.q2zy.com" && path ~ "^(.*)$" ) goto "$1 [L,QSA,R=301]"
- rewrite: if ( in_header["host"] ~ "^.note.q2zy.com" && path ~ "^(.*)$" ) goto "$1 [L,QSA,R=301]"

第二行用于将SAE的二级域名重定向过来(用!is_file和!is_dir的判断,确保了所有的文章页面都能重定向过来),第三行和第四行是将自己分配的几个域名都定向为一个,需要注意的是第一行,因为根目录is_dir判断为true,故第二行的重定向对根目录无效,所以需要专门添加一条重定向规则。

然后弄域名的时候有几件事真是弄的比较无奈,根域名由于配置了邮件,故而不能配CNAME记录了,而用SAE意味着得靠CNAME,当然可以自己强行直接得到ip指过去,不过SAE一旦换ip就会失效……然后有几个网页用Python写的,不能在SAE的AppConfig里直接设了,得到代码里面去做重定向,不过还好影响不大。

然后最近忙的比较多的就是三叶草比赛的事了,被几个朋友拉着陪他们一起参加三叶草,虽然感觉各种不靠谱,不过最后也算是勉强做出了个东西,结果哪知道,最终项目展示的时候,我不在,他们后台服务器竟然拉不起来了,当天凌晨他们还专门弄了试了是好的,结果临近开始告诉我挂了,但是真是急都急死了,远程教了半天也没能搞定,好像就是个没能连上MySQL或者连上了权限不足而已,结果到最后开始都没能搞定。最无语的时,就在最后到时间的时候,旁边朋友问我怎么不远程(TeamViewer),我就凌乱了,Mac也能远程,真是不知道,之前就一直在想要是能远程就好了……

比赛结果就别提了,只是拿了个参与奖,然而,另一个朋友参加比赛让我挂了个名(虽然不知道为啥不怕两个队都有我的名字),却拿了特等奖,我也是醉了……

最后,一眨眼BCTF就要开始了,虽说已经是由于和某比赛冲突导致推了一周,不过还是没能做什么准备,整个假期都被各种事占据着时间,大好的资源也没有看一点,真的感觉挺对不起队友的,不过也是很无奈,只希望比赛的时候能人品好点了。然后BCTF一完马上就跟着个0CTF,真的就是完全不知道该怎么办了,好希望能去一趟现场赛体验一下的说,哪怕打酱油也好啊~~~