Từ 1 tới 8 trên tổng số 8 kết quả

Đề tài: Vị trí trong tập tin và trên bộ nhớ thực thi của một số thành phần C\C++.

  1. #1
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Mặc định Vị trí trong tập tin và trên bộ nhớ thực thi của một số thành phần C\C++.

    Để mô tả các ví dụ trong đề tài này, tôi sử dụng hệ thống và các chương trình sau:
    _ Hệ điều hành Win10 "không chính thức".
    _ Code::blocks 20.03 (CB)
    _ Microsoft Visual 2015 (VS)
    _ DevC++ 5.11 (DC)
    _ Chương trình xem Pe5.exe (mã nguồn được biên dịch trong VS2015 - có trên diễn đàn)
    Bài viết dựa trên cách hiểu cá nhân nên không tránh khỏi sai sót nhầm lẫn, mời các bạn góp ý thêm.
    Các mã mô tả sẽ được tạo dự án từ dự án trống trong cả 3 CB, VS và DC; các dự án được thống nhất cấu hình để tạo *.cpp, biên dịch Release và xây dựng .exe theo x64.

    I. Các hằng chuỗi
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main()
    5. {
    6.     cout << "Hello world!" << endl;
    7.     return 0;
    8. }

    Xem các exe đã được biên dịch từ CB,VS,DC trên bộ xem Pe5.exe như các hình bên dưới.






    Nhận xét: cả 3 trình dịch đều đặt dữ liệu hằng "Hello world!" trong phân đoạn (section) ".rdata". VS khác chút ít là không đặt ở đầu Section.

    Xem tiếp các đặc tính của ".rdata" trong VS (CB và DC tương tự)




    Ta thấy ".rdata" có các cờ được bật là: IMAGE_SCN_CNT_INITIALIZED_DATA và IMAGE_SCN_MEM_READ.
    Các cờ này cho ta thấy rằng phân đoạn ".rdata" là vùng dữ liệu sẽ được hệ điều hành nạp lên bộ nhớ và ghi giá trị cho nó (không phải mã chương trình gán giá trị) trước khi hàm main() được trao quyền điều khiển. Song song đó thì nó là phân đoạn chỉ đọc, HDH sẽ ngăn chặn tất cả các tác vụ ghi lên phân đoạn này từ tất cả các tiến trình khác đang chạy (kể cả tiến trình chương trình). Điều này lý giải tại sao TBD báo lỗi khi ta viết mã truy xuất ghi lên một thành phần được khai báo là hằng. Và nó cũng có thể trả lời cho các thắc mắc như các hằng được cấp phát trên Stack hay Heap (một phần là do trong giảng dạy không nói rõ nên các bạn sinh viên thường có nhầm lẫn là chỉ có Stack và Heap).

    Tạm nghỉ ở đây thôi, lần sau chúng ta xét tiếp các hằng dạng toàn cục, sẽ có vài điểm thú vị với các TBD.
    .
    .

  2. #2
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Chúng ta xem tiếp các exe được biên dịch với đoạn mã sau:
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. const   char    aaa[] = "HELLO WORLD!";
    5. const   char    *bbb = "HELLO world!";
    6.  
    7. int main()
    8. {
    9.     cout << "Hello world!" << endl;
    10.     return 0;
    11. }




    Nhận xét về các trình dịch:
    . VS "ăn" mất cả 2 hằng "HELLO WORLD!" và "HELLO world!".
    . CB "ăn" chỉ 1 hằng "HELLO WORLD!".
    . DC là "trong sạch".
    Phải chăng VS "thông minh" hơn CB và CB hơn DC ? Theo tôi thì đây là do cách thức biên dịch để hướng tới sự tương thích tốt nhất của từng TBD. Nếu xem kích thước các tập tin exe chúng ta thấy VS.exe và CB.exe là không đáng kể nhưng DC.exe rất lớn. DC.exe có kích thước rất lớn và nó ít "thông minh" nhưng nếu đưa sang một máy khác chưa từng nạp các IDE, khả năng chạy được là lớn nhất. Tiếp đến là CB.exe, nó cần được cung cấp đi kèm ít nhất là 3 tập tin libgcc_s_seh-1.dll (16KB) libstdc++-6.dll(1384KB) libwinpthread-1.dll. Với VS thì rất tệ phải tới gần chục dll đi kèm và cũng không chắc nó chạy được không (còn tùy vào sự đỏng đảnh của Windows).

    Có thể bạn sẽ nói, không phải các trình dịch "ăn" mất các hằng mà nó nằm đâu đó trong tập tin thôi, bạn có thể dùng các chương trình xem nhị phân nào đó để xem các exe và tôi dám chắc sẽ chẳng có.
    Hoặc bạn có thể báo cho trình dịch rằng có sử dụng các hằng đó, đừng nên "ăn" bớt của tôi như

    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. const   char    aaa[] = "HELLO WORLD!";
    5. const   char    *bbb = "HELLO world!";
    6.  
    7. int main()
    8. {
    9.     cout << aaa << endl;
    10.     cout << bbb << endl;
    11.     cout << "Hello world!" << endl;
    12.     return 0;
    13. }
    Và các hằng lại hiện diện đầy đủ trong ".rdata".
    .
    .

  3. #3
    Ngày gia nhập
    12 2015
    Nơi ở
    Đà Nẵng
    Bài viết
    656

    Nếu không dùng đến thì giữ lại làm gì. Theo ý kiến chủ quan của mình thì VS thông minh hơn các TBD khác trong việc xác định cái nào không được dùng đến thì bỏ hẵn luôn, không lưu giữ lại ở bất kỳ vị trí nào. Không biết có đúng không.
    Đây là một vấn đề rất thú vị.
    Hóng bài viết tiếp theo của bạn

  4. #4
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Trích dẫn Nguyên bản được gửi bởi khoaph
    Nếu không dùng đến thì giữ lại làm gì. Theo ý kiến chủ quan của mình thì VS thông minh hơn các TBD khác trong việc xác định cái nào không được dùng đến thì bỏ hẵn luôn, không lưu giữ lại ở bất kỳ vị trí nào. Không biết có đúng không.
    Đây là một vấn đề rất thú vị...
    Đây là điều mình cũng rất phân vân, không lẽ một công ty lớn tạo ra phần mềm tích hợp trình biên dịch lại không đủ khả năng xác định có/không một thành phần được sử dụng, chúng ta cũng chỉ biết phỏng đoán thôi.

    Tiếp tục theo đề tài. Bây giờ chúng ta thay đổi khai báo như sau và xem các exe
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. char    aaa[] = "HELLO WORLD!";         // Chuỗi khởi tạo cho biến
    5. char    *bbb = (char*)"HELLO world!";       // Chuỗi thực sự là hằng
    6.  
    7. int main()
    8. {
    9.     cout << aaa << endl;
    10.     cout << bbb << endl;
    11.     cout << "Hello world!" << endl;
    12.     return 0;
    13. }





    Nhận xét: Cả 3 TBD đối xử chuỗi "Hello world!" và "HELLO world!" là như nhau (đều trong ".rdata"). Và cả 3 phải chăng đã "gặm mất" chuỗi "HELLO WORLD!" hay chuỗi "HELLO WORLD!" sẽ nằm trên stack-heap hay ở đâu ? Chúng ta tìm câu trả lời.

    . bbb được khai báo là con trỏ tới chuỗi nên nó chỉ chứa địa chỉ của chuỗi, trong khi đó chuỗi vẫn thực sự là hằng nên vẫn có trong ".rdata".
    . aaa được khai báo là 1 biến mảng ký tự và nó được khởi tạo từ chuỗi "HELLO WORLD!", vì aaa là biến nên nó có thể thay đổi giá trị trong quá trình thực thi của chương trình. Chúng ta có thể suy luận rằng TBD sẽ chẳng cần lưu giữ chuỗi này mà chỉ cần biên dịch thẳng vào vị trí của aaa trong tập tin. Với nhận định này thì chúng ta sẽ đi tìm trong các Section, Section nào có đặc tính là dữ liệu khởi tạo trước, cho phép cả đọc và ghi sẽ có nhiều khả năng chứa chuỗi này (hay vị trí của biến mà chứa chuỗi này khi Loader nạp chương trình lên bộ nhớ).

    Nhìn vào cấu trúc PE của các exe ta thấy VS.exe có 7/7 (khai báo/dữ liệu) của section, CB có 9/8 và DC có 17/16. Có vẻ như VS hướng tới biên dịch không để phát sinh nhiều section có cùng tập hợp đặc tính trong khi CB và DC chia nhỏ ra thành nhiều section, DC "chuối" nhất ở điểm này (hay cũng vì mục đích nào khác đã đề cập ở các thảo luận trước không chừng).
    Trong VS.exe ta tìm thấy chỉ có section ".data" là đáp ứng cả 3 đặc tính cần tìm : IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE




    từ đây ta "khui" ra chuỗi "HELLO WORLD!" trong VS.exe. Và chắc chắn nếu CB và DC có ".data" thì chuỗi cũng nắm trong đó, "khui" các section ".data" của CB và DC, kết quả không ngoài dự đoán.





    Như vậy: Một chuỗi dùng để khởi tạo cho một biến toàn cục sẽ không được lưu trữ như 1 hằng.
    Ghi chú: Chúng ta đang xét các chuỗi, với các số (hoặc dữ liệu có kích thước tương thích thanh ghi CPU) có thể hên sui tính sau.
    .
    .

  5. #5
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Xem xét các hằng là số

    Chúng ta xem thử các exe được tạo với mã sau
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. const   char    g_array[] = { 'H','E','L','L','O',' ','W','O','R','L','D','!' };
    5. const   char    A = 'A', B = 'B', C = 'C', D = 'D', E = 'E', F = 'F', G = 'G', H = 'H', I = 'I', J = 'J', K = 'K', L = 'L', M = 'M';
    6. const   char    N = 'N', O = 'O', P = 'P', Q = 'Q', R = 'R', S = 'S', T = 'T', U = 'U', V = 'V', W = 'W', X = 'X', Y = 'Y', Z = 'Z';
    7.  
    8. int main()
    9. {
    10.     cout << g_array;
    11.     printf("%c", A);
    12.     printf("%c", B);
    13.     printf("%c", C);
    14.     printf("%c", D);
    15.     printf("%c", E);
    16.     printf("%c", F);
    17.     printf("%c", G);
    18.     printf("%c", H);
    19.     printf("%c", I);
    20.     printf("%c", J);
    21.     printf("%c", K);
    22.     printf("%c", L);
    23.     printf("%c", M);
    24.     printf("%c", N);
    25.     printf("%c", O);
    26.     printf("%c", P);
    27.     printf("%c", Q);
    28.     printf("%c", R);
    29.     printf("%c", S);
    30.     printf("%c", T);
    31.     printf("%c", U);
    32.     printf("%c", V);
    33.     printf("%c", W);
    34.     printf("%c", X);
    35.     printf("%c", Y);
    36.     printf("%c", Z);
    37.     return 0;
    38. }
    bạn đừng để ý mã C lẫn lộn C++, chỉ là viết vậy để dễ tìm kiếm vị trí của nó thôi.





    Nhận xét: VS và CB chỉ xem các phần tử trong mảng là hằng trong khi DC xem cả các ký tự riêng lẻ cũng là hằng. VS và CB lại ghi bàn với sự "thông minh" hơn.!!!
    Vậy với VS, CB thì các ký tự riêng lẻ A-Z nằm ở đâu trong exe hay nó trú ngụ trên stack-heap, chúng ta tìm câu trả lời.
    Nếu trong ".rdata" và ".data" của VS và CB không có thì khả năng kế tiếp là nó nằm trong ".text". Tìm trong ".text" ta được




    Trong VS: Offset tập tin của ký tự 'A' là 0x481, 'B' là 0x492, ....
    Trong CB: Offset tập tin của ký tự 'A' là 0x6CFD, 'B' là 0x6D0E, ....

    Nhưng các ký tự riêng lẻ đó có đúng là các hằng đã khai báo ở đầu chương trình không, nếu đúng thì nó ở đó với tư cách gì (hằng, biến,...?).
    Tới đây bắt đầu rắc rối, chúng ta cần mở rộng "điều tra", bạn nào thấy quá tải thì cứ xem như kết quả trên là đủ rồi, muốn hiểu rõ hơn chúng ta tiếp tục.
    Thông thường thì section ".text" là phân đoạn chứa mã máy thực thi (từ xưa đã vậy) như hình



    Chúng ta xem trường IMAGE_FILE_HEADER.Machine của VS.exe, CB.exe hay DC.exe như các hình dưới.





    Trên máy mình các exe đã được biên dịch cho hệ đích với tập lệnh máy AMD64, trên máy bạn có thể khác như IA64 hay I386 chẳng hạn. Vì tập lệnh máy có thể khác nhau nên đoạn bên dưới chỉ mô tả theo máy mình.

    Bạn cần một chương trình dịch ngược ra gợi nhớ hợp ngữ (ASM) để xem mã tại các Offset đã đề cập ở trên.
    Hiện tại mình dùng x64dbg (nó có biểu tượng hình con bọ), nếu bạn muốn dùng nó thì nạp nó ở bên dưới.
    Cách hoạt động của x64dbg là tạo bản đồ trên bộ nhớ y như cách mà Windows nạp chương trình lên bộ nhớ chỉ là nó không trao quyền thực thi cho lệnh trong hàm main() mà thôi, và mọi thứ xem xét đang là vị trí bộ nhớ thực trên máy bạn.

    Mở x64dbg và mở VS.exe trên nó, ở khung bên dưới chọn tab "Dump 5", kéo xuống tới địa chỉ xxxx xxxx xxxx 1000, đây là điểm bắt đầu của dữ liệu section ".text", ký tự 'A' trong VS có Offset 0x481 nên tôi kéo tiếp tới xxxx xxxx xxxx 1000 + 80 = xxxx xxxx xxxx 1080, dòng tại địa chỉ này tôi đã chọn như hình dưới. Tại sao có cách tìm địa chỉ như vậy thì rất dài dòng bạn có thể xem đề tài "Lập trình API cho tập tin PE" ở đâu đó trên diễn đàn.



    Right-Click lên khung xem và chọn Disassembly



    Có 3 dòng mà mình đã tô xám là mã máy tương ứng với lệnh : printf("%c", A); trong mã nguồn. Mã trên máy bạn có thể khác chút đỉnh như đã nói ở trên.
    Các bộ 3 dòng tuần tự kế tiếp tương ứng với các lệnh in kế tiếp trong mã nguồn.

    Với CB cũng tương tự với địa chỉ kéo tới là xxxx xxxx xxxx 78F0



    Kết đoạn: Trong ví dụ này, VS và CB đã đưa các ký tự riêng biệt nhập chung vào mã máy. Mã C\C++ xem là hằng nhưng TBD đã "qua mặt" chúng ta.

    Do chương trình x64dbg quá lớn không thể chuyển trên diễn đàn được, bạn dùng từ khóa "x64dbg" trên Gu-gồ sẽ có ngay
    .
    .

  6. #6
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Mặc định Vị trí trong tập tin và trên bộ nhớ thực thi của một số thành phần C\C++.

    II. CÁC BIẾN:
    Các biến toàn cục.

    Chúng ta xem các exe được tạo với đoạn mã sau

    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. char    g_array[] = { 'M','A','N','G',' ','T','O','A','N',' ','C','U','C','\0' };
    5. char    A = 'B';
    6. char    B = 'I';
    7. char    C = 'E';
    8. char    D = 'N';
    9. char    E = ' ';
    10. char    F = 'T';
    11. char    G = 'O';
    12. char    H = 'A';
    13. char    I = 'N';
    14. char    J = ' ';
    15. char    K = 'C';
    16. char    L = 'U';
    17. char    M = 'C';
    18.  
    19. int main()
    20. {
    21.     cout << g_array << endl;
    22.     cout << A << B << C << D << E << F << G << H << I << J << K << L << M << endl;
    23.     getchar();
    24.     return 0;
    25. }

    Khi chạy, nó hiển thị như hình



    Vì các biến toàn cục là có thể thay đổi giá trị và được khởi tạo nên ta tìm trong các section có đặc tính : IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE và section ".data" có các đặc tính này.





    Nhận xét :
    . Với biến mảng, cả 3 đều đặt vị trí của nó trong tập tin (VS:0x2C38. CB:0x2420. DC:0x71210) và trên vùng dữ liệu section ".data" khi thực thi.
    . Với biến đơn, CB và DC đặt nó ngay sát biến mảng nhưng VS lại không có.
    Như vậy là câu trả lời cho CB và DC đã có, chúng ta tìm câu trả lời với VS và xem nó "thông minh" tới đâu.

    Nếu ".data" không chứa các biến đơn thì ta xem VS có thể lưu nó trong section chỉ đọc và khởi tạo trước không, tìm trong ".rdata", tại 0x1A60 chúng thấy các ký tự khởi tạo nằm ở đó



    Nhưng có đúng là nó được sử dụng trong chương trình hay VS đặt các hằng ở đó cho mục đích khác ? Nếu nó thực sự là nơi đặt các biến đơn lẻ thì nếu chúng ta thay đổi (chỉnh sửa) các offset đó trong tập tin thành các giá trị khác thì chương trình phải thể hiện được các giá trị khác đó khi chạy.

    Trên bộ xem pe5.exe ta click vào dòng dữ liệu đang chứa các ký tự để hiển thị cửa sổ soạn thảo dòng



    Thay đổi các ký tự in thành ký tự thường



    Click ra bên ngoài để pe5.exe cập nhật khung xem



    Dùng chức năng lưu VS.exe với các thay đổi thành tập tin chạy khác, tôi lưu thành aaaa.exe, khi chạy aaaa.exe nó hiển thị như sau



    Không có gì thay đổi so với chương trình gốc hoạt động, như vậy các ký tự lưu ở vị trí này được VS lưu cho mục đích khác, và các offset đó không phải là vị trí của các biến đơn.
    Nếu trong ".rdata" không tìm được, thì section kế tiếp là ".text".



    Ta thấy các offset sau chứa các ký tự mà được khởi tạo cho các biến đơn của chương trình:
    0x428 = 'B'
    0x432 = 'I'
    0x43C = 'E'
    0x446 = 'N'
    0x450 = 0x20 <Khoảng trống>
    0x45A = 'T'
    0x464 = 'O'
    0x46E = 'A'
    0x478 = 'N'
    0x482 = 0x20 <Khoảng trống>
    0x48C = 'C'
    0x496 = 'U'
    0x4A0 = 'C'
    Thay đổi các giá trị trong các offset này thành giá trị các ký tự thường






    Lưu thay đổi VS.exe thành bbbb.exe và chạy chương trình bbbb.exe, kết xuất màn hình của bbbb.exe như sau



    Như vậy, các offset trên chính là vị trí mà VS lưu các biến đơn, nhưng các vị trí đó mang tư cách gì, chúng ta xem tiếp trên bộ debug x64dbg.exe.
    Địa chỉ tìm tới là xxxx xxxx xxxx 1000 + lân cận (428h-400h=28h) = xxxx xxxx xxxx 1020



    Kết đoạn : Trong ví dụ này, VS biên dịch các biến đơn toàn cục trực tiếp vào mã máy.

    .
    .

    - - - Nội dung đã được cập nhật ngày 18-12-2021 lúc 09:51 AM - - -

    Trong đoạn trên mình đã phát biểu : "Trong ví dụ này, VS biên dịch các biến đơn toàn cục trực tiếp vào mã máy"
    Vậy với các trường hợp tổng quát hơn thì sao ? Nghĩa là các biến đơn ở trên có cả truy xuất ghi thì sao ? Chúng ta xem sự "thông minh" của VS trong đoạn mã dưới

    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. char    g_array[] = { 'M','A','N','G',' ','T','O','A','N',' ','C','U','C','\0' };
    5. char    A = 'B';
    6. char    B = 'I';
    7. char    C = 'E';
    8. char    D = 'N';
    9. char    E = ' ';
    10. char    F = 'T';
    11. char    G = 'O';
    12. char    H = 'A';
    13. char    I = 'N';
    14. char    J = ' ';
    15. char    K = 'C';
    16. char    L = 'U';
    17. char    M = 'C';
    18.  
    19. int main()
    20. {
    21.     cout << g_array << endl;
    22.     cout << A << B << C << D << E << F << G << H << I << J << K << L << M << endl;
    23.  
    24.     // Chuyển chữ in thành chữ thường (cố tình tạo truy xuất ghi lên các biến)
    25.     A += 0x20; B += 0x20; C += 0x20; D += 0x20;
    26.     E = '_';
    27.     F += 0x20; G += 0x20; H += 0x20; I += 0x20;
    28.     J = '_';
    29.     K += 0x20; L += 0x20; M += 0x20;
    30.  
    31.     cout << A << B << C << D << E << F << G << H << I << J << K << L << M << endl;
    32.  
    33.     getchar();
    34.     return 0;
    35. }

    Hiển thị khi chạy như hình

    Click vào hình ảnh để lấy hình ảnh lớn

