38 KiB
38 KiB
In [ ]:
import pandas as pd
# Bảng hiện đủ nội dung tin (mặc định pandas cắt ở 50 ký tự)
pd.set_option("display.max_colwidth", 150)
# Bộ HỌC: 50 tin có nhãn · Bộ THI: 10 tin lách luật của bài M1
hoc = pd.read_csv("~/lab/day1_labs/Manual_SOC_Dataset.csv")
thi = pd.read_csv("~/lab/day1_labs/Evasion_Dataset.csv")
# Đặt tên ngắn cho 5 cột: mã tin, kênh, người gửi, nội dung, nhãn đúng
hoc.columns = thi.columns = ["ma", "kenh", "nguoi_gui", "noi_dung", "nhan"]
print("BỘ HỌC:", len(hoc), "tin\n" + hoc.nhan.value_counts().to_string())
print("\nBỘ THI:", len(thi), "tin\n" + thi.nhan.value_counts().to_string())In [ ]:
from sklearn.feature_extraction.text import TfidfVectorizer
# ✏️ SỬA: 3 câu ví dụ, đổi thành câu của bạn
cau = ["chuyển khoản gấp vào tài khoản này",
"chiều nay họp tại phòng họp lớn",
"bấm vào link này để nhận quà"]
# Máy tự lập từ điển từ 3 câu, rồi biến mỗi câu thành một hàng số (điểm của từng từ)
tfidf_thu = TfidfVectorizer()
bang_so = tfidf_thu.fit_transform(cau)
tu_dien = tfidf_thu.get_feature_names_out()
print("Từ điển máy tự tạo (" + str(len(tu_dien)) + " từ):", list(tu_dien))
# Với mỗi câu: in các từ có điểm > 0, điểm cao xếp trước
for i in range(len(cau)):
print("\nCâu", i + 1, ":", cau[i])
diem = bang_so[i].toarray()[0]
for tu, d in sorted(zip(tu_dien, diem), key=lambda x: -x[1]):
if d > 0:
print(f" {tu:8s} {d:.2f}")In [ ]:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
# Bước 1: lập từ điển từ 50 tin HỌC, biến mỗi tin thành một hàng số
tfidf = TfidfVectorizer()
X_hoc = tfidf.fit_transform(hoc.noi_dung)
# Nhãn -> số: 1 = lừa đảo, 0 = an toàn
y_hoc = hoc.nhan.str.contains("Lừa đảo").astype(int)
print("Từ điển:", len(tfidf.vocabulary_), "từ -> mỗi tin là một dãy", len(tfidf.vocabulary_), "con số")
# Bước 2: Naive Bayes HỌC xem từ nào hay đi với lừa đảo, từ nào với an toàn
mo_hinh = MultinomialNB().fit(X_hoc, y_hoc)
# Bước 3: biến tin THI thành số bằng ĐÚNG từ điển đã học (transform, không fit lại), rồi cho máy đoán
doan = mo_hinh.predict(tfidf.transform(thi.noi_dung))
for ma, nd, d in zip(thi.ma, thi.noi_dung, doan):
print(ma, "->", "Lừa đảo" if d == 1 else "An toàn", "|", nd[:60])
print("Đúng:", (doan == 1).sum(), "/", len(thi), " (bộ lọc từ khóa ở M1: 2/10)")In [ ]:
# predict_proba = xác suất của từng tin; [:, 1] = cột xác suất "lừa đảo"
xac_suat = mo_hinh.predict_proba(tfidf.transform(thi.noi_dung))[:, 1]
# ✏️ SỬA: ngưỡng kết luận lừa đảo (0.5 = 50%). Thử 0.6 hoặc 0.7
NGUONG = 0.5
for ma, nd, p in zip(thi.ma, thi.noi_dung, xac_suat):
ket_luan = "LỪA ĐẢO" if p >= NGUONG else "an toàn"
print(f"{ma} | khả năng lừa đảo {p:4.0%} -> {ket_luan:7s} | {nd[:55]}")In [ ]:
# Đảo vai: HỌC từ 10 tin lách luật (toàn lừa đảo), THI trên 50 tin cũ (30 lừa đảo + 20 an toàn)
tfidf_dao = TfidfVectorizer()
mo_hinh_dao = MultinomialNB().fit(tfidf_dao.fit_transform(thi.noi_dung), thi.nhan.str.contains("Lừa đảo").astype(int))
doan_dao = mo_hinh_dao.predict(tfidf_dao.transform(hoc.noi_dung))
dung = (doan_dao == hoc.nhan.str.contains("Lừa đảo").astype(int))
print("Từ điển:", len(tfidf_dao.vocabulary_), "từ")
print("Máy đoán: ", pd.Series(doan_dao).map({1: "Lừa đảo", 0: "An toàn"}).value_counts().to_dict())
print("Đúng:", dung.sum(), "/", len(hoc))In [ ]:
# ✏️ SỬA: viết thêm / đổi tin (nội dung, nhãn đúng: 1 = lừa đảo, 0 = an toàn)
DE_THI = [
("Chiều nay 3h họp toàn công ty tại phòng họp lớn. Mọi người nhớ mang laptop.", 0),
("Con ơi tối nay về ăn cơm nhé mẹ nấu canh chua.", 0),
("Hệ thống email sẽ bảo trì từ 22h đến 23h tối nay. Không cần làm gì thêm.", 0),
("Tài khoản của bạn bị khóa. Bấm vào link xac-minh-ngay.com và nhập mã OTP để mở khóa.", 1),
]
# Dùng mô hình học từ 50 tin ở M2-3
dung = 0
for nd, that in DE_THI:
p = mo_hinh.predict_proba(tfidf.transform([nd]))[0, 1]
doan_1 = 1 if p >= 0.5 else 0
dung += (doan_1 == that)
print(f"{'ĐÚNG' if doan_1 == that else 'SAI '} | lừa đảo {p:4.0%} | thật là {'lừa đảo' if that else 'an toàn'} | {nd[:60]}")
print("Đúng:", dung, "/", len(DE_THI))In [ ]:
%%bash
# Tìm mọi dòng log có mã phiên (session) 54613b5cec67 trong ngày 22/09
grep '"54613b5cec67"' /opt/csai/data/honeypot_m1/cowrie.json.2026-09-22In [ ]:
import glob
# Đọc cả 30 file (mỗi dòng 1 sự kiện JSON) thành một bảng
cac_file = sorted(glob.glob("/opt/csai/data/honeypot_m1/cowrie.json.*"))
log = pd.concat(pd.read_json(f, lines=True, dtype=False, convert_dates=False) for f in cac_file)
print("Đã tải:", len(log), "dòng log từ", len(cac_file), "file")
print(log.eventid.value_counts().to_string())In [ ]:
# Từ các dòng client.version: mỗi session 1 tên phần mềm
danh_ba = log[log.eventid == "cowrie.client.version"].drop_duplicates("session").set_index("session").version
print("Danh bạ có", len(danh_ba), "session")
danh_ba.head(3)In [ ]:
# Mỗi dòng connect = 1 kết nối
d = log[log.eventid == "cowrie.session.connect"][["timestamp", "src_ip", "session"]].copy()
# Tra danh bạ theo session; kết nối ngắt trước khi khai phần mềm -> "unknown"
d["client_version"] = d.session.map(danh_ba).fillna("unknown")
# Tách thời gian (giữ giờ UTC như log gốc): ngày, giờ 0-23, thứ trong tuần
t = pd.to_datetime(d.timestamp, utc=True)
d["date"] = t.dt.date.astype(str)
d["hour"] = t.dt.hour
d["weekday"] = t.dt.day_name()
print("Còn lại:", len(d), "kết nối | từ", d.src_ip.nunique(), "IP")
print(f"Có tên phần mềm: {(d.client_version != 'unknown').mean():.1%} kết nối")
d.head(5)In [ ]:
d.to_csv("~/lab/day1_labs/cleaned_honeypot_data.csv", index=False)
print("Đã lưu cleaned_honeypot_data.csv:", len(d), "dòng")In [ ]:
import matplotlib.pyplot as plt
# Vẽ biểu đồ ngay dưới ô (máy lớp mặc định chỉ lưu ảnh ra file khi chạy trong terminal)
%matplotlib inline
theo_ngay = d.groupby("date").size()
theo_ngay.plot(kind="bar", figsize=(14, 4), color="steelblue", title="Số kết nối mỗi ngày")
plt.show()
print(f"ngày đông nhất: {theo_ngay.idxmax()} ({theo_ngay.max():,}) | vắng nhất: {theo_ngay.idxmin()} ({theo_ngay.min():,}) | trung bình {theo_ngay.mean():,.0f}/ngày")In [ ]:
# ✏️ SỬA: số IP muốn xem (20). Thử 50
TOP = 20
so_lan = d.src_ip.value_counts()
so_lan.head(TOP).plot(kind="bar", figsize=(12, 4), color="firebrick", title=f"Top {TOP} IP kết nối nhiều nhất")
plt.show()
mot_phan_tram = len(so_lan) // 100
print(f"top {TOP} IP = {so_lan.head(TOP).sum() / len(d):.0%} số kết nối; top 1% IP ({mot_phan_tram} IP) = {so_lan.head(mot_phan_tram).sum() / len(d):.0%}")In [ ]:
theo_gio = d.groupby("hour").size()
theo_gio.plot(kind="bar", figsize=(12, 4), color="goldenrod", title="Số kết nối theo giờ (UTC; giờ VN = UTC + 7)")
plt.show()
print(f"giờ đông nhất {theo_gio.idxmax():02d}h UTC ({theo_gio.max():,}) | vắng nhất {theo_gio.idxmin():02d}h UTC ({theo_gio.min():,}) | gấp {theo_gio.max() / theo_gio.min():.1f} lần")In [ ]:
# Rút gọn: "SSH-2.0-Go" -> "Go", "SSH-2.0-PuTTY_Release_0.84" -> "PuTTY"; không khai báo -> "unknown"
d["phan_mem"] = d.client_version.str.extract(r"^SSH-[\d.]+-([A-Za-z]+)")[0].fillna("unknown")
top_pm = d.phan_mem.value_counts().head(8)
top_pm.plot(kind="barh", figsize=(8, 4), color="seagreen", title="Phần mềm (số kết nối)")
plt.gca().invert_yaxis()
plt.show()
print(" · ".join(f"{k} {v / len(d):.1%}" for k, v in top_pm.head(5).items()))In [ ]:
moi_ip = d.groupby("src_ip").agg(tong=("src_ip", "size"), so_ngay=("date", "nunique"))
ti_le = moi_ip.tong / moi_ip.so_ngay
ti_le.plot(kind="hist", bins=50, log=True, figsize=(10, 4), color="purple", title="Số kết nối / ngày hoạt động của mỗi IP (trục đứng log)")
plt.show()
q = ti_le.quantile([0.5, 0.9, 0.99])
print(f"trung vị {q[0.5]:.1f} · 90% {q[0.9]:.1f} · 99% {q[0.99]:.1f} · cao nhất {ti_le.max():,.0f}")In [ ]:
# Gom mọi kết nối của cùng một IP thành MỘT hàng; mỗi cột là một đặc trưng
bang = d.groupby("src_ip").agg(
so_ket_noi=("src_ip", "size"), # tổng số kết nối
so_ngay=("date", "nunique"), # xuất hiện bao nhiêu ngày khác nhau
so_khung_gio=("hour", "nunique"), # hoạt động trong bao nhiêu giờ khác nhau của ngày (tối đa 24)
phan_mem=("phan_mem", lambda s: s.mode()[0]), # phần mềm dùng nhiều nhất
)
bang["ket_noi_moi_ngay"] = (bang.so_ket_noi / bang.so_ngay).round(1)
print("Bảng đặc trưng:", len(bang), "IP x", len(bang.columns), "đặc trưng")
bang.sort_values("so_ket_noi", ascending=False).head(5)In [ ]:
# ✏️ SỬA: đổi số 1 thành số khác để xem 5 IP ngẫu nhiên khác
bang.sample(5, random_state=1)In [ ]:
print(f"Top 5 IP chiếm {d.src_ip.value_counts().head(5).sum() / len(d):.1%} tổng số kết nối")
print("3 IP nhiều nhất ngày 2026-09-20:")
print(d[d.date == "2026-09-20"].src_ip.value_counts().head(3).to_string())In [ ]:
# Với mỗi IP: đếm số phần mềm KHÁC NHAU nó đã dùng (bỏ qua "unknown")
so_phan_mem = d[d.phan_mem != "unknown"].groupby("src_ip").phan_mem.nunique()
print("Số IP chỉ dùng 1 phần mềm: ", (so_phan_mem == 1).sum())
print("Số IP dùng từ 2 phần mềm trở lên:", (so_phan_mem >= 2).sum())
print("\nTOP 5 IP đổi nhiều phần mềm nhất:")
for ip in so_phan_mem.sort_values(ascending=False).head(5).index:
cac_pm = d[(d.src_ip == ip) & (d.phan_mem != "unknown")].phan_mem.unique()
print(f" {ip:16s} {len(cac_pm)} phần mềm: {', '.join(sorted(cac_pm))}")In [ ]:
so_gio = d.groupby("src_ip").hour.nunique()
ip_24h = so_gio[so_gio == 24].index
print("Số IP hoạt động ĐỦ 24 giờ trong ngày:", len(ip_24h))
print("Số IP chỉ hoạt động trong 1 giờ: ", (so_gio == 1).sum())
print(f"-> {len(ip_24h)} IP 'không bao giờ ngủ' tạo ra {d.src_ip.isin(ip_24h).mean():.0%} tổng số kết nối")In [ ]:
# ✏️ SỬA: ngày muốn soi (ngày UTC), trong khoảng 2026-08-24 .. 2026-09-22
NGAY_CAN_SOI = "2026-09-20"
mot_ngay = d[d.date == NGAY_CAN_SOI]
tb = len(d) / d.date.nunique()
print(f"Ngày {NGAY_CAN_SOI}: {len(mot_ngay)} kết nối = gấp {len(mot_ngay) / tb:.1f} lần trung bình ({tb:.0f}/ngày), từ {mot_ngay.src_ip.nunique()} IP")
dai = mot_ngay.src_ip.str.rsplit(".", n=1).str[0] + ".0/24"
print("TOP 3 dải mạng trong ngày:\n" + dai.rename("dai_mang").value_counts().head(3).to_string())
print(f"-> dải đứng đầu chiếm {dai.value_counts().iloc[0] / len(mot_ngay):.0%} số kết nối của ngày")In [ ]:
# Lấy danh sách IP có ít nhất 1 dòng đăng nhập (thất bại hoặc thành công) trong log gốc
ip_dang_nhap = set(log[log.eventid.isin(["cowrie.login.failed", "cowrie.login.success"])].src_ip)
# Nhãn: 1 = dò mật khẩu, 0 = chỉ quét. Đây là "đáp án" — KHÔNG được đưa vào làm đặc trưng
bang["do_mat_khau"] = bang.index.isin(ip_dang_nhap).astype(int)
print(bang.do_mat_khau.value_counts().rename({1: "1 = dò mật khẩu", 0: "0 = chỉ quét"}).to_string())
print(f"Nếu đoán bừa 'IP nào cũng chỉ quét' thì đã đúng {(bang.do_mat_khau == 0).mean():.1%}")In [ ]:
them = d.groupby("src_ip").agg(
so_phan_mem=("phan_mem", "nunique"), # dùng bao nhiêu phần mềm khác nhau
ti_le_khong_khai=("phan_mem", lambda s: (s == "unknown").mean()), # bao nhiêu phần kết nối ngắt trước khi khai phần mềm
)
bang = bang.drop(columns=["so_phan_mem", "ti_le_khong_khai"], errors="ignore").join(them)
bang["dung_go"] = (bang.phan_mem == "Go").astype(int) # phần mềm chính là Go? (1 = có)
print("Các cột của bảng:", list(bang.columns))
# So 2 nhóm: trung bình mỗi đặc trưng của IP chỉ quét (0) và IP dò mật khẩu (1)
bang.groupby("do_mat_khau")[["so_ket_noi", "so_ngay", "so_khung_gio", "ket_noi_moi_ngay", "ti_le_khong_khai", "dung_go"]].mean().round(2)In [ ]:
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.metrics import accuracy_score, confusion_matrix
# ✏️ SỬA: chọn đặc trưng cho máy dùng. Thử thêm "dung_go", "ti_le_khong_khai", "so_phan_mem"
DAC_TRUNG = ["so_ket_noi", "so_ngay", "so_khung_gio", "ket_noi_moi_ngay"]
# ✏️ SỬA: độ sâu của cây = số câu hỏi liên tiếp máy được hỏi. Thử 1, 2, 5, 20
DO_SAU = 3
# Chia 70% IP để học, 30% IP để thi (stratify: 2 phần giữ cùng tỉ lệ dò mật khẩu / chỉ quét)
X_hoc, X_thi, y_hoc, y_thi = train_test_split(bang[DAC_TRUNG], bang.do_mat_khau,
test_size=0.3, random_state=42, stratify=bang.do_mat_khau)
cay = DecisionTreeClassifier(max_depth=DO_SAU, random_state=0).fit(X_hoc, y_hoc)
doan = cay.predict(X_thi)
print("Học trên", len(X_hoc), "IP, thi trên", len(X_thi), "IP")
print(f"Đoán bừa 'tất cả chỉ quét' : {(y_thi == 0).mean():.1%}")
print(f"Cây tự chấm bài đã học : {accuracy_score(y_hoc, cay.predict(X_hoc)):.1%}")
print(f"Cây làm bài THI : {accuracy_score(y_thi, doan):.1%}")
tn, fp, fn, tp = confusion_matrix(y_thi, doan).ravel()
print(f"IP dò mật khẩu bắt được : {tp} / {tp + fn}")
print(f"IP chỉ quét bị nghi nhầm : {fp} / {tn + fp}")
if DO_SAU <= 4:
print("\nLuật máy TỰ tìm ra (class: 1 = dò mật khẩu, 0 = chỉ quét):")
print(export_text(cay, feature_names=DAC_TRUNG))
else:
print("\nCây có", cay.get_n_leaves(), "nhánh lá -> quá dài để in (đặt DO_SAU từ 4 trở xuống để xem luật)")In [ ]:
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
# Dùng đúng DAC_TRUNG và cách chia ở ô trên
cac_mo_hinh = {
"Naive Bayes (như bài M2)": GaussianNB(),
f"Cây quyết định (sâu {DO_SAU})": DecisionTreeClassifier(max_depth=DO_SAU, random_state=0),
"Rừng 200 cây (Random Forest)": RandomForestClassifier(n_estimators=200, random_state=0, n_jobs=2),
}
for ten, m in cac_mo_hinh.items():
p = m.fit(X_hoc, y_hoc).predict(X_thi)
tn, fp, fn, tp = confusion_matrix(y_thi, p).ravel()
print(f"{ten:30s} thi đúng {accuracy_score(y_thi, p):.1%} | bắt được {tp}/{tp + fn} IP dò mật khẩu | nghi nhầm {fp}")In [ ]:
# ✏️ SỬA: IP muốn hỏi (lấy từ các bảng ở trên, ví dụ "130.12.182.196", "185.246.128.133")
IP_CAN_HOI = "154.18.197.29"
hang = bang.loc[[IP_CAN_HOI]]
print(hang[DAC_TRUNG].to_string())
p = cay.predict_proba(hang[DAC_TRUNG])[0, 1]
print(f"\nMáy đoán: khả năng dò mật khẩu {p:.0%} -> {'DÒ MẬT KHẨU' if p >= 0.5 else 'chỉ quét'}")
print("Thật ra :", "DÒ MẬT KHẨU" if hang.do_mat_khau.iloc[0] == 1 else "chỉ quét",
"| IP này nằm ở phần", "THI" if IP_CAN_HOI in X_thi.index else "HỌC")