PDA

View Full Version : جلوگیری از تغییر متغیر توسط کرکرها؟



arash_ebrahimi_nk
شنبه 03 فروردین 1392, 10:15 صبح
سلام آیا روشی یا روش هایی موثری برای جلوگیری از تغییر متغیر توسط کرکرها وجود داره؟

قفل نویسی با فایل لایسنس رو در نظر بگیرد...
تا جایی که من بلدم توی برنامه نویسی شماره سریال سخت افزار باید در داخل یک متغییر قرار داده بشه تا بتونیم از اون استفاده کنیم و حالا اگه کرکر بیاد یه فایل لایسنس بخره و بعد شماره سریال سخت افزار خودش رو در قالب یک عدد ثابت توی اون متغییر (در داخل فایل اجرایی) بازنویسی کنه، اونوقت میتونه برنامه رو با فایل لایسنس خودش توی همه سیستم ها قابل اجرا کنه...
برای مقاومت در برار تغییر متغیر چه کار میشه کرد؟

Nima NT
شنبه 03 فروردین 1392, 18:25 عصر
از رمزنگاری برای ذخیره اطلاعات استفاده کنید ، به عنوان مثال اطلاعات حساس Hash کنید
از پروتکتورها هم که قابلیت SDK دارن استفاده کنید تا بتونید در سورس کد برنامه بخش های خاص رو محافظت کنید

arash_ebrahimi_nk
یک شنبه 04 فروردین 1392, 11:32 صبح
از رمزنگاری برای ذخیره اطلاعات استفاده کنید ، به عنوان مثال اطلاعات حساس Hash کنید
از پروتکتورها هم که قابلیت SDK دارن استفاده کنید تا بتونید در سورس کد برنامه بخش های خاص رو محافظت کنید

در مورد رمزگذاری، من فکر میکنم اگه رمزنگاری هم بکنم همون متغیر رو کرکر میاد همیشه روی عددی که به خودش دادم (چه رمزنگاری شده چه نشده) فیکس میکنه و بعد فایل لایسنس خودش همه جا قابل استفاده میشه.
در مورد محافظت، من نمیخوام از محافظ های آماده استفاده کنم سعی دارم روش هاشون رو روی این قسمت پیاده کنم و متاسفانه روش های محدودی بلدم که موثر بودن یا نبودنش رو نمیدونم چقدر هست:

شماره سریال سخت افزار توسط یک تابع گرفته میشه مثلا به نام GetHardwareID و همونظور که میدونیم هر تابع یه نتیجه بازگشتی (Result) داره که کرکر میتونه اون رو عوض کنه و کل کار رو خراب کنه بنابراین باید از توابع خودم محافظت کنم.
در حالت خیلی ابتدایی میام متغیر رو به عنوان خروجی تابع به تابع میدم که متغیر اصلی از یک مقدار بازگشتی تابع تغذیه نشه بلکه در درون تابع تغییر بکنه و برسه به نقطه مورد نظر.برای مثال

GetHardwareID(out Id: string)
و از اونجایی که کاربر باید شماره سخت افزار رو برای منه برنامه نویس بفرسته تا بهش کُد فعالسازی بدم؛ نـمیتونه یه شماره الکی برام بفرسته، پس در نظر میگیرم که کرکر بخش گرفتن شماره سریال سخت افزار رو دست نمیزنه بلکه اجازه میده برنامه یه شماره سریال سخت افزاری بهش بده و اونوقت از این شماره سریال استفاده کنه.
پس نظر من اینه که محافظت از تابع GetHardwareID در همین حد کفایت میکنه. اما نظر من مهم نیست نظر یه کرکر مهمه.... اگه راهنمایی ای دارید دریغ نکنید لطفا...

خب حالا میرسیم به این خط مهم

Decrypt(License, GetHardwareID)
که کرکر این خط رو به این صورت تغییر میده