Tên:		Image 1.png
Lần xem:	0
Size:		3.5 KB
ID:		74343

    Xem VS.exe trên pe5.exe

    Click vào hình ảnh để lấy hình ảnh lớn

Tên:		Image 2.png
Lần xem:	3
Size:		34.6 KB
ID:		74344

    Ta nhận thấy trong ".data" có mặt đầy đủ các biến đơn, như vậy VS "thông minh" ở chỗ nếu biến đơn chỉ có truy xuất đọc nó biên dịch trực tiếp vào mã máy còn nếu có cả đọc/ghi thì nó biên dịch theo đúng chức năng của biến.

    Chắc cú hơn, ta thử thay đổi giá trị khởi tạo của các ký tự trong tập tin VS.exe thành các ký tự 'X' và lưu thành xxxx.exe như sau

    Click vào hình ảnh để lấy hình ảnh lớn

Tên:		Image 3.png
Lần xem:	3
Size:		34.2 KB
ID:		74345

    Chạy chương trình xxxx.exe, Hiển thị như sau

    Click vào hình ảnh để lấy hình ảnh lớn

Tên:		Image 4.png
Lần xem:	1
Size:		3.1 KB
ID:		74346

    Sự uyển chuyển của VS thật sự làm mình rất hứng thú, các bạn thấy sao. Hôm nay cái trang https://2.pik.vn/ bị điếc nên hình ảnh phải chuyển theo chức năng Insert Image của diễn đàn nên hơi khó xem, bạn nào có các link cho upload miễn phí chia sẻ cho anh em dùng, mình cám ơn.
    .
    .
    Đã được chỉnh sửa lần cuối bởi MHoang : 18-12-2021 lúc 10:12 AM.

  7. #7
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Các biến tĩnh cục bộ
    Trên cách nhìn của các TBD thì nó xem các biến tĩnh và biến toàn cục là tương đồng nhau khi xác định nơi cất giữ. Mình định đưa ví dụ nhưng nghĩ không cần lắm, các bạn có thể thử nghiệm riêng.
    Thay vào đó, cần chuẩn bị khi xem tới các biến cục bộ động, phải nói là nó rất gai góc, từ đầu đề tài tới giờ chưa là gì cả. Mình thấy cần chỉ rõ cách tính toán quy đổi offset tập tin ra vị trí tương đối và vị trí chính xác khi exe được tạo bản đồ trên bộ nhớ thực thi.

    Cách tính toán địa chỉ thực ảo (RVA) và địa chỉ thực (Address) của 1 thể hiện từ offset (OFF) tập tin exe.

    OFF là vị trí của 1 byte trong tập tin so với đầu tập tin.
    RVA là vị trí của 1 byte trên bộ nhớ so với điểm đầu tiên mà exe được nạp lên.
    Address là thể hiện vị trí hiện tại của byte đang xét

    Trong mô tả bên dưới, mình sẽ đi tìm offset tập tin và địa chỉ của 1 byte bộ nhớ thực của một thể hiện bản đồ exe.
    Địa chỉ cần tìm là byte cuối cùng của mã thực của phân đoạn mã ".text".

    Chương trình xem theo offset (trên pe5) như sau:



    Trong thể hiện này, địa chỉ thực mà exe được nạp lên bắt đầu từ (trên x64dbg):
    - Địa chỉ nạp: pMapping = 0000 7FF6 9DEE 0000



    Nhận các thông số của ".text" từ IMAGE_SECTION_HEADER.



    Các giá trị nhận được:
    - Kích thước mã lệnh - Misc: 1048
    - RVA bắt đầu trên bộ nhớ - VirtualAddress: 1000
    - Offset bắt đầu trên tập tin - PointerRawData: 400

    Tính toán:
    OFF = PointerRawData + Misc - 1 = 400 + 1048 - 1 = 1447
    RVA = VirtualAddress + Misc - 1 = 1000 + 1048 - 1 = 2047
    Address = pMapping + RVA = 0000 7FF6 9DEE 0000 + 2047 = 0000 7FF6 9DEE 2047


    Xem kiểm chứng giá trị tại offset và Address, nó phải khớp nhau.




    Giá trị cần tìm là 0xCC
    .
    .
    Đã được chỉnh sửa lần cuối bởi MHoang : 19-12-2021 lúc 04:54 PM.

  8. #8
    Ngày gia nhập
    02 2014
    Nơi ở
    TP.HCM
    Bài viết
    1,006

    Xem offset và address của hàm/phương thức và biến cục bộ động trong hàm

    Đoạn mã ví dụ cho mục này như sau:

    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. void Ham_Toan_Cuc()
    5. {
    6.     char    str[] = "MANGTRONGHAMTOANCUC";
    7.     char    x1 = 'H', x2 = 'E', x3 = 'L', x4 = 'L', x5 = 'O';
    8.  
    9.     printf("%s + ", str);
    10.     printf("%c", x1); printf("%c", x2); printf("%c", x3); printf("%c", x4); printf("%c", x5);
    11.     printf("\n\n");
    12. }
    13. class CAny
    14. {
    15. public:
    16.     void Phuong_Thuc_Lop()
    17.     {
    18.         char    str[] = "MangTrongPhuongThucLop";
    19.         char    x1 = 'H', x2 = 'e', x3 = 'l', x4 = 'l', x5 = 'o';
    20.  
    21.         printf("%s + ", str);
    22.         printf("%c", x1); printf("%c", x2); printf("%c", x3); printf("%c", x4); printf("%c", x5);
    23.         printf("\n\n");
    24.     }
    25. };
    26. int main()
    27. {
    28.     CAny    obj;                    // Tạo đối tượng trên Stack
    29.     CAny    *pObj = new CAny();     // Tạo đối tượng trên Heap
    30.  
    31.     // Các gọi hàm
    32.     Ham_Toan_Cuc();
    33.     obj.Phuong_Thuc_Lop();
    34.     pObj->Phuong_Thuc_Lop();
    35.  
    36.     delete  pObj;
    37.     getchar();
    38.     return 0;
    39. }

    Khi chạy, các exe thể hiện như hình



    Vì các trình biên dịch có cách tính toán tạo mã khác nhau nên ta xem theo từng exe riêng biệt, bắt đầu từ VS.exe, tất cả các giá trị thể hiện theo Hex.
    1. Lấy thông tin điểm nhập chương trình (là địa chỉ lệnh mà HĐH chuyển giao điều khiển hay bộ Debug bắt đầu chạy lệnh theo ngữ cảnh)
    2. Lấy RVA của điểm bắt đầu dữ liệu của section ".text"
    3. Lấy OFF của điểm bắt đầu dữ liệu của section ".text"




    IMAGE_OPTIONAL_HEADER64.AddressOfEntryPoint = 0000 1508
    IMAGE_SECTION_HEADER(".text").VirtualAddress = 0000 1000
    IMAGE_SECTION_HEADER(".text").PointerToRawData = 0000 0400

    Mở x64dbg và mở VS.exe trên nó. Click vào tab "Memory Map", ghi nhận địa chỉ bắt đầu mà VS.exe được nạp lên.



    Địa chỉ thực nạp: <pMapping = 0000 7FF7 AB43 0000>

    Click về lại tab "CPU", nhấn button run(có hình mũi tên phải) trên toolbar hoặc F9 từng lần một cho tới khi gặp địa chỉ như pMapping chỉ khác vài số Hex ở cuối (thường là 4 với chương trình nhỏ).



    Dùng phím <PageUp/PageDown> xem từng trang lệnh máy (trong trường hợp chúng ta viết hàm toàn cục ở trên main() nên tìm ở phía trên trước thì hơn).
    Bạn sẽ tìm được đoạn mã tương tự như bên dưới, nếu dòng địa chỉ đã tô nền đỏ thì có nghĩa x64dbg đã tạo sẵn điểm dừng debug cho bạn, nếu chưa thì bạn click vào chấm tròn bên trái để đặt điểm dừng.
    Tìm tới dòng có lệnh <ret> ở cột mã gợi nhớ hợp ngữ nằm bên dưới và cũng tạo điểm dừng cho nó.



    Ghi nhận địa chỉ đầu và cuối của hàm hiện tại: 0000 7FF7 AB43 1070 => 0000 7FF7 AB43 111C
    Kéo xuống thêm một đoạn bạn sẽ gặp phương thức của lớp, tạo điểm dừng cho đầu và cuối của phương thức lớp



    Ghi nhận địa chỉ đầu và cuối của phương thức hiện tại: 0000 7FF7 AB43 1120 => 0000 7FF7 AB43 11E3
    Nếu bạn muốn so sánh với mã lệnh trong tập tin thì lưu lại mã của 2 hàm/phương thức trên.

    Đến giờ bạn có thể xem x64dbg cho chạy từng đoạn lệnh bạn đã đặt các điểm dừng bằng cách "run" hoặc F9, trên cửa sổ cmd hiển thị theo mỗi lần chạy hết 1 hàm hay phương thức như hình dưới.



    Tính toán OFF bắt đầu và OFF cuối của các hàm/phương thức trong VS.exe

    Với hàm Ham_Toan_Cuc()
    Khoảng cách từ đầu section ".text" trong bộ nhớ tới địa chỉ đầu của hàm
    = Địa chỉ đầu của hàm - (pMapping + Virtual) = 0000 7FF7 AB43 1070 - (0000 7FF7 AB43 0000 + 0000 1000) = 0000 7FF7 AB43 1070 - 0000 7FF7 AB43 1000 = 0070
    Kích thước hàm = Địa chỉ cuối - Địa chỉ đầu + 1 = 0000 7FF7 AB43 111C - 0000 7FF7 AB43 1070 = 00AC

    OFF bắt đầu của hàm = PointerToRawData + Khoảng cách đầu = 0000 0400 + 0070 = 0470
    OFF cuối của hàm = OFF bắt đầu của hàm + Kích thước hàm = 0470 + 00AC = 051C

    So sánh




    Tương tự với phương thức Phuong_Thuc_Lop(), so sánh như hình dưới.




    Tạm nghỉ thôi, các biến ngày sau
    .
    .
    .

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn