Chủ Nhật, 3 tháng 5, 2026

Giới thiệu bộ lập trình giá rẻ CH341A và phần mềm NeoProgrammer 2.2.0.10 “quốc dân” cho dân điện tử

Trong lĩnh vực sửa chữa phần cứng, lập trình firmware hay nghiên cứu hệ thống nhúng, cái tên CH341A gần như đã trở thành “huyền thoại” trong phân khúc giá rẻ. Chỉ với chi phí rất thấp, người dùng đã có thể sở hữu một thiết bị đủ khả năng đọc, ghi và phục hồi dữ liệu cho nhiều loại chip nhớ phổ biến. Vậy CH341A là gì, hoạt động ra sao, và tại sao nó lại được sử dụng rộng rãi đến vậy?

1. CH341A là gì?

CH341A là một bộ lập trình (programmer) sử dụng giao tiếp USB, được thiết kế để làm việc với các loại bộ nhớ không bay hơi như EEPROM và Flash. Về bản chất, nó là một thiết bị trung gian giúp kết nối chip nhớ với máy tính, cho phép người dùng thực hiện các thao tác như đọc (read), ghi (write), xóa (erase) hoặc sao lưu firmware.

Thiết bị này thường được sử dụng với các dòng chip phổ biến như:

  • EEPROM dòng 24Cxx (I2C)
  • SPI Flash dòng 25xx (Winbond, MXIC, SST…)
  • Một số chip BIOS trên mainboard máy tính

Nhờ khả năng này, CH341A trở thành công cụ quen thuộc trong các công việc như:

  • Sửa lỗi BIOS máy tính
  • Dump firmware thiết bị (router, TV, đầu thu…)
  • Phục hồi thiết bị bị lỗi firmware
  • Nghiên cứu bảo mật phần cứng

2. Thiết kế phần cứng và nguyên lý hoạt động

CH341A thường có dạng một USB dongle nhỏ gọn, kích thước chỉ vài cm, rất tiện mang theo. Bên trong là IC CH341A – một chip giao tiếp đa năng có thể hỗ trợ UART, SPI và I2C. 

Nguyên lý hoạt động khá đơn giản:

  1. Người dùng kết nối CH341A với máy tính qua USB
  2. Gắn chip cần đọc/ghi vào socket hoặc dùng kẹp SOP8 (SOIC clip)
  3. Sử dụng phần mềm để giao tiếp và thao tác với chip

Thiết bị sẽ đóng vai trò cầu nối, chuyển đổi dữ liệu từ USB sang giao thức SPI/I2C để giao tiếp trực tiếp với chip nhớ. 

Một điểm đáng chú ý là CH341A hỗ trợ nhiều mức điện áp khác nhau (5V, 3.3V, thậm chí 1.8V với adapter), giúp tương thích với nhiều loại IC. 

3. Tính năng nổi bật

Mặc dù có giá rất rẻ, CH341A vẫn sở hữu nhiều tính năng đáng chú ý:

✔ Hỗ trợ nhiều loại chip

CH341A tương thích với hầu hết các chip EEPROM và SPI Flash phổ biến trên thị trường, đặc biệt là dòng 24 và 25 series. 

✔ Kết nối USB tiện lợi

Chỉ cần cắm vào máy tính là có thể sử dụng, không cần nguồn ngoài phức tạp.

✔ Đa chức năng

Thiết bị cho phép:
  • Đọc dữ liệu từ chip
  • Ghi firmware mới
  • Xóa chip
  • So sánh dữ liệu (verify)

✔ Tương thích phần mềm rộng

CH341A có thể sử dụng với nhiều phần mềm khác nhau như:

  • Flashrom (Linux)
  • IMSProg
  • AsProgrammer
  • CH341A Programmer
  • NeoProgrammer
  • CH341Programmer
  • avrdudess
  • Postal
  • AVR Full mode programmer
  • SiberiaProg-CH341A
  • PinTester
  • Colibri
  • SNANDer
  • CH341ACH347 Programmer

✔ Giá thành cực thấp

Đây chính là điểm mạnh lớn nhất. CH341A thường chỉ có giá vài đô la nhưng vẫn đáp ứng được nhu cầu cơ bản của cả người mới lẫn kỹ thuật viên.

4. Ứng dụng thực tế

CH341A được sử dụng trong rất nhiều tình huống thực tế:

