إكتشاف و تحليل الثغرات : فحص السورس و الهندسة العكسية.

السلام عليكم و رحمة الله

سنتكلم عن موضوع بسيط حول إكتشاف ثغرات البرامج بإستخدام طريقة فحص السورس و الهندسة العكسية لإيجاد الخطأ البرمجي.

لما نريد أن نفحص برنامج من الثغرات فأول شيء لازم نعرف خصائص البرنامج:

– نوع البرنامج.
– وظيفته.
– مفتوح أم مغلق المصدر.
– تحديد مدخلات البرنامج (user inputs) مثلا : عملية login في سيرفير FTP.

1- إكتشاف و تحليل الثغرة بفحص السورس:

سنفترض أن البرنامج مفتوح المصدر.

يعتبر فحص السورس من أفضل الطرق للكشف عن مكان الثغرة بدقة, لكن ماذا لو كان البرنامج كبير فإن قراءة مئات الأسطر من الأكواد أكيد متعب و يأخذ وقت.

لهذا تم توفير أدوات تقوم بعمل فحص أوتوماتيكي للدوال المصابة و المستعملة بطريقة غير آمنة.

ملاحظة: لا تعتبر هذه الأدوات فعالة 100% لأنها تعتمد على البحث عن الدوال المصابة و الطرق المعروفة للحصول على user inputs (recv,argv…)l

لأن من الممكن أن نجد كود يحتوي على دوال خاصة بالبرنامج و طرق مختلفة للحصول على البيانات من المستخدم و لهذا فالفحص اليدوي ينفع في هذه الحالات.

من بين هذه الأدوات RATS يدعم فحص عدة لغات C,C++,php,ruby,python,perl… بإستخدام قواعد بيانات xml تحتوي على دوال غير محمية.

الآن لنجرب السكريبت على برنامج مصاب ( الكود مأخوذ من أحدى مقالات SecurityCompass).

البرنامج عبارة عن سيرفير يتنصت على البورت 200 و يستقبل بيانات من client

// vulnerable server.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "winsock.h"
#include "windows.h"

//load windows socket
#pragma comment(lib, "wsock32.lib")

//Define Return Messages
#define SS_ERROR 1
#define SS_OK 0

void pr( char *str)
{
   char buf[500]=" ";
   strcpy(buf,str);
}
void sError(char *str)
{
   printf("Error %s",str);
   WSACleanup();
}

int _tmain(int argc, _TCHAR* argv[])
{
WORD sockVersion;
WSADATA wsaData;

int rVal;
char Message[5000]=" ";
char buf[2000]=" ";

u_short LocalPort;
LocalPort = 200;

//wsock32 initialized for usage
sockVersion = MAKEWORD(1,1);
WSAStartup(sockVersion, &wsaData);

//create server socket
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);

if(serverSocket == INVALID_SOCKET)
{
   sError("Failed socket()");
   return SS_ERROR;
}

SOCKADDR_IN sin;
sin.sin_family = PF_INET;
sin.sin_port = htons(LocalPort);
sin.sin_addr.s_addr = INADDR_ANY;

//bind the socket
rVal = bind(serverSocket, (LPSOCKADDR)&sin, sizeof(sin));
if(rVal == SOCKET_ERROR)
{
   sError("Failed bind()");
   WSACleanup();
   return SS_ERROR;
}

//get socket to listen
rVal = listen(serverSocket, 10);
if(rVal == SOCKET_ERROR)
{
   sError("Failed listen()");
   WSACleanup();
   return SS_ERROR;
}

//wait for a client to connect
SOCKET clientSocket;
clientSocket = accept(serverSocket, NULL, NULL);
if(clientSocket == INVALID_SOCKET)
{
   sError("Failed accept()");
   WSACleanup();
   return SS_ERROR;
}

int bytesRecv = SOCKET_ERROR;
while( bytesRecv == SOCKET_ERROR )
{
   //receive the data that is being sent by the client max limit to 5000 bytes.
   bytesRecv = recv( clientSocket, Message, 5000, 0 );

   if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET )
   {
      printf( "\nConnection Closed.\n");
      break;
   }
}

//Pass the data received to the function pr
pr(Message);

//close client socket
closesocket(clientSocket);
//close server socket
closesocket(serverSocket);

WSACleanup();

return SS_OK;
}

بعد تشغيل السكريبت و مشاهدة كيفية عمله نستخدم الأمر التالي:

rats -i -w 1 -d rats-c.xml vulnerable server.cpp

-i : يفحص فقط الدوال التي تقبل مدخلات خارجية (user input)

-w 1: إظهار الدوال التي خطورتها عالية.

1-High, 2-Medium, 3 Low

-d : تحديد نوع القاعدة التي بها الدوال اللازم فحصها في هذه الحالة البرنامج بال c++ نستخدم rats-c.xml

ناتج الفحص كان كالتالي:

نستطيع تقسيم ناتج الفحص إلى قسمين:

1-قسم إظهار المتغيرات ذات الحجم المحدود (fixed size) و التي يمكن أن يكون لها إستخدام غير صحيح فيما بعد (فيض في الذاكرة مثلا).

