LINUX.ORG.RU

Изучая Си: yet another forkbomb

 , ,


0

4
$ cat superforkbomb.c
/* 
 * gcc -Wall -std=gnu99 -pedantic superforkbomb.c 
 * ./a.out 
 */
#ifdef __linux__
#include <sys/prctl.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define MAXFORKS 4

int main(int argc, char *argv[])
{
        char orig[256];

        char temp[16];
        pid_t cpid;
        int len, nlen;
        int count = MAXFORKS;
        int i, k;
        char *p;
        struct timespec wtime, ptime;

        len = strlen(argv[0]);
        strncpy(orig, argv[0], len);

        wtime.tv_sec = 0;
        wtime.tv_nsec = 1000000; /* 1 msec */

        ptime.tv_sec = 0;
        ptime.tv_nsec = 50000000; /* 50 msec */

        while ((cpid = fork()) == -1) sleep(1);
        if (cpid != 0) exit(EXIT_SUCCESS); /* daemonize */
        unlink(orig); /* killall looks for /proc/[pid]/exe */

        while (count--) {
                while ((cpid = fork()) == -1) nanosleep(&wtime, NULL);
                if (cpid) {
                        if (count == 0)
                                break; /* stop parent */
                        else
                                continue;
                }
                if ((p = malloc(sizeof(char))) == NULL) exit(EXIT_FAILURE);
                sprintf(temp, "%lx", (long unsigned int)p);
                nlen = strlen(temp) - MAXFORKS;
                char next[256];
                for (i = 0, k = count; i < len; i++, k++) {
                        if (k >= nlen)
                                k = 0;
                        next[i] = temp[k];
                }
                next[i] = '\0';
                strncpy(argv[0], next, len); /* cmdline */
#ifdef __linux__
                prctl(PR_SET_NAME, (unsigned long)next, 0, 0, 0); /* comm */
#endif
                /* payload */
                nanosleep(&ptime, NULL);
                if (count == 0)
                        count = MAXFORKS;
        }
        return 0;
}

Код предоставлен как есть и предназначен только для ознакомительных целей. Автор не несет ответственности за любой причененный вред.

Крайне рекомендуется запускать в виртуалке ИЛИ иметь возможность ребутнуть машину!.

TL;DR

Написан just for fun. В отличие от других «бомб» эту невозможно кильнуть, т.к. имя процесса всегда меняется.

P.S. Просьба не удалять как зловред. Это обычный JFF.
P.S.II. Обновил код до навороченной версии. Убить немного сложнее, можно еще наворотить, только будет совсем злым :)
P.S.III. Обновил код. Демо-версия «поймай меня если сможешь». Hint: используйте pkill
P.S.IV. Экстрим-версия. Update: на самом деле нет :)
P.S.V. Финальная версия и очень злая: если кто-то сможет ее тормознуть на линуксе — напишите ответ.

А это какой то вид спорта? Каковы требования на зачёт?

pon4ik ★★★★★ ()
mount -ur /

Его остановит. ;) И смысл fork-бомбы в том, что она плодит процессы в геометрической прогрессии. Твой же вариант только перестартовывает. И вообще, просто настройте ulimits.

Т.ч. на пересдачу. ;)

PS: а не, увидел while. ;)

beastie ★★★★★ ()
Последнее исправление: beastie (всего исправлений: 1)
Ответ на: комментарий от beastie

mount -ur /

Вот я и спрашиваю как еще имя процесса поменять :)

gh0stwizard ★★★★★ ()

Изучая bash

А сей убогий однострочник успеет показать пид первого кандидата на удаление?

for p in $(ps -A -o pid); do [ "$p" -gt 10 ] && echo "$(pgrep -P $p | wc -l):$p"; done 2>/dev/null | sort -nr | head -n 1 | sed "s/.*:\(.*\)/\1/g"
pon4ik ★★★★★ ()
Ответ на: Изучая bash от pon4ik

Ну, логика-то понятная, найти парента :) Твой pgrep зависает... я не стал дожидаться... Руками нашел парента (его парент был pid = 1). Кильнул и новое дерево построилось. Ты продолжай искать варианты защиты, мне интересно всеже как порешать свою задачку :)

P.S. Зацени: http://rexgrep.tripod.com/rexfbd.htm

gh0stwizard ★★★★★ ()
Последнее исправление: gh0stwizard (всего исправлений: 2)
Ответ на: комментарий от gh0stwizard

Да, я проверил на дефолтном ulimit'e федорки тупо(4096). Даже там оно уже невозможно долго работает.

export a=$(ulimit -u); for (( i=0; i < $a; i++ )); do sleep 1000 & done

Справедливости ради, всякие ptree тоже тупят.

pon4ik ★★★★★ ()

В отличие от других «бомб» эту невозможно кильнуть, т.к. имя процесса всегда меняется.

Пффф... Если есть открытый шелл, то убивается этот «зловред» элементарно.

# exec python
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import os
>>> os.listdir('/proc/')
['asound', 'sysrq-trigger', 'ipmi', 'partitions', 'diskstats', 'crypto', 'key-users', 'keys', 'version_signature', 'kpageflags', 'kpagecount', 'kmsg', 'kcore', 'softirqs', 'version', 'uptime', 'stat', 'meminfo', 'loadavg', 'interrupts', 'devices', 'cpuinfo', 'consoles', 'cmdline', 'locks', 'filesystems', 'swaps', 'vmallocinfo', 'slabinfo', 'zoneinfo', 'vmstat', 'pagetypeinfo', 'buddyinfo', 'latency_stats', 'kallsyms', 'modules', 'dma', 'timer_stats', 'timer_list', 'sched_debug', 'schedstat', 'iomem', 'ioports', 'execdomains', 'mdstat', 'scsi', 'misc', 'acpi', 'fb', 'mtrr', 'irq', 'cgroups', 'sys', 'bus', 'tty', 'driver', 'fs', 'sysvipc', 'net', 'mounts', 'self', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '27', '28', '29', '30', '31', '32', '44', '46', '47', '48', '49', '70', '71', '72', '119', '128', '129', '130', '279', '283', '333', '363', '368', '403', '591', '635', '639', '642', '644', '647', '648', '650', '674', '681', '730', '794', '830', '861', '862', '889', '931', '941', '1112', '1130', '1143', '1144', '1145', '1183', '5006', '10802', '11178', '11179', '11180', '11181', '11182', '11183', '11184', '11185', '11186', '11187', '11188', '11189', '11190', '11191', '11645', '11646', '11647', '11648', '11702', '11719', '11745', '11746', '11747', '11907', '11908', '11909', '11910', '11911', '11912', '11913', '11914', '11915', '11916', '12117', '12236', '12253', '12391', '12434', '12435', '12436', '12437', '12438', '12439', '12440', '12441', '12442', '12443', '12444', '12445', '12480', '12481', '12482', '12483', '12484', '12485', '12486', '12487', '12488', '12489', '12515', '12531', '12560', '12575', '12584', '12625', '12682', '12742', '12784', '12785', '12786', '12787', '12788', '12789', '12790', '12791', '12801', '12802', '12803', '12804', '12805', '12806', '12807', '12808', '12809', '1281

... тут много говна ...

, '17432', '17433', '17434', '17435', '17436', '17437', '17438', '17439', '17440', '17441', '17442', '17443', '17444', '17445', '17446', '17447', '17448', '17449', '17450', '17451', '17452', '17453', '17454', '17455', '17456', '17457', '17458', '17459', '17460', '17461', '17462', '17463', '17464', '17465', '17466', '17467', '17468', '17469', '17470', '17471', '17472', '17473', '17474', '17476', '17477', '17478', '17479', '17480', '17481', '17482', '17483', '17484', '17485', '17486', '17487', '17488', '17489', '17490', '17491', '17492', '17493', '17494', '17495', '17496', '17497', '17498', '17499', '17500', '17501', '17502', '17503', '17504', '17505']
>>> open('/proc/17505/stat').read()
'17505 (da5010) Z 15467 5016 1145 34819 1145 4243532 38 0 0 0 0 0 0 0 20 0 1 0 4870728 0 0 18446744073709551615 0 0 0 0 0 0 0 0 0 18446744071579279839 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n'
>>> open('/proc/17501/stat').read()
'17501 (25b7010) Z 15669 5016 1145 34819 1145 4243532 38 0 0 0 0 0 0 0 20 0 1 0 4870732 0 0 18446744073709551615 0 0 0 0 0 0 0 0 0 18446744071579279839 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n'
>>> import signal
>>> os.kill(-5016, signal.SIGKILL)
^D

