[Plug] Nkiller - a TCP exhaustion/stressing tool

Giorgos Keramidas keramida at ceid.upatras.gr
Wed Oct 22 04:58:19 EEST 2008


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.  Μερικά σχόλια, τώρα που δεν έχω πιεί καφέ ακόμα
και μπορώ να γίνω ρόμπα δημοσίως χωρίς να φοβάμαι να χρησιμοποιήσω τη
δικιολογία «ε ναι, αλλά νύσταζα λίγο ακόμα όταν το έγραψα!» είναι τα εξής...

Με το παρακάτω Makefile θέλει NO_WERROR για να κάνει build, επειδή
βγάζει κάποια warnings:

: keramida at kobe:/var/tmp/nkiller$ cat -n Makefile
:      1  PROG= nkiller
:      2  NO_MAN?= No manpage available.
:      3
:      4  WARNS?= 6
:      5  WFORMAT?= 2
:      6
:      7  DPADD+= ${LIBPCAP} ${LIBSSL} ${LIBCRYPTO}
:      8  LDADD+= -lpcap -lssl -lcrypto
:      9
:     10  .include <bsd.prog.mk>
: keramida at kobe:/var/tmp/nkiller$

Τα warnings που είδα με FreeBSD 8.0-CURRENT είναι:

: keramida at kobe:/var/tmp/nkiller$ make NO_WERROR=1
: Warning: Object directory not changed from original /var/tmp/nkiller
: cc -O2 -pipe -march=pentiumpro -g -fstack-protector -Wsystem-headers \
:   -Wall -Wno-format-y2k -W -Wno-unused-parameter -Wstrict-prototypes \
:   -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual \
:   -Wwrite-strings -Wswitch -Wshadow -Wcast-align -Wunused-parameter \
:   -Wchar-subscripts -Winline -Wnested-externs -Wredundant-decls \
:   -Wno-pointer-sign -Wformat=2 -Wno-format-extra-args -c nkiller.c
: nkiller.c: In function 'check_replies':
: nkiller.c:348: warning: cast discards qualifiers from pointer target type
: nkiller.c: In function 'main':
: nkiller.c:875: warning: comparison of unsigned expression < 0 is always false
: nkiller.c:908: warning: comparison between signed and unsigned
: nkiller.c:340: warning: 'datagram_len' may be used uninitialized in this function
: nkiller.c:340: note: 'datagram_len' was declared here
: cc -O2 -pipe -march=pentiumpro -g -fstack-protector -Wsystem-headers \
:   -Wall -Wno-format-y2k -W -Wno-unused-parameter -Wstrict-prototypes \
:   -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual \
:   -Wwrite-strings -Wswitch -Wshadow -Wcast-align -Wunused-parameter \
:   -Wchar-subscripts -Winline -Wnested-externs -Wredundant-decls \
:   -Wno-pointer-sign -Wformat=2 -Wno-format-extra-args \
:   -o nkiller nkiller.o -lpcap -lssl -lcrypto
: keramida at kobe:/var/tmp/nkiller$

Ακόμα κι όταν κάνει compile όμως, πετάει core:

: keramida at kobe:/var/tmp/nkiller$ sudo ./nkiller -t 127.0.0.1 -p 1024-65534
: Segmentation fault: 11 (core dumped)
: keramida at kobe:/var/tmp/nkiller$

Κάνοντας ένα debug build με:

: keramida at kobe:/var/tmp/nkiller$ make clean
: [...]
: keramida at kobe:/var/tmp/nkiller$ env DEBUG_FLAGS='-ggdb' CFLAGS='' make NO_WERROR=1
[...]
: keramida at kobe:/var/tmp/nkiller$

Είδα ότι το core dump φαίνεται να γίνεται όταν το πρόγραμμα προσπαθεί να
προσπελάσει μνήμη που δεν έχει αρχικοποιηθεί:

: keramida at kobe:/var/tmp/nkiller$ sudo gdb
: [...]
: (gdb) file nkiller
: Reading symbols from nkiller...done.
: (gdb) run -t 127.0.0.1 -p 1024-65534
: Starting program: /var/tmp/nkiller/nkiller -t 127.0.0.1 -p 1024-65534
:
: Program received signal SIGSEGV, Segmentation fault.
: 0x08049e7c in port_add (Target=0x8121020, port=1024) at nkiller.c:667
: 667             current->next = newNode;
: (gdb) bt
: #0  0x08049e7c in port_add (Target=0x8121020, port=1024) at nkiller.c:667
: #1  0x0804a4fe in main (argc=5, argv=0xbfbfece4) at nkiller.c:909
: (gdb) p current
: $1 = (port_elem *) 0xa5a5a5a5
: (gdb)

Αυτό γίνεται επειδή το -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 αρχικοποιείται μόνο μια φορά.

Μια δεύτερη πρόταση είναι, αφού θέλεις να ξέρεις ποιές ports να κάνεις
scan, να χρησιμοποιήσεις κάτι σαν `port bitmap' στην αρχικοποίηση, με
ένα bit για κάθε port που σε ενδιαφέρει να κάνεις scan:

    #define PORTMAP_SIZE        (IPPORT_MAX / CHAR_BIT)
    uint8_t portmap[PORTMAP_SIZE];

Στο πρώτο port range parsing, απλά θέτεις ένα bit στο portmap[] για τα
ports που θα γίνουν scan.  Το «parsing» από port ranges όπως "1-100,201"
ή "1-200,1024-4096" θα έχει _πλάκα_ αλλά είναι το πιο βαρετό κομμάτι.
Το *ΠΡΑΓΜΑΤΙΚΑ* ενδιαφέρον αρχίζει ακριβώς μετά...

Στο δεύτερο πέρασμα, διατρέχεις μία φορά το bitmap, και κρατάς σε ένα
vector τα port numbers, π.χ.:

    in_port_t *scanports;
    in_port_t nscanports;
    in_port_t pindex;

    scanports = malloc(IPPORT_MAX * sizeof(*scanports));
    if (scanports == NULL)
        err(1, "malloc");
    for (nscanports = 0, pindex = 0; pindex < IPPORT_MAX; pindex++) {
            uint16_t bytepos = pindex / CHAR_BIT;
            uint16_t bitpos = pindex % CHAR_BIT;

            if ((portmap[bytepos] & (1 << bitpos)) != 0)
                    scanports[nscanports++] = pindex;
    }

Μετά μπορείς να αντιγράψεις σε ένα δεύτερο vector το scanports[], να το
«ανακατέψεις» με random τρόπο μία φορά στην αρχή (αντί να διαλέγεις ένα
random port κάθε φορά που το χρειάζεσαι), και να κάνεις διάφορα άλλα
κόλπα... όπως π.χ. να «μοιράσεις» το randomized port range σε κομμάτια
και να ταΐσεις το κάθε sub-range σε ξεχωριστό scanner thread, κλπ.

HTH,
Γιώργος



More information about the Plug mailing list