0x006 reverse1.exe – Static Analyze PE structure [Part 2]

0x006 reverse1.exe - Static Analyze PE structure [Part 2]

จาก Part 1
ที่เราได้คุยกันไป ไฟล์ reverse1.exe ที่เรา reverse ใน windows ไปแล้ว
ซึ่ง part 1 จุดประสงค์ก็เพื่อให้เห็นว่าเราทำอะไรกับมันได้บ้าง ยังไม่เน้นสอนหรือเล่าความหมายอะไร
ทำให้เห็น ว่าใช้เครื่องมือวิเคราะห์ มีกลไก ทำอย่างไร ดึงอะไรมาดูได้บ้าง และง่าย หรือยาก อย่างไร

วันนี้ใน part 2 เราจะเริ่ม reverse และอธิบายกันต่อบน linux ครับ
เพราะผมไม่ได้เอา laptop windows กลับมาบ้าน คอมบ้านผมใช้แต่ Ubuntu ก็ทำงานได้
และยังคงใช้ rizin เหมือนเดิม มันก็ Static Analyze เบื้องต้น กับโปรแกรมที่แสนจะง่ายโปรแกรมนี้ reverse1.exe
ไม่มีให้โหลดนะครับ ไปเขียนแล้วก็ compile กันเองถ้าอยากทดสอบ ไม่ถึง 10 บรรทัดเลยโปรแกรมนี้

#include <stdio.h>
int main() {
    int number;
    printf("Enter a number:");
    scanf("%d", &number);
    printf("Number input =%d\n",number);
    return 0;
}

Scope การเล่าของผมเป็น Windows PE Executable File เสมอ เพราะผมถนัด

แน่นอนว่า reverse1.exe เป็นโปรแกรมที่เราเขียนมาเอง ทำให้เรารู้โค้ดโปรแกรม ว่ามันมีขั้นตอนอะไรบ้าง
reverse1.exe ::: ประกาศตัวแปรเดียว คือ int number หมายความว่า ตัวแปร int ในภาษา C เป็น Data Type ที่เป็น
เลขจำนวนเต็ม ไม่มีทศนิยม ขนาด 2 byte หรือมีค่าเท่ากับ 16 bit จะต้องเกิดการจองพื้นที่ใน memory อย่างน้อยที่สุด 2 byte แน่ๆ เพื่อให้ตัวแปรนี้ได้ทำงาน
reverse1.exe :::
มี main function ที่เรียกใช้ตัวแปร number นี้
reverse1.exe ::: มีการใช้ printf , scanf ซึ่งมาจาก stdio.h ใน console app พื้นฐานทั่วไปที่จะใช้ได้ง่ายๆ

แปลว่า , ตอนที่เรา reverse เราจะต้องมองเห็นสิ่งต่างๆ ที่เชื่อมโยงกับ source code จริงเหล่านี้
และเราจะเข้าใจสิ่งต่างๆได้ สอดคล้องกัน
มาเริ่มจากสิ่งง่ายๆ กันก่อนครับ


แต่ละ Section ของไฟล์ reverse1.exe จะไปแปลงร่างเป็น Process ยังไง และ เพื่ออะไร

เริ่มจาก standard PE file Section ประกอบด้วยอะไรบ้าง จะแบ่งเป็น 2 ส่วนใหญ่ๆ คือส่วนของ Header และส่วนของ Section ขอเขียนไว้เป็นตารางแบบนี้ครับ

เมื่อเราดับเบิ้ลคลิกไฟล์ reverse1.exe จะเกิดอะไรขึ้น ? ก็จะมีกลไกไป trig Windows Loader เพื่อให้เข้ามาทำงานเรียก และอ่านไฟล์ PE นี้เข้า Memory  (ภาพอธิบายเพื่อเสริมความเข้าใจว่าแต่ละส่วนของโครงสร้าง PE File ทำอะไรกับ Memory) ภาพ diagram นี้จะเป็นขั้นตอนคร่าวๆก่อน ยังไม่ระบุ Address