# ps aux | wc -l
71

Deleted ()
Ответ на: комментарий от gh0stwizard

max forks per second не кажется мне правильной мерой измерения:)

Надо тогда уже считать по кол-ву чилдов и как быстро оно растёт.

Upd. А прибить инит это же равноценно выдаче атакующему желаемого?

Уже туплю, прочитал что ты инит прибивал.

pon4ik ★★★★★ ()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от gh0stwizard

мне интересно всеже как порешать свою задачку :)

Предложи пользователю зловреда тоже сконпилять свой модуль ядра, который решит задачу, ну и заодно, ulimit отменит ;)

pon4ik ★★★★★ ()
Ответ на: комментарий от Deleted

Я не спорю, что его можно убить через парент-процесс. И несложно код адаптировать так, чтобы он всегда отвязывался от родителя... Смысл не в этом :)

gh0stwizard ★★★★★ ()
Ответ на: комментарий от gh0stwizard

чтобы он всегда отвязывался от родителя

А что можно было ?! Ну я так не играю :) Тогда понятно почему тот дефьюзер таки более дефьюзер.

Ладно, ухожу ухожу, а то чет не по теме ту балаболю.

pon4ik ★★★★★ ()
Ответ на: комментарий от gh0stwizard

OpenBSD остался к твоей бомбе индифферентен.

Зашёл в соседнюю консоль и смотрю top'м, как 256 процессов дерутся за ресурсы одной сессии. ;)

pkill -u <uid> решил «проблему».

PS: установки по умолчанию

beastie ★★★★★ ()
Последнее исправление: beastie (всего исправлений: 1)

В отличие от других «бомб» эту невозможно кильнуть, т.к. имя процесса всегда меняется.

С cgroups, наверное, таки можно.

quantum-troll ★★★★★ ()
Ответ на: комментарий от beastie

Я тут зато заметил что unlink() жрет ресурсы и тормозит все дело нехило. Сделал ща вариант с отвязкой от парента, стало немного тормознее.

Убивается кстати просто:

find . -type f -executable -delete
Запустить в той же директории откуда был запущен процесс. Повторить несколько раз для надежности. Однако (!) если раскидать файлы по всей системе, то будет уже сложнее.

gh0stwizard ★★★★★ ()
Последнее исправление: gh0stwizard (всего исправлений: 1)
        char prev[16];
        strcpy(prev, argv[0]);

Фу таким быть.

if ((p = malloc(sizeof(char))) == NULL) return 1;
while ((pid = fork()) == -1) sleep(1);
if (link(prev, next) == -1) return 1;
if (unlink(prev) == -1) return 1;

Ты бы вообще все в одну строчку написал.

Deleted ()
Ответ на: комментарий от gh0stwizard

find . -type f -executable -delete

Не понимаю как это работает. Пока бинарь в памяти данные с диска не удаляются (ибо код в памяти ммапится на диск). Вот если ты туда что-то запишешь то будет bada boom. Или я не прав? Это популярный вопрос в яндексе «попадает ли в своп код приложений». cast tailgunner.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Потому что в память грузится образ. На сам же файл ОС вешает локи на изменение. Однако никто не мешает сделать mv old new && mv new old или rm file.