🔧 Sửa lỗi BIOS máy tính

Khi BIOS bị lỗi (ví dụ update sai), máy không thể khởi động. CH341A cho phép nạp lại firmware trực tiếp vào chip BIOS mà không cần boot hệ thống.

📺 Sửa thiết bị điện tử

TV, router, đầu thu kỹ thuật số… thường lưu firmware trong SPI Flash. Khi firmware bị lỗi, CH341A giúp nạp lại dữ liệu để khôi phục thiết bị.

🔍 Dump firmware và reverse engineering

Trong lĩnh vực bảo mật, CH341A được dùng để đọc firmware nhằm phân tích hệ thống, tìm lỗ hổng hoặc nghiên cứu thiết bị.

🧪 Học tập và nghiên cứu

Với giá rẻ, CH341A là lựa chọn phổ biến cho sinh viên và người mới học về embedded systems.

5. Ưu điểm

CH341A được ưa chuộng không phải ngẫu nhiên, mà nhờ những ưu điểm rõ ràng:

  • Giá cực rẻ, phù hợp với mọi đối tượng
  • Dễ sử dụng, chỉ cần phần mềm đơn giản
  • Nhỏ gọn, tiện lợi
  • Hỗ trợ đa dạng chip
  • Cộng đồng sử dụng lớn, dễ tìm tài liệu

Chính vì vậy, nó thường được gọi là “tool quốc dân” trong giới sửa chữa phần cứng.

6. Nhược điểm và lưu ý quan trọng

Tuy nhiên, CH341A không phải là hoàn hảo. Người dùng cần lưu ý một số điểm:

⚠ Vấn đề điện áp

Một số phiên bản CH341A (đặc biệt bản đen phổ biến) có thể xuất mức điện áp 5V trên chân dữ liệu, gây nguy hiểm cho chip 3.3V và có thể làm hỏng IC.

⚠ Cần adapter cho chip 1.8V

Nhiều chip hiện đại (đặc biệt BIOS laptop) dùng 1.8V, nên cần adapter riêng.

⚠ Tốc độ không cao

So với các programmer chuyên nghiệp, CH341A có tốc độ đọc/ghi khá chậm.

⚠ Phụ thuộc phần mềm

Một số chip mới không được hỗ trợ tốt, cần chọn đúng phần mềm hoặc cấu hình thủ công.

7. So sánh với các giải pháp khác

So với các programmer cao cấp như TL866 hoặc Bus Pirate, CH341A có thể thua về:

  • Độ ổn định
  • Tốc độ
  • Khả năng hỗ trợ chip mới

Tuy nhiên, xét về giá/hiệu năng, CH341A gần như không có đối thủ trong phân khúc giá rẻ.

NeoProgrammer 2.2.0.10 – phần mềm “quốc dân” cho CH341A

Trong hệ sinh thái các công cụ dành cho bộ lập trình CH341A, bên cạnh những phần mềm quen thuộc như CH341A Programmer hay AsProgrammer, NeoProgrammer 2.2.0.10 nổi lên như một lựa chọn mạnh mẽ, ổn định và được cộng đồng kỹ thuật viên đánh giá rất cao. Đây là phần mềm được cải tiến từ AsProgrammer, với nhiều nâng cấp về khả năng hỗ trợ chip, giao diện và độ ổn định khi làm việc thực tế. 

1. NeoProgrammer là gì?

NeoProgrammer là phần mềm chuyên dùng để đọc, ghi và quản lý dữ liệu trên các chip nhớ khi sử dụng programmer như CH341A. Nó đóng vai trò là “bộ não” điều khiển toàn bộ quá trình giao tiếp giữa máy tính và IC.

Phần mềm này hỗ trợ nhiều loại bộ nhớ như:

  • SPI NOR Flash
  • SPI NAND Flash
  • EEPROM (24Cxx, 25xx, 93Cxx…)
  • FRAM và một số vi điều khiển (MCU)

Nhờ khả năng này, NeoProgrammer được sử dụng rộng rãi trong:

  • Sửa chữa BIOS máy tính
  • Dump firmware thiết bị điện tử
  • Nạp firmware cho router, TV box
  • Nghiên cứu phần cứng và reverse engineering

2. Nguồn gốc và sự phát triển

