[Plug] Nkiller - a TCP exhaustion/stressing tool

Giorgos Keramidas keramida at ceid.upatras.gr
Thu Oct 23 20:57:06 EEST 2008


On Wed, 22 Oct 2008 14:40:10 +0300, ithilgore <advent.cloud.strife at gmail.com> wrote:
> Giorgos Keramidas wrote:
>> On Tue, 21 Oct 2008 23:29:04 +0300, ithilgore <advent.cloud.strife at gmail.com> wrote:
>>> http://sock-raw.homeunix.org/projects/nkiller/nkiller.c
>>
>> Ενδιαφέρον σαν concept.  Μερικά σχόλια, τώρα που δεν έχω πιεί καφέ ακόμα
>> και μπορώ να γίνω ρόμπα δημοσίως χωρίς να φοβάμαι να χρησιμοποιήσω τη
>> δικιολογία «ε ναι, αλλά νύσταζα λίγο ακόμα όταν το έγραψα!» είναι τα
>> εξής...

[...]
>> Αυτό γίνεται επειδή το -p option parsing απέτυχε, και στη main() ισχύει
>> ότι portlen == 0.  Μόλις δεσμεύεται μνήμη για το `Target' object κάνεις:
>>
>>         Target = safe_malloc(sizeof(HostInfo));
>>
>> κι ύστερα:
>>
>>         Target->portlen = portlen;
>>
>> Αλλά το παρακάτω:
>>
>> 907             i = 0;
>> 908             while (i < Target->portlen) {
>> 909                     port_add(Target, o.portlist[i]);
>>
>> προσπαθεί (έμμεσα μέσα στην port_add() function) να προσπελάσει τα
>> members του `Target' που δεν έχουν αρχικοποιηθεί ποτέ.  Μια λύση γι αυτό
>> είναι να αντικαταστήσεις την:
>>
>>     /* malloc version for fewer checks in code */
>>     static void *safe_malloc(size_t size)
>>     {
>>             void *mymem;
>>             if ((int) size < 0)  /* Catch caller errors */
>>                     fatal("Tried to malloc negative amount of memory!!!\n");
>>             mymem = malloc(size);
>>             if (mymem == NULL)
>>                     fatal("Malloc Failed! Probably out of space.\n");
>>             return mymem;
>>     }
>>
>> με κάτι σαν αυτό το ζευγάρι από malloc/calloc wrappers:
>>
>>     /*
>>      * malloc wrapper for fewer checks in code
>>      */
>>     static void *
>>     xcalloc(size_t number, size_t objsize)
>>     {
>>             void *p;
>>
>>             /*
>>              * Το casting σε `int' και το check για αρνητικές τιμές εδώ δε
>>              * χρειάζεται.  Δεν υπάρχει κανένας λόγος γιατί ένα πρόγραμμα
>>              * θα 'πρεπε να «σκάει» αν προσπαθήσει να δεσμεύσει πάνω από
>>              * 2 GB μνήμης σε 32-bit μηχανήματα.
>>              */
>>
>>             p = calloc(number, objsize);
>>             if (p == NULL)
>>                     fatal("xcalloc failed.  Running low on space?\n");
>>             return p;
>>     }
>>
>>     /*
>>      * malloc wrapper for fewer checks in code
>>      */
>>     static void *
>>     xmalloc(size_t nbytes)
>>     {
>>             void *p;
>>
>>             p = xcalloc(1, nbytes);
>>             if (p == NULL)
>>                     fatal("xmalloc failed.  Running low on space?\n");
>>             return p;
>>     }
>>
>> Η διαφορά είναι ότι πλέον κάθε malloc() return buffer θα αρχικοποιείται
>> τουλάχιστον σε μηδενική τιμή, οπότε το assumption ότι οι μη
>> αρχικοποιημένοι pointers είναι null pointers θα «ισχύει».
>>
>> ------------------------------------------------------------------------
>>
>> Μια πρόταση που έχω να κάνω είναι να μη χρησιμοποιείς linked list για τα
>> port numbers, αλλά κάτι σαν array.  Έτσι κι αλλιώς, από ότι φαίνεται το
>> post_list ενός HostInfo αρχικοποιείται μόνο μια φορά.
>>
>
> Η αρχική ιδέα ήταν να γίνει array. Όμως προτιμήθηκε linked list για τον
> εξής λόγο.
>
> Ήθελα να υπάρχει η δυνατότητα να μπορεί να γίνει remove δυναμικά ένα port
> από τη λίστα -- κάτι που ενεργοποιείται με το option -y.  Αυτό ουσιαστικά
> που γίνεται είναι να κοιτάει το Νkiller αν πήρε ποτέ RST από κάποιο από
> τα ports που χτυπάει.  Αυτό μπορεί να είναι αποτελέσμα διάφορων λόγων:

