LapTrinhBlockchain

Chia sẻ kiến thức về Lập Trình Blockchain

Kiến thức lập trình, Kiến thức phần mềm, Lập trình GoLang, Nâng cao Kiến thức

Hướng dẫn build an toàn cho các ngôn ngữ C/C++, Go lang,…

Hướng dẫn build an toàn cho các ngôn ngữ C/C++, Go lang,...

Hướng dẫn build an toàn cho các ngôn ngữ C/C++, Go lang,...

Chia sẻ bài viết
5
(68)

Chúng ta thường nghĩ rằng khi mã nguồn được biên dịch thành một tệp thực thi (executable file hay exe file), chương trình đã trở nên an toàn và khó bị phân tích. Nhưng thực tế hoàn toàn khác: binary vẫn có thể được dịch ngược, phân tích assembly và tìm ra những lỗ hổng tiềm ẩn để khai thác.

Đừng nhầm executable với một lớp bảo vệ. Khi mã nguồn được biên dịch, nó không biến mất — nó chỉ chuyển sang một dạng khác. Với reverse engineering, attacker có thể phân tích binary, khôi phục cấu trúc chương trình, lần theo luồng thực thi và phát hiện những lỗ hổng mà developer tưởng rằng đã được che giấu. Binary không phải bức tường bảo mật; nó chỉ là một mục tiêu khó đọc hơn.

Hướng dẫn build an toàn trên một số ngôn ngữ

Hướng dẫn build an toàn cho các ứng dụng viết bằng Go lang

Đối với ứng dụng Go, việc build bằng cấu hình mặc định thường để lại khá nhiều thông tin hữu ích cho quá trình phân tích. Trước khi phát hành, nên sử dụng các tùy chọn như -trimpath-ldflags="-s -w" để loại bỏ đường dẫn build, symbol table và dữ liệu debug không cần thiết. Điều này giúp giảm kích thước binary và hạn chế lượng thông tin mà kẻ tấn công có thể thu thập trực tiếp.

Tuy nhiên, cần hiểu rằng strip symbol hay loại bỏ debug information không phải là biện pháp bảo mật tuyệt đối. Các công cụ hiện đại như Ghidra, IDA Pro hay GoReSym vẫn có thể khôi phục đáng kể cấu trúc của chương trình Go. Vì vậy, lớp phòng thủ quan trọng nhất vẫn là chất lượng mã nguồn: kiểm soát đầu vào chặt chẽ, loại bỏ các endpoint debug, bảo vệ bí mật bằng cơ chế quản lý khóa phù hợp, cập nhật dependency thường xuyên và áp dụng các nguyên tắc secure coding ngay từ giai đoạn phát triển.

Hãy xem quá trình build production như một phần của chiến lược phòng thủ nhiều lớp. Mục tiêu không phải làm cho binary “không thể phân tích”, mà là giảm tối đa thông tin rò rỉ, thu hẹp bề mặt tấn công và khiến việc khai thác trở nên khó khăn hơn đáng kể.

# Lệnh build an toàn
go build -trimpath -ldflags="-s -w" -o app .

# Nếu không sử dụng thư viện C/C++
export CGO_ENABLED=0
go build -trimpath -ldflags="-s -w" -o app .

Giải thích chi tiết:

  • -trimpath: Loại bỏ đường dẫn filesystem của máy build khỏi binary.
  • -ldflags=”-s -w”: Trong đó
    • -s: Loại bỏ symbol table/debug information.
    • -w: Loại bỏ DWARF debugging information.

Chế độ build này của Go gần nhất với chế độ “Release build” trên Visual Studio.