NeoProgrammer thực chất là một phiên bản nâng cấp từ AsProgrammer, một dự án mã nguồn mở phổ biến. tác giả  TTAV134 tại diễn đàn https://4pda.to/forum/index.php?showuser=9359810 cải tiến thêm nhiều tính năng và độ ổn định. 

Phiên bản 2.2.0.10 được xem là một trong những bản ổn định nhất, với các cải tiến:

Nhờ đó, NeoProgrammer dần thay thế các phần mềm cũ vốn kém ổn định hoặc hỗ trợ hạn chế.

3. Giao diện và cách sử dụng

Một trong những điểm mạnh lớn của NeoProgrammer là giao diện trực quan, dễ sử dụng hơn so với nhiều tool khác.

Quy trình sử dụng cơ bản:

  1. Kết nối CH341A với máy tính
  2. Gắn chip vào socket hoặc kẹp SOP8
  3. Mở NeoProgrammer
  4. Nhấn Detect IC để nhận diện chip hoặc chọn thủ công
  5. Thực hiện Read / Write / Erase / Verify

Ngoài ra, phần mềm còn tích hợp sơ đồ chân (pinout) trực quan, giúp người dùng tránh cắm sai chip – một lỗi rất phổ biến với người mới.

4. Tính năng nổi bật

✔ Hỗ trợ số lượng chip lớn

NeoProgrammer hỗ trợ hơn 1600 loại IC khác nhau, bao gồm cả các dòng chip mới. 

✔ Hỗ trợ nhiều định dạng firmware

Phần mềm cho phép làm việc với các file:

  • BIN
  • HEX
  • ELF

✔ Tự động kiểm tra dữ liệu (Verify)

Sau khi ghi firmware, phần mềm sẽ tự động so sánh dữ liệu để đảm bảo không bị lỗi.

✔ Tương thích tốt với CH341A

NeoProgrammer được tối ưu đặc biệt cho CH341A (cả bản đen và bản xanh), giúp giảm lỗi nhận chip và tăng độ ổn định. 

✔ Portable – không cần cài đặt

Chỉ cần giải nén và chạy, không cần cài đặt phức tạp. 

✔ Công cụ chẩn đoán và sửa lỗi

Một số bản tích hợp thêm:

  • Công cụ test LED (set-top box)
  • Công cụ chẩn đoán firmware
  • Hỗ trợ sửa lỗi thiết bị

5. Ứng dụng thực tế

NeoProgrammer thường được dùng trong các tình huống:

🔧 Sửa BIOS (cứu máy “brick”)

Đây là ứng dụng phổ biến nhất. Khi BIOS lỗi, NeoProgrammer + CH341A có thể:

  • Dump BIOS cũ
  • Nạp BIOS mới
  • Phục hồi mainboard

📺 Sửa thiết bị điện tử

Router, TV, đầu thu kỹ thuật số… thường sử dụng SPI Flash, có thể nạp lại firmware bằng NeoProgrammer.

🔍 Nghiên cứu bảo mật

Các chuyên gia bảo mật sử dụng NeoProgrammer để:

  • Dump firmware
  • Phân tích hệ thống
  • Tìm lỗ hổng

🧪 Học tập

Đây là công cụ rất phù hợp cho sinh viên học embedded systems.

6. Ưu điểm

NeoProgrammer 2.2.0.10 được ưa chuộng nhờ:

  • Miễn phí hoàn toàn
  • Giao diện dễ dùng
  • Hỗ trợ nhiều chip hơn phần mềm gốc
  • Hoạt động ổn định với CH341A
  • Cộng đồng sử dụng lớn

Ngoài ra, nhiều người đánh giá đây là “cứu tinh” khi cần phục hồi thiết bị bị lỗi firmware (brick). 

7. Nhược điểm và hạn chế

Tuy mạnh mẽ, NeoProgrammer vẫn có một số điểm cần lưu ý:

⚠ Không phải chip nào cũng hỗ trợ hoàn hảo

Dù danh sách chip lớn, vẫn có những IC mới hoặc đặc biệt không nhận diện được.

⚠ Phụ thuộc phần cứng CH341A

Nếu CH341A gặp lỗi (điện áp sai, tiếp xúc kém), phần mềm cũng không thể hoạt động đúng.

⚠ Một số lỗi “IC not responding”

Đây là lỗi phổ biến, thường do:

  • Kẹp chip không chắc
  • Sai điện áp
  • Chip đang bị khóa