Decrypt(License, 'GX167BN1567723')
یعنی شماره سریال سخت افزار خودش رو به عنوان یک عدد ثابت به جای متغیر مورد نظر من توی خطی که من میخوام فایل لایسنس رو باز کنم قرار میده و به این صورت فایل لایسنسی که به خودش دادم همیشه و در همه سیستم های دیگه به راحتی و بدون چک کردن شماره سخت افزار، باز میشه و برنامه قابل استفاده میشه.
خب حالا باید از این خط محافظت کنم.
به نظر خودم دو روش کلی برای محافظت وجود داره
اول اینکه از طریق شرط های مختلف و تابع ReadProcessMemory این قسمت رو در نقاط مختلف برنامه (و حتی در داخل یک سری Threadهای محافظتی) چک کنم که آیا تغییر کرده یا نکرده (برای تشخیص کرک شدن یا نشدن برنامه). در این قسمت میخوام بپرسم که به غیر از ReadProcessMemory به چه کارهایی میشه کرد، شنیدم که CRC یه پروسه یا تابع رو هم میشه چک کرد و... اگه راهنمایی ای دارید دریغ نکنید لطفا...
دوم اینه که با استفاده از تابع ReadProcessMemory یا گرفتن مستقیم آدرس و مقدار هر خط اسمبلی در نقطه مورد نظر، بیایم مقدار بدست اومده رو در یک جای دیگه برنامه بعنوان محرک استفاده کنیم مثلا اگر تابع باز کردن فایل لایسنس در نقطه 5211FF00 قرار داشته باشه و چند خط بعدش مثلا در خط 5211FF26 که بخشی از کد بالا نوشته شده مقدار 3270FF رو داشته باشیم بیایم از این مقدار در یک جای دیگه برای باز کردن رمز یک چیز دیگه استفاده کنیم یا در یک جای پیچده تر از برنامه که ممکنه اعداد مختلفی چک بشه این عدد رو هم چک کنیم. حالا اگه کرکر کارش رو بدرستی انجام نده و فقط بیاد خط باز کردن فایل لایسنس رو عوض کنه برنامه به مشکل بر میخوره.
منتظر راهنمایی اساتید هستم.

arash_ebrahimi_nk
دوشنبه 12 فروردین 1392, 14:48 عصر
خب خودم کمی فکر کردم و کمی کد نوشتم.
توی همین تاپیک توضیح میدم ببینم نظر دوستان کرکر و کدنویس چیه...

خب اولین راهی پس از کمی فکر کردن به نظرم رسید این بود که تعداد دفعات استفاده از متغیر رو (در تابع مربوطه) بشمارم تا زمانیکه کرکر اومد یکی از اونها رو NOP کرد بتونم بفهمم بعدش کمی پیشرفته تر در موردش فکر کردم و کمی کد نوشتم که بصورت زیره:

unit MainFrmU;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, System.Win.Registry;

type
TMainFrm = class(TForm)
Button1: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

type
PByteArry = array of PByte;
var
MainFrm: TMainFrm;
iRunNo: Integer;


implementation

{$R *.dfm}

procedure RunNoCheckValid;
begin
MainFrm.Button1.Caption := 'Application Run Successfully';
//----
end;

procedure CheckRunNo;
var
Reg: TRegistry;
label
_End;
begin
Reg := TRegistry.Create;
// try
if Reg.OpenKey('Software\CheckRunNoApp', True) then
if Reg.ValueExists('RunNo') then
iRunNo := {Decrypt(}Reg.ReadInteger('RunNo'){)};
//MainFrmU.pas.48: iRunNo := {Decrypt(}Reg.ReadInteger('RunNo'){)};
//asm in delphi debuger
//005A838F BA04845A00 mov edx,$005a8404
//005A8394 8B45FC mov eax,[ebp-$04]
//005A8397 E87C44F4FF call TRegistry.ReadInteger
//005A839C A35CC35B00 mov [$005bc35c],eax <<<<<<<<<<<Important<Line<<