Tôi ban đầu nghĩ build ra tệp executable là an toàn rồi nhưng không, sau khi tìm hiểu tôi đang build ra tệp ở dạng Debug, rất dễ bị khai thác. Sau khi thêm cờ như trên và build lại thì tệp executable của tôi giảm rất nhiều. Cụ thể:

  • Tệp thứ 1: Ban đầu kích thước tệp là 11.5M, sau khi build với cờ trên giảm xuống còn 7.9M => Giảm hơn 30% kích thước
  • Tệp thứ 2: Từ 13.7M giảm xuống còn 9.7M => Giảm khoảng 29% kích thước

Như vậy sau khi sử dụng chế độ build ở trên, rất nhiều thông tin đã bị loại bỏ khỏi tệp thực thi, giúp giảm thiểu khả năng truy ngược thông tin của attacker.

Hướng dẫn code an toàn

Xử lý chuỗi kí tự nhạy cảm khi code

Khi lập trình, chắc chắn có nhiều biến chuỗi được bạn khai báo giá trị sẵn, trong những biến này sẽ có 1 số biến nhạy cảm mà bạn không muốn bất kỳ ai biết. Nhưng thực tế, dù bạn đã build mã nguồn ra các tệp executable rồi thì sử dụng các công cụ Reverse Engineering, bạn vẫn nhìn thấy nội dung các xâu này.

Trong tệp thực thi, chúng ta cần phaanbieetj 2 loại: Ký hiệu (Symbols)Chuỗi văn bản thuần túy (String Literals).

  • Khi biên dịch ở chế độ Release, trình biên dịch chỉ xóa bảng ký hiệu (Symbol table – Tên hàm, tên biến, debug info) để các công cụ khó có thể dịch ngược.
  • Nhưng nội dung các chuỗi chúng không phải ký hiệu mà chúng là dạng “String Literals”, được nhúng trực tiếp vào mã nguồn.

Vậy làm để xử lý được trường hợp này. Có ba cách xử lý như sau:

  • C1: Sử dụng công cụ xáo trộn mã – Khuyến khích dùng
    • Với ngôn ngữ Go, bạn hãy sử dụng công cụ Garble
  • Cách 2: Tự mã hóa thủ công (XOR hoặc Base64 cơ bản)
    • Nếu không muốn dùng công cụ ngoài, bạn không được phép khai báo chuỗi trực tiếp. Hãy đổi nó sang dạng mảng Byte đã được mã hóa (XOR hoặc hoán vị vị trí) rồi giải mã khi khởi chạy chương trình.
    • Cách này hơi phức tạp và mất công
  • Cách 3: Chuyển cấu hình ra ngoài Source Code
    • Đối với các hệ thống production, việc hardcode KMS Key hay API Key vào file thực thi là một lỗi bảo mật nghiêm trọng.
    • Bạn nên chuyển cấu hình này ra ngoài:
      • Đọc từ Biến môi trường (Environment Variables) bằng os.Getenv(“KMS_KEY”).
      • Đọc từ file cấu hình bảo mật bên ngoài (.env, config.json) được phân quyền chặt chẽ trên server.
    • Cái này thì rủi ro bảo mật nằm ở người vận hành server và triển khai hệ thống

Tôi thấy C1 sử dụng Garble khá đơn giản và dễ sử dụng, sau khi sử dụng garble để build với 2 tham số -literals -tiny thì dung lượng file từ 9.7M lên tới 23.4M.

Hướng dẫn triển khai an toàn

Kiểm tra lại tệp thực thi trước khi triển khai trên môi trường thật

Sau khi build xong executable cho Production, đừng vội cho rằng quá trình bảo vệ đã hoàn tất. Hãy kiểm tra lại chính binary vừa build bằng các công cụ reverse engineering như Ghidra, IDA Pro hoặc GoReSym.

Mục tiêu không phải để ngăn binary bị phân tích hoàn toàn — điều này gần như không thể — mà để xác định những thông tin nào vẫn đang bị lộ: cấu trúc function, symbol, chuỗi nhạy cảm, endpoint, thông tin build, logic quan trọng và các thành phần có thể trở thành điểm tấn công.