Η λίστα είναι μια χαρά τότε.  Ίσως έχει νόημα ένα είδος από hash σαν
«overlay» πάνω από τη λίστα, για faster lookups, αλλά και μια απλή λίστα
είναι μια χαρά για ένα μικρό αριθμό από ports.

Σήμερα το βράδυ θα έχω λίγη ώρα να κοιτάξω κάπως τα warnings που βγάζει το
BSD για χρήση uninitialized variables, και θα σου στείλω patches με mail :)

> Έτσι, το data structure που αποθηκεύονται τα ports που χτυπάμε πρέπει να
> μπορεί να αλλάζει δυναμικά. Αυτό κάνει η function port_remove(). Βέβαια,
> μια άλλη πιθανή λύση υπό εξέταση μπορεί να ήταν να χρησιμοποιήσω array
> και να βάζω μια special value ( πχ - 1 ) στις θέσει των ports που
> έκλεισαν δυναμικά λόγω RST.  Το πρόβλημα με αυτή τη μέθοδο είναι με το
> πως παίρνεις random value από αυτή τη δομή:
>
> Αν πχ καλείς την rand() ώσπου να πάρεις ένα value που δεν είναι - 1,
> σκέψου τι θα γίνει όταν έχουν μείνει 2 ports και το ένα από αυτά είναι
> "κλεισμένο". Θα καλείς την rand() με πιθανότητα 1/2 να πετύχεις το -1,
> όπου θα πρέπει να την ξανακαλείς ξανά και ξανά μέχρι να πετύχεις το
> "καλό" port. Κάτι τέτοιο σπαταλάει χρόνο χωρίς λόγο.

Αυτό λύνεται ψιλο-εύκολα με κάτι σαν «work queue».

Αντί να έχεις ένα list με όλα τα ports, μπορείς να φτιάξεις κάτι σαν:

    * Λίστα με τα ports που δεν έχουν γίνει «process»
    * Λίστα με τα ports που έχουν «in flight» πακέτα.
    * Λίστα με τα ports που έχουν γίνει επιτυχώς process.

Κάθε λίστα μπορεί να κρατάει και διαφορετικά flags, options, status και
άλλη πληροφορία, κοκ.

> Από την άλλη, αν κάθε φορά που άλλαζε το state ενός port λόγω RST, για να
> πάρεις random-port διάλεγες και έπαιρνες όλα τα [όχι -1], και τα
> μετέφερες σε ένα άλλο καθαρό array, τότε θα είχαμε περιττά memcpy, κάτι
> που πάλι θα σπαταλούσε χρόνο.

Ναι, σίγουρα.  Για σχετικά μικρό αριθμό από ports το memcpy() δεν είναι
τόσο κακό πάντως.  Ακόμα και για μεγάλα port ranges, έχει το έξτρα
πλεονέκτημα ότι δεν κάνει pollute το L2-cache με pointers που πιθανόν να
είναι όλοι aligned στο ίδιο memory chunk size (και πέφτουν στο ίδιο CPU
cache line).

> Τη λύση του αρχικού ανακατέματος δεν τη βρίσκω τόσο καλή για τον λόγο ότι
> απλά δεν είναι really random.

Καλά κι εμένα δε μου αρέσει η srand(), αλλά σε BSD μπορείς να πάρεις πιο
καλό randomness με την arc4random() ;)

> (τουλάχιστον δεν είναι αρκετά random, αφού πραγματικά random σχεδον δεν
> υφίσταται) Tο "randomness" του συστήματος επαναλαμβάνεται αφού αν πχ
> έχουν ανακατευτεί τα ports ως εξής 22,111,80,21 όταν τελειώσει ο ένας
> γύρος, ο επόμενος γύρος θα τα βρει στην ίδια σειρά (δηλαδή πάλι θα τα
> χτυπήσει με τη σειρά 22,111,80,21) - κάτι που είναι πιο "προδοτικό" στα
> IDSs ως signature για attack.  Άλλωστε, τέτοιου είδους αρχικό ανακάτεμα,
> θα μπορούσε να κάνει και ο χρήστης δίνοντας τα ports σε μη-άυξουσα και
> μη-φθίνουσα σειρά e.g -p111,80,3389,21

Είναι ψιλο-εύκολο να γράψεις κάτι σαν shuffleports():

    void
    shuffleports(in_port_t *pvec, size_t nports)
    {
            uint32_t rval;
            size_t j, k;
            in_port_t tmp_port;

            assert(pvec != NULL);
            for (j = 0; j < nports; j++) {
                    rval = arc4random();
                    rval = ((rval >> 16) ^ (rval & 0xFFFF)) % nports;
                    k = rval;           /* Convert to size_t */
                    tmp_port = pvec[k];
                    pvec[k] = pvec[j];
                    pvec[j] = tmp_port;
            }
    }

Αυτό μπορείς να το κάνεις σε κάθε iteration από το port range, για να μην
φαίνεται ότι «χτυπάς» πάντα τα ίδια port sequences :)

> Όσο για τα scanner threads, ενώ είναι καλή ως ιδέα, στην προκειμένη
> περίπτωση, δεν υφίστανται καν threads και ούτε χρειάζεται να υπάρχουν.
> Όλη η ταχύτητα του tool προέρχεται από το statelessness που του δίνουν
> τα reverse syn cookies. Αυτό που γίνεται είναι κάτι ανάλογο με τα syn
> cookies που όλοι ξέρουμε. Στην περίπτωση μας όμως, γίνεται από client
> πλευρά. Δηλαδή περνάνε τα { source ip, source port, destination ip,
> destination port } μέσα από μια hash function (πχ sha1) μαζί με ένα
> secret key και από το αποτέλεσμα παίρνουμε τα 32 πρώτα bit και τα
> τοποθετούμε στο TCP sequence field του TCP header του SYN packet που θα
> στείλουμε. Όταν λάβουμε ένα SYN|ACK packet, κάνουμε swap τα values {
> source ip, source port, destination ip, destination port } ώστε τα
> source να γίνουν destination και αντίστροφα και υπολογίζουμε εκ των
> προτέρων με το ίδιο secret key και την ίδια hash function, έναν μαγικό
> αριθμό. Αν αυτός ο μαγικός αριθμός (πάλι τα 32 πρώτα bit του) είναι ίσος
> με το acknowledgment number - 1 (το οποίο acknowledgement number στο
> 3way handshake είναι ίσο με το (sequence + 1) του πακέτου στο οποίο
> απαντάμε) τότε σημαίνει πως πρόκειται για απάντηση σε ένα πακέτο που
> στείλαμε εμείς προηγουμένως.
>
> Κατά τον παραπάνω τρόπο, μπορούμε απλά να στείλουμε ένα burst από
> packets, χωρίς να φροντίσουμε να κρατήσουμε καμία πληροφορία για
> αυτά. Αυτό είναι που κάνει και το tool τόσο ταχύ.

Heh.  Neat!



More information about the Plug mailing list