ReverseEngineering
1.32K subscribers
50 photos
11 videos
106 files
888 links
Download Telegram
[rbp-20h]
↓
+5
↓
×3
↓
[rbp-18h]

Now we need to see where [rbp-18h] is used later

If we end up with something like this

mov eax, [rbp-18h]
ret

We understand that this value is part of the data path that leads to the function output

Combine Forward and Backward

Here's where things get more interesting
Assume the function input is in EDI

We start from the input

Forward Slicing

EDI
↓
EAX
↓
ADD
↓
IMUL
↓
SUB
↓
RETURN

Now on the other hand we go back from RETURN

Backward Slicing

RETURN
↑
SUB
↑
IMUL
↑
ADD
↑
EAX
↑
EDI

If these two analyses reach a common path

We are more confident that we found the main Data Flow path correctly

EDI
↓
ADD
↓
IMUL
↓
SUB
↓
RETURN

What to look for in Ghidra
When you are doing Program Slicing on a real function, there are a few things that are very important

Decompiler
Function Graph
References
Register Usage
Memory References
Variable Definitions
Variable Uses

But the two most important questions are

Where is this value created

And

Where is this value used next

These two questions seem simple, but they are the basis of a lot of serious Reverse Engineering analysis

Every time you see a Register or Memory Location, you should follow this relationship

Definition
↓
Value
↓
Use
↓
New Value
↓
Next Use

A slightly more realistic example
Let's say we have this function

C++
int verify(int input) {

int x = input ^ 0x55;

int y = x + 0x1234;

int noise = 777;

noise *= 4;

int z = y ^ 0xAA;

return z == 0x5678;
}

Here our goal is to understand the last condition
lua
return z == 0x5678;

After return, we go back
first z
then y
then x
and finally input
the path
return
↑
comparison
↑
z
↑
y
↑
x
↑
input

If we add the calculations

input
↓
XOR 0x55
↓
+ 0x1234
↓
XOR 0xAA
↓
compare 0x5678
↓
return

But this part

noise = 777
noise *= 4

has nothing to do with z and the final condition
So for the analysis of this condition, it can be outside our Slice for now

Why is this important to us

Because in a real program, a function may have hundreds or even thousands of instructions

But what we are really looking for may be just this

Input
↓
Transformation
↓
Comparison
↓
Decision

Slicing
It helps to separate this path from a large amount of code
Especially when dealing with things like this We will

Obfuscation
Junk Code
Function Complexity
Chain of Calculations
Multiple Conditions

The function may look very busy
But when we follow the Data Flow, we will find that only a few instructions actually affect the result that matters to us

Exercise:

Examine this function

int verify(int input) {

int a = input ^ 0x31;

int junk1 = 1234;

junk1 += 55;

int b = a * 7;

int junk2 = 999;
junk2 ^= 0x44;

int c = b - 12;

return c == 0x500;
}

Extract two slices
First, the Backward Slice from return
That is, start here
return
↑
?
↑
?
↑
?
↑
input
Then find the Forward Slice from input

input
↓
?
↓
?
↓
?
↓
comparison
↓
return

In the end, you should be able to get the main path to look something like this

input
↓
XOR 0x31
↓
× 7
↓
- 12
↓
comparison 0x500
↓
return

And these two parts

junk1
junk2

should not go into the Slice for this output because they have no effect on the final result
Now there is a more important step
Do the same analysis on Assembly, there is no more clean and beautiful input and result names
You have to follow the Registers and Memory Locations yourself and reconstruct the Data Flow from the Instructions
This is where the difference between just reading the Decompiler and really analyzing the binary becomes clear

@reverseengine
System Call
برنامه چجور با Kernel حرف میزنه؟

تا اینجا با چیزهایی مثل:
lua
fork()
exec()
wait()
exit()

کار کردیم
اما یک سوال مهم وجود داره

این توابع چطور میتونن از Kernel درخواست انجام یک کار بکنن؟

مثلا برنامه‌ای که داخل User Space اجرا میشه چطور میتونه:

فایل باز کنه؟
داده بخوانه؟
Process
جدید بسازه؟
حافظه بگیره؟

