-
-
[原创]看雪 2024 KCTF 大赛 第四题 神秘信号
-
发表于: 2024-8-22 03:06 3792
-
pyinstaller打包的二进制,用 pyinstxtractor 或 pyinstxtractor-ng 解包(一定要用最新版本,可以省去自己补pyc文件头的步骤)
解释器版本是 Python3.8,看到 main.pyc 熟练的掏出 uncompyle6 和 decompyle3 反编译一下:
看着很友好,但是,CrackMe模块在哪里呢??
(各种失败的尝试,可以跳过)
既然 _internel 目录下的 pyd 文件会被加载,那么不妨试试在这里做注入。
将 _internal/_lzma.pyd 文件删除,放一个 _internal/_lzma.py 文件,里面写上自己想执行的语句:
启动 main.exe,sys.modules真的被打印出来了,但是里面还是没有 CrackMe ??
那就再直接一点,看看它的真面目:
输出:
CrackMe的真身竟然是base64?
找到解包出来的 base64.pyc ,反编译一下,果然在末尾有偷梁换柱:
所以 CrackMe.main 就是 base64.main,而它的字节码也被替换掉了
稳妥起见,不去处理上面反编译的东西,而是直接在_lzma.py的hook中dump原始字节码:
(pyc_data参考自 https://stackoverflow.com/questions/73439775/how-to-convert-marshall-code-object-to-pyc-file )
得到的 crackme_main.pyc,uncompyle6和decompyle3都会报错,不过标准库的dis模块能正常反汇编。
人工翻译了一会……突然想起了 pycdc ,试了下效果非常完美(需要小修一下)(对反编译的代码重新生成pyc再dis对比,与dump出来的dis完全相同):
写出逆向算法:
事情看起来解决了?不,问题才刚刚开始
逆向出来的main函数,其参数data的类型显然需要是bytes,但是main.py调用的时候传递的是str
只好先按bytes传递,先用给的序列号做验证:
rev的逻辑没有错误,问题出现在main.py里面,疑似ztokey时实际拼接的几个字符串常量与看到的不同
先不去深究,根据上面输出的key和m的对应关系,猜测真正的ztokey2应该是这样:
得到输出:
最终提交答案为 Hello World!KCTF
拿到题第一时间看了下 _internel 目录下 python38.dll 发现带有官方的数字签名,如蒙大赦,感谢出题人没搞什么自己魔改解释器的恶心套路。
(譬如更换字节码定义之类的……对攻击方真就是纯体力活增加工作量了。今年实在没时间没精力更没兴趣花费大量时间破代码混淆(例如第三题),过于浪费时间收益又极其有限(解法只能针对这道题,又不像vmp之类的能广泛应用)。在校生可能更有动力搞,社畜的周末还不如用来补觉)
题目的未解之谜还有很多,继续通过_lzma.py的hook探索:
最后给sys.modules赋值是必须的,否则会出现异常:
打印出 CrackMe.main 函数的实际输入输出,发现输入 "KCTF"
时,CrackMe.main 函数的输入是 b'QI8F'
,输出是 'QQMlP7!!'
输入值在此之前被修改过。
输入和输出与逆向出来的算法能对应,至少说明逆向过程没有问题。
回忆一下main.py的片段:
那么只有两种可能:main.py是虚假的,或者,input被修改过
hook一下input看看:
得到输出:
问题得到确认,builtin的input被修改过,它的返回值是经过变换的。但是,repr仍然标记为built-in,所以这里是如何实现的?
知己知彼还是非常重要的。每道题目放出前会习惯性的看一下出题人曾经在论坛发过的文章。出题人今年发了一系列python源码分析的文章,所以有预感第四题可能是python,而事实确实如此。
在此回顾下出题人的几篇文章:
第一篇讲了pyinstaller打包时篡改标准库注入代码
第二篇讲了替换函数的__code__属性改变其逻辑
第三篇讲了修改_frozen_importlib._find_and_load.__code__改变模块加载过程
第四篇讲了内存patch修改builtin函数(builtin函数没有__code__属性,通过内置id函数可以获得PyMethodDef结构的地址,PyMethodDef偏移16字节处是指向PyCFunction结构的指针,PyCFunction偏移8字节的地方是真正的C函数起始地址)
一和二已经观察到了,现在确认一下三和四
老方法通过_lzma.py的hook把_frozen_importlib._find_and_load.__code__的内容dump出来:
然后用pycdc反编译:(有 WARNING: Decompyle incomplete ,可以用 pydas 看反汇编,缺少的地方不重要,先不去管)
前面hook内置input函数时顺便打印了id(input)
的值,挂上调试器按照文章的说法找到最终函数所在,dump内存,ida分析:
注意到 v33[m + 5] = (v32[m] ^ 0x77) + 21
做个验证:
与先前的所有观察都能对应上,包括main.py里面的常量。
也能解释为什么胡乱输入偶尔会触发builtin input return NULL的System Error:
至于为什么忽略convertinput的转换也能找到正确的答案,回顾一下CrackMe.main函数的逻辑,依次是单字节异或、换表base64、相邻两字节交换
那么,输出的每4个字节实际只受对应输入的3个字节影响。
而main.py里恰恰是对CrackMe.main输出以4字节为单位做重组,对于"KCTF"这样的短输入,甚至只是直接移动到末尾
所以,最终的serial,一定是输入的name与"Hello World!"这个12字节的常量的组合。
事实上,convertinput可以改成任意的单字节映射,都不影响这个结论
至此,题目最后的谜团只有对_frozen_importlib._find_and_load和input两处的修改是在何处初始化的。
估计是藏在了某个模块的初始化代码里,具体可能要追pyinstaller的初始化流程。不过,从上面SystemError的调用栈来看,这两处修改的位置相当早,至少在加载_lzma.py之前已经完成了。
等出题人公开完整的题目设计了
import
CrackMe
while
True
:
while
True
:
print
(
"(账号密码由字母大小写、数字、!、空格组成)"
)
print
(
"请输入账号:"
)
h
=
input
()
z
=
CrackMe.main(h)
if
len
(z) <
20
:
key
=
"dZpKdrsiB6cndrGY"
+
z
else
:
key
=
z[
0
:
4
]
+
"dZpK"
+
z[
4
:
8
]
+
"drsi"
+
z[
8
:
12
]
+
"B6cn"
+
z[
12
:
16
]
+
"drGY"
+
z[
16
:]
print
(
"请输入验证码:"
)
h
=
input
()
m
=
CrackMe.main(h)
if
key
=
=
m:
print
(
"Success"
)
break
print
(
"Fail"
)
continue
import
CrackMe
while
True
:
while
True
:
print
(
"(账号密码由字母大小写、数字、!、空格组成)"
)
print
(
"请输入账号:"
)
h
=
input
()
z
=
CrackMe.main(h)
if
len
(z) <
20
:
key
=
"dZpKdrsiB6cndrGY"
+
z
else
:
key
=
z[
0
:
4
]
+
"dZpK"
+
z[
4
:
8
]
+
"drsi"
+
z[
8
:
12
]
+
"B6cn"
+
z[
12
:
16
]
+
"drGY"
+
z[
16
:]
print
(
"请输入验证码:"
)
h
=
input
()
m
=
CrackMe.main(h)
if
key
=
=
m:
print
(
"Success"
)
break
print
(
"Fail"
)
continue
import
sys
print
(sys.modules)
import
sys
print
(sys.modules)
import
CrackMe
print
(CrackMe)
import
CrackMe
print
(CrackMe)
<module
'base64'
from
'...\\main\\_internal\\base64.pyc'
>
<module
'base64'
from
'...\\main\\_internal\\base64.pyc'
>
a
=
main.__code__.replace(
1
, (), b
'd\x01}\x01d\x02}\x02d\x03}\x03d\x04}\x04|\x00D\x00]\x1c}\x05|\x05d\x05A\x00}\x05|\x04|\x05\xa0\x00d\x06d\x07\xa1\x02\x17\x00}\x04q\x14|\x04}\x00t\x01d\x02t\x02|\x00\x83\x01d\x08\x83\x03D\x00]\x90}\x05|\x00|\x05|\x05d\x08\x17\x00\x85\x02\x19\x00}\x06d\x01\xa0\x03d\td\n\x84\x00|\x06D\x00\x83\x01\xa1\x01}\x07t\x01d\x02t\x02|\x07\x83\x01d\x0b\x83\x03D\x00]V}\x08|\x07|\x08|\x08d\x0b\x17\x00\x85\x02\x19\x00}\tt\x02|\t\x83\x01d\x0bk\x00r\xc2|\x02d\x0bt\x02|\t\x83\x01\x18\x007\x00}\x02|\td\x0cd\x0bt\x02|\t\x83\x01\x18\x00\x14\x007\x00}\t|\x01|\x03t\x04|\td\r\x83\x02\x19\x007\x00}\x01q~qF|\x01d\x0e|\x02d\r\x1a\x00\x14\x007\x00}\x01t\x01t\x02|\x01\x83\x01d\r\x1a\x00\x83\x01D\x00]L}\x05|\x01|\x05d\r\x14\x00\x19\x00}\n|\x01|\x05d\r\x14\x00d\x06\x17\x00\x19\x00}\x0b|\x01d\x00|\x05d\r\x14\x00\x85\x02\x19\x00|\x0b\x17\x00|\n\x17\x00|\x01|\x05d\r\x14\x00d\r\x17\x00d\x00\x85\x02\x19\x00\x17\x00}\x01q\xf8|\x01S\x00'
, (
None
, '
', 0, '
ZQ
+
U7tSBEKVzyf5coCwb94Dd6raT0eLNin12Hp8mOxFuvMgIPlhRY3WjksqJAXG
/
', b'
', 85, 1, '
little
', 3, compile('
', '
', '
exec
').replace(1, (), b'
|\x00]\x10}\x01t\x00|\x01d\x00\x83\x02V\x00\x01\x00q\x02d\x01S\x00
', ('
08b
', None), '
', 19, 115, (), 0, b'
', '
', ('
format
',), 2, 0, 4, ('
.
0
', '
byte
'), **('
co_argcount
', '
co_cellvars
', '
co_code
', '
co_consts
', '
co_filename
', '
co_firstlineno
', '
co_flags
', '
co_freevars
', '
co_kwonlyargcount
', '
co_lnotab
', '
co_name
', '
co_names
', '
co_nlocals
', '
co_posonlyargcount
', '
co_stacksize
', '
co_varnames
')), '
', 6, '
0
', 2, '
!
'), '
', 4, 67, (), 0, b'
', '
', ('
to_bytes
', '
range
', '
len
', '
join
', '
int
'), 12, 0, 7, ('
data
', '
encoded_str
', '
padding
', '
base64_chars
', '
ww
', '
i
', '
chunk
', '
binary_str
', '
j
', '
six_bits
', '
a
', '
b
'), **('
co_argcount
', '
co_cellvars
', '
co_code
', '
co_consts
', '
co_filename
', '
co_firstlineno
', '
co_flags
', '
co_freevars
', '
co_kwonlyargcount
', '
co_lnotab
', '
co_name
', '
co_names
', '
co_nlocals
', '
co_posonlyargcount
', '
co_stacksize
', '
co_varnames'))
main.__code__
=
a
a
=
main.__code__.replace(
1
, (), b
'd\x01}\x01d\x02}\x02d\x03}\x03d\x04}\x04|\x00D\x00]\x1c}\x05|\x05d\x05A\x00}\x05|\x04|\x05\xa0\x00d\x06d\x07\xa1\x02\x17\x00}\x04q\x14|\x04}\x00t\x01d\x02t\x02|\x00\x83\x01d\x08\x83\x03D\x00]\x90}\x05|\x00|\x05|\x05d\x08\x17\x00\x85\x02\x19\x00}\x06d\x01\xa0\x03d\td\n\x84\x00|\x06D\x00\x83\x01\xa1\x01}\x07t\x01d\x02t\x02|\x07\x83\x01d\x0b\x83\x03D\x00]V}\x08|\x07|\x08|\x08d\x0b\x17\x00\x85\x02\x19\x00}\tt\x02|\t\x83\x01d\x0bk\x00r\xc2|\x02d\x0bt\x02|\t\x83\x01\x18\x007\x00}\x02|\td\x0cd\x0bt\x02|\t\x83\x01\x18\x00\x14\x007\x00}\t|\x01|\x03t\x04|\td\r\x83\x02\x19\x007\x00}\x01q~qF|\x01d\x0e|\x02d\r\x1a\x00\x14\x007\x00}\x01t\x01t\x02|\x01\x83\x01d\r\x1a\x00\x83\x01D\x00]L}\x05|\x01|\x05d\r\x14\x00\x19\x00}\n|\x01|\x05d\r\x14\x00d\x06\x17\x00\x19\x00}\x0b|\x01d\x00|\x05d\r\x14\x00\x85\x02\x19\x00|\x0b\x17\x00|\n\x17\x00|\x01|\x05d\r\x14\x00d\r\x17\x00d\x00\x85\x02\x19\x00\x17\x00}\x01q\xf8|\x01S\x00'
, (
None
, '
', 0, '
ZQ
+
U7tSBEKVzyf5coCwb94Dd6raT0eLNin12Hp8mOxFuvMgIPlhRY3WjksqJAXG
/
', b'
', 85, 1, '
little
', 3, compile('
', '
', '
exec
').replace(1, (), b'
|\x00]\x10}\x01t\x00|\x01d\x00\x83\x02V\x00\x01\x00q\x02d\x01S\x00
', ('
08b
', None), '
', 19, 115, (), 0, b'
', '
', ('
format
',), 2, 0, 4, ('
.
0
', '
byte
'), **('
co_argcount
', '
co_cellvars
', '
co_code
', '
co_consts
', '
co_filename
', '
co_firstlineno
', '
co_flags
', '
co_freevars
', '
co_kwonlyargcount
', '
co_lnotab
', '
co_name
', '
co_names
', '
co_nlocals
', '
co_posonlyargcount
', '
co_stacksize
', '
co_varnames
')), '
', 6, '
0
', 2, '
!
'), '
', 4, 67, (), 0, b'
', '
', ('
to_bytes
', '
range
', '
len
', '
join
', '
int
'), 12, 0, 7, ('
data
', '
encoded_str
', '
padding
', '
base64_chars
', '
ww
', '
i
', '
chunk
', '
binary_str
', '
j
', '
six_bits
', '
a
', '
b
'), **('
co_argcount
', '
co_cellvars
', '
co_code
', '
co_consts
', '
co_filename
', '
co_firstlineno
', '
co_flags
', '
co_freevars
', '
co_kwonlyargcount
', '
co_lnotab
', '
co_name
', '
co_names
', '
co_nlocals
', '
co_posonlyargcount
', '
co_stacksize
', '
co_varnames'))
main.__code__
=
a
import
CrackMe
import
marshal
import
importlib
code
=
CrackMe.main.__code__
marshal_data
=
marshal.dumps(code)
pyc_data
=
importlib._bootstrap_external._code_to_timestamp_pyc(code)
with
open
(
"crackme_main.marshal"
,
"wb"
) as f:
f.write(marshal_data)
with
open
(
"crackme_main.pyc"
,
"wb"
) as f:
f.write(pyc_data)
import
CrackMe
import
marshal
import
importlib
code
=
CrackMe.main.__code__
marshal_data
=
marshal.dumps(code)
pyc_data
=
importlib._bootstrap_external._code_to_timestamp_pyc(code)
with
open
(
"crackme_main.marshal"
,
"wb"
) as f:
f.write(marshal_data)
with
open
(
"crackme_main.pyc"
,
"wb"
) as f:
f.write(pyc_data)
def
main(data):
# def是自己补上的
encoded_str
=
''
padding
=
0
base64_chars
=
'ZQ+U7tSBEKVzyf5coCwb94Dd6raT0eLNin12Hp8mOxFuvMgIPlhRY3WjksqJAXG/'
ww
=
b''
for
i
in
data:
i
=
i ^
85
ww
=
ww
+
i.to_bytes(
1
,
'little'
)
data
=
ww
for
i
in
range
(
0
,
len
(data),
3
):
chunk
=
data[i:i
+
3
]
# binary_str = ''.join((lambda .0: for byte in .0: format(byte, '08b'))(chunk)) 反编译出来这里的语法不太对
binary_str
=
'
'.join(format(byte, '
08b
')
for
byte
in
chunk)
for
j
in
range
(
0
,
len
(binary_str),
6
):
six_bits
=
binary_str[j:j
+
6
]
if
len
(six_bits) <
6
:
padding
+
=
6
-
len
(six_bits)
six_bits
+
=
'0'
*
(
6
-
len
(six_bits))
encoded_str
+
=
base64_chars[
int
(six_bits,
2
)]
encoded_str
+
=
'!'
*
(padding
/
/
2
)
for
i
in
range
(
len
(encoded_str)
/
/
2
):
a
=
encoded_str[i
*
2
]
b
=
encoded_str[i
*
2
+
1
]
encoded_str
=
encoded_str[:i
*
2
]
+
b
+
a
+
encoded_str[i
*
2
+
2
:]
return
encoded_str
def
main(data):
# def是自己补上的
encoded_str
=
''
padding
=
0
base64_chars
=
'ZQ+U7tSBEKVzyf5coCwb94Dd6raT0eLNin12Hp8mOxFuvMgIPlhRY3WjksqJAXG/'
ww
=
b''
for
i
in
data:
i
=
i ^
85
ww
=
ww
+
i.to_bytes(
1
,
'little'
)
data
=
ww
for
i
in
range
(
0
,
len
(data),
3
):
chunk
=
data[i:i
+
3
]
# binary_str = ''.join((lambda .0: for byte in .0: format(byte, '08b'))(chunk)) 反编译出来这里的语法不太对
binary_str
=
'
'.join(format(byte, '
08b
')
for
byte
in
chunk)
for
j
in
range
(
0
,
len
(binary_str),
6
):
six_bits
=
binary_str[j:j
+
6
]
if
len
(six_bits) <
6
:
padding
+
=
6
-
len
(six_bits)
six_bits
+
=
'0'
*
(
6
-
len
(six_bits))
encoded_str
+
=
base64_chars[
int
(six_bits,
2
)]
encoded_str
+
=
'!'
*
(padding
/
/
2
)
for
i
in
range
(
len
(encoded_str)
/
/
2
):
a
=
encoded_str[i
*
2
]
b
=
encoded_str[i
*
2
+
1
]
encoded_str
=
encoded_str[:i
*
2
]
+
b
+
a
+
encoded_str[i
*
2
+
2
:]
return
encoded_str
def
rev(encoded_str):
tmp
=
encoded_str
tmp
=
"".join(tmp[
2
*
i
+
1
]
+
tmp[
2
*
i]
for
i
in
range
(
len
(tmp)
/
/
2
))
tmp
=
tmp.rstrip(
"!"
)
trans
=
str
.maketrans(
"ZQ+U7tSBEKVzyf5coCwb94Dd6raT0eLNin12Hp8mOxFuvMgIPlhRY3WjksqJAXG/"
,
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
);
tmp2
=
tmp.translate(trans)
tmp2
+
=
"="
*
((
4
-
len
(tmp2))
%
4
)
tmp3
=
base64.b64decode(tmp2)
data
=
bytes(c ^
85
for
c
in
tmp3)
return
data.decode()
def
rev(encoded_str):
tmp
=
encoded_str
tmp
=
"".join(tmp[
2
*
i
+
1
]
+
tmp[
2
*
i]
for
i
in
range
(
len
(tmp)
/
/
2
))
tmp
=
tmp.rstrip(
"!"
)
trans
=
str
.maketrans(
"ZQ+U7tSBEKVzyf5coCwb94Dd6raT0eLNin12Hp8mOxFuvMgIPlhRY3WjksqJAXG/"
,
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
);
tmp2
=
tmp.translate(trans)
tmp2
+
=
"="
*
((
4
-
len
(tmp2))
%
4
)
tmp3
=
base64.b64decode(tmp2)
data
=
bytes(c ^
85
for
c
in
tmp3)
return
data.decode()
def
ztokey(z):
if
len
(z) <
20
:
key
=
'dZpKdrsiB6cndrGY'
+
z
else
:
key
=
z[
0
:
4
]
+
'dZpK'
+
z[
4
:
8
]
+
'drsi'
+
z[
8
:
12
]
+
'B6cn'
+
z[
12
:
16
]
+
'drGY'
+
z[
16
:]
return
key
z
=
main(b
"D7C4197AF0806891"
)
key
=
ztokey(z)
m
=
main(b
"D7CHel419lo 7AFWor080ld!6891"
)
print
(z)
print
(key)
print
(m)
print
(rev(m))
def
ztokey(z):
if
len
(z) <
20
:
key
=
'dZpKdrsiB6cndrGY'
+
z
else
:
key
=
z[
0
:
4
]
+
'dZpK'
+
z[
4
:
8
]
+
'drsi'
+
z[
8
:
12
]
+
'B6cn'
+
z[
12
:
16
]
+
'drGY'
+
z[
16
:]
return
key
z
=
main(b
"D7C4197AF0806891"
)
key
=
ztokey(z)
m
=
main(b
"D7CHel419lo 7AFWor080ld!6891"
)
print
(z)
print
(key)
print
(m)
print
(rev(m))
D7DED6vCn6boDrp3W6v3Zr!!
D7DEdZpKD6vCdrsin6boB6cnDrp3drGYW6v3Zr!!
D7DEbBsZD6vCb53xn6bo2ZmODrp3b5YtW6v3Zr!!
D7CHel419lo
7AFWor080ld
!
6891
D7DED6vCn6boDrp3W6v3Zr!!
D7DEdZpKD6vCdrsin6boB6cnDrp3drGYW6v3Zr!!
D7DEbBsZD6vCb53xn6bo2ZmODrp3b5YtW6v3Zr!!
D7CHel419lo
7AFWor080ld
!
6891
def
ztokey2(z):
if
len
(z) <
20
:
key
=
'bBsZb53x2ZmOb5Yt'
+
z
else
:
key
=
z[
0
:
4
]
+
'bBsZ'
+
z[
4
:
8
]
+
'b53x'
+
z[
8
:
12
]
+
'2ZmO'
+
z[
12
:
16
]
+
'b5Yt'
+
z[
16
:]
return
key
z
=
main(b
"KCTF"
)
key
=
ztokey(z)
key2
=
ztokey2(z)
print
(z)
print
(key)
print
(key2)
print
(rev(key))
print
(rev(key2))
def
ztokey2(z):
if
len
(z) <
20
:
key
=
'bBsZb53x2ZmOb5Yt'
+
z
else
:
key
=
z[
0
:
4
]
+
'bBsZ'
+
z[
4
:
8
]
+
'b53x'
+
z[
8
:
12
]
+
'2ZmO'
+
z[
12
:
16
]
+
'b5Yt'
+
z[
16
:]
return
key
z
=
main(b
"KCTF"
)
key
=
ztokey(z)
key2
=
ztokey2(z)
print
(z)
print
(key)
print
(key2)
print
(rev(key))
print
(rev(key2))
nBQ6P7!!
dZpKdrsiB6cndrGYnBQ6P7!!
bBsZb53x2ZmOb5YtnBQ6P7!!
T'
00
-
l5
-
0
(kKCTF
Hello World!KCTF
nBQ6P7!!
dZpKdrsiB6cndrGYnBQ6P7!!
bBsZb53x2ZmOb5YtnBQ6P7!!
T'
00
-
l5
-
0
(kKCTF
Hello World!KCTF
import
CrackMe
import
sys
origin_crackme_main
=
CrackMe.main
def
hook_decompile_crackme_main(data):
print
(
repr
(data))
r
=
origin_crackme_main(data)
print
(
repr
(r))
return
r
CrackMe.main
=
hook_decompile_crackme_main
sys.modules[
"CrackMe"
]
=
CrackMe
import
CrackMe
import
sys
origin_crackme_main
=
CrackMe.main
def
hook_decompile_crackme_main(data):
print
(
repr
(data))
r
=
origin_crackme_main(data)
print
(
repr
(r))
return
r
CrackMe.main
=
hook_decompile_crackme_main
sys.modules[
"CrackMe"
]
=
CrackMe
Traceback (most recent call last):
File
"main.py"
, line
6
,
in
<module>
AttributeError:
'NoneType'
object
has no attribute
'main'
Traceback (most recent call last):
File
"main.py"
, line
6
,
in
<module>
AttributeError:
'NoneType'
object
has no attribute
'main'
...
print
(
"请输入账号:"
)
h
=
input
()
z
=
CrackMe.main(h)
...
...
print
(
"请输入账号:"
)
h
=
input
()
z
=
CrackMe.main(h)
...
old_input
=
input
def
hook_input(
*
args,
*
*
kwargs):
r
=
old_input(
*
args,
*
*
kwargs)
print
(
repr
(old_input),
hex
(
id
(old_input)),
repr
(r))
return
r
__builtins__[
"input"
]
=
hook_input
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课