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?
![]() |
Có thể mua trên app "con cá" hoặc tự làm, có link ở dưới |
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:
- Người dùng kết nối CH341A với máy tính qua USB
- Gắn chip cần đọc/ghi vào socket hoặc dùng kẹp SOP8 (SOIC clip)
- 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 loại board mạch màu đen chỉ hỗ trợ nhiều mức điện áp 5V, như vậy sẽ rất nguy hiểm với những chip hoạt động 3.3V (phải mod lại mạch), 1.8V chắc chắn phải dùng 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:
- Mở rộng danh sách chip hỗ trợ (hiện tại T4/2026 là 2110 loại IC - Luôn được cập nhật)
- Cải thiện tốc độ đọc/ghi
- Sửa lỗi nhận diện chip
- Tối ưu giao tiếp với CH341A
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:
- Kết nối CH341A với máy tính
- Gắn chip vào socket hoặc kẹp SOP8
- Mở NeoProgrammer
- Nhấn Detect IC để nhận diện chip hoặc chọn thủ công
- 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 mặc định ban đầu đã hỗ trợ hơn 1600 loại IC khác nhau từ những đóng góp từ cộng đồng, thời điểm hiện tại hỗ trợ là 2110 chip, 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ểm | Nhượ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
https://oshwhub.com/misslee/ch341a-nextprogrammer
https://www.right.com.cn/forum/thread-8290935-1-1.html
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 anh em 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 Reverse engineering với IDA MCP để trích xuất chiplist từ Memory:
Một lần nữa cảm ơn đặc biệt tới @tongo0448 đã giúp tôi.
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()