Process
خودش رو تموم کنه؟

جواب اصلی:
System Call

User Mode و Kernel Mode

CPU
معمولا اجرای برنامه رو در سطح دسترسی محدودی انجام می‌دهد که به اون:
User Mode

میگیم

برنامه در این حالت نمیتونه مستقیما هر کاری که دلش خواست با سیستم انجام بده

مثلا نمیتونه مستقیما:
Hardware
Kernel Memory
Page Tables
Device Control

رو دستکاری کنه
چون اگر هر برنامه‌ای چنین دسترسی‌ای داشت یک برنامه خرابکار میتونست کل سیستم رو به فنا بده😁
و ادم ها قبلا به اندازه کافی راه‌ های دیگه ای برای این کار پیدا کردن

Kernel Mode

Kernel
در سطح دسترسی بالاتری اجرا میشه:

Kernel Mode

در این حالت Kernel میتونه به منابع حساس سیستم دسترسی داشته باشه
پس ساختار کلی تقریبا این شکلیه:

User Space
│
│ System Call
↓
Kernel Space
│
↓
Hardware / Resources

برنامه مستقیم وارد Kernel نمیشه
بلکه از یک دروازه کنترل‌ شده استفاده میکنه این دروازه همون System Call هست

یک مثال ساده
فرض کنیم برنامه میخاد از یک فایل بخونه
در سطح برنامه ممکنه چیزی شبیه این داشته باشیم:

read(fd, buffer, size);

برنامه درخواست میکنه:

من میخاهم از این File Descriptor
به اندازه مشخصی داده بخونم

در اخر درخواست باید به Kernel برسه

Kernel
بررسی میکنه:

این Process چه کسیه؟
اجازه دسترسی داره؟

fd
معتبره؟

آدرس buffer معتبره؟
چه مقدار داده باید خونده بشه؟

بعد عملیات رو انجام میده و نتیجه رو برمیگردونه

مسیر کلی System Call

به شکل ساده:

Program
│
↓
Library / API Wrapper
│
↓
System Call Mechanism
│
↓
CPU → Kernel Mode
│
↓
Kernel
│
↓
Operation
│
↓
Return Value
│
↓
User Mode

یعنی برنامه برای انجام عملیات حساس از یک مسیر مشخص وارد Kernel میشه

System Call با Function Call
یکی نیست

این دو تا رو قاطی نکنیم

یک Function Call معمولی مثلا:

result = add(a, b);

معمولا در همون فضای اجرای برنامه انجام میشه

اما System Call یک عبور از مرز دسترسی User و Kernel ایجاد میکنه
پس:

Function Call
↓
همون فضای اجرا

System Call
↓
User Mode
↓
Kernel Mode
↓
برگشت به User Mode

این تفاوت در Reverse Engineering خیلی مهمه

fork() و wait()
هم از همین مسیر استفاده میکنن

مثلا در برنامه C مینویسیم:

fork();

اما این به معنی یک Function ساده مثل:

C
int add(int a, int b)

نیست
در سیستم‌های Unix-like این درخواست در اخر باید به Kernel برسه تا Kernel بتونه Process جدید ایجاد کنه

همین ایده برای عملیات‌ هایی مثل:
shell

read
write
open
mmap
fork
exec
wait

هم وجود داره
البته یک نکته مهم
چیزی که در کد میبینیم لزوما خود System Call خام نیست ممکنه یک Library Wrapper باشه که در اخر System Call مناسب رو انجام میده


System Call
چطور به Kernel میرسه؟

در معماری‌های مدرن CPU مکانیزم مخصوصی برای این انتقال داره
مثلا در x86-64 لینوکس دستور:

syscall

برای ورود به مسیر System Call استفاده میشه
به صورت مفهومی:

User Code
↓
Arguments آماده میشن
↓
System Call Number
↓
syscall
↓
CPU
↓
Kernel Entry
↓
Kernel Handler

Kernel
بعد از ورود درخواست رو بررسی و پردازش میکنه

برای ما چرا مهمه؟
فرض کنید داخل یک Binary این رو ببینید:

syscall

دیگه فقط با یک دستور اسمبلی معمولی طرف نیستید
این دستور میتونه نشون‌ دهنده عبور برنامه از:

