是否有用于垃圾邮件处理的明显相似的 Unicode 字符字典?

信息安全 垃圾邮件 统一码
2021-08-16 12:48:31

我有看起来像这样的垃圾邮件:

мy вυddy'ѕ мoм мαĸeѕ $74/нoυr oɴ тнe lαpтop。ѕнe нαѕ вeeɴ lαιd oғғ 或 ѕeveɴ мoɴтнѕ вυт lαѕт мoɴтн нer pαy cнecĸ wαѕ $19420 jυѕт worĸιɴɢ oɴ тнe lαpтop ғ或 α ғew нoυrѕ。нere'ѕ тнe ѕιтe тo reαd мore something.com

显然这是使用 Unicode 来避免垃圾邮件检测。是否有任何 ASCII -> Unicode 字典可以帮助查找相似的字母?

这是此Stack Overflow 答案的变体,但特定于 spam 。

3个回答

我会在我的垃圾邮件分类算法中添加一些可以检测同一个单词/句子中的多个编码的东西。例如,lαѕт有拉丁语l、希腊语/科普特语 alpha、西里尔字母 dze、西里尔字母 te 似乎很可疑。使用 unicodedata 可以在 python 中完成一件非常快速和肮脏的事情。那是

>>> unicode_str = u"""мy вυddy'ѕ мoм мαĸeѕ $74/нoυr oɴ тнe lαpтop. ѕнe нαѕ 
вeeɴ lαιd oғғ ғor ѕeveɴ мoɴтнѕ вυт lαѕт мoɴтн нer pαy cнecĸ wαѕ $19420 jυѕт 
worĸιɴɢ oɴ тнe lαpтop ғor α ғew нoυrѕ. нere'ѕ тнe ѕιтe тo reαd мore something­.c­o­m­"""

>>> import unicodedata
>>> for uc in unicode_str:
        print uc, unicodedata.name(uc).split(' ')[0], unicodedata.category(uc)
м CYRILLIC Ll
y LATIN Ll
  SPACE Zs
в CYRILLIC Ll
υ GREEK Ll
d LATIN Ll
d LATIN Ll
y LATIN Ll
' APOSTROPHE Po
ѕ CYRILLIC Ll
(...)

其中“Ll 表示小写字母”,我们仅使用字母的 unicode 名称的第一部分来获取其类别。那么我们可以做类似的事情

>>> def count_letter_encodings(unicode_str):
        unicode_block_set = set()
        for uc in unicode_str:
            category = unicodedata.category(uc)
            if category[0] == 'L':
                unicode_block = unicodedata.name(uc).split(' ')[0]
                if unicode_block not in unicode_block_set:
                    unicode_block_set.add(unicode_block)
        return len(unicode_block_set)

>>> map(count_letter_encodings, unicode_str.split(' '))
[2, 3, 2, 3, 3, 1, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 0, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 2, 3, 2, 2, 2, 1]

有几个单词具有 3 种不同的字母编码这一事实非常可疑(甚至两个都是可疑的)。以及其他可疑的事情;例如,软连字符在.com.

从网上提取一些简短的外国文本样本,您会发现大多数时候单词应该只有一种编码:

>>> greek_text = u'Ελληνας ϰαὶ δὴ ϰαὶ γράμματα'
>>> spanish_text = u'¿Cómo estas? Espero que todo esté bien. Hace bastantes años que estoy'
>>> russian_text = u'Все люди рождаются свободными и равными в своем достоинстве и правах. Они наделены'
>>> for text in (greek_text, spanish_text, russian_text):
        print map(count_letter_encodings, text.split(' '))
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

显然,您必须使用机器学习进行彻底测试,因为可能存在混合编码的良性情况,但这对于在大型数据集上训练的垃圾邮件分类器可能是有用的输入。

我会检查在同一个单词(或句子)中使用多个编码。对于这种事情,那将是一个死铃声。