// finally
Reg.Free;
// end;

//check----------------------------------------------
// if iDate_Initiation > 100 then Exit;
asm
cmp DWORD PTR[iRunNo], $64;
jnle _End;
end;
//-----------------------------------------------------

RunNoCheckValid;

_End:

end;

function CountFuncUsage(const FuncAddress, InFunction: Pointer): Integer;

function CheckOpCode8(CodeByte: Byte): Boolean;
begin
Result := False;
case CodeByte of
$05,$0D,$15,$1D,$25,$2D,$35,$3D,$68,
$A0..$A3,$B8..$BF,$69,$C7,$80..$8F: Result := True;
end;
end;

var
pb: PByte;
pdwRelativeAddress: PDWORD;
dwCurAddress: DWORD;
S: string;
ProcEndCheck: Integer;
dwRelativeAddress: DWORD;
begin
ProcEndCheck := 0;
Result := 0;
pb := InFunction;
Repeat
if (pb^ = $E8) or (pb^ = $E9) then begin
pdwRelativeAddress := Pointer(DWord(pb)+1);
dwCurAddress := DWord(pb);
dwRelativeAddress := DWORD(pdwRelativeAddress^);
S := IntToHex(dwCurAddress + (dwRelativeAddress+5), -1);
if DWORD(FuncAddress) = DWORD(StrToInt('$'+S)) then
Inc(Result);
pb := Pointer(DWord(pb) + 4);
end;
pb := Pointer(DWord(pb) + 1);

if (pb^ = $C8) then ProcEndCheck := 6 else
if CheckOpCode8(pb^) then ProcEndCheck := 8 else
if (pb^ = $9A) or (pb^ = $EA) then ProcEndCheck := 12 else
Dec(ProcEndCheck, 2);
until ((pb^ = $C2) or (pb^ = $C3)) and (ProcEndCheck <= 0);
end;

function CountVarUsage(const VarAddress, InFunction: Pointer; var UsageAddresses: PByteArry): Integer;

function CheckOpCode8(CodeByte: Byte): Boolean;
begin
Result := False;
case CodeByte of
$05,$0D,$15,$1D,$25,$2D,$35,$3D,$68,$E8,$E9,
$A0..$A3,$B8..$BF,$69,$C7,$80..$8F: Result := True;
end;
end;

var
// aDisAsmCode: array of Byte;
p: PByte;
VarAddressByte: array [1..4] of Byte;
VarAddressStr, sTemp: string;
VarAddressFound: Integer;
ProcEndCheck: Integer;
begin
VarAddressStr := IntToHex(DWORD(VarAddress), 8);
sTemp := VarAddressStr[7] + VarAddressStr[8];
VarAddressByte[1] := StrToInt('$'+sTemp);
sTemp := VarAddressStr[5] + VarAddressStr[6];
VarAddressByte[2] := StrToInt('$'+sTemp);
sTemp := VarAddressStr[3] + VarAddressStr[4];
VarAddressByte[3] := StrToInt('$'+sTemp);
sTemp := VarAddressStr[1] + VarAddressStr[2];
VarAddressByte[4] := StrToInt('$'+sTemp);

ProcEndCheck := 0;
VarAddressFound := 0;
Result := 0;
P := InFunction;
// SetLength(aDisAsmCode, 1);
Repeat
if (P^ = VarAddressByte[1]) and (VarAddressFound <> 1) then
VarAddressFound := 1 else
if (P^ = VarAddressByte[2]) and (VarAddressFound = 1) then VarAddressFound := 2 else
if (P^ = VarAddressByte[3]) and (VarAddressFound = 2) then VarAddressFound := 3 else
if (P^ = VarAddressByte[4]) and (VarAddressFound = 3) then VarAddressFound := 4;
if VarAddressFound = 4 then begin
SetLength(UsageAddresses, Length(UsageAddresses)+1);
UsageAddresses[High(UsageAddresses)] := Pointer(DWORD(P)+1);
Inc(Result);
VarAddressFound := 0;
end;
// aDisAsmCode[High(aDisAsmCode)] := p^;
p := Pointer(DWord(p) + 1);
// SetLength(aDisAsmCode, Length(aDisAsmCode)+ 1);