User Space
↓
Kernel Space

باشه
از طرف دیگه اگر داخل کد برنامه API هایی مثل:

CreateFile
ReadFile
VirtualAlloc
CreateProcess
`

رو ببینید باید بدونید این API ها در اخر برای انجام تعداد زیادی از عملیات‌ های سیستمی به مکانیزم‌های Kernel وابسته هستن پس در RE باید بتونید این زنجیره رو در ذهنتون ببینید:

Application
↓
Library / API
↓
System Call
↓
Kernel
↓
Resource / Hardware

این دیدگاه بعدا هنگام تحلیل Process ها Memory File System Thread ها و حتی رفتارهای پیچیده‌تر Binary ها خیلی به کارتون میاد
System Call
رابط کنترل‌شده‌ ای که یک برنامه در User Mode از طریق اون درخواست‌ هایی رو از Kernel میخاد

مهم‌ترین نکته:

User Mode
│
│ System Call
↓
Kernel Mode
│
↓
Kernel performs operation
│
↓
User Mode

و این دقیقا یکی از پایه‌ های مهم درک OS Internals و Reverse Engineering هست

@reverseengine
ReverseEngineering
System Call برنامه چجور با Kernel حرف میزنه؟ تا اینجا با چیزهایی مثل: lua fork() exec() wait() exit() کار کردیم اما یک سوال مهم وجود داره این توابع چطور میتونن از Kernel درخواست انجام یک کار بکنن؟ مثلا برنامه‌ای که داخل User Space اجرا میشه چطور میتونه:…
System Call
How does a program talk to the Kernel?

So far we have worked with things like:
lua
fork()
exec()
wait()
exit()

But there is an important question

How can these functions request the Kernel to do something?

For example, how can a program running in User Space:

Open a file?

Read data?

Create a new process?

Get memory?

Process
terminate itself?

Original answer:
System Call

User Mode and Kernel Mode

The CPU
usually executes the program at a limited access level, which we call:
User Mode


The program in this mode cannot directly do anything it wants with the system

For example, it cannot directly manipulate:
Hardware
Kernel Memory
Page Tables
Device Control

Because if any program had such access, a malicious program could destroy the entire system😁
And people have already found enough other ways to do this

Kernel Mode

The Kernel is executed at a higher access level:

Kernel Mode

In this mode, the Kernel can access sensitive system resources
So the overall structure is approximately as follows:

User Space
│
│ System Call
↓
Kernel Space
│
↓
Hardware / Resources

The program does not enter the Kernel directly
But uses a controlled gateway, this gateway is the System Call

A simple example
Suppose the program wants to Read a file
At the program level we might have something like this:

read(fd, buffer, size);

The program requests:

I want to read a certain amount of data from this File Descriptor

At the end of the request, it should reach the Kernel

The Kernel checks:

Who is this Process?

Does it have access?

Is the fd
valid?

Is the buffer address valid?

How much data should be read?

Then it performs the operation and returns the result

General System Call Path

In simple terms:

Program
│
↓
Library / API Wrapper
│
↓
System Call Mechanism
│
↓
CPU → Kernel Mode
│
↓
Kernel
│
↓
Operation
│
↓
Return Value
│
↓
User Mode

That is, the program enters the Kernel from a specific path to perform sensitive operations

System Call is not the same as Function Call

Let's not confuse the two

A normal Function Call, for example:

result = add(a, b);

Usually it is done in the same execution space of the program

But System Call creates a crossing of the User and Kernel access boundaries

So:

Function Call
↓
Same execution space

System Call
↓
User Mode
↓
Kernel Mode
↓
Return to User Mode

This difference is very important in Reverse Engineering

Fork() and wait()
also use the same path

For example, in a C program we write:

fork();

But this does not mean a simple Function like:

C
int add(int a, int b)

In Unix-like systems this request must finally reach the Kernel so that the Kernel can create a new Process

The same idea exists for operations like:
shell

read
write
open
mmap
fork
exec
wait

Of course, one important point

What we see in the code is not necessarily the raw System Call itself, it may be a Library Wrapper that finally performs the appropriate System Call

How does System Call
reach the Kernel?

In modern architectures, the CPU has a special mechanism for this transfer. For example, in x86-64 Linux, the command:

syscall

is used to enter the System Call path. Conceptually:

User Code
↓
Arguments are prepared
↓
System Call Number
↓
syscall
↓
CPU
↓
Kernel Entry
↓
Kernel Handler

The Kernel
checks and processes the request after it has entered.

Why is it important to us?
Suppose you see this inside a Binary:

syscall

You are no longer dealing with just a regular assembly instruction

This instruction can indicate that the program passes through:

User Space
↓
Kernel Space

On the other hand, if you see APIs like:

CreateFile
ReadFile
VirtualAlloc
CreateProcess


in the program code, you should know that these APIs ultimately depend on Kernel mechanisms to perform a large number of system operations, so in RE you should be able to see this chain in your mind:

Application
↓
Library / API
↓
System Call
↓
Kernel
↓
Resource / Hardware

This perspective will come in handy later when analyzing Processes, Memory, File System, Threads, and even more complex behavior of Binary.
👍1
System Call A controlled interface through which a program in User Mode makes requests to the Kernel

The most important point:

User Mode
│
│ System Call
↓
Kernel Mode
│
↓
Kernel performs operation
│
↓
User Mode

And this is exactly one of the important foundations of understanding OS Internals and Reverse Engineering

@reverseengine
Trap
و ورود از User Mode به Kernel Mode

تا اینجا فهمیدیم برنامه ها توی User Mode اجرا میشن و وقتی بخوان از سیستم عامل یه کار حساس بخوان از System Call استفاده میکنن
ولی یه سوال مهم داریم

CPU
دقیقا چطوری از اجرای عادی برنامه توی User Mode میره داخل Kernel Mode
اینجا مفهوم Trap وارد ماجرا میشه


Trap
یعنی یه انتقال کنترل شده از اجرای عادی برنامه به Kernel یعنی برنامه داره توی User Mode اجرا میشه
User Mode
│
│ درخواست سرویس
▼
Trap
│
▼
Kernel Mode

بعد از اینکه Kernel کارش رو انجام داد کنترل دوباره به برنامه برمیگرده

System Call و Trap
چه فرقی دارن
این دوتا یکی نیستن

System Call
در واقع درخواست برنامه از سیستم عامله
مثلا برنامه میگه من میخوام از فایل بخونم
یا چیزی بنویسم
یا حافظه بگیرم

مثل:

read()
write()
mmap()

اما Trap مکانیزمیه که باعث میشه CPU کنترل اجرا رو به Kernel منتقل کنه

پس خیلی ساده
Program
│
│ System Call
▼
Trap / System call entry
│
▼
Kernel

یعنی System Call میگه چه سرویسی میخوام

Trap
یا مسیر ورود باعث میشه اجرای برنامه وارد Kernel بشه

مثال x86 64:

توی Linux روی معماری x86 64 معمولا System Call با دستور
syscall
انجام میشه
قبل از اجرای این دستور برنامه اطلاعات لازم رو داخل Register های مشخص قرار میده

مفهوم:

Registers
│
├── شماره System Call

├── Argument 1
├── Argument 2
└──
│
▼
syscall
│
▼
Kernel

Kernel
وقتی وارد میشه شماره System Call رو بررسی میکنه تا بفهمه برنامه دقیقا چه درخواستی داشته
مثلا برنامه ممکنه درخواست خوندن اطلاعات از یه فایل رو داشته باشه
پس Kernel باید بفهمه این درخواست مربوط به کدوم System Call هست

Kernel
از کجا میفهمه چه System Call ای درخواست شده
اینجا یکی از چیزهای مهم System Call Number هست
هر System Call توی Linux یه شماره مشخص داره
البته این شماره ها به معماری و ABI وابسته هستن
یعنی نباید فکر کنیم شماره یه System Call روی همه سیستم ها یکیه

به صورت مفهومی:

System Call Number
+
Arguments
│
▼
syscall
│
▼
Kernel

Kernel
با استفاده از این اطلاعات مسیر مربوط به اون درخواست رو پیدا میکنه

Kernel
هنگام ورود چه کار میکنه
وقتی CPU وارد Kernel میشه Kernel باید درخواست برنامه رو بررسی کنه

به صورت ساده
User Code
│
▼
syscall
│
▼
Kernel Entry
│
▼
بررسی درخواست
│
▼
اجرای عملیات
│
▼
Return Value
│
▼
User Mode

Kernel
ممکنه چیزهایی مثل مجوز دسترسی و معتبر بودن بعضی اطلاعات رو بررسی کنه
مثلاً برنامه User Space نباید بتونه هر آدرس حافظه ای که دلش خواست رو بدون کنترل دستکاری کنه
سیستم عامل اینجا نقش یک مراقب رو بازی میکنه چون اگر هر برنامه ای میتونست هر کاری با منابع سیستم بکنه عملا سیستم عامل تبدیل میشد به چیزی که کاربردی نداره

Trap
فقط برای System Call نیست
اینجا یه نکته مهم داریم
وقتی میگیم Trap نباید فکر کنیم فقط برای System Call استفاده میشه
در سیستم عامل و معماری CPU انتقال کنترل به Kernel میتونه به دلایل مختلف اتفاق بیفته

مثلا:

System Call
Exception

بعضی رویدادهای سخت افزاری از مسیر Interrupt البته این مفاهیم دقیقا یکی نیستن و باید جدا از هم بررسی بشن

Exception چیه

Exception
وقتی اتفاق میفته که CPU موقع اجرای یه دستور با یه شرایط خاص روبرو بشه

مثلا:

برنامه
│
▼
اجرای دستور
│
▼
شرایط خاص
│
▼
Exception
│
▼
Kernel Handler
یکی از مثال های معروفش
Page Fault

فرض کنید برنامه به یه صفحه حافظه دسترسی پیدا کنه ولی اون صفحه در اون لحظه شرایط لازم برای دسترسی رو نداشته باشه
CPU
یه Page Fault ایجاد میکنه
بعد Kernel وارد ماجرا میشه و بررسی میکنه باید چه کاری انجام بشه

یه نکته مهم:

Page Fault
لزوما به معنی Crash نیست
ممکنه سیستم عامل بتونه اون رو کاملا عادی مدیریت کنه

مثلا صفحه موردنظر رو آماده کنه و برنامه دوباره ادامه بده

Interrupt
معمولا از طرف سخت افزار یا کنترل کننده سخت افزار ایجاد میشه مثلا یه دستگاه میخواد CPU رو از یه اتفاق باخبر کنه

به صورت ساده:

Hardware
│
▼
Interrupt
│
▼
CPU
│
▼
Kernel
مثلا یه عملیات I O تموم شده
سخت افزار میتونه با Interrupt به CPU خبر بده که عملیات تموم شده
بعد Kernel میتونه این اتفاق رو مدیریت کنه

تفاوت این سه مفهوم:

System Call
درخواست برنامه برای گرفتن یه سرویس از سیستم عامل

Exception
اتفاقی که در نتیجه اجرای دستور یا شرایط مربوط به CPU رخ میده
Interrupt
اعلام یه رویداد که معمولا از طرف سخت افزار میاد
Trap
هم یه اصطلاح معماری و سیستم عاملیه که برای انتقال کنترل به یه Handler استفاده میشه بسته به منبعی که میخونید ممکنه تعریف دقیق این اصطلاح کمی فرق داشته باشه پس بهتره فقط یه تعریف حفظ نکنید مهم اینه بفهمی چه اتفاقی باعث میشه CPU مسیر اجرا رو عوض کنه و وارد Handler مربوطه بشه

چرا این موضوع برای Reverse Engineering مهمه

اینجا موضوع برای ما جالب تر میشه
وقتی دارید یه باینری رو Reverse میکنید

ممکنه به دستورهایی مثل
syscall
برسی
یا رفتارهایی مربوط به Exception و Page Fault ببینید
اگر فقط اسمبلی رو حفظ کرده باشید میبینید
syscall
ولی اگر OS رو فهمیده باشید میفهمید پشت این دستور چه اتفاقی افتاده
User Code
↓
System Call
↓
CPU / Kernel Entry
↓
Kernel Handler
↓
Operation
↓
Return

یعنی فرق زیادی هست بین اینکه فقط یه دستور رو ببینید و اینکه بفهمید پشت اون دستور چه اتفاقی توی سیستم عامل میوفته

این مفاهیم پایه خیلی از چیزهاییه که بعدا توی OS و Reverse Engineering باهاشون سروکار داریم

@reverseengine
ReverseEngineering
Trap و ورود از User Mode به Kernel Mode تا اینجا فهمیدیم برنامه ها توی User Mode اجرا میشن و وقتی بخوان از سیستم عامل یه کار حساس بخوان از System Call استفاده میکنن ولی یه سوال مهم داریم CPU دقیقا چطوری از اجرای عادی برنامه توی User Mode میره داخل Kernel…
Trap

and Entering from User Mode to Kernel Mode

So far we have understood that programs run in User Mode and when they ask the operating system for a sensitive task, they use System Call

But we have an important question

How exactly does the CPU go from normal program execution in User Mode to Kernel Mode

Here the concept of Trap comes into play

Trap

means a controlled transition from normal program execution to Kernel, meaning the program is running in User Mode

User Mode
│
│ Service Request
▼
Trap
│
▼
Kernel Mode

After the Kernel has done its job, control returns to the program

What is the difference between System Call and Trap

These two are not the same

System Call

Actually, the program requests the operating system

For example, the program says I want to read from a file

or write something

or get memory

For example:

read()
write()
mmap()

But Trap is a mechanism that causes the CPU to transfer execution control to the Kernel

So very simply
Program
│
│ System Call
▼
Trap / System call entry
│
▼
Kernel

That is, the System Call tells what service I want

Trap

or the entry path causes the program to enter the Kernel

Example x86 64:

In Linux on the x86 64 architecture, a System Call is usually executed with the
syscall

command

Before executing this command, the program places the necessary information into specific registers

Concept:

Registers
│
├── System Call Number

├── Argument 1
├── Argument 2
└──
│
▼
syscall
│
▼
Kernel

Kernel

When it enters, it checks the System Call number to find out exactly what the program requested. For example, the program may request to read information from a file. So the Kernel needs to know which System Call this request is related to.

How does the Kernel know what System Call was requested? Here, one of the important things is the System Call Number. Each System Call in Linux has a specific number. Of course, these numbers depend on the architecture and ABI. There are
That is, we should not think that the number of a System Call is the same on all systems

Conceptually:

System Call Number
+
Arguments
│
▼
syscall
│
▼
Kernel


Kernel

Using this information, it finds the path to that request

Kernel

What does it do when it enters
When the CPU enters the Kernel, the Kernel must check the program request

Simply
User Code
│
▼
syscall
│
▼
Kernel Entry
│
▼
Check the request
│
▼
Execution of the operation
│
▼
Return Value
│
▼
User Mode

Kernel

It may check things like access permissions and the validity of some information

For example, a User Space program should not be able to manipulate any memory address it wants without control
The operating system plays the role of a watchdog here because if any program could do anything with system resources, the operating system would practically become useless

Trap

Is not just for System Calls
Here is an important point We have

When we say Trap, we should not think that it is only used for System Call
In the operating system and CPU architecture, control transfer to the Kernel can occur for various reasons

For example:

System Call

Exception

Some hardware events via Interrupt, of course, these concepts are not exactly the same and should be examined separately

What is Exception

Exception

It happens when the CPU encounters a special condition while executing an instruction

For example:

Program
│
▼
Execution of instruction
│
▼
Special conditions
│
▼
Exception
│
▼
Kernel Handler

One of its famous examples is

Page Fault

Suppose the program accesses a memory page, but that page does not have the necessary conditions for access at that moment

The CPU
creates a Page Fault

Then the Kernel enters the story and checks what should be done

An important point:

Page Fault

does not necessarily mean Crash

The operating system may be able to handle it completely normally

For example, it can prepare the desired page and the program can continue again Give

Interrupt

Usually generated by hardware or hardware controller, for example, a device wants to notify the CPU of an event

Simply: