第二届强网杯中应用的一种反作弊新思路

各位做题的师傅们辛苦,强网杯线上赛已经告一段落,目前业界的强悍我们已经快承受不住了,喂了30多个题目都喂不饱。

作者:FlappyPig

前言

这次比赛的题目总体上我们自己的收获还是很多的,但也出现了很多的非预期,这里代表战队向大家抱歉。

我们一些很多有趣的思路因为非预期没有体现出来。比如流密码级数给的是我测试时用的级数,少了一倍,导致有3个流密码考点都没有考到;星星的qemu monitor的console没关,正解:https://bbs.pediy.com/thread-225488.htm ;lowkey的pwn中出现的各类2b解法;同样我的题目中,windows pwn中的flag忘了padding,不用heartbleed都可以算出flag;bendawang在汪神的oj部署时直接装了docker,忘了关和改很多东西,正解看orange师傅的wp:http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html

中途还发生了两起dos,一起还是1个t的ddos。不得不说阿里云的清洗功能还是厉害的,是不是得给我广告费=……=。

言归正传,在本次比赛中,秉承公平公正的原则,我们开发了一些新的技术用于反作弊,改善目前较为恶劣的CTF环境,一定程度上保证了竞赛的公平性。运用这种方法,我们实锤了若干队伍,也希望被实锤的队伍不要有怨言,认真打比赛,提升自己的水平才是正解。

这个方法叫做基于信息不对等的竞赛作弊检测技术(是不是可以发论文了=)。这个方法的适用条件需要满足:

交互类题目

通常来说,在PWN类型、PPC类型、Crypto类型应用面较广。在不同类型的题目上应用需要有很多调整,这里我们以nextrsa为例进行介绍。

Nextrsa

Nextrsa这个题目原本思路源自某次比赛的Twin Primes,只有nextprime一个关卡,解题思路也很简单,首先进行假设:

nextprime(p)*nexprime(q)=(p+x)*(q+y)

然后和n=p*q列方程组,最后爆破x和y解一元二次方程,即可分解n:

import gmpy2
n = 0x78e2e04bdc50ea0b297fe9228f825543f2ee0ed4c0ad94b6198b672c3b005408fd8330c36f55d36fb129d308c23e5cb8f4d61aa7b058c23607cef83d63c4ed0f066fc0b3c0062a2ac68c75ca8035b3bd7a320bdf29cfcf6cc30377743d2a8cc29f7c588b8043412366ab69ec824309cb1ef3851d4fb14a1f0a58e4a1193f5518fa1d0c159621e1f832b474182593db2352ef05101bf367865ad26efe14fce977e9e48d3310a18b67991958d1a01bd0f3276a669866f4deaef2a68bfaefd35fe2ba5023a22c32ae8b2979c26923ee3f855363f18d8d58bb1bc3b7f585c9d9f6618c727f0f7b9e6f32af2864a77402803011874ed2c65545ced72b183f5c55d4d1L
m = 0x78e2e04bdc50ea0b297fe9228f825543f2ee0ed4c0ad94b6198b672c3b005408fd8330c36f55d36fb129d308c23e5cb8f4d61aa7b058c23607cef83d63c4ed0f066fc0b3c0062a2ac68c75ca8035b3bd7a320bdf29cfcf6cc30377743d2a8cc29f7c588b8043412366ab69ec824309cb1ef3851d4fb14a1f0a58e4a1193f5a58ee70a59ac06b64dbe04b876ff69436b78cf03371f2062707897bf4e580870e42b5e62709b69f6d4939ac5641ea0f29de44aaee8f2fcd0f66aaa720b584f7c801e52ce7cd41db45ceb99ebd7b51bef8d0cd2deb5c50b59f168276c9c98d46a1c37bd3d6ef81f2c6e89028680a172e00d92dd8b392135112dd16efab57d00b26b9L
def quadratic(a,b,c):
    ga=gmpy2.mpz(a)
    gb=gmpy2.mpz(b)
    gc=gmpy2.mpz(c)
    delta=gb**2-4*ga*gc
    if delta<=0 or a==0:
        return 0
    tmp=gmpy2.iroot(delta,2)
    if tmp[1]==True:
        x1 = (-gb + tmp[0]) / (2*ga)
        x2 = (-gb - tmp[0]) / (2 * ga)
        if x1>0 and n%x1==0:
            print x1
            return x1
        if x2>0 and n%x2==0:
            print x2
            return x2
    return 0
def main():
    for x in range(1500):
        print x
        for y in range(1500):
            a=y
            b=x*y-m+n
            c=x*n
            if quadratic(a,b,c)!=0:
                return 0
main()

这个题出完之后,感觉就是水了个题,好弱;于是我把这个做成了交互式的,又好弱;考虑到强网杯普及面很广,萌新很多,于是我准备把RSA所有考点搬过来让大家学习学习。于是就有了:

problem_brute_256bit(conn)
problem_wiener_attack(conn)
problem_LLL_attack(conn)
problem_np_nq(conn)
cheat=problem_yafu_cheat_check(conn, address,teamtoken)
problem_e_3_brute(conn)
problem_gcd_attack(conn)
problem_same_n(conn)
problem_broadcast(conn)