Một binary Production tốt không chỉ cần build thành công, mà còn phải được kiểm tra từ góc nhìn của attacker trước khi được phát hành.

Lệnh strings, readelf – Lệnh đơn giản để trích xuất các xâu trong tệp thực thi và kiểm tra chế độ debug

Trên Ubuntu hay Linux, bạn đơn giản chỉ cần chạy lệnh sau:

strings <Tệp thực thi> | grep <Xâu bạn không muốn xuất hiện>

Nó sẽ list ra toàn bộ xâu string có trong tệp thực thi, bạn kiểm tra lại xem, có xâu string gì bạn không muốn người khác biết lại có mặt trong danh sách trên hay không?

Lệnh khác để xem có phải bạn vẫn build ở chế độ debug không?

readelf -S <Tệp thực thi> | grep -Ei 'debug|symtab'

Ghidra – Công cụ kỹ thuật đảo ngược mã nguồn mở và miễn phí rất mạnh

Ghidra là một công cụ kỹ thuật đảo ngược (Reverse Engineering – RE) mã nguồn mở và miễn phí, được phát triển bởi Cơ quan An ninh Quốc gia Hoa Kỳ (NSA) nhằm mục đích phân tích, dịch phục hồi cấu trúc và kiểm tra mã nguồn của các phần mềm hoặc tệp tin nhị phân.

Ghidra được sử dụng để:

  • Phân tích mã độc (Malware Analysis): Các chuyên gia an ninh mạng dùng Ghidra để “mổ xẻ” virus, ransomware, Trojan. Qua đó, họ biết được mã độc này đánh cắp dữ liệu gì, phá hoại hệ thống ra sao và gửi thông tin về máy chủ nào.
  • Tìm kiếm lỗ hổng bảo mật (Vulnerability Research): Kiểm tra các phần mềm (ngay cả khi không có mã nguồn gốc) để phát hiện lỗi bảo mật, từ đó vá lỗi hoặc đánh giá nguy cơ bị hacker tấn công.
  • Kỹ thuật đảo ngược phần mềm (Reverse Engineering): Khám phá kiến trúc, cách thức hoạt động của một ứng dụng, thuật toán ẩn hoặc các giao thức truyền thông mà nhà phát triển không công khai.
  • Phát triển bản vá và bẻ khóa (Patching & Cracking): Chỉnh sửa trực tiếp file thực thi để thay đổi hành vi chương trình (ví dụ: vá lỗi khẩn cấp, mở khóa tính năng, thay đổi luồng logic).

Các tính năng cốt lõi của Ghidra

  • Trình dịch ngược mạnh mẽ (Decompiler): Chuyển đổi mã máy (Assembly) sang mã giả ngôn ngữ cao (C-like code) cực kỳ trực quan và dễ đọc, đây là tính năng miễn phí vượt trội nhất giúp nó cạnh tranh trực tiếp với phần mềm trả phí đắt đỏ IDA Pro.
  • Hỗ trợ đa nền tảng: Chạy mượt mà trên cả Windows, macOS và Linux.
  • Kho kiến trúc đồ sộ: Phân tích được file của hầu hết các kiến trúc chip phổ biến hiện nay như x86, ARM, MIPS, PowerPC, AVR, v.v. [1]
  • Tự động hóa bằng Script: Cho phép người dùng viết các đoạn mã bằng Java hoặc Python (Jython) để tự động hóa quá trình quét và phân tích dữ liệu lớn.
  • Làm việc nhóm (Collaboration): Hỗ trợ nhiều chuyên gia cùng kết nối vào một máy chủ Ghidra để đồng thời phân tích chung một tệp tin.

IDA Pro – Công cụ kỹ thuật đảo ngược tiêu chuẩn vàng