2-قسم إظهار الدوال المصابة مع توضيح تلميح حول كيفية تجنب حدوث الثغرة.

في مثالنا نلاحظ تحذيرين نبدأ بالثاني و اللذي يتعلق بالدالة recv

recv( clientSocket, Message, 5000, 0 );

الدالة محددة الحد الأعلى لعدد البيانات المستقبلة و العدد هو 5000 نفس حجم المتغير Message.

لكن التحذير يقول أنه يجب عمل تحقيق آخر في حالة ما إذا تم إستخدام نفس المتغير خلال عملية أخرى و هو الحال الذي سنراه بالنسبة للدالة strcpy الموجودة في الدالة

void pr( char *str)

فبعد إستقبال بيانات من الclient الموجودة في المتغير Message عن طريق دالة recv سيتم إستدعاء الدالة pr(Message)l الparameter هو Message.
الآن داخل دالة pr()

void pr( char *str)
{
char buf[500]=" ";
strcpy(buf,str);
}

الخطأ واضح هنا فالدالة تعمل نسخ للبيانات من المتغير str= Message ذات حجم 5000إلى المتغير buf ذات الحجم 500 و بذلك نستطيع إحداث فيض في الذاكرة و التحكم في سير البرنامج.

2- إكتشاف و تحليل الثغرة بإستخدام الهندسة العكسية :

نفترض الآن أننا لا نملك سورس البرنامج في هذه الحالة سنلجأ إما إلى fuzzing أو reverse engineering

سنتكلم عن الخيار الثاني.

لعمل ذلك سنستخدم برنامج IDA.

ميزة هذا disassembler هو أنه يستطيع إستخراج الدوال الخاصة بالبرنامج حتى أسماؤها و الparameters الخاصة بها.
بالإضافة إلى العديد من الخصائص التي تميز هذا البرنامج.

لنفحص الآن كود دالة pr()

نلاحظ أن البرنامج إستطاع تحديد حجم المتغير buf 500 bytes.

-القسم ما قبل par1 :هو عبارة عن prologue الدالة يعني تهيئة المكدس و حفظ المسجل esp و ebp… بالإضافة إلى وضع الcanary(cookie)l هذا لأن عملية ترجمة البرنامج كانت بإضافة GS Flag.

-Part1:

هنا تتم تهيئة المتغير buf لملأ محتواه عن طريق دالة memset

كما نعلم دالة memset بها 3 parameters

void * memset ( void * ptr, int value, size_t num );

1)*ptr : هو مؤشر نحو قسم الذاكرة الذي نريد ملأه في مثالنا هوbuf

lea eax, [ebp+buf+2]
push eax

2) value : القمية التي نريد وضعها في الذاكرة في المثال 0 (قيمة بدائية).

push 0

3)num : و هو عدد البيتات التي ستحمل قيمة value في المثال 500

push 1F2H

-Part2:

الآن بعد أن أصبح buf جاهزا لتلقي التسونامي سيتم إستدعاء الدالة strcpy

char * strcpy(char* destination, const char* source);

نرى فيها 2 parameters:

1-Destination: و سيكون buf

lea ecx, [ebp+buf]
push ecx

2-Source : و سيكون str(Message)l

lea eax, [ebp+str]
push eax

بعد أن يتم إستدعاء دالة strcpy فإنها ستنسخ المتغير بدون عمل تحقق من الحجم مما سيتسبب في كتابة عنوان العودة المحفوظ في المكدس و عند عمل ret يتغير eip بقيمة من قيم str

أظن أن بعد إكتشافنا للثغرة و بعد هذا التحليل البسيط نكون قد أنهينا العملية 🙂

نبذة عن الكاتب

زكريّا. طالب جامعي، تخصص علوم الحاسب، باحث مهتم في فحص و إكتشاف الثغرات.

التعليقات:

أضف تعليقاً | عدد التعليقات: (8)

  1. […] الى التّرحيب به، والى قراءة موضوعه الرائع والمفيد: إكتشاف و تحليل الثغرات : فحص السورس و الهندسة العكسية Share – عدد المشاهدات […]

  2. يقول حسن:

    مشكور على هيك شرج خصوصا اكتشاف تغرات البرامج وباتمنى انو يكون فيه دروس اكتر
    وشروحات اكتر وتمشي معانا من الصفر لهيك شغلى

  3. يقول Eng. Sabri Saleh:

    شكرا زكريا ,,,
    و سلمت يداك يا رجل

    سأطبق بعد إنهاء مافي يدي بإذن الله

    تحياتي و احترامي

  4. […] This post was mentioned on Twitter by Security 4 Arabs. Security 4 Arabs said: إكتشاف و تحليل الثغرات : فحص السورس و الهندسة العكسية. http://bit.ly/atjKdY […]

  5. يقول aesa:

    كيف نكتشف ثغرات الونيدوز ونخترق به

  6. يقول Hit-Man:

    شكرا أخي على المقال المفيد
    تحياتي

  7. يقول Hit-Man:

    ممكن اعادة رفع الصور

  8. يقول zicrosoft:

    مشكوووووووووووووور اخي الكريم

أكتب تعليق