برنامه نویسی

[EN] مقدمه ای بر فریدا: دستکاری حافظه در زمان واقعی با TypeScript (و DOOM)

Summarize this content to 400 words in Persian Lang
ترجمه: این مقاله به زبان پرتغالی نیز موجود است

مقدمه

روز دیگر، داشتم به‌روزرسانی‌ها را در وب‌سایت ابزاری به نام فریدا که دنبال می‌کنم، می‌خواندم، که همانطور که آنها توصیف می‌کنند، «Greasemonkey برای برنامه‌های بومی» است. به عبارت دیگر، این یک جعبه ابزار با API مخصوص به خود برای تجزیه و تحلیل، تعامل و دستکاری برنامه های در حال اجرا است و از چندین پلتفرم پشتیبانی می کند.

به هر حال، من با این به‌روزرسانی سپتامبر 2024 مواجه شدم که نه تنها برخی از ویژگی‌های جدید را اعلام کرد، بلکه یک مورد استفاده جالب را نیز به نمایش گذاشت. این شامل بازی Doom + Doom II بود که اخیرا منتشر شد. در این مثال، آنها ابزاری را نشان دادند که حافظه را اسکن می‌کند تا محل ذخیره‌سازی مهمات را بیابد و با سهولت قابل توجه، نوعی موتور تقلب را برای نگهداری مهمات بی‌نهایت ایجاد می‌کند. من دوست داشتم این مثال چقدر کاربردی بود و تصمیم گرفتم آن را در حالی که جزئیات هر مرحله را در اینجا توضیح می دهم، بازسازی کنم.

نصب فریدا

همانطور که قبلاً اشاره کردم، Frida برای چندین پلتفرم در دسترس است. در اینجا، من از ویندوز استفاده می کنم و از قبل پایتون را نصب کرده ام، بنابراین فرآیند نصب را با استفاده از pip ساده می کنم:

pip install frida-tools

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مثال استفاده

این فریدا-ابزار بسته دارای چندین ابزار برای تعامل با اکوسیستم فریدا است (با مراجعه به اسناد رسمی می توانید اطلاعات بیشتری کسب کنید).

یک مثال عملی استفاده است frida برای پیوست کردن مستقیم به یک برنامه در حال اجرا به عنوان مثال:

frida -n CalculatorApp.exe

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این فریدا را باز می کند و آن را به فرآیند نامیده شده متصل می کند CalculatorApp.exe، که ماشین حساب ویندوز است.

از این دستور جدید، می توانید از چندین تابع داخلی استفاده کنید و مستقیماً جاوا اسکریپت را برای دسترسی به حافظه برنامه، ثبت نام ها، وقفه ها و غیره فراخوانی کنید.

این برای تست‌های سریع عالی است، اما قدرت واقعی از ایجاد اسکریپت‌های شخصی شما، معروف به “عامل” ناشی می‌شود.

انتخاب زبان API

شما می توانید این اسکریپت های عامل را به زبان های مختلف بنویسید. فریدا از Python، JavaScript، C، Go، Swift و دیگران پشتیبانی می کند.

برای این مثال، من با جاوا اسکریپت یا حتی بهتر از آن، TypeScript که تکمیل خودکار و دسترسی سریع به اسناد را فراهم می کند، همانطور که در زیر نشان داده شده است، پیش می روم:

مستندسازی و تکمیل خودکار – برخی از بهترین چیزها در مورد IDE های مدرن (تا زمانی که هوش مصنوعی به وجود آمد)

تنظیم ساختار پروژه

