Python 混乱代码解析

写在前面

一年前在国外看到的 一篇文章 ,也是因为这个脚本,让我喜欢上了 Python。

代码只有一行,有人看了会爱上 Python ,比如像我这么萌萌哒的人。

今天突然想起来了,就靠着记忆重新写了一个。

完成后的代码如下,我已经大概处理了一下,为了好看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python
(lambda _,__,___,____,_____,______,_______, ________:
getattr(
__import__(True.__class__.__name__[_] +
[].__class__.__name__[__]),
().__class__.__eq__.__class__.__name__[:__] +
().__iter__().__class__.__name__[_____:________]
)(
_,
(lambda _,__,___: _(_,__,___))
(lambda _,__,___: chr(___ % __) +
_(_,__,___ // __) if ___ else (lambda: _).func_code.co_lnotab,
____<<______,
(((_____ << _____) + _______) << ((_ << _______) +
(_ << __))) - (((((___ << __) + _) << ____) -
_) << ((((_ << ____) - _) << ___) +_))
- (((_____ << _____) + _______) << ((_______ << ____))) +
(((___ << _____) - ___) << ((((___ << __) + _) << ___) -
_)) - (((((___ << __) - _) << ____) +
_____) << ((___ << _____) - ___)) + (((_______ << ____) -
___) << ((_____ << ____) +___)) + (((((___ << __) -
_) << ___) + _) << (((((_ << ___) + _)) << ___) + (_ << _))) +
(((((___ << __) + _) << ___) - ___) << ((_ << ______))) +
(((_____ << ____) -___) << ((_______ << ___))) +
(((_ << ______) + _) << ((___ << ____) - _)) - (((((___ << __) +
_) << __) + _) << ((_____ << ___) - _)) - (((_____ << __) -
_) << ((_ << _____) - _)) -(((_ << _____) + _) << ((___ << ___) -
_)) - (_____ << (((((_ << ___) + _)) << _))) +
(_ << (((___ << __) + _))) + (((((_ << ___) + _))) << ___) + _
)
)
)(* (lambda _, __: _(_, __))(
lambda _, __:
[__[(lambda: _).func_code.co_nlocals].func_code.co_argcount] +
_(_, __[(lambda _: _).func_code.co_nlocals:]) if __ else [],
(
lambda _: _,
lambda _, __: _,
lambda _, __, ___: _,
lambda _, __, ___, ____: _,
lambda _, __, ___, ____, _____: _,
lambda _, __, ___, ____, _____, ______: _,
lambda _, __, ___, ____, _____, ______, _______: _,
lambda _, __, ___, ____, _____, ______, _______, ________: _
)
)
)

怎么样?是不是吓到了。当时我感叹:逼还是你会装啊

好吧来看分析吧。

功能及要求

这个脚本的效果就是在屏幕上输出一个字符串,比如我们最熟悉的I love Medici.Yan!

要求是:

  1. 不能使用 printf echo 等字符串输出的函数
  2. 不能出现相关的字符串

实现

Step 1

  1. 不能出现 print echo 这些函数,那么我们就可以使用 write 方法, write(1, 'I love Medici.Yan!\n'), 1 是什么?1 就是类 Unix 系统下的标准输出,在类 Uninx 系统上,所有的设备都是文件,所以你向这个文件写东西,就会显示到屏幕上。

    那么在 Python 里面,我们用到了 os 这个模块,

    1
    2
    import os
    os.write(1, 'I love Medici.Yan\n')
  2. 但是,又不能直接出现 'I love Medici.Yan\n' 这个字符串,应该怎么办呢?

    这里我们可以使用每个字母对应的 ASCII 码,然后通过 chr 来将其转换为字符

    比如要输出一个 I

    1
    os.write(1, chr(73))

这种一堆的 chr 显然很不优雅,那么我们考虑用一个大数来存储每一位的字符的 ASCII 码数值.

公式是这样的:

(某字符的 ASCII 码) * 256^(字符的位置)

然后取其和。

1
2
3
4
words = list("I love Medici.Yan\n")
num = sum(ord(words[i]) * 256 ** i for i in xrange(len(words)))
print num
# 908683317850257809145804104980513101193289

Step 2

我们已经把字符串藏在了那个大数里面,那么写一个转换函数,通过递归把字符输出

1
2
3
4
5
6
7
8
9
10
import os
def convert(num):
if num:
os.write(1, chr(num % 256))
return convert(num // 256)
else:
return

num = 908683317850257809145804104980513101193289
convert(num)

Step 3

嗯,我们把这个函数写成 lambda 表达式,lambda 在 Python 里面也可以看作是匿名函数。

1
2
3
4
5
import os
comb = lambda f, n: f(f,n)
convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else ""
num = 908683317850257809145804104980513101193289
os.write(1, comb(convert, num))

这么调用是没问题的,但是太容易让人看懂了,怎么办?

Python 有个内置方法叫 getattr,用于返回一个对象属性,或者方法

我们知道 import 语句是用来导入外部模块的,当然还有 from...import... 也可以,但是其实 import 实际上是使用 builtin 函数 __import__ 来工作的

于是上面这段代码,我们可以写成:

1
2
3
4
comb = lambda f, n: f(f,n)
convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else ""
num = 908683317850257809145804104980513101193289
getattr(__import__('os'), 'write')(1, comb(convert, num))

写成一行吧

1
getattr(__import__('os'), 'write')(1, (lambda f,n: f(f,n))(lambda f,n: chr(n % 256)+f(f, n // 256) if n else "", 908683317850257809145804104980513101193289))

是不是有点感觉了,来,继续

Step 4

我们知道 __import__('os') 这里的 os 是个字符串,直接写个 os 在那里容易就被人看出是做什么的了,那么我们就可以使用字符串拼接的方法来构造 os 和 write 这两个字符串。

1
2
3
4
5
6
7
8
>>> True.__class__.__name__
'bool'
>>> [].__class__.__name__
'list'
>>> ().__class__.__eq__.__class__.__name__
'wrapper_descriptor'
>>> ().__iter__().__class__.__name__
'tupleiterator'
  • 我们用 bool 的 o 和 list 的 s 来拼接成一个 os
  • 取 wrapper_descriptor 的前两个字符 wr 还有 tupleiterator 的 iter 拼成 writer
  • 把 lambda 局部变量的名字改成 _ 和 __ 来降代可读性
  • convert 返回的空字符串 “” 用 (lambda:0).func_code.co_lnotab 来代替。

func_code.co_lnotab 是查找函数中代码对象的行号表,我们用的是匿名函数,就没有行号,于是返回的就是空

那么现在我们的代码就写成这个样子了:

1
getattr(__import__(True.__class__.__name__[1] +[].__class__.__name__[2]),().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8])(1, (lambda _,__:_(_,__))(lambda _,__: chr(__%256) + _(_, __//256) if __ else (lambda: 0).func_code.co_lnotab, 908683317850257809145804104980513101193289))

嗯,看起来已经很不错了。我们下面要做的,就是把数字也藏起来,让读代码的人更加抓狂。

Step 5

现在我们要做的就是在代码里面混淆数字,试着想一下如果每次都重新混淆一次这个数字的话,太痛苦了吧。那么我们就可以把 0-9 这些数字混淆后,再通过局部变量的方式传进来,这样不就可以了?

于是我们的代码就可以写成下面这种形式了:

1
2
3
4
5
6
7
(lambda n1,n2,n3,n4,n5,n6,n7,n8:
getattr(
...
)(
...
)
)(*range(1,9))

于是我们现在要做的,就是想办法实现 range(1,9) 就可以了,这 8 个数字,完全可以通过四则运算组成我们的 256, 908683317850257809145804104980513101193289 还有 5,8

我们可以通过函数的代码对象来获取它的参数个数

1
2
>>> (lambda a, b, c: 0).func_code.co_argcount
3

于是思路就明朗了,我们只要改变参数的个数为 1-8,就能生成我们的 range(1,9) 了。

构造一个元组,元素为 参数个数为 1-8 的函数。

1
2
3
4
5
6
7
8
9
10
funcs = (
lambda _: _,
lambda _, __: _,
lambda _, __, ___: _,
lambda _, __, ___, ____: _,
lambda _, __, ___, ____, _____: _,
lambda _, __, ___, ____, _____, ______: _,
lambda _, __, ___, ____, _____, ______, _______: _,
lambda _, __, ___, ____, _____, ______, _______, ________: _
)

然后通过一个递归函数把这个元组输出成 range(1,9):

1
2
3
4
5
6
7
8
>>> def convert(L):
... if L:
... return [L[0].func_code.co_argcount] + convert(L[1:])
... else:
... return []
...
>>> convert(funcs)
[1, 2, 3, 4, 5, 6, 7, 8]

嗯,写成一行:

1
convert = lambda L: [L[0].func_code.co_argcount] + convert(L[1:]) if L else []

再转成匿名递归函数形式:

1
2
3
4
>>> (lambda f, L: f(f, L))(
... lambda f, L: [L[0].func_code.co_argcount] + f(f, L[1:]) if L else [],
... funcs)
[1, 2, 3, 4, 5, 6, 7, 8]

下面就是 怎么隐藏 0 和 1 了。我们可以通过 检查任意函数的局部变量的个数来得到 0 和 1。

1
2
3
4
>>> (lambda: _).func_code.co_nlocals
0
>>> (lambda _: _).func_code.co_nlocals
1

于是我们现在的代码就成了下面的这个样子了。别急,256 和 908683317850257809145804104980513101193289 这两个数字还没有替换呢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(lambda _,__,___,____,_____,______,_______, ________:
getattr(
__import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________]
)(
_, (lambda _,__,___: _(_,__,___))(lambda _,__,___: chr(___ % __) + _(_,__,___ // __) if ___ else (lambda: _).func_code.co_lnotab,
256,
908683317850257809145804104980513101193289
)
)
)(* (lambda _, __: _(_, __))
(lambda _, __: [__[(lambda: _).func_code.co_nlocals].func_code.co_argcount] + _(_, __[(lambda _: _).func_code.co_nlocals:]) if __ else [],
(
lambda _: _,
lambda _, __: _,
lambda _, __, ___: _,
lambda _, __, ___, ____: _,
lambda _, __, ___, ____, _____: _,
lambda _, __, ___, ____, _____, ______: _,
lambda _, __, ___, ____, _____, ______, _______: _,
lambda _, __, ___, ____, _____, ______, _______, ________: _
)
)
)

嗯,我们现在已经有了 1-8 这 8 个数字了,怎么用它来表示成这两个数字呢?

256 比较好办,我们用 4<<6 就可以表示成 256 了,于是 在上面代码中 256 的位置我们就可以表示成 ____<<______ 当然,1<<8 的结果也是 256,那么完全可以写成_<<________ 没有固定的答案。

对于 908683317850257809145804104980513101193289 这个大数,原文作者提供了一个拆分的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from math import ceil, log

def encode(num, depth):
if num == 0:
return "_ - _"
if num <= 8:
return "_" * num
return "(" + convert(num, depth + 1) + ")"

def convert(num, depth=0):
result = ""
while num:
base = shift = 0
diff = num
span = int(ceil(log(abs(num), 1.5))) + (16 >> depth)
for test_base in xrange(span):
for test_shift in xrange(span):
test_diff = abs(num) - (test_base << test_shift)
if abs(test_diff) < abs(diff):
diff = test_diff
base = test_base
shift = test_shift
if result:
result += " + " if num > 0 else " - "
elif num < 0:
base = -base
if shift == 0:
result += encode(base, depth)
else:
result += "(%s << %s)" % (encode(base, depth),
encode(shift, depth))
num = diff if num > 0 else -diff
return result

这个算法不是特别难懂,就是测试在一个确定区间的不同的数字组合,直到我们找到 一个组合使得以一个为基数,一个为移位长度,然后是最接近 num 的(也就是 他们差的绝对值最小)。

然后我们执行

1
2
>>> convert(908683317850257809145804104980513101193289)
'(((_____ << _____) + _______) << ((_ << _______) + (_ << __))) - (((((___ << __) + _) << ____) - _) << ((((_ << ____) - _) << ___) + _)) - (((_____ << _____) + _______) << ((_______ << ____))) + (((___ << _____) - ___) << ((((___ << __) + _) << ___) - _)) - (((((___ << __) - _) << ____) + _____) << ((___ << _____) - ___)) + (((_______ << ____) - ___) << ((_____ << ____) + ___)) + (((((___ << __) - _) << ___) + _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((((___ << __) + _) << ___) - ___) << ((_ << ______))) + (((_____ << ____) - ___) << ((_______ << ___))) + (((_ << ______) + _) << ((___ << ____) - _)) - (((((___ << __) + _) << __) + _) << ((_____ << ___) - _)) - (((_____ << __) - _) << ((_ << _____) - _)) - (((_ << _____) + _) << ((___ << ___) - _)) - (_____ << (((((_ << ___) + _)) << _))) + (_ << (((___ << __) + _))) + (((((_ << ___) + _))) << ___) + _'

得到了一个很理想的分解,于是用这个来替换。

最终结果

再回过头来看这段代码,是不是清晰了很多?逼还是你会装啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python
(lambda _,__,___,____,_____,______,_______, ________:
getattr(
__import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________]
)(
_,
(lambda _,__,___: _(_,__,___))
(lambda _,__,___: chr(___ % __) + _(_,__,___ // __) if ___ else (lambda: _).func_code.co_lnotab,
____<<______,
(((_____ << _____) + _______) << ((_ << _______) +
(_ << __))) - (((((___ << __) + _) << ____) - _) << ((((_ << ____) - _) << ___) +_))
- (((_____ << _____) + _______) << ((_______ << ____))) +
(((___ << _____) - ___) << ((((___ << __) + _) << ___) - _)) - (((((___ << __) - _) << ____) +
_____) << ((___ << _____) - ___)) + (((_______ << ____) - ___) << ((_____ << ____) +
___)) + (((((___ << __) - _) << ___) + _) << (((((_ << ___) + _)) << ___) + (_ << _))) +
(((((___ << __) + _) << ___) - ___) << ((_ << ______))) + (((_____ << ____) -
___) << ((_______ << ___))) + (((_ << ______) + _) << ((___ << ____) - _)) - (((((___ << __) +
_) << __) + _) << ((_____ << ___) - _)) - (((_____ << __) - _) << ((_ << _____) - _)) -
(((_ << _____) + _) << ((___ << ___) - _)) - (_____ << (((((_ << ___) + _)) << _))) +
(_ << (((___ << __) + _))) + (((((_ << ___) + _))) << ___) + _
)
)
)(* (lambda _, __: _(_, __))(
lambda _, __:
[__[(lambda: _).func_code.co_nlocals].func_code.co_argcount] + _(_, __[(lambda _: _).func_code.co_nlocals:]) if __ else [],
(
lambda _: _,
lambda _, __: _,
lambda _, __, ___: _,
lambda _, __, ___, ____: _,
lambda _, __, ___, ____, _____: _,
lambda _, __, ___, ____, _____, ______: _,
lambda _, __, ___, ____, _____, ______, _______: _,
lambda _, __, ___, ____, _____, ______, _______, ________: _
)
)
)