⚠ Không có tài liệu chính thức đầy đủ

Phần lớn người dùng phải học qua forum và cộng đồng.

8. So sánh với các phần mềm khác

Phần mềmƯu điểmNhược điểm
CH341A Programmer        Đơn giản        Ít chip hỗ trợ
AsProgrammer        Mã nguồn mở        Giao diện khó dùng
NeoProgrammer        Nhiều chip, ổn định        Ít tài liệu

Có thể nói NeoProgrammer là sự cân bằng tốt nhất giữa tính năng – dễ dùng – độ ổn định.

9. Kết luận

CH341A là một công cụ nhỏ gọn nhưng cực kỳ hữu ích trong lĩnh vực điện tử và sửa chữa phần cứng. Với khả năng đọc/ghi EEPROM và Flash, hỗ trợ nhiều loại chip và giá thành thấp, nó đã trở thành lựa chọn hàng đầu cho cả người mới bắt đầu lẫn kỹ thuật viên chuyên nghiệp.

NeoProgrammer 2.2.0.10 là một trong những phần mềm tốt nhất dành cho CH341A hiện nay. Với khả năng hỗ trợ hàng nghìn loại chip, giao diện thân thiện và hiệu năng ổn định, nó đã trở thành công cụ không thể thiếu đối với:

  • Kỹ thuật viên sửa chữa phần cứng
  • Người nghiên cứu firmware
  • Sinh viên và maker

Nếu CH341A là “phần cứng quốc dân”, thì NeoProgrammer chính là “phần mềm quốc dân” đi kèm. Khi kết hợp hai công cụ này, người dùng có thể thực hiện từ những thao tác cơ bản như đọc EEPROM cho đến các công việc nâng cao như phục hồi BIOS hay phân tích firmware một cách hiệu quả.

Nếu bạn đang bước chân vào lĩnh vực phần cứng, thì combo NeoProgrammer + CH341A gần như là một thiết bị “phải có” trong bộ công cụ của mình.


Nguồn tham khảo:

https://4pda.to/forum/index.php?showtopic=884713

Link tải phần mềm dự phòng:

Bonus: Tôi thích chương trình NeoProgrammer vì tính ổn định và hiện được cộng đồng sử dụng rất nhiều, nhưng có 1 điểm là muốn thêm chip mới hơi có vẻ rườm rà và chiplist.dat hiện tại được nén + mã hóa nên không thể trích xuất toàn bộ nội dung của nó ra file xml, thực chất việc này là vô ích vì có thể tự thêm nó vào trong file Import.xml.....nhưng vì sở thích cá nhân nên thích làm chuyện vô bổ.

Dưới đây là cách tôi đã nhờ người bạn @tongo0448 (discord) sử dụng IDA để trích xuất chiplist từ Memory:

import ctypes
import os
import struct
import sys
from ctypes import wintypes

APP_DIR = r"CHANGE_DIR_PATH\NeoProgrammer V2.2.0.10"
APP_EXE = os.path.join(APP_DIR, "NeoProgrammer.exe")
OUT_XML = os.path.join(r"CHANGE_DIR_PATH", "chiplist.xml")

DEBUG_ONLY_THIS_PROCESS = 0x00000002
CREATE_NEW_CONSOLE = 0x00000010
DBG_CONTINUE = 0x00010002
DBG_EXCEPTION_NOT_HANDLED = 0x80010001
EXCEPTION_DEBUG_EVENT = 1
CREATE_PROCESS_DEBUG_EVENT = 3
EXIT_PROCESS_DEBUG_EVENT = 5
EXCEPTION_BREAKPOINT = 0x80000003
STATUS_WX86_BREAKPOINT = 0x4000001F
EXCEPTION_SINGLE_STEP = 0x80000004
CONTEXT_FULL = 0x00010007
MEM_COMMIT = 0x1000
PAGE_GUARD = 0x100
PAGE_NOACCESS = 0x01

RVA_BPS = {
    0x27C70: "load chiplist database",
    0x5B87A: "before stream transform",
    0x5B87F: "after stream transform",
    0x27D1C: "XML parser load",
}