if (P^ = $C8) then ProcEndCheck := 6 else
if CheckOpCode8(P^) then ProcEndCheck := 8 else
if (P^ = $9A) or (P^ = $EA) then ProcEndCheck := 12 else
Dec(ProcEndCheck, 2);
until ((p^ = $C2) or (p^ = $C3)) and (ProcEndCheck <= 0);
// aDisAsmCode[High(aDisAsmCode)] := $C3;

end;

procedure TMainFrm.Button1Click(Sender: TObject);
begin
CheckRunNo;
end;

procedure RemoveCodeInMemory(const CodeAddress: Pointer; CodeLength: Integer);
var
I: Integer;
P: Pointer;
// hProc: THandle;
buf : array[0..1] of PByte;
write: NativeUInt;
begin
P := CodeAddress;
for I := 0 to CodeLength do begin
GetMem(buf[0],1);
GetMem(buf[1],1);
buf[0]^ := $90; // NOP
buf[1]^ := $00;
// hProc := OpenProcess(PROCESS_ALL_ACCESS, False, GetCurrentProcessId);
WriteProcessMemory(GetCurrentProcess, P, buf[0], 1, write);
P := Pointer(DWORD(P)+1);
WriteProcessMemory(GetCurrentProcess, P, buf[1], 1, write);
P := Pointer(DWORD(P)+1);
FreeMem(buf[0]); // Resets buf.
FreeMem(buf[1]); // Resets buf.
end;
end;

procedure TMainFrm.Timer1Timer(Sender: TObject);
var
I: Integer;
UsageAddresses: PByteArry;
P: PByte;
label
_FCS,
_End;
begin
I := CountVarUsage(@iRunNo, @CheckRunNo, UsageAddresses);
if I <> 2 then goto _FCS;//(_FCS = Fucked-up Cracker Silently)

P := UsageAddresses[0];//Low(UsedAddress)];
P := Pointer(DWord(p) - 5);
if P^ <> $A3 then goto _FCS;
P := Pointer(DWord(p) - 5);
if P^ <> $E8 then goto _FCS;

P := UsageAddresses[1];
if P^ <> $64 then goto _FCS;
P := Pointer(DWord(p) + 1);
if P^ <> $7F then goto _FCS;
goto _End;

_FCS:
// App Cracked! we can do some dirty work.
RemoveCodeInMemory(@RunNoCheckValid, 50);//Remove 50 Bytes of DateCheckValid
Button1.Click;
//-----------------------
_End:
end;

end.

فرض کنید میخواهید تعداد دفعاتی که برنامه اجرا شده رو از رجیستری چک کنید و زمانیکه برنامه بیشتر از 100 بار اجرا شد دیگه اجازه اجرای برنامه رو به کاربر ندید یه جور محدودیت مثل محدودیت زمانی که در خیلی از برنامه های تجاری هم فک کنم استفاده میشه.

من یه تابع محافظتی نوشتم که داخل یه تایمر قرار داره (صد البته بهتره که کدهای محافظتی رو در بخش های مختلف برنامه پخش کرد). این تابع محافظتی تعداد دفعات استفاده از متغیر مهم (iRunNO) رو چک میکنه و بعضی از بایت های مهم کُد رو هم مورد بررسی قرار میده تا کرک شدن اون قسمت ها رو تشخیص بده.

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

