Buffer Overflow Part(1)
က်ေနာ္တို့ stack base buffer overflow အေျကာင္းေျပာေတာ့မယ္ဆိုရင္ assembly language နဲ့ stack ဘယ္လိုအလုပ္လုပ္လဲဆိုတာကို အေျခခံအေနနဲ့သိထားရပါမယ္။assembly ကိုေလ့လာမယ္ဆိုရင္အခ်ိန္ေတာ္ေတာ္ေပးရပါမယ္။ အခုက်ေနာ္တို့ ေလ့လာခ်င္တာက exploit development ျဖစ္တဲ့အတြက္ assembly ကို registers ေတြေလာက္ပဲေျပာပါမယ္။ program တစ္ခုစျပီး run ျပီဆိုတာနဲ့ memory ကိုအနည္းနဲ့အမ်ားေတာ့ အသံုးျပုျကပါတယ္။ အဲ့ အသံုးျပုတဲ့ process memory မွာ major component သံုးခုရွိပါတယ္။ အဲ့ဒါေတြကေတာ့
- code segment(processor က execute လုပ္မဲ့ instructions ေတြ)
- data segment (global variables ေတြ, dynamic buffer ေတြ)
- stack segment တို့ပဲျဖစ္ပါတယ္။
- EAX - အေပါင္း၊ အနုတ္၊ compare စတဲ့ calculation ေတြလုပ္ရာမွာအသံုးမ်ားပါတယ္။
- EBX - general purpose မရွိဘူး၊ data ေတြ store လုပ္ဖို့သံုးတယ္။
- ECX - counter ျဖစ္ျပီး loop ေတြမွာ အမ်ားဆံုးအသံုးျပုပါတယ္။
- EDX - EAX လိုပါပဲ။ အထူးသျဖင့္ အေျမွာက္၊ အစား calculation ေတြမွာသံုးပါတယ္။
- ESP - stack pointer ျဖစ္ျပီး stack ရဲ့ top ကိုညြွန္ျပပါတယ္။
- EBP - base pointer, parameter value ေတြကို pass လုပ္တဲ့ေနရာမွာသံုးတယ္။
- ESI - source index, input data ေတြရဲ့ location ကို hold လုပ္ပါတယ္
- EDI - destination index, data operation ေတြရဲ့ result ေတြကို ဘယ္မွာသိမ္းမလဲဆိုတာ point လုပ္ေပးပါတယ္။
- EIP - instruction pointer, ေနာက္လုပ္မဲ့ instructions ေတြကိုညြွန္ျပတဲ့ေကာင္ျဖစ္ပါတယ္။
Stack
program တစ္ခုစျပီးအလုပ္လုပ္ျပီဆိုတာနဲ့ OS က stack ကို memory ေပါ္မွာ allocate လုပ္တယ္။
အဲ့ program ျပီးျပီဆိုတာနဲ့ stack ကို clear လုပ္ပစ္တယ္။ stack ကို functions ေတြမွာ data/arguments ေတြ pass လုပ္ဖို့၊ local variables ေတြကို store လုပ္ဖို့နဲ့ အျကာျကီးသိမ္းထားစရာမလိုတဲ့ information ေတြကို store လုပ္ထားဖို့သံုးပါတယ္။ stack က LIFO(last in first out) အေနနဲ့အလုပ္လုပ္ျပီး higher memory address ကေန lower memory address ကို decrease ျဖစ္သြားတယ္။ ဆိုလိုခ်င္တာကေတာ့ grow downward ေပါ့ဗ်ာ။ stack pointer (ESP) က stack ရဲ့ top ကိုအျမဲျပပါတယ္။ stack မွာ PUSH နဲ့ POP ဆိုျပီးေတာ့ operation နွစ္ခုရွိတယ္။ PUSH ကေတာ့ stack ထဲကို data ေတြထည့္တဲ့ေနရာမွာသံုးျပီး POP ကိုေတာ့ stack ထဲက data ေတြျပန္ထုတ္တဲ့ေနရာမွာသံုးပါတယ္။ PUSH လုပ္ရင္ ESP က decrease ျဖစ္ျပီးေတာ့ POP လုပ္တဲ့ အခါမွာေတာ့ ESP က increase ျဖစ္ပါတယ္။ ရွင္းရွင္းေျပာရရင္ေတာ့ stack ထဲကို information တစ္ခုခု PUSH လုပ္လိုက္တာနဲ့ ESP က decrease ျဖစ္ျပီေတာ့ lower memory address ကိုညြွန္ျပတဲ့သေဘာေပါ့ဗ်ာ။ အဲ့ေတာ့ function တစ္ခုေခါ္
လိုက္ျပီဆိုတာနဲ့ အဲ့ function မွာပါတဲ့ parameters ေတြကို stack ေပါ္ကို push လုပ္တယ္။ျပီးရင္ (EBP,EIP) ကိုပါ save ျပီး stack ေပာ္ကို push လုပ္ခဲ့တယ္။ function ျပီးလို့ return ျပန္တဲ့အခါ save ထားတဲ့ EIP value ကို POP လုပ္ျပီး လက္ရွိ EIP ထဲကိုျပန္ထည့္တယ္။ ျပီးရင္ေတာ့ ပံုမွန္အတိုင္းပဲဆက္လုပ္သြားတယ္။နည္းနည္းေလးေတာ့ရွုပ္သြားျပီထင္တယ္။ ပိုျပီးေတာ့ ရွုပ္ေအာင္ example program ေလးတစ္ခုနဲ့ေလ့လာျကည့္ျကရေအာင္ေလ။
ေနာက္တာပါဗ်ာ ။ program ေလးနဲ့တြဲျကည့္ေတာ့ ပိုနားလည္လြယ္တာေပါ့။ အဲ့ေတာ့ ေအာက္က C code ေလးကိုျကည့္ျကည့္ရေအာင္ဗ်ာ။
vuln.c
#include <stdio.h>
#include <string.h>
void do_something(char **buffer)
{
char MyVar[128];
strcpy(MyVar,buffer);
}
void main(int argc, char **argv)
{
do_something(argv[1]);
}
ဒီ code အရဆိုရင္ do_something ဆိုတဲ့ function တစ္ခုရွိမယ္။ main function ပါမယ္။ main ရဲ့အလုပ္က argument အေနနဲ့၀င္လာတဲ့ input ကို (example: vuln.exe AAAA) do_something function ကိုပို့ေပးလိုက္တယ္။ do_something က main က pass လုပ္ေပးလိုက္တဲ့ value ကို 128 bytes ရွိတဲ့ MyVar ဆိုတဲ့ variable ထဲကို strcpy() function ကိုသံုးျပီးေတာ့ copy ကူးထည့္တယ္။ဒီေလာက္ဆိုရင္ က်ေနာ္တို့ code ရဲ့ general အလုပ္လုပ္ပံုကိုအျကမ္းဖ်င္းသိျပီ။ ဒီ program ကုိ stack ေပါ္မွာ ဘယ္လိုအလုပ္လုပ္လဲဆိုတာ ဆက္ျကည့္ျကည့္ရေအာင္။
program ကို run လိုက္တာနဲ့ parent stack ေပါ္မွာ stack frame တစ္ခုကို create လုပ္တယ္။ ESP က အသစ္လုပ္လိုက္တဲ့ stack ရဲ့ higher memory address ကို point ေနတယ္။
do_something() ကိုမေခာ္ခင္ main ထဲက argv[1] အတြက္ pointer ကို stack မွာအရင္ PUSH တယ္။
ျပီးသြားမွ do_something() ကိုေခါ္တယ္။ function call ကိုလုပ္ရင္ CALL instruction က current instruction pointer(EIP) ကို stack ေပါ္အရင္ PUSH လုပ္တယ္။အဲ့ဒါမွ do_something ျပီးသြားတဲ့အခါ ဘယ္ကို return ျပန္ရမလဲသိမွာျဖစ္တယ္။
ျပီးတဲ့အခါမွာ do_smoething() ကိုစေခါ္ျပီ။ do_something() function ရဲ့ base ကိုမွတ္ထားဖို့ frame pointer/base pointer(EBP) ကို stack ေပါ္ကို PUSH လုပ္တယ္။ function တစ္ခုစတိုင္းစတိုင္း assembly မွာ
- push ebp
- mov ebp,esp ကိုသုုံးတယ္။ function ရဲ့ အစကိုမွတ္လိုက္တဲ့သေဘာပါ။
အဲ့ေတာ့ strcpy() ကေရာ function တစ္ခုမဟုတ္ဘူးလား? သူ့အတြက္က်ေတာ့ stack ေပါ္မွာ ေနရာမေပးေတာ့ဘူးလားလို့ေမးစရာရွိပါတယ္။ strcpy() က function တစ္ခုေတာ့ဟုတ္ပါတယ္။ ဒါေပမဲ့ သူက data ေတြကို copy ကူးတဲ့အခါမွာ PUSH instruction ကိုမသံုးပါဘူး။ သူအလုပ္လုပ္တဲ့ပံုက word တစ္ခုကို read တယ္၊ ျပီးရင္ index ကိုသံုးျပီး stack မွာသြား write တယ္။ဒီ program အရဆိုရင္ argument က ၀င္လာတဲ့ input ကို 128 bytes ရွိတဲ့ MyVar ထဲကို copy ကူးထည့္တယ္။
အဲ့မွာ input က 128 bytes ထပ္နည္းေသးရင္ပဲျဖစ္ျဖစ္၊ 128 bytes အတိရွိရင္ပဲျဖစ္ျဖစ္ problem မရွိေသးဘူး။ တကယ္လို့မ်ား 128 bytes ထက္ပိုတဲ့ input ထည့္လိုက္ရင္ ဘယ္လိုျဖစ္သြားမလဲ။ေအာက္ကပံုကိုျကည့္ျကည့္ပါ။ input က ebp ေရာ၊ eip ကိုပါ overwrite ျဖစ္သြားတာကိုေတြ့ရမွပါ။
strcpy() ကေတာ့ input က၀င္လာသေလာက္ကို MyVar ထဲကို copy ကူးထည့္မွာပဲ။ input ကုန္သြားရင္ သူ့အလုပ္ျပီးျပီ။ အဲ့ေတာ့ do_something() လည္းျပီေရာ stack ေပါ္က LEAVE ျပီ။ က်ေနာ္တို့ function စစေခၚတုန္းက function ရဲ့ အစကို ebp ထဲမွတ္ထားခဲ့တယ္မဟုတ္လား? အခု function ကို ျပန္ဖယ္ေတာ့မယ္ဆိုေတာ့ ထည့္တုန္းကနဲ့ေျပာင္းျပန္ေပါ့။ instruction က
- mov ebp,esp
- pop ebp
program အရဆိုရင္ ၀င္လာသမွ် input ေတြကို MyVar ထဲကို copy ကူးထည့္တယ္။MyVar က 128bytes လက္ခံျပီး ေက်ာ္သြားတဲ့ byte က eip ကို overwrite ျဖစ္သြားတယ္။အဲ့ေတာ့ 128 bytes ထည့္ျပီး ေနာက္ထပ္ 4 bytes ကိုကိုယ္သြားခ်င္တဲ့ address တစ္ခုထည့္ေပးလိုက္ရင္ ok ျပီေပါ့။ က်ေနာ္တို့က system ရဲ့ shell ကိုလိုခ်င္တာမလား? အဲ့ေတာ့ shellcode ကို stack ထဲကိုအရင္ထည့္၊ ျပီးေတာ့ eip ကို အဲ့ shellcode ရွိတဲ့ေနရာကိုခုန္ခိုင္းလိုက္ရင္ shell ရျပီေပါ့၊ (ေျပာေနတာေတာ့ လြယ္လိုက္တာ Hee XD) ...... eip ျပီးတာနဲ့ shellcode ကိုတန္းထည့္တာက သိပ္ျပိီးေတာ့ reliable မျဖစ္ဘူးဗ်။တကယ္လို့ esp က first byte ကသာစမလုပ္ခဲ့ရင္ က်ေနာ္တို့ shell ကအလုပ္ျဖစ္မွာမဟုတ္ဘူး။ အဲ့ေတာ့ ဘယ္လိုလုပ္ရင္ေကာင္းမလဲ....???? NOP(\x90) No operation byte ေတြထည့္ရမယ္။ eip address ထည့္ျပီးတာနဲ့ nop ကို 20bytes ေလာက္ထည့္၊ ျပီးမွ shellcode ကိုထည့္၊ အဲ့ဒါကေတာ့ reliable အျဖစ္ဆံုးပဲ။ တကယ္လို့ eip က nop ရွိေနရာကိုေရာက္သြားျပီပဲထားပါေတာ့။ nop ကိုေရာက္ရင္ nop က "ငါေတာ့ ဘာမွမလုပ္ေသးဘူး၊ ေနာက္ တစ္ byte ကိုသြားလိုက္ပါ။" ဆိုျပီေတာ့ eip ကိုေျပာလိုက္မယ္ ။ အဲ့ေတာ့ ေနာက္ nop ေတြ့လည္းအဲ့လုိ၊ ေနာက္ nop ထပ္ေတြ့လည္းအဲ့လို၊ အဲ့လို အဲ့လိုနဲ့ shellcode ကိုေရာက္သြားေရာ။ program က shellcode ကိုသာ execute လုပ္လိုက္ရင္ က်ေနာ္တို့လိုခ်င္တဲ့ shell ရျပီေပါ့။
က်ေနာ္ ဒီ part(1) မွာ theory ေတြခ်ည္းပဲေျပာသြားလို့ စိတ္မညစ္သြားပါနဲ့ဦး။ part(2) ေရာက္တဲ့အခါမွာ lab ေလးနဲ့ လက္ေတြ့စမ္းျပပါမယ္။