├── .gitignore
├── PypiScan.py
├── README.md
├── db
├── .gitkeep
└── pypi
│ └── .gitkeep
├── lib
├── __init__.py
├── consle_width.py
├── pypi_package.py
├── rule
│ ├── __init__.py
│ ├── content.py
│ └── start.py
├── scan_python.py
└── threads.py
├── log
└── log.txt
├── pic
└── 111.png
├── requirements.txt
├── test
├── Acqusition-4.4.2.tar.gz
├── dark-magic-0.1.2.tar.gz
├── jeIlyfish-0.7.0.tar.gz
├── libpeshnx-0.1.tar.gz
├── reols-0.1.tar.gz
└── req-tools-0.4.tar.gz
└── tmp
└── .gitkeep
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | .idea/
4 |
--------------------------------------------------------------------------------
/PypiScan.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # 作者:咚咚呛
4 | # 版本:v0.1
5 | # 功能:本程序旨在为扫描整个pypi库的恶意代码特征,包含:后门类、挖矿类、勒索类、信息盗取类等
6 |
7 | from lib.threads import *
8 | import optparse
9 |
10 | if __name__ == "__main__":
11 | parser = optparse.OptionParser()
12 | parser.add_option("--thread", dest="thread", type="int", default=10, help=u"线程数")
13 |
14 | options, _ = parser.parse_args()
15 |
16 | pypi = Threads_Scan(thread=options.thread, systempath=os.path.dirname(os.path.abspath(__file__)))
17 | pypi.run()
18 | pypi.outfile.flush()
19 | pypi.outfile.close()
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PypiScan 0.1
2 |
3 | 这个脚本主要提供对pypi供应链的源头进行安全扫描研究,扫描并发现未知的恶意包情况。
4 |
5 | ## 作者 ##
6 |
7 | 咚咚呛
8 |
9 | 如有其他建议,可联系微信280495355
10 |
11 | ## 技术细节 ##
12 | 技术细节如下
13 |
14 | 1、脚本采取多线程方式爬取pypi所有包信息,默认10个线程,根据主机和带宽的配置,建议增加。
15 | 2、每个项目包含多个版本包,releases包存在两种类型,whl和tar,whl类型实质为zip压缩。
16 | 3、由于文件数量过大,硬盘存储有限,故采取下载/扫描完毕后会删除原始包,但会保存恶意文件到指定目录。
17 | 4、扫描以静态扫描为住,扫描特征行包括:网络链接行为、特定文件操作、命令执行行为、特定编码行为
18 | 5、作者执行了一次全量扫描,项目数量:21W+、包数量:150W+,用时10天+,目前误报较多,脚本主要用于研究使用,如要生产环境使用,请识别规则
19 |
20 | ## 程序使用 ##
21 |
22 | > root# git clone https://github.com/grayddq/PypiScan.git
23 | >
24 | > root# cd PypiScan
25 | >
26 | > root# sudo pip install -r requirements.txt
27 | >
28 | > root# python python PypiScan.py --thread 100
29 |
30 | ## 运行截图 ##
31 |
32 | 
33 |
34 |
35 | ## 历史风险参考 ##
36 |
37 | 历史pypi恶意包
38 |
39 | https://snyk.io/vuln/SNYK-PYTHON-JEILYFISH-536726
40 | https://snyk.io/vuln/SNYK-PYTHON-PYTHON3DATEUTIL-536644
41 | https://snyk.io/vuln/SNYK-PYTHON-LIBARI-460155
42 | https://snyk.io/vuln/SNYK-PYTHON-LIBPESH-460156
43 | https://snyk.io/vuln/SNYK-PYTHON-LIBPESHNX-460157
44 | https://snyk.io/vuln/SNYK-PYTHON-DAJNGO-72531
45 | https://snyk.io/vuln/SNYK-PYTHON-DIANGO-72529
46 | https://snyk.io/vuln/SNYK-PYTHON-DJAGO-72530
47 | https://snyk.io/vuln/SNYK-PYTHON-MYBIUBIUBIU-72532
48 | https://snyk.io/vuln/SNYK-PYTHON-PKGUTIL-72527
49 | https://snyk.io/vuln/SNYK-PYTHON-SMPLEJSON-72526
50 | https://snyk.io/vuln/SNYK-PYTHON-TIMEIT-72528
51 | https://snyk.io/vuln/SNYK-PYTHON-COLOURAMA-72537
52 | https://snyk.io/vuln/SNYK-PYTHON-PYCONAUFUNTIMES-72536
53 | https://snyk.io/vuln/SNYK-PYTHON-DJANGA-72533
54 | https://snyk.io/vuln/SNYK-PYTHON-EASYINSTALL-72534
55 | https://snyk.io/vuln/SNYK-PYTHON-LIBPESHKA-72535
56 | https://snyk.io/vuln/SNYK-PYTHON-SSHDECORATE-40786
57 | https://snyk.io/vuln/SNYK-PYTHON-ACQUSITION-40662
58 | https://snyk.io/vuln/SNYK-PYTHON-APIDEVCOOP-40663
59 | https://snyk.io/vuln/SNYK-PYTHON-BZIP-40664
60 | https://snyk.io/vuln/SNYK-PYTHON-CRYPT-40665
61 | https://snyk.io/vuln/SNYK-PYTHON-DJANGOSERVER-40666
62 | https://snyk.io/vuln/SNYK-PYTHON-PWD-40667
63 | https://snyk.io/vuln/SNYK-PYTHON-SETUPTOOLS-40668
64 | https://snyk.io/vuln/SNYK-PYTHON-TELNET-40669
65 | https://snyk.io/vuln/SNYK-PYTHON-URLIB3-40670
66 | https://snyk.io/vuln/SNYK-PYTHON-URLLIB-40671
67 |
68 | 文章参考链接:
69 |
70 | https://github.com/dateutil/dateutil/issues/984
71 | https://blog.reversinglabs.com/blog/suppy-chain-malware-detecting-malware-in-package-manager-repositories
72 | https://medium.com/@bertusk/detecting-cyber-attacks-in-the-python-package-index-pypi-61ab2b585c67
73 | https://medium.com/@bertusk/cryptocurrency-clipboard-hijacker-discovered-in-pypi-repository-b66b8a534a8
74 | https://www.bleepingcomputer.com/news/security/backdoored-python-library-caught-stealing-ssh-credentials/
75 | https://www.bleepingcomputer.com/news/security/ten-malicious-libraries-found-on-pypi-python-package-index/
76 |
--------------------------------------------------------------------------------
/db/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/db/pypi/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/lib/__init__.py
--------------------------------------------------------------------------------
/lib/consle_width.py:
--------------------------------------------------------------------------------
1 | """ getTerminalSize()
2 | - get width and height of console
3 | - works on linux,os x,windows,cygwin(windows)
4 | """
5 |
6 | __all__=['getTerminalSize']
7 |
8 |
9 | def getTerminalSize():
10 | import platform
11 | current_os = platform.system()
12 | tuple_xy=None
13 | if current_os == 'Windows':
14 | tuple_xy = _getTerminalSize_windows()
15 | if tuple_xy is None:
16 | tuple_xy = _getTerminalSize_tput()
17 | # needed for window's python in cygwin's xterm!
18 | if current_os == 'Linux' or current_os == 'Darwin' or current_os.startswith('CYGWIN'):
19 | tuple_xy = _getTerminalSize_linux()
20 | if tuple_xy is None:
21 | print "default"
22 | tuple_xy = (80, 25) # default value
23 | return tuple_xy
24 |
25 | def _getTerminalSize_windows():
26 | res=None
27 | try:
28 | from ctypes import windll, create_string_buffer
29 |
30 | # stdin handle is -10
31 | # stdout handle is -11
32 | # stderr handle is -12
33 |
34 | h = windll.kernel32.GetStdHandle(-12)
35 | csbi = create_string_buffer(22)
36 | res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
37 | except:
38 | return None
39 | if res:
40 | import struct
41 | (bufx, bufy, curx, cury, wattr,
42 | left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
43 | sizex = right - left + 1
44 | sizey = bottom - top + 1
45 | return sizex, sizey
46 | else:
47 | return None
48 |
49 | def _getTerminalSize_tput():
50 | # get terminal width
51 | # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
52 | try:
53 | import subprocess
54 | proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
55 | output=proc.communicate(input=None)
56 | cols=int(output[0])
57 | proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
58 | output=proc.communicate(input=None)
59 | rows=int(output[0])
60 | return (cols,rows)
61 | except:
62 | return None
63 |
64 |
65 | def _getTerminalSize_linux():
66 | def ioctl_GWINSZ(fd):
67 | try:
68 | import fcntl, termios, struct, os
69 | cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
70 | except:
71 | return None
72 | return cr
73 | cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
74 | if not cr:
75 | try:
76 | fd = os.open(os.ctermid(), os.O_RDONLY)
77 | cr = ioctl_GWINSZ(fd)
78 | os.close(fd)
79 | except:
80 | pass
81 | if not cr:
82 | try:
83 | cr = (env['LINES'], env['COLUMNS'])
84 | except:
85 | return None
86 | return int(cr[1]), int(cr[0])
87 |
88 | if __name__ == "__main__":
89 | sizex,sizey=getTerminalSize()
90 | print 'width =',sizex,'height =',sizey
--------------------------------------------------------------------------------
/lib/pypi_package.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import requests, os, tarfile, shutil, zipfile
4 | from bs4 import BeautifulSoup
5 | from scan_python import scan_python
6 |
7 |
8 | class Pypi_Scan:
9 | def __init__(self, systempath):
10 | self.systempath = systempath
11 | # 文件目录和解压目录
12 | self.tmppath = systempath + "/tmp/"
13 | # 数据存放目录
14 | self.dbpath = systempath + "/db/pypi/"
15 | # 日志存放目录
16 | self.logpath = systempath + "/log/log.txt"
17 | # pypi网站地址
18 | self.PypiURL = "https://pypi.org"
19 | # pypi包路径
20 | self.PypiURLSimple = "https://pypi.org/simple/"
21 |
22 | # 获取pypi有多少项目,项目的名称和链接
23 | def pypi_packages_projects(self):
24 | packagesProjects = []
25 | response = requests.get(self.PypiURLSimple)
26 | if response.status_code != 200: return packagesProjects
27 | soup = BeautifulSoup(response.text, "html.parser")
28 | for a in soup.find_all('a'):
29 | packagesProjects.append({"name": a.string, "url": self.PypiURL + a['href']})
30 |
31 | return packagesProjects
32 |
33 | def get_file_extension(self, filename):
34 | return filename.split(".")[-1]
35 |
36 | # 通过项目信息,获取项目安装文件和链接信息
37 | def get_packages_project_files(self, project):
38 | info = project
39 | response = requests.get(project["url"])
40 | if response.status_code != 200: return
41 | soup = BeautifulSoup(response.text, "html.parser")
42 | projectInfo = []
43 | for a in soup.find_all('a'):
44 | if self.get_file_extension(a.string) == "whl":
45 | projectInfo.append({"name": a.string, "url": a['href'], "type": "whl"})
46 | else:
47 | projectInfo.append({"name": a.string, "url": a['href'], "type": "tar"})
48 |
49 | info["versioninfo"] = projectInfo
50 |
51 | return info
52 |
53 | # 下载包文件
54 | def download_package_file(self, url_file, filename):
55 | try:
56 | r = requests.get(url_file, stream=True)
57 | f = open(filename, "wb")
58 | for chunk in r.iter_content(chunk_size=512):
59 | if chunk:
60 | f.write(chunk)
61 | return True
62 | except Exception as e:
63 | return False
64 |
65 | # 删除某目录下所有文件
66 | def del_file(self, tardir, filename):
67 | try:
68 | os.remove(filename)
69 | shutil.rmtree(tardir)
70 | except Exception as e:
71 | return False
72 |
73 | # 解压文件
74 | def untar(self, filename, path, type):
75 | try:
76 | if type == "whl":
77 | t = zipfile.ZipFile(filename)
78 | else:
79 | t = tarfile.open(filename)
80 | t.extractall(path=path)
81 | t.close()
82 | return True
83 | except Exception as e:
84 | return False
85 |
86 | # 拷贝恶意文件到指定留存目录
87 | def copyfile(self, projectname, filepath):
88 | try:
89 | path = self.dbpath + projectname
90 | if not os.path.exists(path):
91 | os.makedirs(path)
92 | shutil.copy(filepath, path)
93 | except Exception as e:
94 | print(e)
95 | return False
96 |
97 | # 扫描目录文件内容的安全性
98 | def scan_security(self, project, path):
99 | try:
100 | filepath = scan_python(self.systempath,path)
101 | if not filepath: return False
102 | self.copyfile(project, filepath)
103 | return True
104 | except Exception as e:
105 | return False
106 |
107 | # 扫描pypi包的安全性
108 | # 参数:项目名称、包地址、文件下载到本地文件路径、解压路径、包类型
109 | # 处理流程:
110 | # 1、下载安全包
111 | # 2、解压安全包
112 | # 3、扫描恶意内容
113 | # 4、存储恶意文件
114 | # 5、删除临时文件
115 | def scan_package(self, project, url, filename, tardir, type):
116 | security = False
117 | # 下载文件
118 | self.download_package_file(url, filename)
119 | # 解压文件
120 | if self.untar(filename, tardir, type):
121 | # 扫描文件内容
122 | if self.scan_security(project, tardir):
123 | security = True
124 | # 删除临时文件目录
125 | self.del_file(tardir, filename)
126 | return security
127 |
128 |
129 | if __name__ == "__main__":
130 | # Pypi_Scan("/Users/grayddq/Grayddq/01.mygit/21.PypiScan/").scan_security("a","/Users/grayddq/Grayddq/01.mygit/21.PypiScan/tmp/libpeshnx-0.1")
131 | if Pypi_Scan("/Users/grayddq/Grayddq/01.mygit/21.PypiScan/").scan_package("0121",
132 | "https://files.pythonhosted.org/packages/99/b1/8329b44e81c794ebe8772531fbb94df3afb107102d183c1b0a17abb49471/0121-0.0.1.tar.gz#sha256=c340f511c652c50e67fac4e85528064f4253f5850446c9258949574a5d541f92",
133 | "/Users/grayddq/Grayddq/01.mygit/21.PypiScan/tmp/0121-0.0.1.tar.gz",
134 | "/Users/grayddq/Grayddq/01.mygit/21.PypiScan/tmp/0121/",
135 | "gz"):
136 | print("True")
137 |
--------------------------------------------------------------------------------
/lib/rule/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/lib/rule/__init__.py
--------------------------------------------------------------------------------
/lib/rule/content.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # 解码类
5 | # rule1
6 | encry_keywords = ["base64.b64decode("]
7 |
8 | # rule2
9 | # 网络传输类
10 | net_keywords = ["socket.socket",
11 | "urllib.request",
12 | "urllib.urlopen",
13 | "urllib2.urlopen",
14 | "requests.get",
15 | "requests.post"
16 | ]
17 |
18 | # rule3
19 | # 命令执行类
20 | exec_keywords = ["os.system",
21 | "os.popen",
22 | "commands.getstatusoutput",
23 | "commands.getoutput",
24 | "commands.getstatus",
25 | "os.chmod",
26 | " exec("
27 | ]
28 |
29 | # rule4
30 | # 本地敏感文件
31 | local_keywords = [".bashrc",
32 | "/.ssh/id_rsa"
33 | ]
34 |
35 | # rule5
36 | # 敏感特征
37 | malice_keywords = ["exec(base64.b64decode("]
38 |
39 |
40 | # 规则,符合如下
41 | # rule2 + rule1
42 | # rule2 + rule3
43 | # rule2 + rule4
44 | # rule5
45 |
46 |
47 | def Check(filestr,filename):
48 | filestr = filestr.lower()
49 | rule1, rule2, rule3, rule4, rule5 = False, False, False, False, False
50 |
51 | if filename == "setup.py":
52 | if "base64.b64decode(" in filestr:
53 | return True
54 |
55 | for key in encry_keywords:
56 | if key in filestr:
57 | rule1 = True
58 | for key in net_keywords:
59 | if key in filestr:
60 | rule2 = True
61 | for key in exec_keywords:
62 | if key in filestr:
63 | rule3 = True
64 | for key in local_keywords:
65 | if key in filestr:
66 | rule4 = True
67 | for key in malice_keywords:
68 | if key in filestr:
69 | rule5 = True
70 |
71 | if rule1 & rule2:
72 | return True
73 | elif rule1 & rule3:
74 | return True
75 | elif rule2 & rule3:
76 | return True
77 | elif rule2 & rule4:
78 | return True
79 | elif rule5:
80 | return True
81 | else:
82 | return False
83 |
--------------------------------------------------------------------------------
/lib/rule/start.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # 启动项类
5 | start_keywords = ["Software\Microsoft\Windows\CurrentVersion\Run"]
6 |
7 |
8 | def Check(filestr, filename):
9 | filestr = filestr.lower()
10 |
11 | for key in start_keywords:
12 | if key in filestr:
13 | return True
14 |
15 | return False
16 |
--------------------------------------------------------------------------------
/lib/scan_python.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os, sys
4 |
5 |
6 | def load_rule(systempath):
7 | rules = []
8 | for root, dirs, files in os.walk(systempath + "/lib/rule"):
9 | for filespath in files:
10 | if filespath[-3:] == '.py':
11 | rulename = filespath[:-3]
12 | if rulename == '__init__':
13 | continue
14 | __import__('lib.rule.' + rulename)
15 | rules.append(rulename)
16 | return rules
17 |
18 |
19 | def scan_python(systempath, path):
20 | rules = load_rule(systempath)
21 | for root, dirs, files in os.walk(path):
22 | for filename in files:
23 | if filename.split(".")[-1] != 'py': continue
24 | filepath = os.path.join(root, filename)
25 | if os.path.getsize(filepath) < 500000:
26 | for rule in rules:
27 | file = open(filepath, "rb")
28 | filestr = file.read()
29 | file.close()
30 | if sys.modules['lib.rule.' + rule].Check(filestr,filename):
31 | return filepath
32 | return ""
33 |
34 |
35 | if __name__ == "__main__":
36 | print scan_python("/Users/grayddq/Grayddq/01.mygit/21.PypiScan/test/libpeshnx-0.1")
37 |
--------------------------------------------------------------------------------
/lib/threads.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import time, threading, Queue, sys, os
4 | from consle_width import getTerminalSize
5 | from pypi_package import *
6 |
7 |
8 | class Threads_Scan:
9 | def __init__(self, thread=10, systempath=os.path.dirname(os.path.abspath(__file__))):
10 | # 线程数
11 | self.thread = thread
12 | # 系统目录
13 | self.system_path = systempath
14 | # 日志存放目录
15 | self.logpath = systempath + "/log/log.txt"
16 | # 文件目录和解压目录
17 | self.tmppath = systempath + "/tmp/"
18 | # 日志存储
19 | self.outfile = open(self.logpath, 'w')
20 | # 多线程框架
21 | self.thread_count = 0
22 | self.scan_count = self.found_count = 0
23 | self.lock = threading.Lock()
24 | self.console_width = getTerminalSize()[0] - 2
25 | self.msg_queue = Queue.Queue()
26 | self.STOP_ME = False
27 | threading.Thread(target=self._print_msg).start()
28 | self._init_queue()
29 |
30 | def _print_msg(self):
31 | while not self.STOP_ME:
32 | try:
33 | _msg = self.msg_queue.get(timeout=0.1)
34 | except:
35 | continue
36 |
37 | if _msg == 'status':
38 | msg = '%s Found| %s projects| %s scanned in %.1f seconds| %s threads' % (
39 | self.found_count, self.queue.qsize(), self.scan_count, time.time() - self.start_time,
40 | self.thread_count)
41 | sys.stdout.write('\r' + ' ' * (self.console_width - len(msg)) + msg)
42 | else:
43 | sys.stdout.write('\r' + _msg + ' ' * (self.console_width - len(_msg)) + '\n')
44 | sys.stdout.flush()
45 |
46 | def _update_scan_count(self):
47 | self.last_scanned = time.time()
48 | self.scan_count += 1
49 |
50 | def _update_found_count(self):
51 | self.found_count += 1
52 |
53 | # 获取项目信息,并加入队列中
54 | def _init_queue(self):
55 | self.msg_queue.put('[+] Initializing, get pypi package...')
56 | self.queue = Queue.Queue()
57 |
58 | project = Pypi_Scan(self.system_path).pypi_packages_projects()
59 | # 加入队列
60 | for info in project:
61 | self.queue.put(info)
62 | self.msg_queue.put('[+] Found pypi project %d in total' % len(project))
63 |
64 | self.outfile.write('[+] Found pypi project %d in total\n' % len(project))
65 | self.outfile.flush()
66 |
67 | # 开始多线程扫描
68 | def _scan(self):
69 | self.lock.acquire()
70 | self.thread_count += 1
71 | self.lock.release()
72 |
73 | pypiScan = Pypi_Scan(self.system_path)
74 |
75 | while not self.STOP_ME:
76 | try:
77 | lst_info = self.queue.get(timeout=0.1)
78 | except Queue.Empty:
79 | break
80 |
81 | while not self.STOP_ME:
82 | self._update_scan_count()
83 | self.msg_queue.put('status')
84 |
85 | # 获取project项目的包信息
86 | info = pypiScan.get_packages_project_files(lst_info)
87 | for t in info["versioninfo"]:
88 | # 扫描项目中的包信息
89 | # 项目名称、包地址、文件下载到本地文件路径、解压路径、包类型
90 | if pypiScan.scan_package(info["name"], t["url"], self.tmppath + t["name"],
91 | self.tmppath + info["name"], t["type"]):
92 | # 检测到恶意代码
93 | self._update_found_count()
94 | msg = ("Malicious project,project: %s, version: %s" % (info["name"], t["name"])).ljust(30)
95 | self.msg_queue.put(msg)
96 | self.msg_queue.put('status')
97 | self.outfile.write(msg + '\n')
98 | self.outfile.flush()
99 | break
100 | break
101 |
102 | self.lock.acquire()
103 | self.thread_count -= 1
104 | self.lock.release()
105 | self.msg_queue.put('status')
106 |
107 | def run(self):
108 | self.msg_queue.put('[+] start scan projects ...')
109 | self.start_time = time.time()
110 | for i in range(self.thread):
111 | try:
112 | t = threading.Thread(target=self._scan, name=str(i))
113 | t.setDaemon(True)
114 | t.start()
115 | except:
116 | pass
117 | while self.thread_count > 0:
118 | try:
119 | time.sleep(1.0)
120 | except KeyboardInterrupt, e:
121 | msg = '[WARNING] User aborted, wait all slave threads to exit...'
122 | sys.stdout.write('\r' + msg + ' ' * (self.console_width - len(msg)) + '\n\r')
123 | sys.stdout.flush()
124 | self.STOP_ME = True
125 | self.STOP_ME = True
126 |
--------------------------------------------------------------------------------
/log/log.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/log/log.txt
--------------------------------------------------------------------------------
/pic/111.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/pic/111.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.22.0
2 | beautifulsoup4==4.8.2
3 |
--------------------------------------------------------------------------------
/test/Acqusition-4.4.2.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/test/Acqusition-4.4.2.tar.gz
--------------------------------------------------------------------------------
/test/dark-magic-0.1.2.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/test/dark-magic-0.1.2.tar.gz
--------------------------------------------------------------------------------
/test/jeIlyfish-0.7.0.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/test/jeIlyfish-0.7.0.tar.gz
--------------------------------------------------------------------------------
/test/libpeshnx-0.1.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/test/libpeshnx-0.1.tar.gz
--------------------------------------------------------------------------------
/test/reols-0.1.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/test/reols-0.1.tar.gz
--------------------------------------------------------------------------------
/test/req-tools-0.4.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/test/req-tools-0.4.tar.gz
--------------------------------------------------------------------------------
/tmp/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayddq/PypiScan/843b66d37327fb6c3bb5c262bc8e68fb8c70b349/tmp/.gitkeep
--------------------------------------------------------------------------------