IDA Pro (Interactive Disassembler) là công cụ kỹ thuật đảo ngược (Reverse Engineering) tiêu chuẩn vàng và có tầm ảnh hưởng lớn nhất trong ngành an ninh mạng toàn cầu. Được phát triển thương mại bởi công ty Hex-Rays, IDA Pro là đối thủ cạnh tranh trực tiếp, cao cấp và lâu đời hơn rất nhiều so với Ghidra. Nếu Ghidra là công cụ miễn phí quốc dân, thì IDA Pro là “vũ khí hạng nặng” có thu phí mà mọi chuyên gia RE và phân tích phần mềm độc hại chuyên nghiệp đều sở hữu.

Để hiểu rõ IDA Pro mạnh như thế nào, hãy xem bảng so sánh trực diện dưới đây:

Tiêu chíIDA ProGhidra
Bản quyềnThương mại (Có phí rất đắt, lên tới hàng ngàn USD/năm)Miễn phí hoàn toàn & Mã nguồn mở (NSA phát hành)
Trình dịch ngược (Decompiler)Chất lượng tốt nhất thế giới, mã giả C++ cực kỳ sạch và tối ưuKhá tốt nhưng đôi khi mã giả bị rườm rà, khó đọc hơn
Tốc độ xử lýCực kỳ nhanh, tối ưu hóa tốt cho các file dung lượng lớnChạy trên Java nên đôi khi ngốn RAM và xử lý file lớn chậm hơn
Giao diện & Đồ thịGiao diện đồ thị (Graph view) trực quan, mượt mà, dễ tương tácGiao diện hơi cũ, điều hướng đồ thị không mượt bằng
Cộng đồng & PluginHệ sinh thái plugin khổng lồ tích lũy qua hàng chục nămCộng đồng đang phát triển nhanh, plugin ít hơn
Khả năng DebugTích hợp trình gỡ lỗi (Debugger) mạnh mẽ chạy trực tiếp mãPhải cấu hình phức tạp hoặc dùng công cụ ngoài

Cũng tương tự như Ghidra, IDA Pro phục vụ các tác vụ phân tích mã máy chuyên sâu nhưng ở cấp độ doanh nghiệp:

  • Phân tích mã độc cấp cao (Advanced Malware Analysis): Đọc và bóc tách các loại mã độc tinh vi được chính phủ tài trợ (APT), virus tống tiền, mã độc có khả năng tự chống dịch ngược.
  • Khai thác lỗ hổng (Exploit Development): Giúp các kỹ sư tìm ra chính xác lỗi tràn bộ đệm (Buffer Overflow) hoặc lỗi bộ nhớ để viết mã khai thác (PoC).
  • Kiểm định mã nguồn đóng (Proprietary Software Auditing): Phân tích các driver hệ thống, firmware của thiết bị phần cứng để đảm bảo chúng không có cửa sau (Backdoor).

Vì giá bản quyền của IDA Pro rất cao, Hex-Rays cung cấp các tùy chọn sau cho người dùng:

  • IDA Free: Phiên bản miễn phí dành cho cá nhân, chỉ hỗ trợ kiến trúc x86/x64, không có đầy đủ các trình dịch ngược cao cấp.
  • IDA Home: Phiên bản giá rẻ hơn dành cho người đam mê học tập tại nhà (giới hạn một số tính năng chuyên nghiệp).
  • IDA Pro: Phiên bản đầy đủ nhất, hỗ trợ mọi kiến trúc chip (ARM, MIPS, PowerPC…) chuyên dùng cho doanh nghiệp.

GoReSym – Công cụ hỗ trợ khôi phục ký hiệu cho các ứng dụng viết từ Go

GoReSym (Go Symbol Recovery Tool) là một công cụ mã nguồn mở được phát triển bởi hãng bảo mật Mandiant (thuộc Google), chuyên dùng để khôi phục ký hiệu (symbol) và siêu dữ liệu (metadata) của các file thực thi được biên dịch bằng ngôn ngữ Go (Golang). Tham khảo: Ready, Set, Go — Golang Internals and Symbol Recovery