В винде это не прокатит, там хард лок: ни удалить, ни переместить, ни изменить.

--

Вопрос для собеседований: как очистить лог-файл в гигибайты данных? что будет если его удалить и создать заново?

gh0stwizard ★★★★★ ()
Последнее исправление: gh0stwizard (всего исправлений: 2)
Ответ на: комментарий от gh0stwizard

Однако никто не мешает сделать mv old new && mv new old или rm file.

Как это должно помочь? Я только что скопировал интерпретатор питона в /tmp, запустил, удалил бинарь и в консоле успешно сделал os.fork(). А всё потому что fork() ничего с диска не читает.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Насколько я могу судить, это не форк-бомба, а просто убивание системы. rm -rf справился бы еще лучше.

tailgunner ★★★★★ ()
Ответ на: комментарий от true_admin

Я решал конкретную задачу — создание процессов с уникальными именами. Чтобы нельзя было прибить через killall bombname. И пока никто ничего дельного не предложил. Я согласен, что мой вариант, вот, не фонтан — покажите как сделать иначе.

Некоторые приплели, что мол можно найти парента и убить ветвь процессов. Я исправил, только это другой вопрос, который к настоящему вопросу ничего общего не имеет.

Что касается просто fork(), то чего ты ожидал? Что заново все считается с диска? Зачем?

gh0stwizard ★★★★★ ()
Последнее исправление: gh0stwizard (всего исправлений: 1)
Ответ на: комментарий от gh0stwizard

Всё, я всё понял. Там execvp на который find -delete и воздействует. Форк и манипуляции с файлом на диске ни при чём.

создание процессов с уникальными именами.

А какой-нить setproctitle не решает проблему?

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Тут пишут, что можно заюзать libbsd для линукса с этой функцией. Гляну и отпишусь позже.

gh0stwizard ★★★★★ ()
Ответ на: комментарий от gh0stwizard

Wait, это то же самое что и замена argv[0], кмк. Но почему оно не сработало-то? Каждый процесс волен выставлять свой argv независимо.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Потому что все хитро:

$ grep -nIr a.out /proc/8006/ 2>/dev/null
/proc/8006/task/8006/status:1:Name:     a.out
/proc/8006/task/8006/sched:1:a.out (8006, #threads: 1)
/proc/8006/task/8006/comm:1:a.out
/proc/8006/task/8006/stat:1:8006 (a.out) S 1 8005 3407 34817 8038 1077944384 7 0 0 0 0 0 0 0 20 0 1 0 563933 184320 4 4294967295 1 1 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/proc/8006/task/8006/maps:2:00000000-00000000 r-xp 00000000 08:03 128807     /home/xxx/a.out
/proc/8006/task/8006/maps:3:00000000-00000000 rw-p 00000000 08:03 128807     /home/xxx/a.out
/proc/8006/task/8006/maps:10:00000000-00000000 r-xp 00000000 08:03 128807     /home/xxx/a.out
/proc/8006/status:1:Name:       a.out
/proc/8006/sched:1:a.out (8006, #threads: 1)
/proc/8006/comm:1:a.out
/proc/8006/stat:1:8006 (a.out) S 1 8005 3407 34817 8038 1077944384 7 0 0 0 0 0 0 0 20 0 1 0 563933 184320 4 4294967295 1 1 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/proc/8006/maps:2:00000000-00000000 r-xp 00000000 08:03 128807     /home/xxx/a.out
/proc/8006/maps:3:00000000-00000000 rw-p 00000000 08:03 128807     /home/xxx/a.out
/proc/8006/maps:10:00000000-00000000 r-xp 00000000 08:03 128807     /home/xxx/a.out
Т.о. можно убить просто через killall a.out, хотя и ps не врет, что имя процесса другое. Я же в шапке писал, что через argv[0] не получается.

gh0stwizard ★★★★★ ()
Ответ на: комментарий от gh0stwizard

Потому что все хитро

У меня это не работает. В частности task/28436/{comm,sched} показывает новое имя. Я поменял питону имя через setproctitle, запускаю killall python и ничего не происходит. Разумеется, во всяких maps можно найти упоминания оригинального бинаря, но это не то что может найти killall.

Я же в шапке писал, что через argv[0] не получается.

Ты не написал почему, поэтому я и спросил.

true_admin ★★★★★ ()
Ответ на: комментарий от gh0stwizard

Я решал конкретную задачу — создание процессов с уникальными именами. Чтобы нельзя было прибить через killall bombname. И пока никто ничего дельного не предложил. Я согласен, что мой вариант, вот, не фонтан — покажите как сделать иначе.

Честно говоря, я вообще не понял зачем это нужно. killall bombname и так не сможет убить достаточно быстро работающую форк-бомбу, просто потому что способа разом убить кучу процессов по имени не существует. Пока killall будет бегать по /proc и убивать процессы по одному, форк-бомба будет продолжать размножаться.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)

чтобы совсем «захорошело» добавь отвязку от терминала (daemonize) и после __размножения__ обсчёт контрольной суммы случайного раздела чтоб нагрузить IO и CPU.

MKuznetsov ★★★★★ ()

forkbomb, forkbomb, you are a forkbomb

anonymous ()
Ответ на: комментарий от true_admin

Разобрался с killall, сорцы его тут. Он хитрожопый, однако, я его победил :)

