放假和小伙伴们打了几把PUBG,大半年没碰,居然也意外地躺着吃了次鸡。吃鸡这个游戏果然得4个认识的人打(dai)战(dai)术(wo)才更有趣。
由于身边搞安全的人比较多,之前也会和一些安全圈的大佬一起玩,经常会有些认识或不认识的黑阔大佬开着高科技带着躺鸡。当然笔者也曾羞耻地开过挂带妹(纪念号被封的第193天)。
那么这些黑阔大佬们,在不开挂的情况下,谁会是他们之中技术NO.1呢?带着这样的疑问,试着从数据分析的角度来看看谁会是安全圈的吃鸡首人。
注:全文所指的“安全圈”是一个非常狭义的概念,在本文中仅覆盖到小部分安全从业者玩家,所以标题有些夸大。如有其他错误也欢迎指出。
一、数据收集
怎么才能知道哪些安全从业者在玩这个游戏,他们的ID又是什么呢?
在一些APP里可以查到玩家的战绩,其中比较有价值的是,还能看到每位玩家的好友列表。
那么可以有这么个思路,也就是一个广度优先遍历:
- 确定一个初始的ID链表,并保证初始链表内都是安全圈内人士。
- 遍历初始链表内每一位玩家的所有好友。
- 当链表内H的好友J,同时也是链表内另外两位黑客的共同好友,那么认为J也是安全圈内人士,加入到链表尾处。
- 向后迭代重复2、3。
1. 动手实现
发现走的是https,挂上证书以MITM的方式来监听https流量:
可以发现数据是以json格式传递的,写个python脚本来自动完成获取数据,单线程+限速(一方面不至于给别人家的api爬挂了另一方面也不至于触发IDS、anti-CC等机制)。
脚本主要代码是:
- def r_get(nickname,offset):
- #发送给api的request
- ...
- return json#返回一个json格式
- def get_friends(nickname):
- offset = 0
- res_js = r_get(nickname,str(offset))
- temp_friends = res_js['result']['board']
- if res_js['status'] == "ok":
- while len(res_js['result']['board']) == 30:
- offsetoffset = offset + 30
- res_js = r_get(nickname,str(offset))
- temp_friendstemp_friends = temp_friends + res_js['result']['board']
- print(" {0} 的好友人数 {1}".format(res_js['result']['user_rank']['nickname'], len(temp_friends) -1 ))
- friends = []
- Wxname = ""
- Wxsex = ""
- Wxavatar = ""
- sql = ""
- for playerinfo in temp_friends:
- if playerinfo['nickname'] != res_js['result']['user_rank']['nickname']:
- friends.append(playerinfo['nickname'])
- elif playerinfo.has_key('heybox_info') == True:
- Wxname = playerinfo['heybox_info']['username']
- Wxsex = playerinfo['heybox_info']['sex']
- Wxavatar = playerinfo['heybox_info']['avartar']
- friends_s = ','.join(friends)
- sql = "INSERT INTO player (nickname,avatar,steamID,friends,Wxname,Wxsex,Wxavatar) \
- VALUES ('{0}','{1}','{2}','{3}','{4}','{5}','{6}')".format(res_js['result']['user_rank']['nickname'],res_js['result']['user_rank']['avatar'], res_js['result']['user_rank']['steam_id'],friends_s,Wxname,Wxsex,Wxavatar)
- return friends,sql
- else:
- print("获取{0}的好友人数失败, 返回{1}".format(res_js['result']['user_rank']['nickname'],res_js['msg']))
- return 1
- def record_rds(sql):
- #db操作
- def main():
- ...
笔者先确定一份初始安全圈列表,包括“Rickyhao”、“RicterZ”、“r3dr41n”、“PwnDog”等。这些人或是在bat、360等公司从事安全相关工作,或是笔者信安专业的同学,或ID明显带有安全的特征(PwnDog),总之都是比较确信的安全圈人士。
以这些人为初始列表,很快就有几位玩家被划定为安全圈人士。
在遍历到大约第50来位的时候也看到了自己的游戏昵称:Omego555(正是刚才提到开挂带妹被封的账号)
遍历到“wilson233”时发现直接跳过了,并没有被纳入到安全圈列表中,但笔者读过wilson写下的很多优质文章,他也是某甲方公司的安全从业者。
(该ID在后来的某次遍历中也被纳入了列表中,程序表现出一定的健壮性。 )
在数据量达到1000多的时候笔者手动终止了程序。原因是列表增长的速度越来越快,在单线程+限速的限制下程序迟迟看不到收敛的希望。另一方面笔者只是想做个小测试,并不需要太大规模的数据集。
2. 观察数据集
初步观察数据集,发现很多有意思的事情:比如在遍历到第300多位玩家的时候,发现了一个ID带得有“pkav”字样的玩家”PKAV-xiaoL“(pkav是原来在乌云很有名气的安全组织,其中一名成员“gainover”正是原乌云知识库《安全圈有多大》一文的作者,笔者也是受到这篇文章的启发才打算做这个小项目)。
随着PKAV-xiaoL被确定到安全圈列表中,由于社交关系,更多的pkav成员也被添加进列表中。
除了pkav-xxx,还看到了一些很眼熟的ID:比如【SparkZheng】—正是多个ios越狱的作者蒸米大大
比如【ma7h1as】,笔者大学时的队友,现玄武实验室大佬,多个Google/MS CVE获得者,超级大黑客
再来看这位,从游戏昵称看不出是谁,但微信昵称告诉我们这个账号的主人应该是安全盒子的创始人王松。
当然还有一些活跃在安全论坛,或者笔者有读过的一些高质量技术文章的作者的ID,眼熟的如”lightless233″、”LoRexxar”、”susu43″、”CurseRed”等,这里不再一一列举。
除此之外还有一些玩家,比如这位:
笔者既不认识他,也从未在安全论坛见过他的ID,只是猜想用“sudo”作为ID的人是安全从业者的可能性比较大吧。那么他真的会是安全圈人士吗?
试着搜索一下:
找到了他的GitHub,并在其中发现了很多你懂的东西,很有趣对吧?
二、社群发现与社区关系
我们发现了很多安全圈的吃鸡玩家,但是除了这些眼熟和有迹可循的ID,列表里躺着的绝大多数都是笔者没见过,陌生的ID。为了弄清楚他们之间的社区关系。我们使用一些算法和可视化工具来帮助进行数据分析。
先用环形关系图看看:
圆上的每个红点代表一位玩家,无数条灰边则将各位玩家串联起来。在这份数据集中一共有1270个节点,他们互相组成了共计14216次好友关系,形成了7128条灰边。称得上是复杂的社交网络了。
我们使用无向图来构建力引导关系,虽然在安全领域的风控、反欺诈方向中使用有向图更为广泛一些,但好友关系是双向的,因此这里用无向图。代码如下:
- # -*- coding: UTF-8-*-
- from pyecharts import Graph
- import json
- import sys
- import sqlite3
- conn = sqlite3.connect('db2.db')
- c = conn.cursor()
- print "Opened database successfully";
- ccursor = c.execute("SELECT nickname,friends FROM player")
- nodes = []
- links = []
- temps = []
- for row in cursor:
- temps.append({"name":row[0],"friends":row[1].split(",")})
- nodes.append({"name":row[0],"symbolSize":5})
- for temp in temps:
- for friend in temp["friends"]:
- if {"name":friend,"symbolSize":5} in nodes:
- links.append({"source":temp["name"],"target":friend})
- graph = Graph("力导图",width=1400,height=1600)
- graph.add(
- "",
- nodes,
- links,
- graph_layout = "force",
- label_pos="right",
- graph_repulsion=10,
- line_curve=0.2,
- )
- graph.render()
得到:
俗话说“物以类聚人以群分”,在我们的数据集中也同样适用。可以观察到这份社交网络其实是由多个小社区群落组成的,比如在最左下角的这个部分,这个小社区处于安全圈的边缘地带,很有可能不是安全从业者,我们放大来看:
这个“五边形”是一个完全子图。在这个小社区中,五个人都互为好友,也被称作“派系(Clique)”,这五个人很有可能经常一起开黑。
同时我们可以看到顶点这位玩家:
如果我们把上面的部分看做是安全圈的话,这位叫Feng_Bao的玩家卡在了安全圈与这个5人小社区“道路咽喉”的位置,这样的节点具有较高的“中介中心性(Betweenness Centrality)”,往往具有不可替代的作用。在现实中类似房屋中介一样,买房者与卖房者之间的联系都得靠他。
除了中介中心性,在图论中节点还有另外两个重要性质:度中心性(Degree Centrality)以及紧密中心性(Closeness Centrality)。
一个节点与之相连的边越多,这个节点的度中心性就越高,也就是好友越多,度中心性越高,很可能是具有较高名望的人,比如微博的大V,KOL。
紧密中心性则是衡量一个节点到其他所有节点的最短距离之和的指标,一个节点的紧密中心性越高那么他传播信息的时候也就越不需要依赖其他人。
分别计算一下数据集中三个中心性排名靠前的玩家。
有没有看到眼熟的ID呢:
确实看到一些眼熟的ID,但由于我们前面寻找安全圈的算法并不准确,在收集数据的过程中很可能误入到某些特定的圈子中。比如某些安全圈玩家同时又是二次元爱好者,那么很可能会把这份数据集带入到”二次元圈“。为了尽量避免这种情况,我们使用一些社区发现算法来完成社区的寻找与分割。
社区发现算法用来发现网络中的社区结构,多数是聚类类型的算法。使用社区发现算法可以帮助我们发现联系相对紧密的社区,从而帮助我们把安全圈和其他圈子的人分割开来。
常见的社区发现算法有:Girvan-Newman、Louvai、K-Clique、Label propagation等。
在Python下可以使用NetworkX来完成各类社区发现算法的调试,但NetworkX本身只是算法工具,并不具备可视化功能,而笔者联调plt画出来的图实在奇丑无比。因此这里使用算法单一但可视化功能强悍的gephi来实现。
fast-unfolding是基于Modularity的算法,也是复杂网络当中进行社团划分简单高效、应用最广泛的算法。
用force atlas图布局:
fast-unfolding:
除此之外Gephi还支持GN算法,但内存要求较高,有兴趣的同学可以尝试下其他算法。
经过30000余次迭代,最终得到了19个社区,用图像来表示是这样的:
在社区发现算法中社区的数目和大小通常是不可知的,一般是用模块度Modularity来检查社区分类的合理性。由于本文采集的数据较少且这里的好友关系是双向的,不像微博的关注/粉丝的机制能较准确地找出图的连通性,所以这里的社区发现效果并不理想。
笔者在使用NetworkX尝试了多种算法和不同的参数后,最终选择了一个样本数量为1125的社区,覆盖了原数据集样本总数的88.58%。在简单观察了这个社区的合理性后,决定使用这份数据集来做后续的战绩分析。
三、战绩爬取和分析
1. 谁是安全圈的吃鸡第一人
拿到了要进行战绩数据采集的玩家名单后,我们需要先确定几个指标来衡量一个玩家的吃鸡技术水平,才能有指向性的进行数据采集。笔者最终选取了数个指标,分别是:
- 历史最高Rank,即最高段位
- 最近20场游戏的平均排名
- 最近20场的吃鸡数和前10数
- 最近20场游戏的击杀总数
- 最近20场游戏造成的总伤害
笔者还决定采集一些有趣的指标,能反映玩家的游戏习惯:
- 最近20场的武器使用情况
- 最近20场的死亡地点
- 最近20场的游戏总时长
爬虫写完后数据很快就抓取完毕。
先来看看安全圈玩家们最近20场游戏的情况
在最近的20场比赛中苟到排名前十次数最多的是【RickyHao】和【NeglectLee】两位,达到惊人的17次,85%的前10率。
这一指标在安全圈的平均值是6.33。
单独看看吃鸡情况:
在最近20场比赛中吃鸡次数最多的是这位叫【qingfenggod】的玩家,达到了可怕的10次,近20场次中有一半的比赛都笑到了最后。前十次数第一的【RickyHao】则在吃鸡数上排到了第二位,达到了8次。
而这一数值的平均值仅才0.71,两位玩家都达到了10数倍。
【RickyHao】之所以在这一指标上如此突出是因为最近20场次里包含了很多活动模式,而【qingfenggod】则大部分是在排位中获得的,可以说是非常惊人的胜率了。
在KDA和伤害方面:
可以看到大部分玩家都集中在左下半部分,可以认为正常玩家都在这一点簇群内, 即KDA<2,伤害<400的部分。
而KDA达到4伤害超过550的玩家仅有4位。KDA超过5伤害超过600的仅仅只有一位了。
但有一位玩家达到了令人窒息的:
- KD:8.4
- 伤害:1099.57
是第二名的近两倍,是平均值的近10倍!!!!直接来到了散点图的云端之上,这可是击杀与死亡比啊,如果不是高科技的话这位玩家可能是职业级的水准了。
这位玩家也正是刚才提到吃鸡榜第一的【qingfenggod】。
同样在吃鸡榜中排第二的【RickyHao】,这一数据仅为:
- KD:3.7
- 伤害:461.18
排位第8位。
思考:其实这里已经可以很直观地分类出正常玩家、高级玩家、外挂玩家三大类别。如果是反外挂/风控等场景,对于这种密度相差很大的簇群,可以尝试使用kmeans这类基于距离的聚类算法来将样本分为3~5类,并借助移动速度、平均移动距离等指标来辅助判断是否为外挂玩家。这里不作深入探究。
笔者更感兴趣的是吃鸡和枪法的关系,一个人的枪法越好,越容易吃鸡吗?吃鸡对于笔者这样热衷伏地苟活的玩家会更友好吗?
对于枪法这一表征,直接使用KD和damage来代替,再加上移动距离来分析这三类指标与吃鸡率的相关性
做个简单的线性相关分析,计算Pearson系数:
光从相关系数看,枪法和吃鸡虽然是正相关,但并不是呈现出非常强的相关性,顶多达到了中等程度相关。
而整场游戏的移动距离则和吃鸡完全呈弱相关了,可能是吃鸡这个游戏真的很看运气吧。
而如果一位玩家只是想进入游戏前十,则和个人枪法没什么太大关系了,反而和移动距离关系较大。
换句话说,如果只是想冲进前十,乖乖苟毒跑圈就可以了。
这也基本印证我们对游戏的理解。
如果说以上对最近二十场次游戏的分析还无法回答“谁是安全圈吃鸡第一人”这个问题的话,那么历史最高排位情况应该能给出一个答案了。
那么谁的rank分值会是安全圈中最高的呢,我们同样遍历了1125位玩家的这一指标:
(注:官方API的提示中写到,由于官方服务器问题,一些玩家的这一数据可能丢失或者有误)
取四人TPP的排位情况,前三位分别是:
- Salmonnnnn:4094.7144
- syzhou:3906.409
- ph4nt0mer:3609.1436
通过观察好友关系,笔者相信他们与安全圈关系密切(大家也可以搜索一下这些ID)。
写到这里,“谁是安全圈的吃鸡第一人?”这一问题已经差不多给出了答案。
2. 玩家画像
风控、反APT等场景中经常会用一些手段对黑客或者用户进行画像。在这里笔者也做了一些研究玩家游戏习惯的工作,基于玩家的击杀行为来画像。
挑选一位玩家游戏记录较多的玩家,以【sanmao2054】为例。
通过分析他550场次比赛中的的891次击杀,来推测一下该玩家的游戏习惯,刻画出这位玩家的游戏风格。
从武器使用情况来看:
sanmao2054最钟爱步枪,最常使用的是M416和AK47这两把万精油老款自动步枪,两把枪的击杀人数加起来超过了250次。
笔者最喜欢用的ScarL步枪在他的手里排在了优先级非常靠后的位置。
在狙击枪方面:
sanmao2054偏爱SKS这种连发狙击步枪,击杀次数达到了22次。而对于m24和kar98这种单发拉栓步枪就不太热衷使用,两把枪使用次数加起来也不过29次。
总体来看,这位玩家在狙击枪的使用频率上远不如步枪。所有狙击枪的击杀次数加起来都不及AK或者M4的一半。
在冲锋枪方面:
最爱的当属UMP,而vector紧随其后,达到44次击杀。要知道热爱vector的玩家并不多,所以这可以算是这位玩家较明显的特点。
其他:
空投枪的使用次数并不多,看来这位玩家对追梦没什么兴趣。
虽然是近身型玩家,但使用喷子的次数并不多。更偏向于自动武器。
而使用爆破手雷击杀了高达31次,这是个非常亮眼的数据。
从击杀距离来看:
平均击杀距离排在第一位的自然是狙中之王,精准度最强劲的AWM,达到了120多米。
排在第二的则是这位玩家最爱的SKS,达到111米了。
对于这位玩家最喜爱的m4和ak两类步枪,平均击杀距离仅只有19到24米。
从这里可以看出这位玩家偏好近距离作战,热爱刚枪,对于杀伤力较大的自动步枪情有独钟。
sanmao2054的最远击杀距离达到了285米,使用的却是SKS这一款连狙步枪,也从侧面印证此人刚枪的风格。
从平均击杀时间点来看:
sanmao2054在前期击杀使用的基本都是手枪/冲锋枪,DP28等武器,在中期会使用AK等自动步枪。后期则以空投枪为主。
有趣的一点是,这位玩家使用爆破手雷完成击杀的时间点也比较靠后。
可以合理地推测出,他比较倾向于在最后使用手雷来打扫战场,快速结束战斗。这也是比较聪明的做法。
根据以上信息基本可以脑补一下这位玩家的打法是:
先跳伞到人多的区域,随意捡起一两把武器(甚至是手枪)就开始干架,成功击杀对手后就寻找ak/m4等自动步枪过渡到中期,会留雷到后期来结束战斗,在少数情况下后期也会去考虑空投枪。
用一些关键词来描述sanmao2054可能会是:【刚枪小王子】、【步枪之王】、【不擅长狙击】、【爆破手】、【使用vector的大手子】之类的。
最后用两张安全圈所有玩家的死亡热力图来结束全文:
四、最后
本文仅是一个For fun的周末项目,涉及的数据有限。在真正的网络攻防实践中,数据挖掘和分析能为安全工程师带来更多的便利,特别是在流量分析/异常检测/溯源取证/风控画像等方面。