Phase 13: Tools & Protocols

Phân tích chương trình: Một người đọc ELF từ đầu

Một chương trình được biên soạn là một tập hợp các bảng chỉ vào nhau. Một trình đọc mà bạn đã viết cho ra hiệu mà bạn có thể kiểm tra byte byte.

Type: Build

Languages: Python (stdlib), C (fixture only)

Prerequisites: Phase 0 · 01 (Dev Environment) with clang installed, Python 3.10 or later, hexadecimal arithmetic

Time: ~120 minutes

Mục tiêu học tập

  • Cụ thể quy tắc cho phép cho mỗi mục tiêu trong đường đua này và bốn giới hạn của 17 U.S.C. 1201 ((f).
  • Đọc tiêu đề ELF, bảng tiêu đề phần và cả hai bảng chuỗi với struct.unpack- Tôi không biết.
  • Tự giải mã .symtabCác mục và .relaghi chép với tên từ gABI và x86-64 psABI.
  • Trả lại các byte chính xác của một hàm có tên và lập bản đồ mỗi tệp được chuyển đổi sang cấu trúc sở hữu nó.
  • Đọc tiêu đề chương trình và lập bản đồ địa chỉ ảo cho một tệp được chuyển đổi qua PT_LOAD- Tôi không biết.
  • Đọc lệnh tải của một vật thể Mach-O và đặt tên cho ý tưởng ELF mà mỗi vật thể phù hợp.

Trước khi bắt đầu: Giấy phép và Luật pháp

Mỗi mục tiêu trong bài học 32 đến 36 là một chương trình mà bạn đã viết và biên soạn.code/fixtures/ledger.cBạn sở hữu nguồn, lệnh biên dịch và các tệp đối tượng nó tạo ra. Không có giấy phép đọc, không có bảo vệ để thông qua, và không có bên thứ ba mà bạn mở mã.

Quy tắc tồn tại bởi vì luật pháp coi việc tháo rời như là sao chép. Tại Sega Enterprises Ltd. v. Accolade, Inc., 977 F.2d 1510 (9th Cir. 1992), tòa án đã phán quyết rằng các bản sao trung gian được thực hiện trong quá trình tháo gỡ là bản sao theo Luật Bản quyền. Sau đó, tòa tuyên bố rằng "nếu việc tháo gỡ là cách duy nhất để có được quyền truy cập vào những ý tưởng và các yếu tố chức năng được thể hiện trong một chương trình máy tính có bản quyền và nếu có lý do hợp pháp để tìm kiếm quyền truy cập như vậy, việc tháo gỡ là một cách sử dụng công bằng tác tác phẩm có bản quyền, như một vấn đề pháp lý".

Trường hợp thứ hai là Sony Computer Entertainment, Inc. v. Connectix Corp., 203 F.3d 596 (9th Cir. 2000). Connectix đã sao chép PlayStation BIOS trong khi nó xây dựng một trình mô phỏng, và tòa án cho rằng các bản sao đó được sử dụng công bằng. Cả hai trường hợp đều bảo vệ nghiên cứu.

Hai quy định đặt ra giới hạn. 17 U.S.C. 1201 ((f) cho phép một người "đã có được pháp lý quyền sử dụng bản sao của một chương trình máy tính" đi vòng qua kiểm soát truy cập "nhằm mục đích duy nhất xác định và phân tích những yếu tố của chương trình cần thiết để đạt được khả năng tương tác của một chương trình máy tính được tạo ra độc lập với các chương trình khác". Bốn ngăn giới hạn: một bản sao hợp pháp, mục đích tương tác, các yếu tố chưa sẵn sàng sẵn sàng và phân tích không vi phạm. Mục 1201 ((f) là ngoại lệ chỉ đối với quy tắc chống trốn tránh. Câu hỏi sao chép vẫn được sử dụng công bằng.

Trong Liên minh châu Âu, Điều 5 của Đạo luật 2009/24/EC (3) bao gồm nghiên cứu. Một người có quyền sử dụng bản sao có thể "xem, nghiên cứu hoặc kiểm tra hoạt động của chương trình để xác định những ý tưởng và nguyên tắc cơ bản của bất kỳ yếu tố nào của chương trình".

Điều 6 cho phép phân tích mà không có sự cho phép của người có quyền khi "có thể thu được thông tin cần thiết để đạt được khả năng tương tác của một chương trình máy tính được tạo ra độc lập". Điều kiện phù hợp với 1201 ((f). Một người có giấy phép thực hiện các hành động, thông tin chưa sẵn sàng sẵn sàng, và các hành động vẫn nằm trong các phần cần thiết. Điều 6 ((2) cấm sử dụng kết quả cho bất kỳ mục tiêu nào khác hoặc cho một chương trình tương tự về mặt đáng kể. Điều 8 làm cho các điều khoản hợp đồng trái với Điều 6 hoặc Điều 5 (3) vô hiệu và vô hiệu.

SourceWhat it allowsConditions
17 U.S.C. 1201(f)Circumventing an access control to analyze a programLawful copy, interoperability purpose, information not readily available, no infringement
Directive 2009/24/EC Art. 5(3)Observing, studying, and testing a running programA right to use the copy, acts within normal loading and running
Directive 2009/24/EC Art. 6DecompilationLicensee, interoperability purpose, information not readily available, confined to the needed parts
Sega v. AccoladeDisassembly as fair useThe only way to reach the ideas and functional elements, a legitimate reason
Sony v. ConnectixIntermediate copies during reverse engineeringA non-infringing final product

Những ghi chú này đến từ các quy định và ý kiến, và chúng không phải là tư vấn pháp lý. Khi một mục tiêu trong tương lai không phải của bạn, hãy xin giấy phép bằng văn bản trước khi bạn mở hồ sơ và giữ giấy phép với bằng chứng của bạn. Phần 2.1 của hướng dẫn kỹ thuật ngược 101 cung cấp đầy đủ các trích dẫn và các trường hợp của Tòa án Tối cao Liên bang châu Âu.

Vấn đề

Anh đã biên soạnledger.cvà chạyllvm-objdump -ttrên đối tượng.percent_changegiá trị 0000000000000000với kích thước 0000000000000010. 16 byte nào trong file 1.768-byte là hàm đó?R_X86_64_PC32 .rodata+0x10trong sự bù đắp0x36, và bốn byte ở vị trí đó trong tệp là không.0x10khi bàn bắt đầu ở 0x14- Không .

rea trả lời các câu hỏi về bố cục với inspect_binary_layout, và công cụ đó chỉ tồn tại trên Linux x64 với pwntools 4.15.0 phía sau REA_PWNTOOLS_PYTHONTrên máy Mac này nó không có.address_to_file_offsetcần một phiên bản Ghidra và một bản sao đầy đủ của các byte tập tin gốc với một SHA-256 phù hợp. inspect_artifactNó không bao giờ mở các bảng bên trong một tệp đối tượng. Vì vậy bạn tự viết cho người đọc trong khoảng 950 dòng Python với struct.unpackCác bài kiểm tra so sánh từng bảng vớillvm-objdumpvăn bản được ghi lại một lần trong các thiết bị.

Các tập tin là những bảng chỉ ra các bảng

Hệ thống V gABI, chương 4, Hình 4-1, vẽ hai quan điểm của một tập tin. Quan điểm liên kết có tiêu đề ELF, bảng tiêu đề chương trình tùy chọn, các phần và bảng tiêu đề phần yêu cầu. Quá trình xem thực hiện có tiêu đề ELF, bảng tiêu đề chương trình yêu cầu, các phân đoạn và bảng tiêu đề phần tùy chọn. GABI thêm một câu điều khiển toàn bộ người đọc: "Chỉ đầu ELF có vị trí cố định trong tệp". Mỗi cấu trúc khác được tìm thấy bởi một sự thay thế được lưu trữ trong một cấu trúc khác.

GABI cũng sửa các loại scalar và nói rằng "ELF không sử dụng các bit field". Elf64_Addrvà Elf64_Offlà 8 byte, Elf64_Halflà 2, Elf64_Wordlà 4, vàElf64_XwordCác x86-64 psABI, phần 4. 1.1, yêu cầu ELFCLASS64- ELFDATA2LSB- e_machine EM_X86_64(62), vàe_flags0. Tiểu động nhỏ với chiều rộng cố định trung bình struct.unpackvới một <Prefix đọc mọi bản ghi trong một cuộc gọi.

64 byte đầu tiên của ledger-x86_64-O1.o- Có thể là:

text00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0100 3e00 0100 0000 0000 0000 0000 0000  ..>.............
00000020: 0000 0000 0000 0000 2804 0000 0000 0000  ........(.......
00000030: 0000 0000 4000 0000 0000 4000 0b00 0100  ....@.....@.....

Đọc chúng so với trang "ELF Header" của GABI. e_identlà 16 byte 7f 45 4c 46(đồ phép), lớp 2 (64-bit), dữ liệu 1 (chúng nhỏ), phiên bản 1, OS ABI 0 (System V), và đệm.

Vậy thìe_type 0x0001là ET_REL, một vật thể có thể di chuyển.e_machine 0x003elà 62, EM_X86_64- e_entrylà không vì một đối tượng không có điểm nhập. e_phofflà không vì một đối tượng không có tiêu đề chương trình. e_shofflà 0x428, file offset của bảng tiêu đề phần.e_ehsize 0x40- e_phentsize0, e_phnum0, e_shentsize 0x40- e_shnum 0x0b(11 tiêu đề), vàe_shstrndx 0x0001- Tôi không biết.

Người đọc biến dòng đó thành một cuộc gọi:

pythonfields = struct.unpack_from("<HHIQQQIHHHHHH", data, 16)

Dòng định dạng là con số gABI đọc từ trái sang phải: hai nửa từ, một từ, ba giá trị 8byte, một từ và sáu nửa từ. Người đọc từ chối một tệp có phép thuật, lớp hoặc thứ tự byte khác nhau, và nó từ chối e_shentsizehoặc e_phentsizegiá trị không phải là 64 và 56. Một chiều rộng sai sẽ làm cho mỗi lần bù đắp sau đó sai, vì vậy kiểm tra nằm ở phía trên.

Các tiêu đề phần và hai bảng dây

Mỗi tiêu đề phần là một Elf64_Shdr64 byte (gABI Hình 4-8): sh_name- sh_type- sh_flags- sh_addr- sh_offset- sh_size- sh_link- sh_info- sh_addralign, vàsh_entsize. Dòng định dạng là <IIQQQQIIQQ- sh_namelà một sự bù đắp vào phần có chỉ số là e_shstrndx, và tên kết thúc ở 0 byte đầu tiên.e_shstrndxlà 1, và clang đặt tên cho phần đó .strtab, vì vậy một bảng phục vụ cả tên của phần và tên của biểu tượng.

Người đọc in 11 tiêu đề của đối tượng -O1:

textIdx Name             Type         Flags   Offset     Size       Addr Link Info Align
  0                  NULL                    0x0      0x0        0x0    0    0     0
  1 .strtab          STRTAB                0x368     0xbe        0x0    0    0     1
  2 .text            PROGBITS     AX        0x40     0x9f        0x0    0    0    16
  3 .rela.text       RELA         I        0x2a8     0x48        0x0   10    2     8
  4 .rodata          PROGBITS     A         0xe0     0x50        0x0    0    0     8
  5 .rela.rodata     RELA         I        0x2f0     0x78        0x0   10    4     8
  6 .rodata.str1.1   PROGBITS     AMS      0x130      0xa        0x0    0    0     1
  7 .comment         PROGBITS     MS       0x13a     0x2f        0x0    0    0     1
  8 .note.GNU-stack  PROGBITS              0x169      0x0        0x0    0    0     1
  9 .llvm_addrsig    LLVM_ADDRSIG E        0x368      0x0        0x0   10    0     1
 10 .symtab          SYMTAB                0x170    0x138        0x0    1    9     8

Chỉ số 0 là tiêu đề null được lưu trữ. .textchứa 159 byte mã tại file offset 0x40, ngay sau tiêu đề, với cờ A(được phân bổ trong bộ nhớ) vàX(có thể thực hiện). .rodatachứa 80 byte dữ liệu chỉ đọc. .rodata.str1.1giữ 5 chuỗi nhãn F\0D\0C\0B\0A\0, 10 byte, với cờ M(tích hợp) và S(câu)

.commentgiữ dấu biên tập viên 47-byte \0Apple clang version 17.0.0 (clang-1700.6.3.2)\0- .llvm_addrsigcó kiểu 0x6fff4c03, trong phạm vi cụ thể của hệ điều hành SHT_LOOSđếnSHT_HIOSrằng các dự trữ của gABI, và LLVM ELF.hTên của nó.Elà SHF_EXCLUDE, nên một liên kết sẽ làm rơi nó.

  • sh_linkvà sh_infoCột là các chỉ số giữa các bảng. .rela.text- Có sh_info2 và sh_link10: nó váy phần 2 và lấy các biểu tượng của nó từ phần 10. .symtab- Có sh_link1, bảng chuỗi cho tên biểu tượng, và sh_info9, chỉ số của biểu tượng không địa phương đầu tiên. sh_addrlà 0 trong mỗi hàng vì một đối tượng chưa có địa chỉ.

Hai quy tắc gABI cho phép người đọc vẽ một bản đồ đầy đủ của tệp. Các phần "có liên kết và không chồng chéo", và tệp "có thể chứa không gian không hoạt động". Người đọc sắp xếp mọi cấu trúc theo sự bù đắp và lấp đầy các lỗ:

text     0x0 +64    header   ELF header
    0x40 +159   section  .text
    0xdf +1     gap      inactive space
    0xe0 +80    section  .rodata
   0x130 +10    section  .rodata.str1.1
   0x13a +47    section  .comment
   0x169 +7     gap      inactive space
   0x170 +312   section  .symtab
   0x2a8 +72    section  .rela.text
   0x2f0 +120   section  .rela.rodata
   0x368 +190   section  .strtab
   0x426 +2     gap      inactive space
   0x428 +704   table    section header table

Ba khoảng cách tổng cộng là 10 byte, và mỗi một là sự sắp xếp. .rodata- Có sh_addralign8, vì vậy nó bắt đầu từ 0xe0thay vì0xdf- .symtabcó đường thẳng 8, vì vậy bảy byte không được sử dụng sau đó .commentBảng tiêu đề phần tự được sắp xếp với 8.

Các phần kiểu SHT_NOBITSkhông chiếm được các byte tập tin, vì vậy bản đồ bỏ qua chúng. Không có gì trong đối tượng này có loại đó..bsstrong một chương trình liên kết làm, và một độc giả tin tưởng sh_sizeCó một cái tên khác.

Các biểu tượng: Kết nối, loại, phần, giá trị, kích thước

Một Elf64_Symlà 24 byte (gABI Hình 4-18): st_name- st_info- st_other- st_shndx- st_value- st_size, định dạng <IBBHQQ- st_infogói hai trường: liên kết trong bốn bit cao và loại trong bốn thấp.STB_LOCAL0, STB_GLOBAL1, STB_WEAK2, và các loại STT_NOTYPE0, STT_OBJECT1, STT_FUNC2, STT_SECTION3, STT_FILE4. 2 bit thấp của st_othergiữ tầm nhìn,STV_DEFAULT0 ở đây.

textIdx            Value   Size Bind   Type    Section          Name
  0              0x0      0 LOCAL  NOTYPE  *UND*
  1              0x0      0 LOCAL  FILE    *ABS*            ledger.c
  2              0x0     20 LOCAL  OBJECT  .rodata          labels.rel
  3              0x0      0 LOCAL  SECTION .rodata          .rodata
  4              0x2      2 LOCAL  OBJECT  .rodata.str1.1   .L.str.1
  5              0x4      2 LOCAL  OBJECT  .rodata.str1.1   .L.str.2
  6              0x6      2 LOCAL  OBJECT  .rodata.str1.1   .L.str.3
  7              0x8      2 LOCAL  OBJECT  .rodata.str1.1   .L.str.4
  8              0x0      0 LOCAL  SECTION .rodata.str1.1   .rodata.str1.1
  9              0x0     16 GLOBAL FUNC    .text            percent_change
 10             0x10     46 GLOBAL FUNC    .text            grade
 11             0x40     31 GLOBAL FUNC    .text            total
 12             0x60     63 GLOBAL FUNC    .text            grade_label

Chỉ số 0 là biểu tượng không xác định được lưu trữ. st_shndxtên phần xác định biểu tượng, với ba giá trị được lưu giữ: SHN_UNDEF0, SHN_ABS 0xfff1cho tên tệp, và SHN_COMMON 0xfff2Các biểu tượng của phần có tên trống, vì vậy llvm-objdumpGiấy ghi tựa đề của phần cho họ, và người đọc cũng làm như vậy. Quy tắc gABI rằng "tất cả các biểu tượng có liên kết STB_LOCAL trước các biểu tượng yếu và toàn cầu" xuất hiện như chín hàng đầu, và .symtabghi lại 9 nămsh_infoVì lý do đó.

Trong một vật thể có thể di chuyểnst_valuelà một sự thay thế bên trong phần của biểu tượng, bởi vì sh_addrlà không. Các file offset của một hàm do đó là sh_offset + st_value - sh_addr, và chiều dài của nó là st_size- Có thể là:

pythonsection = elf.sections[symbol.st_shndx]
file_offset = section.sh_offset + (symbol.st_value - section.sh_addr)
code = elf.data[file_offset : file_offset + symbol.st_size]

Vì percent_changeĐó là 0x40 + 0, và 16 byte là 85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3- Kích thước cũng quan trọng như sự khởi đầu.gradekết thúc tại .textbồi thường0x3eHai byte.66 90những người theo là một nopbộ sưu tập đã thêm vào để totalbắt đầu từ 0x402 byte đó không thuộc về bất kỳ biểu tượng nào.st_sizenhư là điều kiện dừng cho một máy tháo rời, và bài học 36 che giấu chúng trước khi nó so sánh một xây dựng lại.

Đời: Những gì Zero Byte có nghĩa là

Một Elf64_Relalà 24 byte (gABI Hình 4-23): r_offset- r_info, và một chữ kýr_addend, định dạng <QQq- GABI chia rẽ.r_infovới hai macro: ELF64_R_SYM(i)là i >> 32và ELF64_R_TYPE(i)là i & 0xffffffffTrong một tệp có thể chuyểnr_offsetlà một sự thay thế bên trong phần được đặt tên bởi bảng sh_infoTên và công thức loại được lấy từ x86-64 psABI, Bảng 4.9.Slà giá trị biểu tượng, Abổ sung, Pnơi được dán, và Lmục PLT. R_X86_64_64là S + A- R_X86_64_PC32là S + A - P, vàR_X86_64_PLT32là L + A - P- Tôi không biết.

Chuyện -O1 có tám ghi chép:

text  .rela.text       0x36 R_X86_64_PC32          .rodata+0x10
  .rela.text       0x84 R_X86_64_PC32          .rodata+0x24
  .rela.text       0x93 R_X86_64_PC32          .rodata-0x4
  .rela.rodata      0x0 R_X86_64_PC32          .rodata.str1.1
  .rela.rodata      0x4 R_X86_64_PC32          .L.str.1+0x4
  .rela.rodata      0x8 R_X86_64_PC32          .L.str.2+0x8
  .rela.rodata      0xc R_X86_64_PC32          .L.str.3+0xc
  .rela.rodata     0x10 R_X86_64_PC32          .L.str.4+0x10

Hãy tách ra cái đầu tiên.0x36nằm bên tronggrade, bao gồm:0x10đến0x3e- Các hướng dẫn tại 0x33là 48 8d 0d 00 00 00 00, một leacủa một địa chỉ liên quan đến RIP, và chuyển đổi bốn byte của nó nằm ở 0x36. Bộ xử lý thêm sự di chuyển vào địa chỉ của hướng dẫn tiếp theo, 0x3a- Tôi không biết.

Người biên tập muốn kết quả là .rodata + 0x14, nơi một bảng 5 mục có giá trị 32 bit 1, 2, 3, 4, 4Vì vậy, giá trị được lưu trữ phải là(.rodata + 0x14) - 0x3a- Công thứcS + A - Pvới P = 0x36cho .rodata + A - 0x36Đặt hai số bằng nhau choA = 0x14 - 4 = 0x10. Addend mang theo mục tiêu offset trừ bốn byte giữa việc di chuyển và cuối hướng dẫn.

Đây là bảng cách clang biên soạn các switchtrong grade-O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1 -O1score / 10với một nhân bằng 0x66666667và hai thay đổi, trừ 6, so sánh với 4, và chỉ mục bảng. đối tượng -O1 ELF không giữ bất kỳ biểu tượng nào cho bảng..rodatacộng thêm một phần bổ sung, và chỉ có bộ arm64 của cùng một nguồn đặt tên cho nó l_switch.table.grade. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .0x84trong grade_label, điểm trong bảng thứ hai của giá trị 8 byte tại .rodata + 0x28Thứ ba, ở0x93, chỉ ra tại .rodata + 0x0, sự khởi đầu của labels.rel- Tôi không biết.

5 người.rela.rodataghi chép điền labels.relMỗi mục đều được đặt ở chỗ trượt4ivà nhận được thêm 4i, vậy S + A - Pgiảm xuống địa chỉ của chuỗi trừ đáy của bảng. grade_labelđọc một mục với movslq, thêm cơ sở bảng trở lại, và có chỉ số..rodata.str1.1với phụ lục 0 vì chuỗi Flà byte đầu tiên của phần đó..L.str.1đến.L.str.4, các nhãn địa phương của trình biên dịch cho D- C- B, vàA- Tôi không biết.

Nguồn tương tự biên soạn thành ba bảng khác nhau ở ba mức tối ưu hóa. Người đọc cho thấy sự khác biệt mà không cần một bộ sáp:

BuildWhere labels livesHow grade selectsHow grade_label reaches grade
-O0.data, five 8-byte pointers, five R_X86_64_64 recordsA jump table in .rodata: five R_X86_64_PC32 records pointing into .textA call with R_X86_64_PLT32 grade-0x4
-O1.rodata as labels.rel, five 32-bit relative entriesA value table at .rodata+0x14, one R_X86_64_PC32Inlined, no call
-O2Same as -O1Same as -O1Inlined, total grows to 140 bytes

Ở -0 switchlà một bàn nhảy thực sự mà năm mục chỉ về phía .text+0x91- +0x8c- +0x87- +0x82, và+0x86- Lệnh gọi cho grademang theo cùng một -0x4bổ sung vì cùng lý do như leaỞ -O1, người tối ưu hóa thấy rằng mỗi trường hợp trả lại một liên tục và thay thế các bước nhảy bằng một bảng kết quả.

Các tiêu đề chương trình: Quan điểm của người tải

Một đối tượng có thể di chuyển không có tiêu đề chương trình, vì vậy e_phnumlà 0. Một thể hiện hoặc một đối tượng được chia sẻ có chúng, và bộ tải không đọc gì khác.Elf64_Phdrlà 56 byte (gABI Hình 5-1): p_type- p_flags- p_offset- p_vaddr- p_paddr- p_filesz- p_memsz- p_align, định dạng <IIQQQQQQGABI đã đưa ra 3 quy tắc choPT_LOADNhập vào. Chúng xuất hiện trong tăng lên.p_vaddrlệnh, và một p_memszlớn hơn p_filesznghĩa là không đầy.p_vaddrtương ứng với p_offsetmodulo p_align- Tôi không biết.

Mỗi mục tiêu trong bài hát này là một chương trình bạn đã xây dựng, và không có liên kết trên Mac này tạo ra một Linux thực thi từ ledger.oVậy là thiết bịtiny-x86_64đến từ code/fixtures/build_tiny_elf.py, viết 736 byte với struct.pack. tệp chứa một đầu ELF, hai đầu chương trình, một 36-byte _start, một 3 byte .rodata, một bảng biểu tượng, và sáu tiêu đề phần. _startviết ok\ntrên Linux x86-64 nó chạy. đây là một phần mềm cho con đường tiêu đề chương trình.

textProgram headers:
  PT_LOAD          r-x offset=0x0 vaddr=0x400000 filesz=0x2e0 memsz=0x2e0 align=0x1000
  PT_GNU_STACK     rw- offset=0x0 vaddr=0x0 filesz=0x0 memsz=0x0 align=0x10

entry 0x4000b0 is file offset 0xb0 through PT_LOAD

Đơn vịPT_LOADbản đồ toàn bộ tập tin 736 byte tại 0x400000Người đọc sẽ lập bản đồ địa chỉ của một tập tin bằng cách tìm ra PT_LOADcó chứa nó và tính toán p_offset + (address - p_vaddr). Đối với điểm nhập cảnh là 0 + (0x4000b0 - 0x400000) = 0xb0, sự khởi đầu của .text- Tôi không biết.

Đây là tính toán đằng sau rea.address_to_file_offsetĐối với một mục tiêu Ghidra, công cụ đó sẽ quay lại.{address, file_offset}Người cung cấp Hopper cũng quay lạiprovider_file_offsetvà image_base_file_offsetCông cụ cần một bản sao nguyên bản đầy đủ của tệp, và SHA-256 của bản sao đó phải phù hợp với mục tiêu.inspect_binary_layoutthêm một dòng để giữ: "Một địa chỉ liên kết không phải là địa chỉ thời gian chạy". Một địa chỉ có thể thực hiện độc lập vị trí tải tại một cơ sở mà kernel chọn, và file offset là phối hợp ổn định duy nhất.

Các tiêu đề chương trình cũng giải thích tại sao một trình thực bị tẩy ra vẫn chạy. Một linker có thể xóa bảng tiêu đề phần hoàn toàn, và e_shoffMột công cụ đi qua các phần sau đó không thấy gì.

Những ý tưởng tương tự trong Mach-O

Apple không công bố thông số kỹ thuật văn bản cho Mach-O. Các tiêu đề trong nguồn XNU là thông số kỹ thuật: EXTERNAL_HEADERS/mach-o/loader.h- nlist.h, vàreloc.h- Đồ đạcledger-arm64-O1.olà giống nhau ledger.cđược xây dựng với clang -target arm64-apple-macos -O1 -fno-asynchronous-unwind-tables -c- Tôi không biết.

A mach_header_64là 32 byte: magic- cputype- cpusubtype- filetype- ncmds- sizeofcmds- flags- reserved- Sự kỳ diệu là0xfeedfacf- MH_MAGIC_64- cputypelà 0x0100000c- CPU_TYPE_ARM64- filetypelà 1, MH_OBJECT- ncmdslà 5 và sizeofcmdslà 616: các lệnh tải chiếm 616 byte sau tiêu đề, và mỗi lệnh bắt đầu với {cmd, cmdsize}. . cờ0x2000là MH_SUBSECTIONS_VIA_SYMBOLS- Tôi không biết.

textLoad commands:
  0 LC_SEGMENT_64                cmdsize=472 at 0x20
  1 LC_BUILD_VERSION             cmdsize=24 at 0x1f8
  2 LC_LINKER_OPTIMIZATION_HINT  cmdsize=16 at 0x210
  3 LC_SYMTAB                    cmdsize=24 at 0x220
  4 LC_DYSYMTAB                  cmdsize=80 at 0x238

LC_SEGMENT_64(0x19) là câu trả lời Mach-O cho cả hai bảng ELF.segment_command_64- Có segname- vmaddr- vmsize- fileoff- filesize- maxprot- initprot- nsects, vàflags- Vậy thìnsectshồ sơ về loại section_64, 80 byte mỗi, theo cùng một lệnh.loader.hbình luận về MH_OBJECTnói các phần sống "trong một phân đoạn không có tên mà không có bộ phận đệm".fileoff648, filesize456, và 5 phần.

text  Idx Section                    Addr   Size Offset Reloff Nreloc Type
    1 __TEXT,__text               0x0   0xd0    648   1104      6 S_REGULAR
    2 __DATA,__const             0xd0   0x28    856   1152      5 S_REGULAR
    3 __TEXT,__cstring           0xf8    0xa    896      0      0 S_CSTRING_LITERALS
    4 __TEXT,__const            0x108   0x40    912      0      0 S_REGULAR
    5 __LD,__compact_unwind     0x148   0x80    976   1192      4 S_REGULAR

Mỗi phần đặt tên cho phân khúc chủ của nó trong segname, vì vậy hai phần gọi là__constlà khác nhau bởi vì một là __DATA,__constVà cái kia__TEXT,__constCác chuyển nhượng là mỗi phần qua reloffvà nreloc, nơi ELF có một .rela.Xphần cho mỗi mục tiêu.

Mỗi ngườirelocation_infolà 8 byte.r_addressMột trường 32 bit tiếp theo, đóng gói như r_symbolnum(24 bit), r_pcrel(1) r_length(2) r_extern(1) vàr_type(4) 6__textCác hồ sơ là ba cặp của ARM64_RELOC_PAGE21và ARM64_RELOC_PAGEOFF12, một cặp mỗi bảng tham chiếu.adrp+addlàm gì một x86-64 leaCó, vì vậy mỗi tham chiếu cần hai hồ sơ.__DATA,__constCác hồ sơ là ARM64_RELOC_UNSIGNEDvới r_length3, vì vậy mỗi một patches một chỉ số tuyệt đối 8 byte.

Ở -O1 , bộ máy arm64 được giữ .labelsnhư các chỉ số đơn giản nơi x86-64 xây dựng tạo ra một bảng tương đối.

LC_SYMTAB(0x2) giữ symoff1240, nsyms17, stroff1512, strsize176, và 1512 cộng với 176 là 1688, kích thước tập tin.nlist_64là 16 byte: n_strx- n_type- n_sect- n_desc- n_value- n_typelà một chút lĩnh vực. N_TYPE(đặt mặt nạ 0x0e) bằng với N_SECT(0xe) là được định nghĩa trong phần có đơn vị dựa trên 1 là n_sect- N_EXT(0x01) đánh dấu một biểu tượng bên ngoài.

nlist_64không có kích thước trường. để có được 24 byte của_percent_changengười đọc lấy địa chỉ biểu tượng tiếp theo trong cùng phần, _grade0x18. Các biểu tượng C mang một dấu đầu _ở đây, vì vậy chức năng là _percent_change- Tôi không biết.

LC_DYSYMTAB(0xb) ghi lại cùng một quy tắc địa phương-đầu tiên như ELF sh_infoilocalsym0, nlocalsym13, iextdefsym13, nextdefsym4. LC_BUILD_VERSION(0x32) ghi chép platform1 (macOS), minos26.0 và sdk26,2 như đóng gói XXXX.YY.ZZlệnh đó là lý do tại sao bộ cố định arm64 được cam kết như là byte. Một build trên một Mac với một SDK khác tạo ra một tập tin khác.

Người đọc compare_formatsđặt hai tập tin bên cạnh nhau:

IdeaELF, ledger-x86_64-O1.oMach-O, ledger-arm64-O1.o
File identitye_ident 7f 45 4c 46, class 64, data LSBmagic 0xfeedfacf
Machinee_machine 62, EM_X86_64cputype 0x100000c, CPU_TYPE_ARM64
File kinde_type 1, ET_RELfiletype 1, MH_OBJECT
Where the tables aree_shoff 0x428, 11 section headers5 load commands, 616 bytes after the header
The code.text at 0x40, size 0x9f, flags AX__TEXT,__text at 648, size 0xd0, in the unnamed segment
Symbol names.symtab (section 10), names in .strtab through sh_link 1LC_SYMTAB symoff 1240, nsyms 17, names at stroff 1512
Symbol sizest_size: percent_change is 16 bytesNo field: next symbol minus this one, 24 bytes
Relocations.rela.text, 3 records, R_X86_64_PC32__text reloff 1104, 6 records, PAGE21 and PAGEOFF12
Function bytespercent_change: 16 bytes at 0x40_percent_change: 24 bytes at 0x288

rea đọc Mach-O qua inspect_macho, chạyotool -h -l- nm -gjU- dyld_info -imports -exports, vàdwarfdump --uuidqua xcrunNgười đọc của bạn không cần bất kỳ thứ gì, và nó đọc giống nhau.symoffvà stroffĐó làotool -l- Nhìn ra.open_binarycông cụ của rea phát hiện ELF, MZ, PE, và mỏng hoặc phổ biến Mach-O từ các byte ma thuật.e_machinelà 20 byte đầu tiên của những gì bạn xây dựng ở đây. cho một tệp không có tiêu đề, bạn choformat: raw-binaryNhà cung cấp Ghidra từ chối một binary phổ quát và yêu cầu một mảnh mỏng.0xcafebabe- Tôi không biết.

Hãy xây dựng nó

Động trình đọc thư viện tiêu chuẩn:

bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch
python3 code/main.py

Dòng demo là ledger-x86_64-O1.ovà in tiêu đề, phần, biểu tượng, di chuyển, và file offset map. Nó sau đó in bốn hàm byte chuỗi, đọc ledger-arm64-O1.o, và in bảng so sánh. Nó kết thúc với tiêu đề chương trình của tiny-x86_64- Những dòng dự kiến:

textpercent_change: 16 bytes at file offset 0x40: 85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3
grade: 46 bytes at file offset 0x50: 48 63 c7 48 69 c0 67 66 66 66 ...
entry 0x4000b0 is file offset 0xb0 through PT_LOAD

Gửi một đường dẫn để đọc một tập tin: python3 code/main.py code/fixtures/ledger-x86_64-O0.oNgười đọc chọn ELF hoặc Mach-O từ bốn byte đầu tiên.

Đọc code/main.pytheo thứ tự này:

  1. ElfFile._read_header()kiểm tra phép thuật, lớp, lệnh byte, và kích thước nhập, sau đó mở đầu.
  2. ElfFile._read_sections()và read_cstring()đọc bảng tiêu đề phần và giải quyết tên thông qua e_shstrndx- Tôi không biết.
  3. ElfFile._read_symbols()chia rẽst_info, giải quyết tên thông qua sh_link, và giữ cho các chỉ số phần được đặt lại được đọc.
  4. ElfFile._read_relocations()đi bộ mỗi lần SHT_RELAphần, chia r_info, và các loại tên trong bảng 4.9.
  5. ElfFile.function_span()và function_bytes()biến một biểu tượng thành một phạm vi tập tin và các byte trong đó.
  6. ElfFile.file_offset_map()phân loại mọi cấu trúc theo sự bù đắp và lấp đầy không gian không hoạt động.
  7. ElfFile._read_program_headers()và address_to_file_offset()xử lý các thiết bị thực thi.
  8. MachOFileđọc tiêu đề, lệnh tải,LC_SEGMENT_64với các phần của nó, LC_SYMTAB- LC_BUILD_VERSION, và chuyển chỗ mỗi phần.
  9. compare_formats()tên cùng một ý tưởng trong cả hai tập tin với các giá trị đọc từ mỗi.

Sử dụng nó

Tham nhập người đọc từ các bài học sau đó với from main import ElfFilesau khi thêm bài học này codethư mục đến sys.pathBài học 33 yêu cầu điều đó.function_bytesvà relocations_for(".text")Bài học 35 ghi lại kết quả của nó như bằng chứng quan sát. Bài học 36 yêu cầu nó để bù đắp chuyển đổi trước khi so sánh byte.

Sắp xếp C của riêng bạn thành một đối tượng với cùng một clang -target x86_64-unknown-linux-gnu -cChỉ cần đặt lệnh và chạy bộ đọc trên nó. Chỉ cần thêm tên di chuyển khi bảng x86-64 psABI 4.9 liệt kê nó. Chỉ cần thêm kiểu phần khi gABI hoặc chuỗi công cụ sản xuất tài liệu nó. Khi người đọc gặp một giá trị mà nó không có tên cho, nó in số bằng chữ sáu thập kỷ thay vì đoán.

Phòng thí nghiệm tương tác

Phòng thí nghiệm A: đầu bằng tay

Từ codethư mục, mở Python:

bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch/code
python3 -q

Đi chạy:

pythonimport struct
from main import ElfFile, FIXTURES
data = (FIXTURES / "ledger-x86_64-O1.o").read_bytes()
data[:4], data[4], data[5]
struct.unpack_from("<HHIQQQIHHHHHH", data, 16)
elf = ElfFile(data)
elf.header.e_shoff, elf.header.e_shnum, elf.sections[elf.header.e_shstrndx].name

Những màn trình diễn của Tuplee_type1, e_machine62, và e_shoff1064, đó là 0x428- Dòng cuối cùng cho thấy phần 1.strtab, giữ tên các phần.

Phòng thí nghiệm B: bảng giá trị đằng sau bộ chuyển đổi

pythonrodata = elf.section_bytes(elf.section(".rodata"))
[int.from_bytes(rodata[i : i + 4], "little") for i in range(0x14, 0x28, 4)]
[int.from_bytes(rodata[i : i + 8], "little") for i in range(0x28, 0x50, 8)]
[r for r in elf.relocations_for(".text") if r.symbol == ".rodata"]

Cả hai bảng đều được đọc 1, 2, 3, 4, 4Hãy kiểm tra chúng.ledger.cgrade(65)tính toán65 / 10 - 6 = 0và đọc mục 0, đó là 1, giá trị của case 6- grade(100)đọc mục 4, đó là 4, giá trị chia sẻ bởi case 10và case 9- Tôi không biết.

Phòng thí nghiệm C: ba bản xây dựng của một nguồn

pythonfor level in ("O0", "O1", "O2"):
    obj = ElfFile.from_path(FIXTURES / f"ledger-x86_64-{level}.o")
    print(level, [s.name for s in obj.sections if s.name in (".data", ".rodata")])
    print(level, sorted({r.type_name for r in obj.relocations}))
    print(level, {s.name: s.st_size for s in obj.symbols if s.type == "FUNC"})

Chuyện -O0 có một .dataCác đối tượng -O1 và -O2 không có.datavà một loại.totallà 66 byte ở -O0, 31 ở -O1, và 140 ở -O2.

Phòng thí nghiệm D: cùng một chức năng trong Mach-O

pythonfrom main import MachOFile
macho = MachOFile.from_path(FIXTURES / "ledger-arm64-O1.o")
[(c.name, c.cmdsize) for c in macho.load_commands]
macho.function_span("_percent_change")
macho.function_bytes("_percent_change")[:4].hex()

Thời gian là 24 byte tại file offset 648.a0000034, hình thức nhỏ bé của hướng dẫn 0x340000a0, mà llvm-objdump -dNhận dạng như cbz w0, 0x14Đó là before == 0Guard, cùng một thử nghiệm mà x86-64 xây dựng mã hóa như 85 ff 74 09- Tôi không biết.

Phòng thí nghiệm thực hành

Chuyển mở trình đọc đến một chương trình liên kết.clang -O1 -shared -fPIC code/fixtures/ledger.c -o libledger.sovà clang -O1 code/fixtures/ledger.c -o ledger_mainvới một mainbạn thêm, sau đó chạy người đọc trên cả hai.

Yêu cầu:

  • Đọc .dynsymvới cùng một Elf64_Symthiết kế, kể từ khi SHT_DYNSYMlà 11 và hồ sơ không thay đổi.
  • Đọc .dynamicnhư Elf64_Dynghi chép của d_tagvà d_val, và tên DT_NEEDED- DT_STRTAB- DT_SYMTAB- DT_RELA, vàDT_JMPRELtừ trang "Dynamic Section" của gABI.
  • Đăng danh các biểu tượng không được xác định với st_shndx SHN_UNDEFnhư nhập khẩu, vàR_X86_64_JUMP_SLOTghi chép trong .rela.pltnhư các trang web gọi đến họ.
  • Bản đồ mọi thứPT_LOADphân đoạn đến các phần nó chứa, sử dụng quy tắc gABI rằng "một phân đoạn tệp đối tượng chứa một hoặc nhiều phần".
  • Hãy chứng minh với address_to_file_offsetrằng các byte của mỗi hàm được xác định trong đối tượng được chia sẻ là các byte function_bytestrở lại.
  • Tắt llvm-objdump -h -t -r -pvà llvm-objdump -Tvăn bản cho cả hai tệp vào code/fixtures/và so sánh người đọc với họ trong thử nghiệm.

Thành công có nghĩa là danh sách nhập khẩu, bản đồ phân đoạn và mỗi phạm vi chức năng phù hợp với sản lượng công cụ được chụp, mà không có công cụ chạy trong các thử nghiệm.

Bài học: Cây tạo vật

Bài học này sẽ mang lại kết quả outputs/skill-program-anatomy-elf-from-scratch.mdSử dụng nó với Claude, Cursor, Codex, OpenClaw, Hermes, hoặc bất kỳ đại lý nào đọc SKILL.md. Khả năng biến một đối tượng được biên soạn hoặc có thể thực hiện được người dùng xây dựng thành một báo cáo bố cục. báo cáo cho phép phạm vi tệp của mỗi cấu trúc, biểu tượng và di chuyển, kiểm tra với đầu ra công cụ được chụp.

Hãy kiểm tra

Chạy demo và bộ xác định:

bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch
python3 code/main.py
python3 -m unittest discover -s code/tests -v

Phòng chứng minh:

  • Mỗi mục chỉ mục, tên, kích thước, địa chỉ và loại phù hợp với các hình ảnh bị bắt llvm-objdump -hvăn bản cho ba đối tượng x86-64 và các thể hiện nhỏ
  • Mỗi giá trị biểu tượng, liên kết, loại, phần, kích thước và tên phù hợp với các hình ảnh được chụp llvm-objdump -tvăn bản
  • Mỗi chuyển nhượng, tên kiểu, biểu tượng và phụ kiện phù hợp với những gì đã chụp.llvm-objdump -rvăn bản, bao gồm:R_X86_64_PLT32và R_X86_64_64ở -O0
  • cả hai người đứng đầu chương trình của tiny-x86_64Đáp lại những kẻ bị bắt llvm-objdump -pvăn bản, và các đối tượng không có
  • percent_changelà 16 byte tại file offset 0x40với các byte dự kiến, và mọi hàm nằm trong .text
  • Bản đồ file offset bao gồm mỗi byte của mỗi vật cố định một lần, với ba khoảng cách sắp xếp của đối tượng -O1 ở vị trí của họ
  • điểm nhập cảnh của tiny-x86_64bản đồ để tệp trục 0xb0qua PT_LOAD, và một đối tượng có thể di chuyển từ chối bản đồ địa chỉ
  • Đầu Mach-O, mỗi lệnh tải, phân đoạn, 5 phần,LC_SYMTAB, vàLC_BUILD_VERSIONĐáp lại những kẻ bị bắt --private-headersvăn bản
  • Các biểu tượng Mach-O và 15 di chuyển phù hợp với những người bị bắt llvm-objdumpvăn bản, và_percent_changelà 24 byte tại 648
  • một phép thuật sai, một lớp 32-bit, một tập tin big-endian, một bảng trunced, một binary phổ quát, và một tập tin nguồn C tất cả tăng lên.FormatError
  • build_tiny_elf.pytái tạo các cam kết tiny-x86_64Byte cho byte
  • mô-đun hình ảnh site/figures-re-32.jsmang theo các byte chính xác của ledger-x86_64-O1.o

Các chế độ sản xuất thất bại

FailureWhat the weak reading reportsWhat the reader must prove
st_value used as a file offset"percent_change is at 0"Offset is sh_offset + st_value - sh_addr, here 0x40
Padding counted as code"grade is 48 bytes"st_size is 46 and the 66 90 after it belongs to no symbol
Section names read from the wrong tableGarbage names or an exceptione_shstrndx names the table, here section 1
sh_size trusted for SHT_NOBITSA read past the end of the file.bss has no file bytes and the map skips it
Zero bytes read as the operand"lea 0(%rip)"The .rela.text record at 0x36 names .rodata+0x10
Addend read as the target offset"the table starts at 0x10"S + A - P with the instruction end gives 0x14
Sections walked on a stripped executable"the file has no code"PT_LOAD maps every address the loader uses
A linked address treated as a runtime addressA wrong offset under a random baseThe file offset from PT_LOAD is the stable coordinate
A Mach-O section looked up by sectname aloneTwo __const sections collapse into onesegname,sectname is the name
A size field expected in nlist_64An exception or zero bytesThe next symbol in the same section ends the function
A universal binary read as one Mach-O"magic 0xcafebabe is unknown"Refuse it and ask for a thinned slice
The arm64 object rebuilt on another Mac"the fixture changed"LC_BUILD_VERSION records the SDK, so the committed bytes are the fixture

Quy tắc hoạt động

Đọc tiêu đề mà bạn có thể tin tưởng, theo các lệnh thay thế nó giữ, và đặt tên cho mỗi trường từ chi tiết xác định nó. Giữ công cụ đầu ra bạn so sánh với như một vật cố định cam kết, do đó việc so sánh chạy mà không có công cụ. Khi một giá trị không có tên trong chi tiết, in số. Người đọc bạn có thể kiểm tra byte byte là một người mà mỗi bài học sau đó xây dựng trên.

Đọc thêm

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.