0x006-01-after-execute-pe-file
แต่เรายังไม่ไปไกลถึงขั้นดับเบิ้ลคลิกเพื่อรันนะครับ เราทำแค่ Static Analysis โดย rizin ดังนั้นเราจะไป Section 2 กันต่อ เพื่อยืนยันความเข้าใจกันครับ

rizin มีตัวเลือกในการเปิดไฟล์ได้หลายแบบ ขึ้นอยู่กับจุดประสงค์ของเรา และเรารู้เรื่องราวของไฟล์นั้นมามากแค่ไหน เช่น
- รู้ว่าเป็น PE File ธรรมดาที่สมบูรณ์ (เช่น reverse1.exe ของผมเองไฟล์นี้) รู้อยู่แก่ใจ
- รู้ว่าเป็น PE File แต่อาจจะเสียหาย หรือพังมา และรันไม่ได้ หรือมีการ edit แก้ไขมา
- รู้ว่าเป็น Malware แต่ไม่ได้ pack อะไรมากมาย เป็น malware อ่อนๆ
- รู้ว่าเป็น Malware และเป็น advanced threat ถูก obfuscate หรือถูกทำ anti debug มาอย่างดี

เราจะต้องใช้การเปิด rizin <ไฟล์ pe นั้น> ใน parameter ที่แตกต่างกัน แต่มันก็แบ่งได้ใหญ่ๆเป็น สองแบบชัดเจน ดังนี้ครับ

1. กรณี: ไฟล์ PE ปกติ สมบูรณ์ รู้จักไฟล์ดี (เช่น reverse1.exe ที่เขียนเอง) หรือ ใช้ในกรณี Malware ระดับพื้นฐาน ไม่มี pack หรือ obfuscation
rizin reverse1.exe
หรือระบุ bit ให้ชัดก็ได้
rizin -b 64 reverse1.exe

เหตุผล:

ให้ Rizin ใช้ loader วิเคราะห์แบบ full-featured ซึ่งเหมาะกับการใช้คำสั่ง aaa, afl, pdf, iS, ie, iI ฯลฯ ผมจะพาไปดู เพราะไฟล์นี้เข้าเงื่อนไขแรก จะทำให้เห็นการ map memory เหมือนตอนโปรแกรมถูกรันจริง

2. กรณี: ไฟล์ PE เสียหาย / ถูกแก้ไขบางส่วน / ข้อมูลบางจุดไม่ปกติ และใช้ในกรณี Malware ระดับสูง มี obfuscation, anti-debug, หรือ technique แปลก
rizin -n reverse1.exe
ถ้าต้องการ "หยุดการ auto analysis" เราจะเพิ่ม n ไปอีกตัว แน่นอนวิธีนี้เราจะต้อง manual เองเยอะมาก ต้องเข้าใจ address ต่างๆอย่างลึกซึ้งครับ
rizin -nn 64 reverse1.exe

เหตุผล:

ไม่โหลด PE structure อัตโนมัติ (เลี่ยง crash จาก section เพี้ยน)
เหมาะกับการ ดู header เอง, หาจุดเริ่มต้นของ code, สร้าง analysis flow ด้วยมือ
ไม่ให้ Rizin วิเคราะห์อะไรอัตโนมัติ เพื่อหลีกเลี่ยง misinterpretation เหมาะกับการดู payload, stub, shellcode ดิบๆ เอาไว้เราเรียนกันไปลึกๆ ค่อยเปิดด้วยวิธีนี้

แปลว่าเราจะเลือกใช้วิธีการเปิดไฟล์จาก "เรารู้มาก หรือ รู้น้อย แค่ไหนกับไฟล์นี้" นี่คือหัวใจสำคัญเริ่มต้นของการทำ Static Analysis
ไฟล์นี้ผมรู้แล้วว่าเป็นไฟล์ปกติ เพราะผมเขียนขึ้นมาเองใช่มั้ยละ ผมก็จะทำการโหลดแบบปกตินะครับ

keng@kengUbuntu:~/re/stack_study$ rizin -b 64 reverse1.exe
 -- The '?' command can be used to evaluate math expressions. Like this: '? (0x34+22)*4'
