77 KiB
77 KiB
In [ ]:
import pandas as pd
from pathlib import Path
THU_MUC = Path("/opt/csai/data/osint") # nơi dữ liệu của bài (chỉ đọc, dùng chung cả lớp)
pd.set_option("display.max_colwidth", 80) # không cắt chữ dài thành "..."
# dtype=str + keep_default_na=False: đọc mọi ô thành chữ, KHÔNG biến ô trống / "NA" thành NaN
doc = lambda ten: pd.read_csv(THU_MUC / ten, dtype=str, keep_default_na=False) # lambda = hàm 1 dòng
c2 = doc("c2_list.csv") # danh sách đen IP C2 (toàn bộ lịch sử)
c2_hl = doc("c2_active.csv") # phần còn hiệu lực
kho_mau = doc("vault_c2.csv") # IP trong sổ kho mẫu mã độc
chan_web = doc("web_bans.csv") # IP fail2ban từng chặn trên web thật
for ten, bang in [("c2", c2), ("c2_hl", c2_hl), ("kho_mau", kho_mau), ("chan_web", chan_web)]:
print(f"📦 {ten:8s} = bảng {bang.shape[0]:,} hàng × {bang.shape[1]} cột: {list(bang.columns)}") # .shape = (số hàng, số cột)
print()
print(c2.head(3).to_string()) # 3 hàng đầu để thấy dữ liệu thật trông thế nàoIn [ ]:
%%bash
# grep -oE: cắt ra "mã IP + cột sources" (cột có dấu phẩy được bọc trong ngoặc kép "...")
# cut -d, -f2-: bỏ mã IP, giữ phần nguồn; sort | uniq -c: đếm từng kiểu; sort -rn: nhiều nhất lên đầu
grep -oE '^IP-[0-9a-f]+,("[^"]*"|[^,]*)' /opt/csai/data/osint/c2_list.csv | cut -d, -f2- | sort | uniq -c | sort -rn | head -12In [ ]:
TAP_HL = set(c2_hl.ip_token) # set = tập hợp, tra "có trong tập không" rất nhanh
TAP_KHO = set(kho_mau.ip_token)
def cong_cu_1_tra_danh_tieng(ip): # giống công cụ "check_ip_reputation": dò danh sách còn hiệu lực
return "XẤU (có trong danh sách đen)" if ip in TAP_HL else "không biết"
def cong_cu_2_thong_ke_feed(ip): # giống công cụ "thống kê feed C2": đọc danh sách đầy đủ, báo IP đến từ nguồn nào
hang = c2[c2.ip_token == ip]
return f"XẤU (feed ghi nguồn: {hang.sources.iloc[0]})" if len(hang) else "không có trong feed"
def cong_cu_3_kho_ma_doc(ip): # giống tra sổ kho mẫu: IP này có dính tới mẫu mã độc nào không
return "XẤU (xuất hiện trong kho mẫu mã độc)" if ip in TAP_KHO else "không có trong kho"
# chọn một IP mà cả 3 công cụ đều "biết": có trong kho mẫu VÀ còn trong danh sách hiệu lực
ung_vien = sorted(TAP_KHO & TAP_HL) # & = phần giao của hai tập
IP_THU = ung_vien[0] # ✏️ SỬA: đổi chỉ số [0] → [1] để thử IP khác
print(f"📦 ung_vien = {len(ung_vien)} IP vừa ở kho mẫu vừa còn hiệu lực: {ung_vien}")
print(f"Tra IP {IP_THU}:")
for ham in (cong_cu_1_tra_danh_tieng, cong_cu_2_thong_ke_feed, cong_cu_3_kho_ma_doc):
print(f" {ham.__name__:26s} → {ham(IP_THU)}") # __name__ = tên của hàmIn [ ]:
# ✏️ SỬA: bảng quy đổi tên nguồn → gốc thật. Tên nào không có ở đây sẽ bị coi là "honeypot của mình"
GOC = {"urlhaus": "abuse.ch", "feodo_tracker": "abuse.ch", "new": "chưa rõ"}
def cac_goc(chuoi_nguon):
ten_nguon = [t.strip() for t in chuoi_nguon.split(",")] # "a,b" → ["a", "b"]
return sorted({GOC.get(t, "honeypot của mình") for t in ten_nguon}) # .get(t, mặc định): không có thì lấy mặc định
c2["so_ten_nguon"] = c2.sources.str.split(",").str.len() # đếm số tên trong chuỗi
c2["goc"] = c2.sources.map(lambda s: " + ".join(cac_goc(s))) # .map: áp hàm cho từng ô
c2["so_goc"] = c2.sources.map(lambda s: len(cac_goc(s)))
print("📦 c2 có thêm 3 cột: so_ten_nguon, goc, so_goc. Ví dụ hàng có nhiều tên nguồn nhất:")
print(c2.sort_values("so_ten_nguon", ascending=False)[["ip_token", "sources", "so_ten_nguon", "goc", "so_goc"]].head(3).to_string())
nhieu_ten = c2[c2.so_ten_nguon >= 2]
print(f"\nIP được ≥ 2 TÊN nguồn báo : {len(nhieu_ten):,}")
print(f"IP được ≥ 2 GỐC độc lập báo: {(c2.so_goc >= 2).sum():,}")
print("\nCác IP 'nhiều tên nguồn' thực ra đến từ gốc nào:")
print(nhieu_ten.goc.value_counts().to_string())In [ ]:
import re
la_ngay = c2.threat_type.str.match(r"^\d{4}-\d{2}-\d{2}") # regex: bắt đầu bằng YYYY-MM-DD
print("10 giá trị hay gặp nhất của threat_type (TRƯỚC khi làm sạch):")
print(c2.threat_type.value_counts().head(10).to_string())
print(f"\nSố hàng mà threat_type thực ra là NGÀY THÁNG: {la_ngay.sum():,} / {len(c2):,}")
print("Những hàng đó đến từ nguồn nào:", c2[la_ngay].sources.value_counts().head(3).to_dict())
# Làm sạch: ngày tháng không phải loại mối đe doạ → đổi thành "không rõ (cột bị ghi lệch)"
c2["loai_sach"] = c2.threat_type.where(~la_ngay, "không rõ (cột bị ghi lệch)") # where: giữ chỗ đúng, thay chỗ sai
print("\nSAU khi làm sạch:")
print(c2.loai_sach.value_counts().head(6).to_string())In [ ]:
kho_trong_c2 = c2[c2.ip_token.isin(TAP_KHO)] # isin: hàng nào có mã nằm trong tập kho mẫu
print(f"📦 kho_trong_c2 = {len(kho_trong_c2)} / {len(TAP_KHO)} IP của kho mẫu đã có trong danh sách đen")
print("Danh sách đen biết các IP đó từ gốc nào:")
print(kho_trong_c2.goc.value_counts().to_string())
print(f"\nTrong đó còn hiệu lực (công cụ 1 còn báo): {len(TAP_KHO & TAP_HL)}")In [ ]:
TAP_WEB = set(chan_web.ip_token)
print(f"📦 TAP_WEB = {len(TAP_WEB)} IP từng bị fail2ban chặn. Theo nhà tù (jail):")
print(chan_web.jail.value_counts().to_string())
biet = TAP_WEB & set(c2.ip_token)
print(f"\nCó trong danh sách đen (mọi thời điểm): {len(biet)} / {len(TAP_WEB)} = {100 * len(biet) / len(TAP_WEB):.1f}%")
print(f"Công cụ 1 (còn hiệu lực) trả lời 'XẤU' : {len(TAP_WEB & TAP_HL)}")
print(f"Công cụ 1 trả lời 'không biết' : {len(TAP_WEB - TAP_HL)}") # - = hiệu hai tậpIn [ ]:
# ✏️ SỬA: chấm lại theo ý bạn (chữ A–F, số 1–6) rồi chạy lại
cham = pd.DataFrame([
("fail2ban trên web thật", "quan sát trực tiếp trên máy mình", "B", 2),
("honeypot của mình (Cowrie)", "quan sát trực tiếp, nhưng trên máy mồi", "B", 2),
("abuse.ch (URLhaus, Feodo)", "cộng đồng báo cáo, một tổ chức tổng hợp", "B", 3),
("kho mẫu mã độc của mình", "rút từ honeypot của mình → KHÔNG độc lập", "B", 2),
], columns=["nguồn", "bản chất", "tin_cay_nguon", "tin_cay_thong_tin"])
cham["goc_doc_lap"] = ["máy mình (web)", "máy mình (honeypot)", "abuse.ch", "máy mình (honeypot)"]
print(cham.to_string(index=False))
print(f"\nSố nguồn trong bảng: {len(cham)} → số GỐC độc lập thật: {cham.goc_doc_lap.nunique()}")
# Với IP thử ở 1.1: những gốc nào thật sự đứng sau 3 câu "XẤU"?
goc_ip = set(cac_goc(c2.loc[c2.ip_token == IP_THU, "sources"].iloc[0]))
print(f"\nIP {IP_THU}: 3 công cụ nói 'XẤU', nhưng gốc thật chỉ là: {sorted(goc_ip)} ({len(goc_ip)} gốc)")In [ ]:
%matplotlib inline
import matplotlib.pyplot as plt
dong_gop = c2[c2.ip_token.isin(TAP_HL)].goc.value_counts() # đếm IP còn hiệu lực theo gốc
fig, ax = plt.subplots(figsize=(8, 2.8))
ax.barh(dong_gop.index[::-1], dong_gop.values[::-1], color="#4c78a8") # [::-1]: đảo để thanh lớn nhất nằm trên
for i, v in enumerate(dong_gop.values[::-1]): # enumerate: lấy cả vị trí i lẫn giá trị v
ax.text(v, i, f" {v:,}", va="center")
ax.set_xlabel("số IP còn hiệu lực"); ax.set_title("Danh sách đen C2 còn hiệu lực — đóng góp theo gốc")
plt.tight_layout(); plt.show()
print(dong_gop.to_string())In [ ]:
su_that = doc("honeypot_facts.csv")
# 📦 su_that = mỗi hàng một câu hỏi kẻ tấn công có thể hỏi; cột 2 = honeyfs trả lời; cột 3 = cowrie.cfg nói
print(su_that.to_string(index=False))In [ ]:
hang = su_that.set_index("muc") # set_index: tra theo tên mục thay vì số thứ tự
mau_thuan = []
# 1) nhân Linux: cat /proc/version (honeyfs) với uname -r (cowrie.cfg)
nhan_cat = re.search(r"\d+\.\d+", hang.loc["Nhân Linux (cat /proc/version)", "honeyfs_tra_ve"]).group()
nhan_uname = re.search(r"\d+\.\d+", hang.loc["Nhân Linux (uname -r)", "cowrie_cfg_noi"]).group()
if nhan_cat != nhan_uname:
mau_thuan.append(f"nhân: 'cat /proc/version' ra {nhan_cat} nhưng 'uname -r' ra {nhan_uname}")
# 2) hệ điều hành: /etc/issue với banner SSH
issue = hang.loc["Hệ điều hành (/etc/issue)", "honeyfs_tra_ve"]
banner = hang.loc["Phiên bản SSH (banner)", "cowrie_cfg_noi"]
if "7" in issue and "deb12" in banner:
mau_thuan.append(f"hệ điều hành: /etc/issue = '{issue}' nhưng banner SSH = '{banner}' (Debian 12)")
# 3) tên máy ở 3 chỗ khác nhau
ten = {hang.loc["Tên máy (hostname)", "honeyfs_tra_ve"], hang.loc["Tên trong /etc/hosts", "honeyfs_tra_ve"],
hang.loc["Tên máy (hostname)", "cowrie_cfg_noi"]}
if len(ten) > 1:
mau_thuan.append(f"tên máy: {len(ten)} tên khác nhau {sorted(ten)}")
# 4) phần cứng quá cũ so với nhân mới
cpu = hang.loc["CPU (/proc/cpuinfo)", "honeyfs_tra_ve"]
mau_thuan.append(f"phần cứng: CPU '{' '.join(cpu.split())}' (đời 2008) đi với nhân {nhan_uname} (đời 2023)")
print(f"📦 mau_thuan = {len(mau_thuan)} chỗ:")
for i, m in enumerate(mau_thuan, 1):
print(f" {i}. {m}")In [ ]:
lenh = pd.read_csv("/opt/csai/data/m5_honeypot/commands.csv", dtype=str, keep_default_na=False)
print(f"📦 lenh = {len(lenh):,} lệnh trong {lenh.session.nunique():,} phiên. Ví dụ: {lenh.lenh.iloc[0]!r}")
# ✏️ SỬA: thêm / bớt kiểu lệnh dò (tên: regex)
DO_THAM = {"uname": r"\buname\b", "hostname": r"\bhostname\b", "whoami": r"\bwhoami\b",
"nproc": r"\bnproc\b", "lscpu": r"\blscpu\b", "/proc/cpuinfo": r"/proc/cpuinfo",
"/proc/version": r"/proc/version", "/etc/issue, os-release": r"/etc/(?:issue|os-release)"} # (?:...) = nhóm không bắt
tong_phien = lenh.session.nunique()
ty_le = pd.Series({ten: lenh[lenh.lenh.str.contains(mau, regex=True)].session.nunique() / tong_phien * 100
for ten, mau in DO_THAM.items()}).sort_values()
fig, ax = plt.subplots(figsize=(8, 3.2))
ax.barh(ty_le.index, ty_le.values, color="#f58518")
for i, v in enumerate(ty_le.values):
ax.text(v, i, f" {v:.1f}%", va="center")
ax.set_xlabel("% phiên có chạy lệnh"); ax.set_title("Bot thật dò hệ thống bằng lệnh gì (honeypot, tháng 7/2026)")
plt.tight_layout(); plt.show()
print(ty_le.sort_values(ascending=False).round(1).to_string())In [ ]:
trang = (THU_MUC / "canary_404.html").read_text()
print(trang)
print("--- soát ---")
print("Máy chủ web tự khai :", re.search(r"<address>(.*?)</address>", trang).group(1))
print("Cổng tự khai :", re.search(r"Port (\d+)", trang).group(1), "(trong khi honeypot web nghe ở cổng 8080)")In [ ]:
import networkx as nx
van_tay_ssh = pd.read_csv(THU_MUC / "edges_hassh.csv") # ip_token, hassh, sessions
van_tay_tls = pd.read_csv(THU_MUC / "edges_ja4.csv") # ip_token, ja4, sessions
dung_khoa = pd.read_csv(THU_MUC / "edges_sshkey.csv") # ip_token, key_id, sightings
khoa = pd.read_csv(THU_MUC / "sshkeys.csv") # key_id, threat_actor, ...
chien_dich = pd.read_csv(THU_MUC / "fp_campaigns.csv") # vân tay đã được gắn nhóm tấn công
print("📦 khoa ="); print(khoa.to_string(index=False))
print("📦 chien_dich ="); print(chien_dich[["fingerprint_type", "fingerprint_value", "campaign_tag"]].to_string(index=False))
G = nx.Graph() # đồ thị vô hướng
# mỗi nút là một cặp (kiểu, giá trị) → hai nút khác kiểu không bao giờ trùng nhau
for r in van_tay_ssh.itertuples(): # itertuples: duyệt từng hàng, r.cot = giá trị
G.add_edge(("IP", r.ip_token), ("HASSH", r.hassh), kieu="DUNG_VAN_TAY_SSH")
for r in van_tay_tls.itertuples():
G.add_edge(("IP", r.ip_token), ("JA4", r.ja4), kieu="DUNG_VAN_TAY_TLS")
for r in dung_khoa.itertuples():
G.add_edge(("IP", r.ip_token), ("KHOA", r.key_id), kieu="DUNG_KHOA")
from collections import Counter # Counter: đếm số lần xuất hiện
print(f"\n📦 G = {G.number_of_nodes():,} nút, {G.number_of_edges():,} cạnh. Nút theo kiểu:", dict(Counter(n[0] for n in G)))
print("Ví dụ một cạnh:", next(iter(G.edges(data=True))))In [ ]:
print("Không có nút nhãn :", nx.number_connected_components(G), "thành phần liên thông")
G_sai = G.copy()
TAP_C2 = set(c2.ip_token)
for n in list(G_sai.nodes):
if n[0] == "IP" and n[1] in TAP_C2:
G_sai.add_edge(n, ("NHAN", "DANH_SACH_DEN"), kieu="CO_TRONG") # một nút nhãn nối với mọi IP bị liệt kê
cc_sai = sorted(nx.connected_components(G_sai), key=len, reverse=True)
print("Có nút nhãn :", len(cc_sai), "thành phần; thành phần lớn nhất có", len(cc_sai[0]), "nút")
print("→ Nút nhãn trở thành 'cầu nối' giả: hai IP chỉ chung việc 'cùng bị ghi tên' bị coi là cùng hạ tầng.")
print(" Cách đúng: nhãn là THUỘC TÍNH của nút, không phải nút.")
nx.set_node_attributes(G, {n: (n[1] in TAP_C2) for n in G if n[0] == "IP"}, "trong_danh_sach_den")In [ ]:
cac_tp = sorted(nx.connected_components(G), key=len, reverse=True)
print(f"📦 cac_tp = {len(cac_tp)} thành phần. 6 thành phần lớn nhất:")
for i, tp in enumerate(cac_tp[:6]):
kieu = Counter(n[0] for n in tp)
nhan = [n[1] for n in tp if n[0] in ("KHOA", "JA4")]
print(f" #{i}: {len(tp):5d} nút {dict(kieu)} khoá/JA4: {nhan[:4]}")In [ ]:
HAT_GIONG = 42 # ✏️ SỬA: Louvain có yếu tố ngẫu nhiên; đổi số để xem cụm có ổn định không
cong_dong = sorted(nx.community.louvain_communities(G, seed=HAT_GIONG), key=len, reverse=True)
print(f"📦 cong_dong = {len(cong_dong)} cộng đồng; kích thước 8 cộng đồng lớn nhất:", [len(c) for c in cong_dong[:8]])
nhan_khoa = dict(zip(khoa.key_id, khoa.threat_actor)) # K1 → nhóm tấn công
nhan_vt = dict(zip(chien_dich.fingerprint_value, chien_dich.campaign_tag))
def chan_dung(cd):
kieu = Counter(n[0] for n in cd)
ip = [n for n in cd if n[0] == "IP"]
nhan = sorted(({nhan_khoa.get(n[1]) for n in cd if n[0] == "KHOA"} | {nhan_vt.get(n[1]) for n in cd if n[0] in ("HASSH", "JA4")}) - {None, "UNKNOWN"}) # bỏ "chưa có nhãn"
trong_dsd = sum(G.nodes[n].get("trong_danh_sach_den", False) for n in ip)
vt = [n[1][:12] for n in cd if n[0] in ("HASSH", "JA4", "KHOA")]
return {"số IP": len(ip), "HASSH": kieu.get("HASSH", 0), "JA4": kieu.get("JA4", 0), "khoá": kieu.get("KHOA", 0),
"nhãn đã biết": ", ".join(nhan) or "—", "IP trong d.s. đen": trong_dsd, "vân tay / khoá": " ".join(vt[:3])}
bang_cd = pd.DataFrame([chan_dung(cd) for cd in cong_dong[:8]])
print(bang_cd.to_string())In [ ]:
# HASSH nào được nhiều IP dùng nhất?
do_pho_bien = van_tay_ssh.groupby("hassh").ip_token.nunique().sort_values(ascending=False) # groupby: gom theo vân tay, đếm IP khác nhau
print("📦 do_pho_bien = số IP dùng mỗi HASSH (5 cái phổ biến nhất):")
print(do_pho_bien.head(5).to_string())
print(f"\nHASSH phổ biến nhất được {do_pho_bien.iloc[0]} IP dùng = {100 * do_pho_bien.iloc[0] / van_tay_ssh.ip_token.nunique():.0f}% số IP có vân tay SSH.")
# Cạnh QUAN SÁT vs cạnh SUY LUẬN
print("\nCạnh trong đồ thị này đều là QUAN SÁT (honeypot thấy IP dùng vân tay / khoá đó).")
print("Nhãn nhóm tấn công là SUY LUẬN của người phân tích → phải ghi kèm độ tin cậy khi đưa vào đồ thị.")In [ ]:
# chọn cộng đồng có nhãn đã biết, nhỏ (≤ 80 nút, vẽ được rõ), có NHIỀU KIỂU nút nhất; ✏️ SỬA: đổi điều kiện để vẽ cộng đồng khác
co_nhan = [cd for cd in cong_dong if chan_dung(cd)["nhãn đã biết"] != "—" and len(cd) <= 80]
cd_ve = max(co_nhan, key=lambda cd: (len({n[0] for n in cd}), -len(cd))) # nhiều kiểu nút nhất; hoà thì lấy cộng đồng nhỏ hơn
H = G.subgraph(cd_ve)
vi_tri = nx.spring_layout(H, seed=1) # spring_layout: đặt nút sao cho nút nối nhau ở gần nhau
fig, ax = plt.subplots(figsize=(8, 6))
KIEU_VE = {"IP": ("o", "#4c78a8", 60), "HASSH": ("s", "#f58518", 200), "JA4": ("^", "#b279a2", 220), "KHOA": ("D", "#e45756", 220)}
nx.draw_networkx_edges(H, vi_tri, ax=ax, alpha=0.4)
for kieu, (hinh, mau, co) in KIEU_VE.items():
nut = [n for n in H if n[0] == kieu]
vien = ["red" if H.nodes[n].get("trong_danh_sach_den") else "white" for n in nut]
nx.draw_networkx_nodes(H, vi_tri, nodelist=nut, node_shape=hinh, node_color=mau, node_size=co, edgecolors=vien, ax=ax, label=kieu)
nx.draw_networkx_labels(H, vi_tri, {n: n[1][:10] for n in H if n[0] != "IP"}, font_size=8, ax=ax)
ax.legend(scatterpoints=1); ax.set_title(f"Cộng đồng có nhãn {chan_dung(cd_ve)['nhãn đã biết']}: {len(cd_ve)} nút"); ax.axis("off")
plt.tight_layout(); plt.show()
print(chan_dung(cd_ve))In [ ]:
import mmh3, codecs, base64, hashlib
THU_MUC_ICON = THU_MUC / "favicons"
dong = []
for f in sorted(THU_MUC_ICON.iterdir()): # iterdir: liệt kê file trong thư mục
du_lieu = f.read_bytes() # đọc dạng byte (nhị phân)
dong.append({"file": f.name, "byte": len(du_lieu),
"mmh3 (đúng kiểu Shodan)": mmh3.hash(codecs.encode(du_lieu, "base64")),
"4 byte đầu": du_lieu[:4]})
bang_icon = pd.DataFrame(dong)
print("📦 bang_icon ="); print(bang_icon.to_string(index=False))In [ ]:
du_lieu = (THU_MUC_ICON / "nextcloud_favicon.ico").read_bytes()
kieu_shodan = codecs.encode(du_lieu, "base64") # có ký tự xuống dòng mỗi 76 ký tự
kieu_lien = base64.b64encode(du_lieu) # một dòng liền, không xuống dòng
print("codecs.encode : xuống dòng", kieu_shodan.count(b"\n"), "lần → mmh3 =", mmh3.hash(kieu_shodan))
print("b64encode : xuống dòng", kieu_lien.count(b"\n"), "lần → mmh3 =", mmh3.hash(kieu_lien))
print("Giống base64.encodebytes?", kieu_shodan == base64.encodebytes(du_lieu))In [ ]:
CHU_KY = {b"\x89PNG": "ảnh PNG", b"\x00\x00\x01\x00": "biểu tượng ICO", b"<!do": "TRANG HTML", b"<svg": "ảnh SVG"}
for f in sorted(THU_MUC_ICON.iterdir()):
dau = f.read_bytes()[:4]
loai = next((ten for ky, ten in CHU_KY.items() if dau.lower().startswith(ky.lower())), "không rõ") # next(..., mặc định)
print(f"{f.name:40s} {loai}")In [ ]:
rong = hashlib.sha256(b"").hexdigest() # hash của chuỗi RỖNG (0 byte)
print("SHA-256 của 0 byte:", rong)
print("Thấy giá trị này trong log = bạn vừa hash một phản hồi trống (lỗi 403, hết giờ, file không tồn tại),")
print("KHÔNG phải chữ ký mã độc.")In [ ]:
import ppdeep # ssdeep viết bằng Python thuần
goc = (THU_MUC / "canary_404.html").read_bytes()
sua = goc.replace(b"Port 80", b"Port 81") # ✏️ SỬA: thử sửa chỗ khác, nhiều hơn
print("SHA-256 gốc:", hashlib.sha256(goc).hexdigest()[:24], "…")
print("SHA-256 sửa:", hashlib.sha256(sua).hexdigest()[:24], "…")
print("ssdeep gốc :", ppdeep.hash(goc))
print("ssdeep sửa :", ppdeep.hash(sua))
print("Điểm giống nhau (ssdeep, 0–100):", ppdeep.compare(ppdeep.hash(goc), ppdeep.hash(sua)))In [ ]:
chuong_trinh = Path("/usr/bin/ls").read_bytes()
ban_va = bytearray(chuong_trinh) # bytearray: chuỗi byte sửa được
SO_BYTE_SUA = 16 # ✏️ SỬA: sửa nhiều byte hơn (100, 5000…) rồi chạy lại
giua = len(ban_va) // 2
ban_va[giua:giua + SO_BYTE_SUA] = b"\x90" * SO_BYTE_SUA # ghi đè một đoạn ở giữa file
h_goc, h_va = ppdeep.hash(chuong_trinh), ppdeep.hash(bytes(ban_va))
print(f"/usr/bin/ls ({len(chuong_trinh):,} byte) so với bản bị sửa {SO_BYTE_SUA} byte: điểm ssdeep =", ppdeep.compare(h_goc, h_va))
print("SHA-256 có giống nhau không?", hashlib.sha256(chuong_trinh).digest() == hashlib.sha256(bytes(ban_va)).digest())
h_dir = ppdeep.hash(Path("/usr/bin/dir").read_bytes())
print("/usr/bin/ls so với /usr/bin/dir (hai chương trình anh em, cùng bộ coreutils): điểm ssdeep =", ppdeep.compare(h_goc, h_dir))In [ ]:
from pathlib import Path
THU_MUC_RA = Path.home() / "lab" / "out_osint"
THU_MUC_RA.mkdir(parents=True, exist_ok=True)
bao_cao = f'''BÁO CÁO THREAT HUNTING & OSINT (dữ liệu thật của máy chủ dạy học)
================================================================
Kiểm chứng chéo : IP thử {IP_THU}: 3 công cụ nói XẤU, gốc thật = {sorted(goc_ip)}
IP có ≥ 2 tên nguồn {len(nhieu_ten):,} | có ≥ 2 gốc độc lập {(c2.so_goc >= 2).sum():,}
kho mẫu: {len(kho_trong_c2)}/{len(TAP_KHO)} IP đã có sẵn trong danh sách đen
IP tấn công web bị chặn: {len(TAP_WEB)}, danh sách đen biết {len(biet)} ({100 * len(biet) / len(TAP_WEB):.1f}%)
Làm sạch : {la_ngay.sum():,} hàng threat_type là ngày tháng
Honeypot : {len(mau_thuan)} chỗ mâu thuẫn; bot chạy 'uname' ở {ty_le['uname']:.1f}% phiên
Đồ thị : {G.number_of_nodes():,} nút, {G.number_of_edges():,} cạnh, {len(cac_tp)} thành phần, {len(cong_dong)} cộng đồng Louvain
Hash : rỗng = {rong[:12]}…; sửa 1 ký tự → ssdeep {ppdeep.compare(ppdeep.hash(goc), ppdeep.hash(sua))}
'''
(THU_MUC_RA / "bao_cao_osint.txt").write_text(bao_cao, encoding="utf-8")
print(bao_cao)