否则,这样的事情可以帮助你 - 不幸的是它只是一个部分表,你必须反过来使用它。十六进制代码是 UTF-8,所以在这里c3a0表示U+00E0

a:  c3a0,c3a1,c3a2,c3a3,c3a4,c3a5,c3a6,c481,c483,c485,
c:  c2a2,c3a7,c487,c489,c48b,c48d
d:  c48f,c491
e:  c3a8,c3a9,c3aa,c3ab,c493,c495,c497,c499,c49b
g:  c49d,c49f,c4a1,c4a3
h:  c4a5,c4a7
i:  c2a1,c3ac,c3ad,c3ae,c3af,c4a9,c4ab,c4ae,c4b0,c4ba
j:  c4b5
k:  c4b7,c4b8
l:  c4ae,c4af,c4ba,c4bc
n:  c3b1,c584,c586,c588,c589,c58b
o:  c3b0,c3b2,c3b3,c3b4,c3b5,c3b6,c3b8,c58d,c58f,c591,c593
p:  c3be
s:  c29a
u:  c2b5,c3b9,c3ba,c3bb,c3bc
x:  c397
y:  c3bd,c3bf
z:  c29e
A:  c380,c381,c382,c383,c384,c385,c386,c480,c482,c484
B:  c39f
C:  c387,c486,c488,c48a,c48c
D:  c390,c48e,c490,
E:  c388,c389,c38a,c38b,c492,c494,c496,c498,c49a,c592
G:  c49c,c49e,c4a0,c4a2
H:  c4a4,c4a6
I:  c38c,c38d,c38e,c38f,c4a8,c4aa,c4ac
J:  c4b4
K:  c4b6
L:  c4b9,c4bb,c4bd,c4bf
N:  c391,c583,c585,c587
O:  c392,c393,c394,c395,c396,c398,c58c,c58e,c590,c592
P:  c39e
R:  c594
r:  c595
S:  c28a
U:  c399,c39a,c39b,c39c,
Y:  c29f,c39d
Z:  c28e

再想一想,您可能必须添加一个“ignore-me”字符列表,这些字符可以添加到字符串中以使其看起来不同但看起来相似,例如 U+0082。现在我想起来了,这可以用来打败“每个句子中最多两个编码”。可以合法使用诸如“déja vu”之类的词(我记得在 Mac 编辑器中看到过),但U+0300可以使用组合重音使“Víágŕa”看起来完全不同。

所以首先应该删除所有“组合”,然后必须忽略一些合法字符(例如省略号 - 文字处理器喜欢它......以及各种风格的引号)。最后,可以计算编码,或者您可以将字符替换为上面的 OCR 相似字符。

Unicode 联盟将此类集合作为其数据库的一部分发布。https://util.unicode.org/UnicodeJsps/confusables.jsp

例如https://pypi.org/project/homoglyphs/只需以机器可读的形式下载这些数据并创建一个内存映射,它允许您执行以下操作

Python 3.8.2 (default, May 18 2021, 11:47:11) 
[Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import homoglyphs
>>> hg = homoglyphs.Homoglyphs(strategy=homoglyphs.STRATEGY_LOAD)
>>> hg.to_ascii("𝔱𝐫ⅰ𝗉Ι𝔢е𝖾")
['trip1eee', 'tripIeee', 'tripleee', 'trip|eee']

不幸的是,开箱即用,它对您的示例数据根本不起作用。我发现野外的混淆通常似乎使用不在 Unicode 混淆映射中的代码点。

>>> >>> hg.to_ascii("мy вυddy'ѕ мoм")
[]
>>> hg.to_ascii("something­.c­o­m­")
[]
>>> hg.to_ascii("lαpтop")
[]
>>> hg.to_ascii("oғғ")
[]

我发现的 Python 库已经退役,尽管有一个分支试图恢复它。也许他们将能够提高真实世界混淆数据的性能(或者更好地说服 Unicode 扩展他们的混淆数据库)。