[0x1400013f0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls
[x] Analyze len bytes of instructions for references
[x] Check for classes
[x] Analyze local variables and arguments
[x] Type matching analysis for all functions
[x] Applied 0 FLIRT signatures via sigdb
[x] Propagate noreturn information
[x] Integrate dwarf function information.
[x] Resolve pointers to data sections
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x1400013f0]> iH
PE file header:
IMAGE_NT_HEADERS
  Signature : 0x4550
IMAGE_FILE_HEADERS
  Machine : 0x8664
  NumberOfSections : 0x11
  TimeDateStamp : 0x67d54c29
  PointerToSymbolTable : 0x14c00
  NumberOfSymbols : 0x66b
  SizeOfOptionalHeader : 0xf0
  Characteristics : 0x26
IMAGE_OPTIONAL_HEADERS
  Magic : 0x20b
  MajorLinkerVersion : 0x2
  MinorLinkerVersion : 0x2b
  SizeOfCode : 0xfa00
  SizeOfInitializedData : 0x12800
  SizeOfUninitializedData : 0xc00 
  AddressOfEntryPoint : 0x13f0
  BaseOfCode : 0x1000 
  ImageBase : 0x140000000
  SectionAlignment : 0x1000
  FileAlignment : 0x200
  MajorOperatingSystemVersion : 0x4
  MinorOperatingSystemVersion : 0x0
  MajorImageVersion : 0x0
  MinorImageVersion : 0x0
  MajorSubsystemVersion : 0x5
  MinorSubsystemVersion : 0x2
  Win32VersionValue : 0x0
  SizeOfImage : 0x22000
  SizeOfHeaders : 0x600
  CheckSum : 0x218ec
  Subsystem : 0x3
  DllCharacteristics : 0x160
  SizeOfStackReserve : 0x200000
  SizeOfStackCommit : 0x1000
  SizeOfHeapReserve : 0x100000
  SizeOfHeapCommit : 0x1000
  LoaderFlags : 0x0
  NumberOfRvaAndSizes : 0x10
RICH_FIELDS
IMAGE_DIRECTORY_ENTRY_IMPORT
  VirtualAddress : 0x16000
  Size : 0x820
IMAGE_DIRECTORY_ENTRY_EXCEPTION
  VirtualAddress : 0x13000
  Size : 0x5d0
IMAGE_DIRECTORY_ENTRY_BASERELOC
  VirtualAddress : 0x19000
  Size : 0xa0
IMAGE_DIRECTORY_ENTRY_TLS
  VirtualAddress : 0x12060
  Size : 0x28
IMAGE_DIRECTORY_ENTRY_IAT
  VirtualAddress : 0x16220
  Size : 0x1e0
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
  VirtualAddress : 0x0
  Size : 0xffff
[0x1400013f0]>

มาดูคำสั่ง iS เพื่อให้ rizin ช่วยแจกแจง section ของ reverse1.exe กัน

[0x1400013f0]> iS
paddr      size   vaddr       vsize   align perm name            type flags                                
-----------------------------------------------------------------------------------------------------------
0x00000600 0xfa00 0x140001000 0x10000 0x0   -r-x .text                CNT_CODE,CNT_INITIALIZED_DATA
0x00010000 0x400  0x140011000 0x1000  0x0   -rw- .data                CNT_INITIALIZED_DATA
0x00010400 0xe00  0x140012000 0x1000  0x0   -r-- .rdata               CNT_INITIALIZED_DATA
0x00011200 0x600  0x140013000 0x1000  0x0   -r-- .pdata               CNT_INITIALIZED_DATA
0x00011800 0x600  0x140014000 0x1000  0x0   -r-- .xdata               CNT_INITIALIZED_DATA
0x00000000 0x0    0x140015000 0x1000  0x0   -rw- .bss                 CNT_UNINITIALIZED_DATA
0x00011e00 0xa00  0x140016000 0x1000  0x0   -rw- .idata               CNT_INITIALIZED_DATA
0x00012800 0x200  0x140017000 0x1000  0x0   -rw- .CRT                 CNT_INITIALIZED_DATA
0x00012a00 0x200  0x140018000 0x1000  0x0   -rw- .tls                 CNT_INITIALIZED_DATA
0x00012c00 0x200  0x140019000 0x1000  0x0   -r-- .reloc               CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00012e00 0x200  0x14001a000 0x1000  0x0   -r-- .debug_aranges       CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00013000 0x1200 0x14001b000 0x2000  0x0   -r-- .debug_info          CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00014200 0x200  0x14001d000 0x1000  0x0   -r-- .debug_abbrev        CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00014400 0x200  0x14001e000 0x1000  0x0   -r-- .debug_line          CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00014600 0x200  0x14001f000 0x1000  0x0   -r-- .debug_frame         CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00014800 0x200  0x140020000 0x1000  0x0   -r-- .debug_str           CNT_INITIALIZED_DATA,MEM_DISCARDABLE
0x00014a00 0x200  0x140021000 0x1000  0x0   -r-- .debug_line_str      CNT_INITIALIZED_DATA,MEM_DISCARDABLE
[0x1400013f0]>