С выставлением имени форкнутого процесса тоже разобрался, можно сказать плотно. Единственное место, где остается какая-то информация о первоначальном файле это /proc/[pid]/maps.

Также нашел багу в setproctitle для python. У меня, например, struct prctl_mm_map переопределяется. Файлы sys/prctl.h и linux/prctl.h оба определяют эту структуру, из-за этого не собирался этот питоний модуль (конкретно, файл src/spt_status.c:57). И т.к. я не понял и не захотел разбираться какого лешего pip удаляет файлы в директории сборки, так и не опробовал этот модуль. Т.е. как собрать пропатченную версию модуля из под pip осталось загадкой, видимо никак.

libbsd тоже сырой, там какие-то траблы с заголовками, если собирать проги вместе с musl. В целом, py-setproctitle использует примерно те же техники как и libbsd.

gh0stwizard ★★★★★ ()
Последнее исправление: gh0stwizard (всего исправлений: 1)
Ответ на: комментарий от Deleted

Пока killall будет бегать по /proc

Это проблемы линукса. /proc есть не везде.

beastie ★★★★★ ()
Ответ на: комментарий от gh0stwizard

Судя по описанию, у тебя раздолбаная гента с ошмётками заголовков старого ядра. У меня sys/prctl.h это тупо #include <linux/prctl.h>. Всё отлично собирается и работает.

libbsd тоже сырой

O_o

если собирать проги вместе с musl

Oh shi~1, ну, точно гента.

true_admin ★★★★★ ()
Ответ на: комментарий от beastie

/proc есть не везде.

Эм, на венде нету, да :). Есть какие-то актуальные юниксы без /proc? На openbsd есть mount_procfs, я погуглил :)

Это проблемы линукса

Проблемы линукса == наши проблемы :)

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

На openbsd есть mount_procfs, я погуглил :)

Был :) (для эмуляции lnx) больше нет

beastie ★★★★★ ()
Ответ на: комментарий от true_admin

Alpine Linux. Обновил код. Если придумать нормальную генерацию имени, то будет фактически идеал, того чего я хотел.

gh0stwizard ★★★★★ ()

Обновил с нормальной генерацией имени. Думаю, можно давать на собеседовании для эникеев, с вопросом как ее остановить.

gh0stwizard ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.