class STARTUPINFO(ctypes.Structure):
    _fields_ = [
        ("cb", wintypes.DWORD),
        ("lpReserved", wintypes.LPWSTR),
        ("lpDesktop", wintypes.LPWSTR),
        ("lpTitle", wintypes.LPWSTR),
        ("dwX", wintypes.DWORD),
        ("dwY", wintypes.DWORD),
        ("dwXSize", wintypes.DWORD),
        ("dwYSize", wintypes.DWORD),
        ("dwXCountChars", wintypes.DWORD),
        ("dwYCountChars", wintypes.DWORD),
        ("dwFillAttribute", wintypes.DWORD),
        ("dwFlags", wintypes.DWORD),
        ("wShowWindow", wintypes.WORD),
        ("cbReserved2", wintypes.WORD),
        ("lpReserved2", ctypes.c_void_p),
        ("hStdInput", wintypes.HANDLE),
        ("hStdOutput", wintypes.HANDLE),
        ("hStdError", wintypes.HANDLE),
    ]


class PROCESS_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("hProcess", wintypes.HANDLE),
        ("hThread", wintypes.HANDLE),
        ("dwProcessId", wintypes.DWORD),
        ("dwThreadId", wintypes.DWORD),
    ]


class EXCEPTION_RECORD(ctypes.Structure):
    _fields_ = [
        ("ExceptionCode", wintypes.DWORD),
        ("ExceptionFlags", wintypes.DWORD),
        ("ExceptionRecord", ctypes.c_void_p),
        ("ExceptionAddress", ctypes.c_void_p),
        ("NumberParameters", wintypes.DWORD),
        ("ExceptionInformation", ctypes.c_size_t * 15),
    ]


class EXCEPTION_DEBUG_INFO(ctypes.Structure):
    _fields_ = [
        ("ExceptionRecord", EXCEPTION_RECORD),
        ("dwFirstChance", wintypes.DWORD),
    ]


class CREATE_PROCESS_DEBUG_INFO(ctypes.Structure):
    _fields_ = [
        ("hFile", wintypes.HANDLE),
        ("hProcess", wintypes.HANDLE),
        ("hThread", wintypes.HANDLE),
        ("lpBaseOfImage", ctypes.c_void_p),
        ("dwDebugInfoFileOffset", wintypes.DWORD),
        ("nDebugInfoSize", wintypes.DWORD),
        ("lpThreadLocalBase", ctypes.c_void_p),
        ("lpStartAddress", ctypes.c_void_p),
        ("lpImageName", ctypes.c_void_p),
        ("fUnicode", wintypes.WORD),
    ]


class EXIT_PROCESS_DEBUG_INFO(ctypes.Structure):
    _fields_ = [("dwExitCode", wintypes.DWORD)]


class DEBUG_EVENT_UNION(ctypes.Union):
    _fields_ = [
        ("Exception", EXCEPTION_DEBUG_INFO),
        ("CreateProcessInfo", CREATE_PROCESS_DEBUG_INFO),
        ("ExitProcess", EXIT_PROCESS_DEBUG_INFO),
        ("raw", ctypes.c_byte * 160),
    ]


class DEBUG_EVENT(ctypes.Structure):
    _fields_ = [
        ("dwDebugEventCode", wintypes.DWORD),
        ("dwProcessId", wintypes.DWORD),
        ("dwThreadId", wintypes.DWORD),
        ("u", DEBUG_EVENT_UNION),
    ]


class MEMORY_BASIC_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("BaseAddress", ctypes.c_void_p),
        ("AllocationBase", ctypes.c_void_p),
        ("AllocationProtect", wintypes.DWORD),
        ("RegionSize", ctypes.c_size_t),
        ("State", wintypes.DWORD),
        ("Protect", wintypes.DWORD),
        ("Type", wintypes.DWORD),
    ]


class WOW64_CONTEXT(ctypes.Structure):
    _fields_ = [
        ("ContextFlags", wintypes.DWORD),
        ("Dr0", wintypes.DWORD),
        ("Dr1", wintypes.DWORD),
        ("Dr2", wintypes.DWORD),
        ("Dr3", wintypes.DWORD),
        ("Dr6", wintypes.DWORD),
        ("Dr7", wintypes.DWORD),
        ("FloatSave", ctypes.c_byte * 112),
        ("SegGs", wintypes.DWORD),
        ("SegFs", wintypes.DWORD),
        ("SegEs", wintypes.DWORD),
        ("SegDs", wintypes.DWORD),
        ("Edi", wintypes.DWORD),
        ("Esi", wintypes.DWORD),
        ("Ebx", wintypes.DWORD),
        ("Edx", wintypes.DWORD),
        ("Ecx", wintypes.DWORD),
        ("Eax", wintypes.DWORD),
        ("Ebp", wintypes.DWORD),
        ("Eip", wintypes.DWORD),
        ("SegCs", wintypes.DWORD),
        ("EFlags", wintypes.DWORD),
        ("Esp", wintypes.DWORD),
        ("SegSs", wintypes.DWORD),
        ("ExtendedRegisters", ctypes.c_byte * 512),
    ]


kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.CreateProcessW.argtypes = [
    wintypes.LPCWSTR,
    wintypes.LPWSTR,
    ctypes.c_void_p,
    ctypes.c_void_p,
    wintypes.BOOL,
    wintypes.DWORD,
    ctypes.c_void_p,
    wintypes.LPCWSTR,
    ctypes.POINTER(STARTUPINFO),
    ctypes.POINTER(PROCESS_INFORMATION),
]
kernel32.CreateProcessW.restype = wintypes.BOOL
kernel32.WaitForDebugEvent.argtypes = [ctypes.POINTER(DEBUG_EVENT), wintypes.DWORD]
kernel32.WaitForDebugEvent.restype = wintypes.BOOL
kernel32.ContinueDebugEvent.argtypes = [wintypes.DWORD, wintypes.DWORD, wintypes.DWORD]
kernel32.ReadProcessMemory.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_size_t,
    ctypes.POINTER(ctypes.c_size_t),
]
kernel32.WriteProcessMemory.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_size_t,
    ctypes.POINTER(ctypes.c_size_t),
]
kernel32.VirtualQueryEx.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.POINTER(MEMORY_BASIC_INFORMATION),
    ctypes.c_size_t,
]
kernel32.VirtualQueryEx.restype = ctypes.c_size_t
kernel32.FlushInstructionCache.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.c_size_t,
]
kernel32.OpenThread.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
kernel32.OpenThread.restype = wintypes.HANDLE
kernel32.Wow64GetThreadContext.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(WOW64_CONTEXT),
]
kernel32.Wow64SetThreadContext.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(WOW64_CONTEXT),
]
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]


def fail(msg):
    raise ctypes.WinError(ctypes.get_last_error(), msg)


def read_mem(hp, addr, size):
    buf = ctypes.create_string_buffer(size)
    got = ctypes.c_size_t()
    if not kernel32.ReadProcessMemory(
        hp, ctypes.c_void_p(addr), buf, size, ctypes.byref(got)
    ):
        return None
    return buf.raw[: got.value]


def write_mem(hp, addr, data):
    buf = ctypes.create_string_buffer(data)
    done = ctypes.c_size_t()
    if not kernel32.WriteProcessMemory(
        hp, ctypes.c_void_p(addr), buf, len(data), ctypes.byref(done)
    ):
        fail("WriteProcessMemory")
    kernel32.FlushInstructionCache(hp, ctypes.c_void_p(addr), len(data))


def get_ctx(tid):
    thread = kernel32.OpenThread(0x001F03FF, False, tid)
    if not thread:
        fail("OpenThread")
    try:
        ctx = WOW64_CONTEXT()
        ctx.ContextFlags = CONTEXT_FULL
        if not kernel32.Wow64GetThreadContext(thread, ctypes.byref(ctx)):
            fail("Wow64GetThreadContext")
        return ctx
    finally:
        kernel32.CloseHandle(thread)


def set_eip(tid, eip):
    thread = kernel32.OpenThread(0x001F03FF, False, tid)
    if not thread:
        fail("OpenThread")
    try:
        ctx = WOW64_CONTEXT()
        ctx.ContextFlags = CONTEXT_FULL
        if not kernel32.Wow64GetThreadContext(thread, ctypes.byref(ctx)):
            fail("Wow64GetThreadContext")
        ctx.Eip = eip
        if not kernel32.Wow64SetThreadContext(thread, ctypes.byref(ctx)):
            fail("Wow64SetThreadContext")
    finally:
        kernel32.CloseHandle(thread)


def install_bps(hp, base):
    bps = {}
    for rva, name in RVA_BPS.items():
        addr = base + rva
        orig = read_mem(hp, addr, 1)
        if not orig:
            raise RuntimeError(f"cannot read breakpoint byte at {addr:08X}")
        write_mem(hp, addr, b"\xcc")
        bps[addr] = (orig, name, rva)
        print(f"bp {addr:08X} ({name})")
    return bps