ยังไม่ต้องสนใจอะไรทั้งหมด แต่ให้สนใจเฉพาะส่วนดังนี้ครับ
paddr ย่อมาจาก Physical Address
คือ ตำแหน่งในไฟล์จริงบน disk (offset) — กล่าวง่ายๆ คือ Byte offset จากต้นไฟล์ PE คือมองไปที่ไฟล์ดิบๆโดยไม่สนใจว่ามันจะถูกโหลดไปแรมตรงไหน เช่น 0x600 คือ byte ลำดับที่ 0x600 จากต้นไฟล์ PE
vaddr ย่อมาจาก Virtual Allocation
คือ ตำแหน่งที่ section จะถูกแมปไว้ใน memory (Virtual Memory) เมื่อตัว OS โหลดไฟล์ PE ขึ้นมารัน มีเพราะเราเปิดไฟล์ reverse1.exe ด้วย rizin แบบเปิดปกติ (เสมือนรันบน memory)


ตอนนี้เราพร้อมที่จะทำความเข้าใจคำถามของ Main content ของ blog นี้แล้วครับ ถ้าเลื่อนไปดูข้างบน คำถามคือ
แต่ละ Section ของไฟล์ reverse1.exe จะไปแปลงร่างเป็น Process ยังไง และ เพื่ออะไร
เรารู้หน้าที่ของแต่ละส่วนของ Header และ Section แล้ว ว่ามีไว้เพื่ออะไรจากตารางความหมายที่ผมเขียนไว้ข้างบน
และเรารู้แล้วว่ามี ImageBase ที่ตำแหน่ง 0x140000000
 ซึ่งเป็นค่าเริ่มต้น ImageBase ที่ถูกกำหนดไว้ อารมณ์แบบ มาตรฐานทั่วไปที่รู้กัน คือ :
    EXE บน x64: 0x140000000
    DLL บน x64: 0x180000000 หรือ แล้วแต่ compiler
    EXE บน x86: 0x00400000

ขยี้อีกครั้ง ถ้าแบบเร็วๆ ว่า rizin ช่วยเรายังไง
  • จอง Memory ตามขนาด ImageSize (หาเจอจากคำสั่ง iH)
  • ใช้ PointerToRawData + SizeOfRawData เพื่อดึงข้อมูลแต่ละ Section ออกจากไฟล์ (ใช้ paddr)
  • แมปข้อมูลเข้า memory ตรงตำแหน่ง VirtualAddress (ใช้ vaddr)
  • หลังจากนั้น ทุกการเข้าถึงโค้ดหรือข้อมูลภายใน process จะอ้างอิงจาก vaddr แทน

มีอีกคำนึงที่สำคัญมากคือคำว่า RVA ย่อมาจาก Relative Virtual Address ความหมายของคำนี้ก็คือ offset จาก ImageBase ถึงข้อมูลใดข้อมูลหนึ่ง
RVA = vaddr - ImageBase . ดังนั้นมันจึงมี rva ได้หลายค่า , ส่วนคำว่า offset คือ สิ่งที่เปลี่ยนแปลงไปจากเดิม