9个关卡。本来我构想是大概到15个左右,但是实在是有点忙,9个也够喂饱萌新了,于是题目最终出成了这样。所有给予的信息,都是完整的信息,通过这些判断条件是可以知道用什么方法去解的,如果不知道,可以参考我以前的一篇文章:https://www.anquanke.com/post/id/84632

题目的源码也已经公开,详情参见:https://github.com/fpbibi/nextrsa

基于信息不对等的竞赛作弊检测技术-Crypto版本

这个方法最关键的地方在于信息不对等,也就是选手知道的信息和出题者知道的信息是不对等的。这就造成了一个后果:

题目对选手黑盒 或 运行的题目和选手拿到的题目不一样

前者常用于PPC和Crypto类型的题目,后者常用于PWN类型题目。

我们在题目中设置一些类似于“陷门”的东西,可以100%地实锤某些作弊,主要可以实锤一种行为:

两个队伍交换了解题脚本,并修改teamtoken直接运行

以nextrsa举例,我做了如下设置(因为从平台方拿到teamtoken的时间有点晚,所以我是以IP区分来源的):

首先我选择了9关中的1个关卡,我选择的是第5关使用yafu分解n的关卡,选择这一关的原因有两点:

1 上一关是整个游戏最难的一关,所以到这一关的人很少,可以减少日志数量 2 这一关的yafu是需要把字符串复制出来跑的,难以动态获取立即跑完

选手在遇到这个关卡的时候,会获得以下信息:

1 这个用yafu可以跑出来 2 这个字符串每次跑都是一样的,是固定的

通过上述两个信息,选手会把n直接复制出来扔到yafu里,并分解成功后,将pq,或者将算好的私钥,也就是d放到脚本中过此关卡。
但是,在服务器上的代码并不是固定的n,n是使用这种方式生成的:

q=0xab724df05ca87067ce1573550a6a05f41f93e910b0380c71cdc5de940ef790a475f5c3d512354bc57b3410e7f5158fb287f79397353acb169ef583260eec76f4c46d21e4cb43426e3c66ba9b75d3c1b009ff1f9a0fea9c7d9815eadc5f7ac776d6dcae3c1fa3de865253623b4121e6b4f51deea0b7ae9ca84aad5fe83ba56451L
seed=int(hashlib.sha256(ip).hexdigest()[0:8],16)
p=primefac.nextprime(seed)
n=p*q

这里可惜的是,使用的是ip,所以又有了很多复杂的工作;这里最好是以teamtoken作为种子生成p。这种方式生成的p和d,每个ip或者每个token不一样。当一个队伍成功解题的时候,服务器端会记录其ip/token对应的d和n。

if rec_m == hex(m).replace("L",""):
    d=primefac.modinv(e,(p-1)*(q-1))
    if d<0:
        d+=(p-1)*(q-1)
    successdn.append((d,n))
    f=open("success/"+ip,"a")
    f.write("teamtoken:"+teamtoken+"\n"+"time:"+time.strftime('%Y%m%d%H%M%S')+"\nm:"+hex(m).replace("L","")+"\nd:"+hex(d).replace("L","")+"\nn:"+hex(n).replace("L","")+"\n\n")
    f.close()
    ok(conn)
    return 0

正常解题会发生如下情况,那么不正常解题呢?也就是说,如果这个脚本到了其他人的手里,ip和token都会发生改变,那么服务器给他的n肯定就变了,但是他脚本里的d没变。如下:

某个队伍使用自己的ip/token和服务器交互的时候,提交了和自己的ip/token不对应的d,并且这个d是其他队伍使用的。那么可以认为这个队伍有作弊嫌疑。

最骚的操作是,当服务发现该队伍作弊,使用了其他队伍的私钥后,还继续正常进行,让该队伍获得flag。######不要打我
因为我使用的是ip,不是token,所以还需要进一步分析,就是会存在这样两种情况:

1 同一个队伍的大佬做完题目后,把脚本给了另外一个ip的小弟跑 2 两个ip的p相撞

因此我又编制了一个脚本,用来筛选:

只有在两个不同的teamtoken中出现了相同私钥 and 作弊队伍的teamtoken没有成功的交互记录

for i in cheatlist:
    (cheat_token,cheat_time,cheat_d,cheat_n)=i
    find_father=0
    for j in successlist:
        (token,time,d,n)=j
        if token==cheat_token and cheat_d==d and cheat_n==n and cheat_time>time:
            find_father=1
            break
    if find_father==0:
        print cheat_token
        for j in successlist:
            (token, time, d, n) = j
            if cheat_d==d and cheat_n==n and cheat_time>time:
                print "from",token

只要满足上述所有条件,那么该队伍在概率学上是100%实锤作弊,而且还能一下锤两个。==!

图片.png

项目目录中,ppc_rsa.py是题目服务,自带socket;hide.py是flag文件,flag文件夹内记录成果获取flag的ip和token,success文件夹内记录成功以正确的私钥过那一关的ip和token,cheat文件夹内表示作弊嫌疑人,运行check_cheat.py可以进行数据分析,并给出100%实锤的作弊token。

图片.png

最后

Pwn类型的题目有着类似的思路,有兴趣的可以自己思考一下。也希望CTF的氛围越来越好,大佬越来越多。

发表评论

已有 1 条评论

取消
Loading...

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php