def search_and_dump_xml(hp):
    mbi = MEMORY_BASIC_INFORMATION()
    addr = 0
    hits = []
    while addr < 0x80000000:
        ret = kernel32.VirtualQueryEx(
            hp, ctypes.c_void_p(addr), ctypes.byref(mbi), ctypes.sizeof(mbi)
        )
        if not ret:
            addr += 0x10000
            continue
        base = int(mbi.BaseAddress or 0)
        size = int(mbi.RegionSize)
        readable = mbi.State == MEM_COMMIT and not (
            mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)
        )
        if readable and size:
            data = read_mem(hp, base, min(size, 64 * 1024 * 1024))
            if data:
                for needle in (b"<?xml", b"<chiplist", b"SPI_NOR", b"SPI_NAND"):
                    pos = data.find(needle)
                    if pos != -1:
                        hits.append(
                            (base + pos, needle.decode("ascii", "ignore"), base, data)
                        )
        addr = base + max(size, 0x1000)

    for hit_addr, needle, base, data in hits:
        print(f"hit {needle} at {hit_addr:08X}")

    candidates = []
    for hit_addr, needle, base, data in hits:
        for marker in (b"<?xml", b"<chiplist"):
            start = data.find(marker)
            end = data.find(b"</chiplist>", start)
            if start != -1 and end != -1:
                end += len(b"</chiplist>")
                candidates.append(
                    (0 if marker == b"<?xml" else 1, base + start, data[start:end])
                )

    if candidates:
        candidates.sort(key=lambda item: (item[0], item[1]))
        _, start_addr, xml = candidates[0]
        with open(OUT_XML, "wb") as f:
            f.write(xml)
        print(f"dumped {len(xml)} bytes from {start_addr:08X} to {OUT_XML}")
        return True
    return False


def main():
    si = STARTUPINFO()
    si.cb = ctypes.sizeof(si)
    pi = PROCESS_INFORMATION()
    cmd = f'"{APP_EXE}"'
    if not kernel32.CreateProcessW(
        None,
        cmd,
        None,
        None,
        False,
        DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,
        None,
        APP_DIR,
        ctypes.byref(si),
        ctypes.byref(pi),
    ):
        fail("CreateProcessW")
    print(f"started pid={pi.dwProcessId}")

    hp = pi.hProcess
    bps = {}
    base = None
    saw_initial_bp = False
    dumped = False
    try:
        while True:
            ev = DEBUG_EVENT()
            if not kernel32.WaitForDebugEvent(ctypes.byref(ev), 30000):
                print("timeout waiting for debug event")
                break
            status = DBG_CONTINUE
            if ev.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT:
                base = int(ev.u.CreateProcessInfo.lpBaseOfImage)
                print(f"image base {base:08X}")
                bps = install_bps(hp, base)
                if ev.u.CreateProcessInfo.hFile:
                    kernel32.CloseHandle(ev.u.CreateProcessInfo.hFile)
            elif ev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
                ex = ev.u.Exception.ExceptionRecord
                code = ex.ExceptionCode
                exaddr = int(ex.ExceptionAddress or 0)
                if code in (EXCEPTION_BREAKPOINT, STATUS_WX86_BREAKPOINT):
                    bp_addr = exaddr - 1
                    if bp_addr not in bps and exaddr in bps:
                        bp_addr = exaddr
                    if not saw_initial_bp and bp_addr not in bps:
                        saw_initial_bp = True
                        print(f"initial/system breakpoint at {exaddr:08X}")
                    elif bp_addr in bps:
                        orig, name, rva = bps.pop(bp_addr)
                        write_mem(hp, bp_addr, orig)
                        set_eip(ev.dwThreadId, bp_addr)
                        ctx = get_ctx(ev.dwThreadId)
                        stack0 = read_mem(hp, ctx.Esp, 4)
                        stack0_val = struct.unpack("<I", stack0)[0] if stack0 else None
                        stack0_text = (
                            f"{stack0_val:08X}"
                            if stack0_val is not None
                            else "????????"
                        )
                        print(
                            f"break {bp_addr:08X} {name}: "
                            f"EAX={ctx.Eax:08X} ECX={ctx.Ecx:08X} EDX={ctx.Edx:08X} "
                            f"ESP={ctx.Esp:08X} [ESP]={stack0_text}"
                        )
                        if rva == 0x5B87F:
                            dumped = search_and_dump_xml(hp)
                            if dumped:
                                break
                        elif rva == 0x27D1C:
                            dumped = search_and_dump_xml(hp)
                            break
                    else:
                        status = DBG_EXCEPTION_NOT_HANDLED
                elif code == EXCEPTION_SINGLE_STEP:
                    pass
                else:
                    print(f"exception {code:08X} at {exaddr:08X}")
                    status = DBG_EXCEPTION_NOT_HANDLED
            elif ev.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT:
                print(f"process exited {ev.u.ExitProcess.dwExitCode}")
                break
            kernel32.ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, status)
    finally:
        if not dumped:
            print("XML not dumped")