ایده من شمردن تعداد دفعات استفاده از متغیر بود و بررسی بایت های کنار اونها که در نمونه کدها بالا پیاده سازی کردم.
باز برای محافظت از یک متغیر چه کارهایی میشه انجام داد؟؟ ایده ای دارید؟؟

نظر کرکرها در مورد کدهای بالا چیه؟؟

gholami146
سه شنبه 13 فروردین 1392, 00:20 صبح
خیلی خوب بود
ولی
1- مقدار شمارنده رو از کجا بدست اوردی (بینگو) پس این همه کدی که نوشتی فایده نداشت
2- تا به حال با برنامه چیت انجینگ کار کردی :
از این برنامه برای بدست آوردن مقادیر داخل بلاک مخصوص رم ، مربوط به یک پروسه مشخص استفاده میشه و قابلیت تغییر و در سطوح بالاتر کد اینجکشن و حتی پیدا کردن پوینتر ها رو داره
من تا بحال از این برنامه برای کرک خیلی از برنامه ها ی بزرگ استفاده کردم و جواب گرفتم
بر اساس تجربیات با ید عرض کنم مهم کد نویسی داخل بلاک نیست
چون اگه شما قول برنامه نویسی دنیا هم باشی بازهم برای چک کردن صحت مقادیر باید از دستورات شرطی استفاده کنی که کرکر ها فقط کافی مقادیر شرطی رو معکوس کنن پس بنظر من بهترین راه کد کردن تمامی سورس فایل اجرایی هست
چون تا زمانی که یک برنامه از حالت پک خارج نشه نمیشه به هیچ عنوان سورس اون رو تغییر داد
پس برید و دنبال ساخت یک پکر بسیار قدرتمند باشید ولو اینکه موقتی باشه چرا میگم موقتی احه من اعتقاد دارم که
اولا قفل باعث جلوگیری از عمل سرقت نمیشه بلکه باعث به تاخیر افتادن عملیات سرقت میشه
و ثانیا هر معمایی که توسط یک اسان طرح بشه بدون شک توسط یک انسان حل میشه

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

من فقط به شما مسیر رو نشون دادم امید وارم موفق باشید

Securebit
سه شنبه 13 فروردین 1392, 09:29 صبح
این یک سورس دیگه برای تشخیص تغییر در کد.



function CheckJnz(add:pointer; jnz:integer):boolean;
var
p: ^Byte;
begin
result:=false;
p:=add;
if p^=jnz then
begin
result:=true;
end;
end;
procedure ModifyCode(add:Dword);
var
lpBuffer: DWORD;
mbi: _MEMORY_BASIC_INFORMATION;
a: DWORD;
BaseAddr: Pointer;
hProcess: THandle;
begin
hProcess := OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId);
VirtualQuery(nil,mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
PAGE_READWRITE, // desired access protection
mbi.Protect // address of variable to get old protection
);
a := 0;
lpBuffer := 0;
BaseAddr := Pointer(add);
ReadProcessMemory(
hProcess,
BaseAddr, // address to start reading
@lpBuffer, // address of buffer to place read data
1, // number of bytes to read
a // address of number of bytes read
);
Dec(lpBuffer);
WriteProcessMemory(
hProcess,
BaseAddr,
@lpBuffer,
1,
a
);
VirtualProtect(
mbi.BaseAddress, // address of region of committed pages
mbi.RegionSize, // size of the region
mbi.Protect, // desired access protection
a // address of variable to get old protection
);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if CheckJnz(Pointer($0045C381),$75)=false then
begin
ModifyCode($0045C381);
end;
end;