مستندات فریدا برای جاوا اسکریپت، شبیه سازی مخزن زیر را برای شروع توسعه “عامل” خود پیشنهاد می کند: (https://github.com/oleavr/frida-agent-example). بنابراین، من آن را شبیه سازی می کنم، وابستگی ها را نصب می کنم و آن را در VS Code باز می کنم:

git clone https://github.com/oleavr/frida-agent-example.git doom_example
cd .\doom_example\
npm install
code .

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این مخزن برنامه نویسی در TypeScript و کامپایل جاوا اسکریپت را برای استفاده توسط فریدا ساده می کند. همراه با چند اسکریپت موجود است package.json، مانند pnpm watch، که تماشا و کامپایل می کند ./agent/index.ts فایل به صورت خودکار

با این حال، در آخرین نسخه‌های Frida، می‌تواند مستقیماً TypeScript ما را بخواند، بنابراین ما نیازی به کامپایل نداریم .js.

یک چیز دیگر قبل از اینکه ادامه دهیم

به احتمال زیاد، همانطور که در طول آزمایش های من اتفاق افتاد، مخزن دارای برخی وابستگی های قدیمی است. دویدن را فراموش نکنید npm update –save برای به روز رسانی وابستگی ها، به خصوص تایپ ها.

عامل اصلی

بیایید با همان مثال از پست اصلی شروع کنیم، با اضافه کردن برخی نظرات، تایپ کردن و تطبیق آن برای TypeScript:

نکته مهم: اگر از جاوا اسکریپت ساده برای عامل استفاده می کنید، می توانید مستقیماً هر تابع یا متغیری را که در اسکریپت اعلام شده است فراخوانی کنید. با این حال، هنگام استفاده از TypeScript، توابع و متغیرها به شی سراسری اضافه نمی شوند. شما باید آنها را به صادر کنید globalThis شی من این صادرات را در پایان فیلمنامه انجام می دهم.

let matches: NativePointer[] = [];

// Scans the process memory for a pattern
function scan(pattern: string | MatchPattern) {
const locations = new Set<string>();
for (const r of Process.enumerateMallocRanges()) {
for (const match of Memory.scanSync(r.base, r.size, pattern)) {
locations.add(match.address.toString());
}
}
matches = Array.from(locations).map(ptr);
console.log(‘Found’, matches.length, ‘matches’);
}

// Further filters results for those containing a specific value
function reduce(val: number) {
matches = matches.filter(location => location.readU32() === val);
console.log(‘Filtered down to:’);
console.log(JSON.stringify(matches));
}

// Converts a numeric value into a pattern for a 32-bit unsigned integer
function patternFromU32(val: number) {
return new MatchPattern(ptr(val).toMatchPattern().slice(0, 11));
}

globalThis[‘scan’] = scan;
globalThis[‘reduce’] = reduce;
globalThis[‘patternFromU32’] = patternFromU32;

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیوستن فریدا به بازی

حالا بیایید Frida را اجرا کنیم و آن را به بازی متصل کنیم.

frida -n doom_gog.exe -l agent/index.ts

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مطمئن شوید که بازی به همین صورت اجرا می شود frida می تواند فرآیند را با استفاده از -n گزینه اگر در مورد نام فرآیند مطمئن نیستید، استفاده کنید frida-ps برای فهرست کردن تمام فرآیندهای در حال اجرا

یافتن یک ارزش در حافظه (سوزن در انبار کاه)

در پست اصلی، نویسنده مکان حافظه را که تعداد مهمات در آن ذخیره شده است جستجو می کند. اما بیایید همه چیز را عوض کنیم و به دنبال جایی باشیم که سلامتی ما (یا “HP”، “زندگی” یا هر چیزی که شما آن را می نامید) در کجا ذخیره شده است.

امیدواریم این 100% یک عدد صحیح باشد…

در کنسول باز فریدا، ما را اجرا می کنیم scan تابع با الگوی عدد 100 به عنوان آرگومان.

[Local::doom_gog.exe ]-> scan(patternFromU32(100))
Found 8073 matches

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اوه، 8073 منطبق برای عدد 100! به نظر زیاد است، اما خیلی بد نیست. ما باید آن را محدود کنیم. ابتدا بیایید مقداری آسیب در بازی بگیریم:

آن بشکه پر از مایع سبز مرموز را بگیرید!

اکنون که سلامت ما به 67% کاهش یافته است، اجازه دهید نتایج خود را با استفاده از این فیلتر بیشتر کنیم reduce تابعی که در اسکریپت ایجاد کردیم:

[Local::doom_gog.exe ]-> reduce(67)
Filtered down to:
[“0x13144f4a02c”,”0x131032ed754″]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما با دو احتمال باقی مانده ایم. کدام یک است؟ صبر کن، متوجه می شوم…

با نوشیدن این معجون آبی که روی زمین پیدا کردم…

اکنون که سلامت ما به 68٪ رسیده است، بیایید دوباره نتایج را فیلتر کنیم:

[Local::doom_gog.exe ]-> reduce(68)
Filtered down to:
[“0x179b96a0d0c”,”0x179f5499984″]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هوم، هنوز دو آدرس داریم. این امکان وجود دارد که سلامت در دو مکان حافظه مختلف ذخیره شود یا نشان دهنده دو حالت به روز رسانی جداگانه باشد. شاید من نباید این را با از دست دادن و پس از آن دوباره به دست آوردن سلامتی آزمایش می کردم، اما اوه خوب – بیایید به آزمایش ادامه دهیم.

ایجاد نقاط نظارت پویا

بیایید با ایجاد یک تابع کمکی برای تنظیم نقاط مراقبت، مقاله را ادامه دهیم. این تابع آدرس حافظه، اندازه داده های ذخیره شده و نوع شرط شکست را به عنوان آرگومان می گیرد. استفاده خواهیم کرد w مشخص کنید که فقط باید فعال شود write شرایط

فراموش نکنید که تابع را با globalThis در پایان

function installWatchpoint(address: NativePointerValue, size: number | UInt64, conditions: HardwareWatchpointConditions) {
const thread = Process.enumerateThreads()[0];

Process.setExceptionHandler(e => {
console.log(`\n=== Handler got ${e.type} exception at ${e.context.pc}`);

if (Process.getCurrentThreadId() === thread.id &&
[‘breakpoint’, ‘single-step’].includes(e.type)) {
thread.unsetHardwareWatchpoint(0);
console.log(‘\tDisabled hardware watchpoint’);
return true;
}

console.log(‘\tPassing to application’);
return false;
});

thread.setHardwareWatchpoint(0, address, size, conditions);

console.log(‘Ready’);
}

globalThis[‘installWatchpoint’] = installWatchpoint;

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شناسایی مکان کد (از طریق Watchpoints)

اکنون هدف این است که بفهمیم در کد بازی محاسبه خسارت در کجا انجام می شود. بیایید با ثبت نقطه نظارت برای آدرس اول شروع کنیم:

[Local::doom_gog.exe ]-> installWatchpoint(ptr(‘0x179b96a0d0c’), 4, ‘w’)
Ready

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس از قرار دادن نقطه مراقبت، سعی کردم آسیب بیشتری وارد کنم، اما هیچ اتفاقی نیفتاد. با این حال، وقتی معجون را برداشتم، کد فعال شد:

[Local::doom_gog.exe ]->
=== Handler got single-step exception at 0x7ff6332f7b08
Disabled hardware watchpoint

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این نشان می دهد که اولین آدرسی که پیدا کردیم احتمالاً در زمینه بازیابی سلامت استفاده می شود. از آنجایی که ما بیشتر به زمینه آسیب علاقه مندیم، آدرس دیگر را آزمایش کردم. این بار زمانی که من در بازی آسیب دیدم فعال شد:

[Local::doom_gog.exe ]-> installWatchpoint(ptr(‘0x179f5499984’), 4, ‘w’)
Ready
[Local::doom_gog.exe ]->
=== Handler got single-step exception at 0x7ff6332fafcc
Disabled hardware watchpoint

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیدا کردن محل دقیق اجرای کد

با آدرسی که تغییر مقدار رخ می دهد، 0x179f5499984، بیایید افست نسبی آن را نسبت به پایه برنامه پیدا کنیم.

[Local::doom_gog.exe ]-> healthCode = ptr(‘0x7ff6332fafcc’)
“0x7ff6332fafcc”
[Local::doom_gog.exe ]-> healthModule = Process.getModuleByAddress(healthCode)
{
“base”: “0x7ff633020000”,
“name”: “doom_gog.exe”,
“path”: “F:\\GOG Games\\DOOM + DOOM II\\doom_gog.exe”,
“size”: 15450112
}
[Local::doom_gog.exe ]-> offset = healthCode.sub(healthModule.base)
“0x2dafcc”

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون می دانیم که آدرس در آدرس است doom_gog.exe ماژول و موقعیت آن نسبت به پایه برنامه است 0x2dafcc.

بررسی آدرس در دیاسمبلر

برای تجزیه و تحلیل این بخش و تعیین عملکرد آن، می‌توانیم از هر اشکال‌زدایی با پشتیبانی disassembler استفاده کنیم. می توانید از ابزارهایی مانند radare2، IDA یا GHidra استفاده کنید. در این مثال، من از x64dbg استفاده کردم و با فشار دادن Ctrl+G و وارد کردن عبارت، به آدرس صحیح پیمایش کردم:

doom_gog.exe+0x2dafcc

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

… که جداسازی قطعات زیر را تولید کرد:

در اینجا، دستور اجرا را بلافاصله پس از کاهش مقدار سلامت مشاهده می کنیم. ما می توانیم این فرضیه را مطرح کنیم [rsi+24] ارزش سلامتی ما را حفظ می کند و r15d نشان دهنده خسارت دریافتی است. بیایید فعلاً روی خسارت دریافتی تمرکز کنیم.

ایجاد رهگیر برای دریافت دینامیک داده ها

به دنبال مثال از مقاله اصلی، اجازه دهید یک رهگیر برای این دستورالعمل ایجاد کنیم تا میزان آسیب دریافتی را ثبت کنیم.

از آنجایی که این تابع مستقیماً در اسکریپت اجرا می شود، نیازی به اضافه کردن آن نداریم globalThis.

Interceptor.attach(Module.getBaseAddress(‘doom_gog.exe’).add(0x2dafcc), function () {
const context = this.context as X64CpuContext;
const damageReceived = context.r15;
console.log(`Damage Received: ${damageReceived}`);
});

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس از ذخیره اسکریپت، اگر همچنان به آسیب زدن در بازی ادامه دهیم، کنسول فریدا پیام هایی را چاپ می کند که میزان آسیب را نشان می دهد:

در حال حاضر، دستکاری نهایی!

اگر بخواهیم، ​​مانند مثال اصلی، آسیب را به طور کامل نادیده بگیریم، فقط باید رهگیر را به دستورالعمل قبلی منتقل کنیم (آدرس 0x2dafc8) و به صورت پویا مقدار ثبات را صفر کنید!

Interceptor.attach(Module.getBaseAddress(‘doom_gog.exe’).add(0x2dafc8), function () {
const context = this.context as X64CpuContext;
const damageReceived = context.r15;
console.log(`Damage Received: ${damageReceived.toUInt32()}, but we will just ignore it`);
context.r15 = ptr(0);
});

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

چه کسی به IDDQD نیاز دارد؟

نتیجه گیری

این تمرین برای کشف آنچه ابزاری مانند فریدا می تواند ارائه دهد فوق العاده بود. این روش پویا برای مدیریت داده ها نیز برای زبان آموزان بسیار جالب است. مثال با Doom به دلیل سادگی کامل بود. به خاطر داشته باشید که ایجاد یک تقلب بازی مانند این صرفاً “خراش دادن سطح” احتمالات است. ابزارهایی مانند Frida می توانند – و باید – به طور گسترده برای کارهایی مانند تجزیه و تحلیل بدافزار، مطالعات رفتار نرم افزار، ارزیابی های امنیتی و موارد دیگر مورد استفاده قرار گیرند.

ترجمه: این مقاله به زبان پرتغالی نیز موجود است

مقدمه

روز دیگر، داشتم به‌روزرسانی‌ها را در وب‌سایت ابزاری به نام فریدا که دنبال می‌کنم، می‌خواندم، که همانطور که آنها توصیف می‌کنند، «Greasemonkey برای برنامه‌های بومی» است. به عبارت دیگر، این یک جعبه ابزار با API مخصوص به خود برای تجزیه و تحلیل، تعامل و دستکاری برنامه های در حال اجرا است و از چندین پلتفرم پشتیبانی می کند.

به هر حال، من با این به‌روزرسانی سپتامبر 2024 مواجه شدم که نه تنها برخی از ویژگی‌های جدید را اعلام کرد، بلکه یک مورد استفاده جالب را نیز به نمایش گذاشت. این شامل بازی Doom + Doom II بود که اخیرا منتشر شد. در این مثال، آنها ابزاری را نشان دادند که حافظه را اسکن می‌کند تا محل ذخیره‌سازی مهمات را بیابد و با سهولت قابل توجه، نوعی موتور تقلب را برای نگهداری مهمات بی‌نهایت ایجاد می‌کند. من دوست داشتم این مثال چقدر کاربردی بود و تصمیم گرفتم آن را در حالی که جزئیات هر مرحله را در اینجا توضیح می دهم، بازسازی کنم.


نصب فریدا

همانطور که قبلاً اشاره کردم، Frida برای چندین پلتفرم در دسترس است. در اینجا، من از ویندوز استفاده می کنم و از قبل پایتون را نصب کرده ام، بنابراین فرآیند نصب را با استفاده از pip ساده می کنم:

pip install frida-tools
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


مثال استفاده

این فریدا-ابزار بسته دارای چندین ابزار برای تعامل با اکوسیستم فریدا است (با مراجعه به اسناد رسمی می توانید اطلاعات بیشتری کسب کنید).

یک مثال عملی استفاده است frida برای پیوست کردن مستقیم به یک برنامه در حال اجرا به عنوان مثال:

frida -n CalculatorApp.exe
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این فریدا را باز می کند و آن را به فرآیند نامیده شده متصل می کند CalculatorApp.exe، که ماشین حساب ویندوز است.

تصویری که اعدام فریدا را نشان می دهد

از این دستور جدید، می توانید از چندین تابع داخلی استفاده کنید و مستقیماً جاوا اسکریپت را برای دسترسی به حافظه برنامه، ثبت نام ها، وقفه ها و غیره فراخوانی کنید.

این برای تست‌های سریع عالی است، اما قدرت واقعی از ایجاد اسکریپت‌های شخصی شما، معروف به “عامل” ناشی می‌شود.


انتخاب زبان API

شما می توانید این اسکریپت های عامل را به زبان های مختلف بنویسید. فریدا از Python، JavaScript، C، Go، Swift و دیگران پشتیبانی می کند.

برای این مثال، من با جاوا اسکریپت یا حتی بهتر از آن، TypeScript که تکمیل خودکار و دسترسی سریع به اسناد را فراهم می کند، همانطور که در زیر نشان داده شده است، پیش می روم:

تصویر پشتیبانی از اسناد TypeScript در VS Code را نشان می دهد

مستندسازی و تکمیل خودکار – برخی از بهترین چیزها در مورد IDE های مدرن (تا زمانی که هوش مصنوعی به وجود آمد)

تنظیم ساختار پروژه

مستندات فریدا برای جاوا اسکریپت، شبیه سازی مخزن زیر را برای شروع توسعه “عامل” خود پیشنهاد می کند: (https://github.com/oleavr/frida-agent-example). بنابراین، من آن را شبیه سازی می کنم، وابستگی ها را نصب می کنم و آن را در VS Code باز می کنم:

git clone https://github.com/oleavr/frida-agent-example.git doom_example
cd .\doom_example\
npm install
code .
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این مخزن برنامه نویسی در TypeScript و کامپایل جاوا اسکریپت را برای استفاده توسط فریدا ساده می کند. همراه با چند اسکریپت موجود است package.json، مانند pnpm watch، که تماشا و کامپایل می کند ./agent/index.ts فایل به صورت خودکار

با این حال، در آخرین نسخه‌های Frida، می‌تواند مستقیماً TypeScript ما را بخواند، بنابراین ما نیازی به کامپایل نداریم .js.


یک چیز دیگر قبل از اینکه ادامه دهیم

به احتمال زیاد، همانطور که در طول آزمایش های من اتفاق افتاد، مخزن دارای برخی وابستگی های قدیمی است. دویدن را فراموش نکنید npm update --save برای به روز رسانی وابستگی ها، به خصوص تایپ ها.


عامل اصلی

بیایید با همان مثال از پست اصلی شروع کنیم، با اضافه کردن برخی نظرات، تایپ کردن و تطبیق آن برای TypeScript:

نکته مهم: اگر از جاوا اسکریپت ساده برای عامل استفاده می کنید، می توانید مستقیماً هر تابع یا متغیری را که در اسکریپت اعلام شده است فراخوانی کنید. با این حال، هنگام استفاده از TypeScript، توابع و متغیرها به شی سراسری اضافه نمی شوند. شما باید آنها را به صادر کنید globalThis شی من این صادرات را در پایان فیلمنامه انجام می دهم.

let matches: NativePointer[] = [];

// Scans the process memory for a pattern
function scan(pattern: string | MatchPattern) {
  const locations = new Set<string>();
  for (const r of Process.enumerateMallocRanges()) {
    for (const match of Memory.scanSync(r.base, r.size, pattern)) {
      locations.add(match.address.toString());
    }
  }
  matches = Array.from(locations).map(ptr);
  console.log('Found', matches.length, 'matches');
}

// Further filters results for those containing a specific value
function reduce(val: number) {
  matches = matches.filter(location => location.readU32() === val);
  console.log('Filtered down to:');
  console.log(JSON.stringify(matches));
}

// Converts a numeric value into a pattern for a 32-bit unsigned integer
function patternFromU32(val: number) {
  return new MatchPattern(ptr(val).toMatchPattern().slice(0, 11));
}

globalThis['scan'] = scan;
globalThis['reduce'] = reduce;
globalThis['patternFromU32'] = patternFromU32;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


پیوستن فریدا به بازی

حالا بیایید Frida را اجرا کنیم و آن را به بازی متصل کنیم.

frida -n doom_gog.exe -l agent/index.ts
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مطمئن شوید که بازی به همین صورت اجرا می شود frida می تواند فرآیند را با استفاده از -n گزینه اگر در مورد نام فرآیند مطمئن نیستید، استفاده کنید frida-ps برای فهرست کردن تمام فرآیندهای در حال اجرا

تصویری که اعدام فریدا را نشان می دهد


یافتن یک ارزش در حافظه (سوزن در انبار کاه)

در پست اصلی، نویسنده مکان حافظه را که تعداد مهمات در آن ذخیره شده است جستجو می کند. اما بیایید همه چیز را عوض کنیم و به دنبال جایی باشیم که سلامتی ما (یا “HP”، “زندگی” یا هر چیزی که شما آن را می نامید) در کجا ذخیره شده است.

تصویر بازی Doom

امیدواریم این 100% یک عدد صحیح باشد…

در کنسول باز فریدا، ما را اجرا می کنیم scan تابع با الگوی عدد 100 به عنوان آرگومان.

[Local::doom_gog.exe ]-> scan(patternFromU32(100))
Found 8073 matches
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اوه، 8073 منطبق برای عدد 100! به نظر زیاد است، اما خیلی بد نیست. ما باید آن را محدود کنیم. ابتدا بیایید مقداری آسیب در بازی بگیریم:

بازیکن به یک بشکه شلیک می کند و باعث انفجار آن می شود

آن بشکه پر از مایع سبز مرموز را بگیرید!

اکنون که سلامت ما به 67% کاهش یافته است، اجازه دهید نتایج خود را با استفاده از این فیلتر بیشتر کنیم reduce تابعی که در اسکریپت ایجاد کردیم:

[Local::doom_gog.exe ]-> reduce(67)
Filtered down to:
["0x13144f4a02c","0x131032ed754"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما با دو احتمال باقی مانده ایم. کدام یک است؟ صبر کن، متوجه می شوم…

بازیکن یک معجون آبی برمی دارد

با نوشیدن این معجون آبی که روی زمین پیدا کردم…

اکنون که سلامت ما به 68٪ رسیده است، بیایید دوباره نتایج را فیلتر کنیم:

[Local::doom_gog.exe ]-> reduce(68)
Filtered down to:
["0x179b96a0d0c","0x179f5499984"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هوم، هنوز دو آدرس داریم. این امکان وجود دارد که سلامت در دو مکان حافظه مختلف ذخیره شود یا نشان دهنده دو حالت به روز رسانی جداگانه باشد. شاید من نباید این را با از دست دادن و پس از آن دوباره به دست آوردن سلامتی آزمایش می کردم، اما اوه خوب – بیایید به آزمایش ادامه دهیم.


ایجاد نقاط نظارت پویا

بیایید با ایجاد یک تابع کمکی برای تنظیم نقاط مراقبت، مقاله را ادامه دهیم. این تابع آدرس حافظه، اندازه داده های ذخیره شده و نوع شرط شکست را به عنوان آرگومان می گیرد. استفاده خواهیم کرد w مشخص کنید که فقط باید فعال شود write شرایط

فراموش نکنید که تابع را با globalThis در پایان

function installWatchpoint(address: NativePointerValue, size: number | UInt64, conditions: HardwareWatchpointConditions) {
    const thread = Process.enumerateThreads()[0];

    Process.setExceptionHandler(e => {
      console.log(`\n=== Handler got ${e.type} exception at ${e.context.pc}`);

      if (Process.getCurrentThreadId() === thread.id &&
          ['breakpoint', 'single-step'].includes(e.type)) {
        thread.unsetHardwareWatchpoint(0);
        console.log('\tDisabled hardware watchpoint');
        return true;
      }

      console.log('\tPassing to application');
      return false;
    });

    thread.setHardwareWatchpoint(0, address, size, conditions);

    console.log('Ready');
}

globalThis['installWatchpoint'] = installWatchpoint;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


شناسایی مکان کد (از طریق Watchpoints)

اکنون هدف این است که بفهمیم در کد بازی محاسبه خسارت در کجا انجام می شود. بیایید با ثبت نقطه نظارت برای آدرس اول شروع کنیم:

[Local::doom_gog.exe ]-> installWatchpoint(ptr('0x179b96a0d0c'), 4, 'w')
Ready
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس از قرار دادن نقطه مراقبت، سعی کردم آسیب بیشتری وارد کنم، اما هیچ اتفاقی نیفتاد. با این حال، وقتی معجون را برداشتم، کد فعال شد:

[Local::doom_gog.exe ]->
=== Handler got single-step exception at 0x7ff6332f7b08
        Disabled hardware watchpoint
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این نشان می دهد که اولین آدرسی که پیدا کردیم احتمالاً در زمینه بازیابی سلامت استفاده می شود. از آنجایی که ما بیشتر به زمینه آسیب علاقه مندیم، آدرس دیگر را آزمایش کردم. این بار زمانی که من در بازی آسیب دیدم فعال شد:

[Local::doom_gog.exe ]-> installWatchpoint(ptr('0x179f5499984'), 4, 'w')
Ready
[Local::doom_gog.exe ]->
=== Handler got single-step exception at 0x7ff6332fafcc
        Disabled hardware watchpoint
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


پیدا کردن محل دقیق اجرای کد

با آدرسی که تغییر مقدار رخ می دهد، 0x179f5499984، بیایید افست نسبی آن را نسبت به پایه برنامه پیدا کنیم.

[Local::doom_gog.exe ]-> healthCode = ptr('0x7ff6332fafcc')
"0x7ff6332fafcc"
[Local::doom_gog.exe ]-> healthModule = Process.getModuleByAddress(healthCode)
{
    "base": "0x7ff633020000",
    "name": "doom_gog.exe",
    "path": "F:\\GOG Games\\DOOM + DOOM II\\doom_gog.exe",
    "size": 15450112
}
[Local::doom_gog.exe ]-> offset = healthCode.sub(healthModule.base)
"0x2dafcc"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون می دانیم که آدرس در آدرس است doom_gog.exe ماژول و موقعیت آن نسبت به پایه برنامه است 0x2dafcc.


بررسی آدرس در دیاسمبلر

برای تجزیه و تحلیل این بخش و تعیین عملکرد آن، می‌توانیم از هر اشکال‌زدایی با پشتیبانی disassembler استفاده کنیم. می توانید از ابزارهایی مانند radare2، IDA یا GHidra استفاده کنید. در این مثال، من از x64dbg استفاده کردم و با فشار دادن Ctrl+G و وارد کردن عبارت، به آدرس صحیح پیمایش کردم:

doom_gog.exe+0x2dafcc
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

… که جداسازی قطعات زیر را تولید کرد:

آدرس نمایش داده شده در x64dbg

در اینجا، دستور اجرا را بلافاصله پس از کاهش مقدار سلامت مشاهده می کنیم. ما می توانیم این فرضیه را مطرح کنیم [rsi+24] ارزش سلامتی ما را حفظ می کند و r15d نشان دهنده خسارت دریافتی است. بیایید فعلاً روی خسارت دریافتی تمرکز کنیم.


ایجاد رهگیر برای دریافت دینامیک داده ها

به دنبال مثال از مقاله اصلی، اجازه دهید یک رهگیر برای این دستورالعمل ایجاد کنیم تا میزان آسیب دریافتی را ثبت کنیم.

از آنجایی که این تابع مستقیماً در اسکریپت اجرا می شود، نیازی به اضافه کردن آن نداریم globalThis.

Interceptor.attach(Module.getBaseAddress('doom_gog.exe').add(0x2dafcc), function () {
    const context = this.context as X64CpuContext;
    const damageReceived = context.r15;
    console.log(`Damage Received: ${damageReceived}`);
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس از ذخیره اسکریپت، اگر همچنان به آسیب زدن در بازی ادامه دهیم، کنسول فریدا پیام هایی را چاپ می کند که میزان آسیب را نشان می دهد:

آسیب در کنسول نمایش داده می شود


در حال حاضر، دستکاری نهایی!

اگر بخواهیم، ​​مانند مثال اصلی، آسیب را به طور کامل نادیده بگیریم، فقط باید رهگیر را به دستورالعمل قبلی منتقل کنیم (آدرس 0x2dafc8) و به صورت پویا مقدار ثبات را صفر کنید!

Interceptor.attach(Module.getBaseAddress('doom_gog.exe').add(0x2dafc8), function () {
    const context = this.context as X64CpuContext;
    const damageReceived = context.r15;
    console.log(`Damage Received: ${damageReceived.toUInt32()}, but we will just ignore it`);
    context.r15 = ptr(0);
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

انیمیشن بازیکن آسیب می بیند اما ارزش سلامتی آن کاهش نمی یابد

چه کسی به IDDQD نیاز دارد؟

نتیجه گیری

این تمرین برای کشف آنچه ابزاری مانند فریدا می تواند ارائه دهد فوق العاده بود. این روش پویا برای مدیریت داده ها نیز برای زبان آموزان بسیار جالب است. مثال با Doom به دلیل سادگی کامل بود. به خاطر داشته باشید که ایجاد یک تقلب بازی مانند این صرفاً “خراش دادن سطح” احتمالات است. ابزارهایی مانند Frida می توانند – و باید – به طور گسترده برای کارهایی مانند تجزیه و تحلیل بدافزار، مطالعات رفتار نرم افزار، ارزیابی های امنیتی و موارد دیگر مورد استفاده قرار گیرند.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا