![cover](https://img-blog.csdnimg.cn/6d7a83a51d334972ae982c6b75a3599f.png)
“黑产“识别算法(社区检测,相似度,关联关系)
"黑产"识别算法前言黑产的特性通过业务特性识别通过关联关系识别(非监督学习)通过行为相似度识别(非监督学习)通过用户画像识别(分类、预测)前言我们讨论的黑产识别,实务上并非单纯算法的问题,在更多的情况下,是一种基于经验性、合规性对于业务全流程和每一个节点的风险控制手段。黑产的特性黑产即黑色产业,是利用非法手段获利的行业或群体。其中当下处在风口浪尖的便是“网络黑产”。但是黑产,并不单单是通过网络手段
前言
我们讨论的黑产识别,实务上并非单纯算法的问题,在更多的情况下,是一种基于经验性、合规性对于业务全流程和每一个节点的风险控制手段。
黑产的特性
黑产即黑色产业,是利用非法手段获利的行业或群体。
其中当下处在风口浪尖的便是“网络黑产”。但是黑产,并不单单是通过网络手段实时,传统行业、生活中,我们都可能与黑产擦肩而过,受害、有时可能是受益于黑产带来的结果。比如:勒索诈骗电话、黄牛等。
侵犯了国家和社会的利益,自然会有国家有关部门去控制,但很有意思有一些黑产专薅羊毛,似乎并没有对社会人民造成多大损失,但对于一些公司和组织的发展,是极大隐患。
说了这么多,我们对黑产会有一点点感觉。总结来看,黑产具有以下几个特质:
1. 模块化、组织化、团伙操作。
“并不是一个人在战斗”。如果只是一个人投机取巧,倒也容易识别,毕竟属于一个极端异常的行为,而且个人作妖并不会考虑及其复杂的情况,极容易暴露。常见的异常识别手段即可。
但是大多数,黑产是一个高智商的团体。
2. 迷惑性、隐蔽性强。
这些高智商的团体懂得隐蔽,有时会分散收益,流向无数个“小号”。有时会利用无数虚拟ip操作,无法通过常规技术手段去定位。有时甚至开发虚拟机进行短时间内大规模作案。
3. 利益为导向、多方利益勾结。
不过“庆幸”的是,无论如何他们最终都会流向利益。从这个点出发总不会有错,但问题是这条路如何走。
通过业务特性识别
黑产团伙一般受限于资源和任务的约束,有聚集性、短期高频等特征。当然,不同的行业背景会有不同的表象。我们首先要明确梳理这个业务的全流程,每一个对于黑产可能产生利益流入的节点,以及它的前置和后置节点,再根据上述的一些特征,加以识别。
这本质上是一种运营的手段,举个例子:
1.背景:某商家卖内裤,在微信公众号发了一个广告帖,只要你分享链接给你的一个好友,就可以得到3元红包,不设上限。活动期限7天。
2.结果:不负众望,在第一天该贴的浏览量就达到100w,去重访问用户数8w。按照历史经验,每一个用户平均购买2.5条内裤来算,单价20元的内裤销量预计将有2.5x20x80000 = 400w的毛收入。
3.思考: 事实上,该商家这次活动最终的收入只有100+w,原因调查后是因为被黑产薅羊毛了。
首先说这次的营销活动策略是有严重漏洞的。很多商家为了裂变营销,会不计成本的投入资金,抢占份额。但是对于这种日用品,是否真的有这个必要如此大费周章?
很多时候,黑产的出现会帮助商家公司完善自己的策略,比如不设上限的红包是否可以用于商城内的购买?是否一定要现金红包?红包金额是否需要设计为递减?
可以说,上述策略某种程度上可以止损,但是势必影响最初的目标:裂变。 毕竟用户是要有动机去帮你拉新的。 所以这里,我们识别黑产(某种程度上讲,异常用户群体) 对于止损就显得尤为重要了。因为这些异常用户,势必不会对你的业务产生长期价值。
方法:
梳理埋点事件
- 计算类似7天内在某事件(分享链接、购买按钮点击、页面停留时长、事件时间等)相关指标。
- 设备or uid关联超过xx个账户 (xx天内)。
- 1天内某IP关联超过xx个账户。
- 收益核算。
- …
我们简单的filter,就可以把这些收益极高,行为异常的用户筛选出来。
他们的特点可以用一句话概括:短时间内,高密度分享链接,集中在下午3点,但是从未发生购买行为,推荐的uid和decive id也无购买行为,页面停留时间过短,浏览时间很长等。
这就是第一种识别的方法:业务规则筛选。
通过关联关系识别(非监督学习)
通过连通子图算法识别出一个个连通的社区,如果社区规模较大,可能背后业务含义是黑产控制一批账户。 定义社区规模为score,通过调节阈值来控制误杀、召回。
这里介绍一个算法模型-社区检测Girvan Newman。
参考链接:https://memgraph.com/blog/community_detection-algorithms_with_python_networkx
算法逻辑
本质上是寻找亲密关系的群体uid。
检测社区的方法主要有两种:
(1) Agglomerative Methods:
在凝聚方法中,我们从一个空图开始,该空图由原始图的节点组成,但没有边。接下来,将边一个接一个地添加到图中,从强边到弱边依次添加。一条边的强度,或边的权值,可以用不同的方法计算。
(2) Divisive Methods:
在分裂的方法中,我们走的是相反的道路。我们从完整的图开始,迭代地取走这些边。权重最大的边缘先被移除。每一步都要重复计算边权值,因为删除一条边后,剩余边的权值会发生变化。经过一定数量的步骤,我们得到密集连接的节点簇。Girvan-Newman(GN)算法就是典型的分裂算法。
步骤:
在格文-纽曼算法下,根据图的边介数中心性(Edge Betweenness Centrality,EBC)值,通过迭代去除图的边来发现图中的社区。边介数中心性最大的边最先被移除。
边介数中心性(edge betweenness centrality, EBC)可以定义为网络中通过一条边的最短路径的数量。根据图中所有节点之间的最短路径,给每条边一个EBC评分。
对于图和网络来说,最短路径是指任意两个节点之间距离最小的路径。让我们举一个例子来了解EBC分数是如何计算的。
EBC分数的计算是个迭代过程:
- 每次取一个节点,并绘制从选定节点到其他节点的最短路径;
- 基于最短路径,计算所有边的EBC分数;
- 对图中的每个节点重复这个过程。上图中有6个节点,因此,这个过程将有6次迭代;
- 每条边都有6个分数,将这些分数逐条边加起来;
最后,每条边的总分数除以2就是EBC得分了。
现在我们从节点A开始,与节点A直接连接的节点为节点B和节点D,那么从A到B和D的最短路径分别为AB和AD。
从A到结点C和E的最短路径经过B和D。
从节点A到最后一个节点F的最短路径,经过节点B、D、C、E。
上面的图只描述了从节点A到所有其他节点的最短路径。现在我们来看看每条边的得分。
在给边打分之前,我们将给最短路径图中的节点分配一个分数。为了分配这些分数,我们必须从根节点遍历图。从节点A到最后一个节点(节点F)。
给节点分配分数:
如图所示,节点B和D的得分都是1。这是因为从结点A到任意一个结点的最短路径只有1。出于同样的原因,节点C被赋值为1,因为从节点a到节点C只有一条最短路。继续到节点e,它通过两条最短路径连接到节点A,即ABE和ADE。因此,它得到了2分。最后一个节点F通过三条最短路径ABCF、ABEF和ADEF连接到A。所以它得到3分。
计算每条边的得分:
简单讲,每条边的分数等于,
自下而上,(1+下面n边的分数)/上面n条路径
到目前为止,我们已经计算出了相对于节点A的最短路径的边缘分数,我们将再对剩下的5个节点重复同样的步骤。最后,我们将得到网络中所有边的6个分数。我们将这些分数相加,并将其分配到原始图表中,如下图所示:
由于这是一个无向图,我们将这些分数除以2,最终得到EBC分数:
根据Girvan-Newman算法,计算EBC得分后,去掉得分最高的边,直到图一分为二。因此,在上图中,我们可以看到AB, BC, DE, EF这条边得分最高,即4。我们把这些边去掉,就得到了三个子图,我们称之为社区:
代码实现
R
install.packages("igraph")
install.packages("tidyverse")
# Library
library(igraph)
library(tidyverse)
options(scipen=200)
set.seed(1)
data <- read_csv(file = 'xxxxxxxxx.csv',
col_types = c('c','c','n', 'c','c'))
data <- data %>% mutate(relat =case_when(activity_phase==101 ~ '加油',
activity_phase==120 ~ '呵呵'))
relations <- data.frame(from=data$uid,
to=data$tuid,
status=data$relat)
network <- graph_from_data_frame(relations, directed = TRUE, vertices = NULL )
print(network, e=TRUE, v=FALSE)
# Default network
par(mar=c(0,0,0,0))
#E(network)$size <- 0.5
# 社区检测
ceb <- cluster_edge_betweenness(network) # 构造模型
dendPlot(ceb) # 可视化
# 将组别存储
group = data.frame(uid=0,group=0)
for (i in (1:length(ceb))) {
tmp = data.frame(uid=ceb[i],group=i)
colnames(tmp) <- c('uid','group')
colnames(group) <- c('uid', 'group')
group <- rbind2(group,tmp)
}
group <- group[-1,]
png(file="graph_go.png",
width=8000, height=8000)
plot(ceb,network,
layout=layout.auto,
vertex.shape=c("circle","square"), # One of “none”, “circle”, “square”, “csquare”, “rectangle” “crectangle”, “vrectangle”, “pie”, “raster”, or “sphere”
vertex.size=1.5, # Size of the node (default is 15)
vertex.size2=0.5, # The second size of the node (e.g. for a rectangle)
vertex.label = NA,
edge.arrow.size=0.1,
edge.arrow.width = 0.1
#label = NA
)
dev.off()
python
pip install python-igraph
import igraph
""
读取csv将关键字段如uid,权重装进列表
""
import csv
edges=[]
with open('test.csv','r') as f:
for row in csv.reader(f.read().splitlines()):
u,v,s=[i for i in row]
edges.append((u,v,s))
from igraph import Graph as IG
g=IG.TupleList(edges,directed=False,vertex_name_attr='name',edge_attrs=None,weights=False)
print(g)
commnities=g.community_edge_betweenness(directed=False,weights=None)
# print(commnities)
# print(g.vs['name'])
print(commnities)
print(g.vs['name'])
"""
walkstrp方法
"""
import csv
edges = []
firstline = True
with open('anti_test.csv','r') as f:
for row in csv.reader(f.read().splitlines()):
if firstline == True:
firstline = False
continue
u,v,weight = [i for i in row]
edges.append((u,v,int(weight)))
from igraph import Graph as IGraph
g = IGraph.TupleList(edges,directed = True,vertex_name_attr="name",edge_attrs=None,weights = True)
print(g)
#网络直径:一个网络的直径被定义为网络中最长最短路径
print(g.diameter())
names = g.vs["name"]
print(g.get_diameter)
[names[x] for x in g.get_diameter()]
#尝试下 "Jon"到“Margaery”之间的最短路径
print(g.shortest_paths("6994301573845976066","6951976933169071105"))
print("---------------------")
print([names[x] for x in g.get_shortest_paths("6994301573845976066","6951976933169071105")[0]])
print("---------------------")
#看下“jon”
paths = g.get_all_shortest_paths("6994301573845976066")
for p in paths:
print([names[x] for x in p])
#度的中心性
print(g.maxdegree())
for p in g.vs:
if p.degree() > 15:
print(p["name"],p.degree())
#社区检测(community Detection)
clusters = IGraph.community_walktrap(g,weights="weight").as_clustering()
nodes = [{"name":node["name"]} for node in g.vs]
community ={}
for node in nodes:
idx = g.vs.find(name=node["name"]).index
node["community"] = clusters.membership[idx]
if node["community"] not in community:
community[node["community"]] = [node["name"]]
else:
community[node["community"]].append(node["name"])
for c,l in community.items():
print("community",c,":",l)
通过行为相似度识别(非监督学习)
上面关联关系提到的算法其实最终的结论不是黑产识别本身,而是帮我们识别出了一批关系最为紧密的一群人,但这群人可能只是来自同一个学校、同一家公司或者有着某种相同的渊源。
当黑产的技术十分“高超“时,它会使用轮换proxy_ip,会避开设备性指纹,会有多个”老鼠仓“,等等一系列手段去绕开现有风控策略。我们其实还有一种方法去识别,通过行为相似度。
要计算行为相似度,需要明确两件事。1. 制作用户行为数据集,行为画像。当然也包括一些用户属性类特征。2. 明确计算什么样的相似度,本质上计算相似度就是计算距离。
我们来举个例子:
背景: 某游戏公司发布一个活动,分享你的战斗视频给好友or朋友圈,获得虚拟金币10枚(金币可以购买武器装备)。分享的视频附带游戏下载链接,如果有人通过你的链接下载游戏并注册激活,将奖励你50元现金到账。
数据集如下:
账号id | 设备id | 注册时间 | 拉新账号id | 每日活跃时间 | 每日消耗金币 | ip地址 | 手机型号 | 游戏等级 | 活动获得金币 | 活动获得返现 | … |
---|---|---|---|---|---|---|---|---|---|---|---|
1645346 | k11232 | 2020-10-09 | 66722334 | 2.5 | 286 | 999-232-111 | 苹果12 | 18 | 200 | 0 | |
23423434 | k66777 | 2020-09-02 | 77802556 | 8,0 | 10 | 999-222-111 | 华为mate20 | 50 | 100 | 100 | |
632453 | k922003 | 2021-01-03 | 20094448 | 0.5 | 1.2 | 989-212-1e1 | oneplus | 20 | 30 | 2200 | |
… | … | … | … | … | … | … | … | … | … | … |
真实情况可选特征远比这个数据集要多,我们观察一下数据。
黑产可能出现的特殊族群可能具备以下特点:
a. 同一个账号id高拉新id。
b. 多账号共用相似度较高的设备id或者ip地址。
c. 手机型号等系统信息是否密度高?
d. 注册时间较为接近。
e. 游戏等级固定(比如必须达到某一个最小等级才可以分享战绩或者消耗金币)
f. 拉新收获现金很高,但是日活跃游戏时长极低。
g. 分享链接获得金币/成功注册激活新用户的现金奖励,这个比值很低。说明分享的少,但是拉新却很高,可能团伙作案或者虚拟机操作。
h. …
我们可以选择聚类算法,可以参考本野鸡之前写的聚类算法篇😿 (非监督学习-聚类算法概述与代码实现(*K-means, k-modes, k-prototypes, DMSCAN密度聚类, GMM, 层次聚类))
这里需要注意,不同特征有的是连续变量,有的是分类变量,有的是Nominal,因此需要选择不同计算距离的方法,比如对于Ip地址这种,最好选择计算汉明距离,而非几何距离。
我们这边附一个DBSCN的代码,因为有时我发现k-means,k-modes等传统的partition-based的聚类算法并不是很显著,实务中如果更畸形可以选择类似这种密度聚类。
# 导入DBSCN训练算法
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
eps=0.007# 领域的大小,使用圆的半径表示
MinPts=10# 领域内,点个数的阈值
model=DBSCAN(eps,MinPts)
# 数据匹配
data['type']=model.fit_predict(data[['xxxx','xxx','xxxxx']])
# 绘图
plt.figure(figsize=(12, 8))
plt.scatter(
data['xxxx'],
data['xxxx'],
data['xxxxx'],
c=data['type'] # 表示颜色
)
plt.axis('off')
plt.show()
这里注意调整eps,MinPts两个参数以达到最优效果。
最终用type字段得分进行分簇,找到可能的黑产群体,这是基于行为等特征距离相似度计算的结果。
通过用户画像识别(分类、预测)
到了这一步,其实可以说某种意义上是最终末端了。我们想要预测和准确分类黑产群体,甚至识别个体,这个的大前提是:我们已经有明确的黑产、舞弊账号id的记录。我们才能选择监督学习的方式去训练模型,当下一个黑产id出现(前提是他们的手段特征不会改变),就可以及时预测诊断出它来。
数据集还是之前那个,可能特征惠更丰富,也需要增加一些高信息含量的特征,搭建模型可以参考本野鸡写的 (利用XGBoost、Information Value、SHAP寻找“小北极星“指标与分层处理) 用XGB去试试。
但是现实中我不敢用这种方法,因为总管目前反舞弊行业,黑产们的技术手段和舞弊方式千变万化,甚至不断迭代更新,我不相信有什么模型可以有那么强的鲁棒性一直长时间高准招地去预测黑产id,模型不断训练优化,成本也太高了。一旦黑产们“变”了,你的一模型基本上废掉,问题是你也不知道他们什么时候变,变了什么…对吧…
因此非监督学习还是首选,毕竟,只要是黑产一定向“钱”看齐,那么他的行为一定会出现异常/不合群的地方!!!
但是最最优秀的方法,还是经验判断,给予你对自家业务深入细致的了解,对任何漏洞策略的敏感性,不断完善漏洞。
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)