arash_ebrahimi_nk
سه شنبه 13 فروردین 1392, 19:45 عصر
خیلی خوب بود
ولی
1- مقدار شمارنده رو از کجا بدست اوردی (بینگو) پس این همه کدی که نوشتی فایده نداشت
2- تا به حال با برنامه چیت انجینگ کار کردی :
از این برنامه برای بدست آوردن مقادیر داخل بلاک مخصوص رم ، مربوط به یک پروسه مشخص استفاده میشه و قابلیت تغییر و در سطوح بالاتر کد اینجکشن و حتی پیدا کردن پوینتر ها رو داره
من تا بحال از این برنامه برای کرک خیلی از برنامه ها ی بزرگ استفاده کردم و جواب گرفتم
بر اساس تجربیات با ید عرض کنم مهم کد نویسی داخل بلاک نیست
چون اگه شما قول برنامه نویسی دنیا هم باشی بازهم برای چک کردن صحت مقادیر باید از دستورات شرطی استفاده کنی که کرکر ها فقط کافی مقادیر شرطی رو معکوس کنن پس بنظر من بهترین راه کد کردن تمامی سورس فایل اجرایی هست
چون تا زمانی که یک برنامه از حالت پک خارج نشه نمیشه به هیچ عنوان سورس اون رو تغییر داد
پس برید و دنبال ساخت یک پکر بسیار قدرتمند باشید ولو اینکه موقتی باشه چرا میگم موقتی احه من اعتقاد دارم که
اولا قفل باعث جلوگیری از عمل سرقت نمیشه بلکه باعث به تاخیر افتادن عملیات سرقت میشه
و ثانیا هر معمایی که توسط یک اسان طرح بشه بدون شک توسط یک انسان حل میشه

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

من فقط به شما مسیر رو نشون دادم امید وارم موفق باشید

در مورد گزینه اول که گفتید متوجه نشدم.
منظورتون از مقدار شمارنده همون مقدار شمارش متغیر بود؟ (مثلا 2 بار متغیر در تابع استفاده شده بود و مقدار شمارش شده بود 2).
در مورد گزینه دوم برنامه Cheat Engine که فرمودید با برنامه ش قبلا یه بار کار کرده بودم اینطور که شما میفرمایید این برنامه روی یک متغیر خاص قفل میکنه و هر جایی که به این متغیر اشاره شده یا میشه رو پیدا میکنه.
ولی چون زیاد باهاش کار نکردم نمیدونم خوب نتونستم با صحبت های شما ارتباط برقرار کنم.
اگه امکان داره یه مثال از برنامه Cheat Engine برام بزنید و کمی من رو روشن کنید.
چون من الان دارم یه برنامه تجاری مینویسم واسه بازار جهانی که دو سه ماه مونده کارش تمام بشه و میخوام روش قفل هم بذارم، وقت نوشتن یه پروتکتور ندارم قصد دارم خودم یه قفل توش پیاده بکنم که کار کرک رو کمی سخت کنم و در نهایت از UPX (کمی سفارشی - که البته فقط شنیدم هنوز دنبال سفارشی کردنش نرفتم) برای پک کردنش استفاده کنم.
از اونجایی که باید به ملت شماره سریال یا لایسنس بفروشم نمیخوام هر کرکر آماتوری بتونه راحت با چند تا حرکت ساده (مثلاً با یه آنپکر آماده یا UPX -e و تغییر چند خط شرطی یا ثابت کردن مقدار یه متغیر) کرک برنامه ام رو بده بیرون.
لااقل اگه کسی میخواد کرک کنه مهندسی معکوس سرش بشه و بتونه بفهمه چطوری قفل رو نوشتم ارتباطات رو بفهمه، از سد چندین بخش محافظی به درستی رد بشه و محدودیت های مختلف رو که ممکنه در ابتدا معلوم نباشه رو تشخیص بده و....
البته در نظر دارم که چند تا CRACK ME مشابه با قفلی که مایلم بنویسم توی همین بخش قرار بدم تا اگه کرکری وقت داشت و خواست لطفی به من بکنه کمی با قفلم ور بره و ضعف هاش رو بهم بگه تا بتونم در حد امکان قفلم رو قوی بکنم.
از نظرات مفیدتون استفاده میبرم. از قبل تشکر میکنم.