Khi phân tích một file binary thông thường bằng Ghidra hay IDA Pro, nếu lập trình viên hoặc kẻ viết mã độc đã strip (xóa bỏ toàn bộ bảng ký hiệu) hoặc dùng công cụ làm rối mã (obfuscator) như Garble, các hàm trong chương trình sẽ bị mất tên gốc và biến thành những cái tên vô nghĩa như sub_401000. Điều này làm việc đọc mã giả (Decompile) trở nên cực kỳ khó khăn. GoReSym sinh ra để giải quyết chính xác bài toán này đối với phần mềm viết bằng Go.

GoReSym giúp trích xuất được những gì? Khi bạn nạp một file .exe hoặc ELF (viết bằng Go) vào GoReSym, nó chỉ mất 1-5 giây để trả về một file định dạng JSON chứa:

  • Tên của tất cả các hàm gốc: Giúp bạn biết hàm nào là của hệ thống Go, hàm nào do lập trình viên tự viết (ví dụ: main.payloadDownload).
  • Đường dẫn file mã nguồn ban đầu trên máy người code: Tiết lộ tên tài khoản Windows/Linux của kẻ tạo mã độc hoặc cấu trúc thư mục dự án của họ.
  • Kiểu dữ liệu (Types) và Giao diện (Interfaces): Tái tạo lại các cấu trúc struct, map, channel mà chương trình sử dụng.
  • Thông tin biên dịch: Phiên bản Go được sử dụng, hệ điều hành đích, kiến trúc chip, và các cờ cấu hình.

Cách phối hợp GoReSym với IDA Pro và Ghidra: GoReSym không phải là một trình dịch ngược độc lập có giao diện đồ họa. Nó là một công cụ dòng lệnh (Command-line) hoạt động theo quy trình:

  1. Trích xuất bằng GoReSym: Bạn chạy lệnh để quét file nghi vấn và xuất ra file JSON:
    GoReSym.exe -t -d -p sample_malware.exe > result.json
  2. Nạp vào công cụ RE: Hãng Mandiant cung cấp sẵn các script (bằng Python/Java) đi kèm trên GitHub Mandiant GoReSym. Bạn mở file đó trên IDA Pro hoặc Ghidra, chạy script này rồi import file result.json vừa tạo.
  3. Kết quả: Ngay lập tức, hàng ngàn hàm có tên vô nghĩa trên IDA/Ghidra sẽ tự động đổi thành tên gốc ban đầu, biến một file mã độc Go rối rắm thành một tệp tin cực kỳ dễ đọc.

Đảm bảo môi trường triển khai đã được bảo mật

Đảm bảo môi trường triển khai đã được bảo mật

Bảo vệ executable là chưa đủ. Một binary được build và kiểm tra kỹ vẫn có thể bị khai thác nếu môi trường triển khai tồn tại điểm yếu. Hãy đảm bảo ứng dụng chạy với quyền tối thiểu, không sử dụng root khi không cần thiết, giới hạn network access, bảo vệ file cấu hình và secret, vô hiệu hóa các service không cần thiết, cập nhật hệ điều hành và dependency thường xuyên.

Đối với ứng dụng Go chạy trên server hoặc container, cần đặc biệt chú ý đến filesystem permission, environment variables, exposed ports, debug/pprof endpoint, Docker configuration, TLS, firewall và logging. Mục tiêu là giảm tối đa attack surface ngay cả khi attacker đã tìm được cách tương tác với executable.

Một hệ thống an toàn không chỉ có secure binary, mà phải là secure binary + secure runtime + secure infrastructure.

Bài viết này có hữu ích với bạn?

Kích vào một biểu tượng ngôi sao để đánh giá bài viết!

Xếp hạng trung bình 5 / 5. Số phiếu: 68

Bài viết chưa có đánh giá! Hãy là người đầu tiên đánh giá bài viết này.

Trả lời

Giao diện bởi Anders Norén