if __name__ == "__main__":
    main()

Update cơ chế decrypt từ chiplist.dat:

import hashlib
import base64
import pathlib
import struct
import zlib
INPUT = pathlib.Path("chiplist.dat")
OUTPUT = pathlib.Path("chiplist_algorithm.xml")
def rc4_init(key: bytes) -> list[int]:
    s = list(range(256))
    j = 0
    key_len = len(key)
    for i in range(256):
        j = (j + s[i] + key[i % key_len]) & 0xFF
        s[i], s[j] = s[j], s[i]
    return s
def rc4_apply(data: bytes, s: list[int]) -> bytes:
    i = 0
    j = 0
    out = bytearray(len(data))
    for n, b in enumerate(data):
        i = (i + 1) & 0xFF
        j = (j + s[i]) & 0xFF
        s[i], s[j] = s[j], s[i]
        out[n] = b ^ s[(s[i] + s[j]) & 0xFF]
    return bytes(out)
def rc4(data: bytes, key: bytes) -> bytes:
    return rc4_apply(data, rc4_init(key))
def rc4_chunked(data: bytes, key: bytes, chunk_size: int = 0x2000) -> bytes:
    s = rc4_init(key)
    out = bytearray()
    for offset in range(0, len(data), chunk_size):
        out += rc4_apply(data[offset:offset + chunk_size], s)
    return bytes(out)
def build_password() -> str:
    first_order = ["vd7", "SQP", "RBs", "HgX", "0bv", "pii", "sn8"]
    second_order = ["z1J", "92Z", "7xA", "eex", "MEW", "ulI", "wdX"]
    return "".join(s[1:3] for s in first_order + second_order)
def derive_real_password() -> bytes:
    obfuscated = base64.b64decode(build_password().encode("ascii"))
    unwrap_key = hashlib.sha1(b"chiplist.dat").digest()
    return rc4(obfuscated, unwrap_key)
def decrypt_chiplist(raw: bytes) -> bytes:
    if len(raw) < 8:
        raise ValueError(f"chiplist data is too short: {len(raw)} bytes")

    password = derive_real_password()
    key = hashlib.sha1(password).digest()
    decrypted = rc4_chunked(raw, key)

    unpacked_size, reserved = struct.unpack_from("<II", decrypted, 0)
    if reserved != 0:
        raise ValueError(f"unexpected header reserved value: 0x{reserved:08X}")

    xml = zlib.decompress(decrypted[8:])
    if len(xml) != unpacked_size:
        raise ValueError(f"size mismatch: header={unpacked_size}, zlib={len(xml)}")
    return xml
def main():
    xml = decrypt_chiplist(INPUT.read_bytes())
    OUTPUT.write_bytes(xml)
    print(f"obfuscated password: {build_password()}")
    print(f"real password: {derive_real_password().decode('ascii')}")
    print(f"wrote {len(xml)} bytes to {OUTPUT}")
if __name__ == "__main__":
    main()

Dựa vào cơ chế này xây dựng 1 ứng dụng GUI cho phép chỉnh sửa trực tiếp file chiplist.dat


Không có nhận xét nào:

Đăng nhận xét

Cám ơn bạn đã để lại nhận xét

Bài đăng nổi bật

Giới thiệu bộ lập trình giá rẻ CH341A và phần mềm NeoProgrammer 2.2.0.10 “quốc dân” cho dân điện tử

Trong lĩnh vực sửa chữa phần cứng, lập trình firmware hay nghiên cứu hệ thống nhúng, cái tên CH341A gần như đã trở thành “huyền thoại” tron...

Popular Posts