คำว่า "offset" ในที่นี้ ไม่ใช่ offset บนไฟล์ดิบ (raw) นะครับ — แต่หมายถึง ค่าที่เปลี่ยนจาก ImageBase ไปยังจุดหมายใน Virtual Address Space
เช่น ข้อมูลใน .text, .data, .rdata หรือ section อื่นๆ ล้วนมีค่า RVA เป็นของตัวเอง
    แล้ว RVA ของอะไรล่ะ? — ก็ขึ้นอยู่กับว่าเรากำลังพูดถึง section ไหน หรือข้อมูลอะไร เช่น RVA ของ EntryPoint, RVA ของฟังก์ชัน, RVA ของสตริง หรือ RVA ของ Import Table ก็ได้
    แล้วมันอยู่ตรงไหน? — ก็ต้อง “คำนวณจาก ImageBase” นั่นแหละ ด้วยสูตรข้างบน
RVA จึงเป็นแกนกลางสำคัญที่ทำให้ PE มีความยืดหยุ่นในการถูกโหลดไปที่ไหนก็ได้ใน memory

แล้วทำไมต้องมี RVA?

เพราะว่า : ไฟล์ PE ยังไม่รู้ล่วงหน้าว่าตัวเองจะถูกโหลดไปที่ไหน
สมมุติ EXE 2 ตัวมี ImageBase ชนกัน (เช่นทั้งคู่ใช้ 0x140000000) แล้วจะรันพร้อมกันไม่ได้ — Windows จะ "Relocate" ไฟล์หนึ่งไปที่ address อื่น
ดังนั้น: เวลาเขียน pointer ลงใน PE ต้องเขียนเป็น RVA เพราะมัน “ยืดหยุ่น”

ในไฟล์ของผม reverse1.exe "ถ้าไฟล์ pe นี้จะถูกโหลดเข้า memory มันจะต้อง copy .text section ที่เป็น machine code ไปใน memory ในตำแหน่งที่ถูกต้อง"
เพื่อให้เข้าเส้น เราก็จะมาคำนวณจริงจาก reverse1.exe ของผมกัน:
    ImageBase = 0x140000000
    .text section vaddr = 0x140001000 (ผมไฮไลต์สีเหลืองไว้ข้างบนแล้ว จาก output ของคำสั่ง iS)
    ดังนั้น:
RVA ของ .text section = 0x140001000 - 0x140000000 = 0x1000

0x006-02-after-execute-pe-file-ImageBase-Loaded-RVA

ลูกศรสุดท้าย = จุดเริ่มต้นของโปรแกรม เรียกว่า EntryPoint
เรายืนยัน address of EntryPoint ได้จากคำสั่ง iH ก่อนเลยครับ
หรืออีกมุมมองหนึ่ง ถ้าเปิดไฟล์ใน rizin แบบปกติ (ไม่มี -n หรือ -nn) , rizin ก็จะพาเรามายืนตรง entry point ให้เราเลยใน shell ครับ (กรอบแดงแรก) แต่ใน shell มันมี 0x14000 ข้างหน้าใช่มั้ยครับ ?

เพราะมันมาจากการ "เปลี่ยนแปลงไปจาก ImageBase" นั่นเอง (OffSet)
OffSet ! OffSet ! OffSet !

rizin จะนำเรายืนอยู่ที่ address ของ entry point ทันที (ใน shell หรือใน Cutter ก็จะชี้ตรงนั้นเลย)
แล้วทำไม address ที่เราเห็นมันไม่ใช่ 0x13f0 ตรงๆ แต่กลายเป็น 0x1400013f0 ละ ??
เพราะ rizin แสดง “Virtual Address” เต็มๆ ซึ่งมาจาก = ImageBase + RVA นั่นเองครับ
และนั่นคือ address แรกที่ CPU จะเริ่ม execute instruction ตอนโปรแกรม run จริง

บวก / ลบ กัน เปลี่ยนแปลงไป ภาษา Reverser เค้าเรียกว่า OffSet !  ครับ

0x006-03-after-execute-pe-file-iH-comand

ต้องขยี้เรื่อง RVA , OffSet , ให้แม่นๆ นะครับ
เจอกัน Blog ต่อไปครับ