frostegater
16.06.2012, 11:36
Большое спасибо Davee и some1 за всё.
Сегодня я хочу вам рассказать про поиск kernel эксплоитов. Для начала разберемся для чего нам это нужно. PSP имеет много разделов памяти (вы можете посмотреть их ниже в спойлере), но речь пойдет только про 2: user и kernel. Различается память в уровне доступа. Код выполненный с kernel правами (запущенный из модуля с kernel правами) имеет права системы и имеет доступ ко всему, в свою очередь user имеет доступ только к: Scratchpad, VRAM, P5 и Extended Memory (в общем ко всему, кроме kernel).
И так, для чего же нам нужны kernel права? Для того чтобы сделать изменения в прошивке. Проще говоря, создание полноценного HEN или CFW невозможно без наложения патчей на модули системы. Для этого мы и будем получать kernel права. Хочу огорчить, вам необходимы знания языка Си и ассемблера под архитектуру MIPS (чтобы изучить прочти всё про MIPS в разделе "Интересные ссылки" (см. в конец)).
|-------------------------------|------------|------------|------------|--------|--------|
| | | | Размер | Размер | Размер |
| Название блока памяти: | Адрес: | Размер: | в | в | в |
| | | | байтах | Кб | Мб |
|-------------------------------|------------|------------|------------|--------|--------|
| Scratchpad | 0x00010000 | 0x00004000 | 16'384 | 16 | 0,0156 |
| VRAM | 0x04000000 | 0x00200000 | 2'097'152 | 2'048 | 2 |
| P5 | 0x08000000 | 0x00800000 | 8'388'608 | 8'192 | 8 |
| User memory | 0x08800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
| Extended memory | 0x0A000000 | 0x02000000 | 33'554'432 | 32'768 | 32 |
|-------------------------------|------------|------------|------------|--------|--------|
| Scratchpad (uncached) | 0x40010000 | 0x00004000 | 16'384 | 16 | 0,0156 |
| VRAM (uncached) | 0x44000000 | 0x00200000 | 2'097'152 | 2'048 | 2 |
| P5 (uncached) | 0x48000000 | 0x00800000 | 8'388'608 | 8'192 | 8 |
| User memory (uncached) | 0x48800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
| Extended memory (uncached) | 0x4A000000 | 0x02000000 | 33'554'432 | 32'768 | 32 |
|-------------------------------|------------|------------|------------|--------|--------|
| Kernel memory (low) | 0x88000000 | 0x00400000 | 4'194'304 | 4'096 | 4 |
| Kernel memory (high) | 0x88800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
| Kernel memory (low uncached) | 0xA8000000 | 0x00400000 | 4'194'304 | 4'096 | 4 |
| Kernel memory (high uncached) | 0xA8800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
|-------------------------------|------------|------------|------------|--------|--------|
| Internal RAM | 0xBFC00000 | 0x00100000 | 1'048'576 | 1'024 | 1 |
|-------------------------------|------------|------------|------------|--------|--------|
В прошивке PSP модули подразделяются на 2 категории: user и kernel. Права модулей различаются соответственно. User модули грузятся в user память, kernel модули грузятся в kernel память. Ниже приведен список kernel модулей.
wlan.prx
vshbridge_msapp.prx
vshbridge.prx
videocodec_260.prx
vaudio.prx
utility.prx
usbstorms.prx
usbstormgr.prx
usbstorboot.prx
usbstor.prx
usbpspcm.prx
usbmic.prx
usbgps.prx
usbdmb.prx
usbcam.prx
usbacc.prx
usb1seg.prx
usb.prx
umdcache.prx
threadman.prx
sysmem.prx
sircs.prx
sc_sascore.prx
rtc.prx
registry.prx
pspnet_adhoc_auth.prx
psheet.prx
power_%psp_model%.prx
popsman.prx
pops_%psp_model%.prx
openpsid.prx
npdrm.prx
np_inst.prx
np_core.prx
np9660.prx
msstor.prx
msaudio.prx
mpegbase_260.prx
modulemgr.prx
mlnbridge_msapp.prx
mlnbridge.prx
mgvideo.prx
mesg_led_%psp_model%.prx
mediaman.prx
me_wrapper.prx
lowio.prx
loadexec_%psp_module%.prx
loadcore.prx
libdnas_core.prx
lfatfs.prx
isofs.prx
iofilemgr.prx
interruptman.prx
impose_%psp_module%.prx
ifhandle.prx
http_storage.prx
hpremote_%psp_module%.prx
ge.prx
g729.prx
fatms.prx
exceptionman.prx
display_%psp_module%.prx
ctrl.prx
codepage.prx
chnnlsv.prx
cert_loader.prx
avcodec.prx
audiocodec_260.prx
audio.prx
Первое, что вы должны сделать - это дизассемблировать модули из списка выше. Для этого используйте программу prxtool.
Если модуль не загружен, вы можете попробовать загрузить его функцией sceUtilityLoadModule(int module_id).
/* Net Modules */
#define PSP_MODULE_NET_COMMON 0x0100
#define PSP_MODULE_NET_ADHOC 0x0101
#define PSP_MODULE_NET_INET 0x0102
#define PSP_MODULE_NET_PARSEURI 0x0103
#define PSP_MODULE_NET_PARSEHTTP 0x0104
#define PSP_MODULE_NET_HTTP 0x0105
#define PSP_MODULE_NET_SSL 0x0106
/* USB Modules */
#define PSP_MODULE_USB_PSPCM 0x0200
#define PSP_MODULE_USB_MIC 0x0201
#define PSP_MODULE_USB_CAM 0x0202
#define PSP_MODULE_USB_GPS 0x0203
/* Audio/video Modules */
#define PSP_MODULE_AV_AVCODEC 0x0300
#define PSP_MODULE_AV_SASCORE 0x0301
#define PSP_MODULE_AV_MPEGBASE 0x0303
#define PSP_MODULE_AV_VAUDIO 0x0305
#define PSP_MODULE_AV_G729 0x0307
#define PSP_MODULE_NP_DRM 0x0500
Для того чтобы получить kernel права мы должны сделать jump (jr, jalr) из kernel модуля на свой код, который может быть в любом участке памяти. Не будем изобретать велосипед и будем использовать уже известный метод. Это использование функции sceKernelLibcTime.
Посмотрим в код модуля sysmem.prx, который и содержит функцию sceKernelLibcTime, найдите его, задав поиск по тексту. Я напишу его в двух вариациях, смотрите на второй. Первый понадобится позже.
sceKernelLibcTime:
lui $v1, 0x0 ; 0x0000F794: 0x3C030000 '...<'
lw $a1, 2244($v1) ; 0x0000F798: 0x8C6508C4 '..e.'
addiu $sp, $sp, -16 ; 0x0000F79C: 0x27BDFFF0 '...''
sw $s0, 0($sp) ; 0x0000F7A0: 0xAFB00000 '....'
sll $v1, $k1, 11 ; 0x0000F7A4: 0x001B1AC0 '....'
move $s0, $k1 ; 0x0000F7A8: 0x03608021 '!.`.'
sw $ra, 4($sp) ; 0x0000F7AC: 0xAFBF0004 '....'
beqz $a1, loc_0000F7CC ; 0x0000F7B0: 0x10A00006 '....'
move $a2, $zr ; 0x0000F7B4: 0x00003021 '!0..'
move $k1, $v1 ; 0x0000F7B8: 0x0060D821 '!.`.'
and $v1, $v1, $a0 ; 0x0000F7BC: 0x00641824 '$.d.'
bgez $v1, loc_0000F7E0 ; 0x0000F7C0: 0x04610007 '..a.'
nop ; 0x0000F7C4: 0x00000000 '....'
loc_0000F7C8: ; Refs: 0x0000F7E8
move $k1, $s0 ; 0x0000F7C8: 0x0200D821 '!...'
loc_0000F7CC: ; Refs: 0x0000F7B0
lw $ra, 4($sp) ; 0x0000F7CC: 0x8FBF0004 '....'
lw $s0, 0($sp) ; 0x0000F7D0: 0x8FB00000 '....'
move $v0, $a2 ; 0x0000F7D4: 0x00C01021 '!...'
jr $ra ; 0x0000F7D8: 0x03E00008 '....'
addiu $sp, $sp, 16 ; 0x0000F7DC: 0x27BD0010 '...''
loc_0000F7E0: ; Refs: 0x0000F7C0
jalr $a1 ; 0x0000F7E0: 0x00A0F809 '....'
nop ; 0x0000F7E4: 0x00000000 '....'
j loc_0000F7C8 ; 0x0000F7E8: 0x08003DF2 '.=..'
move $a2, $v0 ; 0x0000F7EC: 0x00403021 '!0@.'
sceKernelLibcTime(arg0, arg1) // arg1 это второй аргумент функции...
{
v1 = 0;
arg1 = mem[v1 + 2244]; // ...но здесь он перекрывается, то есть
// arg1 уже задается не из аргумента и мы
// не можем его контроллировать.
// Назовем это место "ЦЕЛЬ", запомните его и
// его адрес 0x0000F798 в sysmem.prx.
if(arg1 == 0) return 0; // Проверка второго аргумента, можно не обращать
// внимания, он всё равно не будет равен 0
if((0x80000000 & arg0) >= 0) // нам нужно пройти эту проверку,
{ // то есть коньюнкция v1 и arg0 должна
// быть положительной или равной 0, поэтому
// первый аргумент (arg0) мы сделаем 0.
// То есть: 0x80000000 & 0 = 0
jalr $arg1 // делаем jump по адресу, записаному в arg1
}
return 0;
}
Прочитав псевдокод функции sceKernelLibcTime вы видите, что мы можем сделать jump на любой адрес, но нам мешает операция "ЦЕЛЬ" по адресу 0x0000F798. Нужно её уничтожить. А уничтожать можно любой инструкцией, которая не взаимодействует с $a1. В идеале это nop (0x00000000). Проще говоря мы должны забить нулями 4 байта по адресу 0x0000F798 в модуле sysmem.prx. Но не тут то было. Мы не можем этого сделать из программы с user правами, ведь вы помните: к kernel памяти из user нет доступа.
Кстати, забивать нулями мы должны 4 байта в памяти, то есть адрес_модуля+адрес_инструкции_в_модуле. sysmem.prx всегда находится по адресу 0x88000000, поэтому реальный адрес будет 0x88000000 + 0x0000F798 = 0x8800F798.
Теперь предопределим цель следующего пункта. Мы должны зануллить 4 байта памяти по адресу 0x8800F798.
И вот опять незадача. Адрес 0x8800F798 это kernel память к которой мы не имеем доступа из user памяти.
Использовать kernel память мы можем только из kernel модулей. Для этого мы будем использовать функции из kernel модулей. Использовать можно лишь те функции, которые экспортированы в user память.
Поставим подцель. Мы должны иметь полный контроль над sw, sh или sb инструкцией в kernel функции.
sw $a0, C($a1) - эта инструкция позволяет заполнить 4 байта ($a0) в памяти по адресу $a1 + C
Пример использования:
li $a0, 0x12345678 ; значение
li $a1, 0x88000000 ; адрес
sw $a0, 0($a1)
sh $a0, C($a1) - эта инструкция позволяет заполнить 2 байта ($a0) в памяти по адресу $a1 + C
Пример использования:
li $a0, 0x1234 ; значение
li $a1, 0x88000000 ; адрес
sh $a0, 0($a1)
sb $s0, C($a1) - эта инструкция позволяет заполнить 1 байт ($a0) в памяти по адресу $a1 + C
Пример использования:
li $a0, 0x12 ; значение
li $a1, 0x88000000 ; адрес
sb $a0, 0($a1)
Допустим у нас есть функция:
sceLolFunc1:
sw $zr, 0($a0)
Мы можем напрямик записать 0x00000000 в любой адрес памяти, но такие функции это мечта и таких, увы, не бывает (по крайней мере импортированных в user). Sony хорошо защитила код от посягательств на kernel память и сейчас мы рассмотрим как.
Есть такой регистр: $k1. Изначально он равен 0x00010000.
Рассмотрим пример защищённой функции:
sceLolFunc2:
sll $k1, $k1, 11 ; k1 << 11 = 0x80000000
and $v1, $k1, $a0 ; Если один из аргументов будет = 0
; то v1 будет = 0.
bgezl $v1, loc1 ; Переход к подпрограмме loc1 если v1 >= 0.
ret:
jr $ra ; выход
loc1:
sw $zr, 0($a0) ; делаем заветное sw
j ret ; перенаправление на выход (ret)
Чтобы перейти к подпрограмме с sw нам нужно выполнить условие: v1 >= 0. Если вы подумали, что 0x8800F798 это положительное число, то это неправда. Просто число записано в типе u32. Чтобы узнать реальное число (signed) отнимите u32 число от 0xFFFF. signed=0xFFFF-u32.
Но не суть. Мы можем только сделать 1 аргумент нулем чтобы условие оказалось верно. И это будет k1, т.к. a0(аргумент функции) не может быть равен 0, так как он должен быть 0x8800F798.
Теперь сделаем k1 нулем. Это просто, но не просто найти подходящую функцию.
Допустим есть ещё 1 функция sceLolFunc3:
sceLolFunc3:
sll $k1, $k1, 11 ; k1 = 0x80000000
move $a0, $a0 ; просто чтобы было видно что аргумент
; текущей функции управляет аргументом функции sceLolFunc2
jal sceLolFunc2
nop
sceLolFunc2:
sll $k1, $k1, 11 ; k1 = 0x40000000000, но k1 вмещает
; только 32 бита, то есть k1 будет равен 0x00000000
and $v1, $k1, $a0 ; v1 будет = 0
bgezl $v1, loc1 ; и мы спокойно пройдем ЭТУ проверку
ret:
jr $ra
loc1:
sw $zr, 0($a0) ; и зарисуем нолик в нужный адрес
j ret
Теперь напишем эксплоит для наших выдуманных функций:
void clear_caches()
{
sceKernelIcacheInvalidateAll();
sceKernelDcacheWritebackInvalidateAll();
}
int kernel_permission_call()
{
// код с kernel правами
return 0;
}
void do_exploit()
{
sceLolFunc3(0x8800F798); // перетираем нулем 4 байта (u32) значение по адресу 0x8800F798
// тем самым убиваем заглушку второго аргумента в функции sceKernelLibcTime
clear_caches();
u32 intr = pspSdkDisableInterrupts();
sceKernelLibcTime(0, &kernel_permission_call); // делаем jump на kernel_permission_call
pspSdkEnableInterrupts(intr);
}
После того как эксплоит выполнен важно сделать восстановление kernel памяти.
Cнимите дамп kernel памяти до и после выполнения эксплоита.
В psplink даем команду:
savemem 0x88000000 0x400000 dumpname.bin
Назовите их before.bin и after.bin соответственно.
Положите их рядом с этой прогой: 8057.
Запустите прогу.
Получите на выходе файл output.c с функцией sysmem_recovery().
Примените эту функцию в kernel_permission_call.
А теперь соберем всё воедино:
void clear_caches()
{
sceKernelIcacheInvalidateAll();
sceKernelDcacheWritebackInvalidateAll();
}
void recovery_sysmem()
{
_sw(0x8C6508C4, 0x8800F798)
}
int kernel_permission_call()
{
recovery_sysmem();
clear_caches();
// код с kernel правами
return 0;
}
void do_exploit()
{
sceLolFunc3(0x8800F798); // перетираем нулем 4 байта (u32) значение по адресу 0x8800F798
// тем самым убиваем заглушку второго аргумента в функции sceKernelLibcTime
clear_caches();
u32 intr = pspSdkDisableInterrupts();
sceKernelLibcTime(0, &kernel_permission_call); // делаем jump на kernel_permission_call
pspSdkEnableInterrupts(intr);
}
Интересные ссылки:
Учебник по MIPS (англ.): http://chortle.ccsu.edu/assemblytutorial/tutorialcontents.html.
Описание MIPS архитектуры (рус.): http://ru.wikipedia.org/wiki/MIPS_(%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0).
Документация к процессорам архитектуры MIPS (англ.): http://www2.engr.arizona.edu/~ece369/Resources/MipsInstructionSetReference.pdf
Описание 6.20 kxploit'а от TN (англ.): http://lolhax.org/2010/12/23/arcanum/.
Описание 6.39 kxploit'а от some1 (англ.):http://wololo.net/talk/viewtopic.php?f=6&t=6612&start=0.
Сегодня я хочу вам рассказать про поиск kernel эксплоитов. Для начала разберемся для чего нам это нужно. PSP имеет много разделов памяти (вы можете посмотреть их ниже в спойлере), но речь пойдет только про 2: user и kernel. Различается память в уровне доступа. Код выполненный с kernel правами (запущенный из модуля с kernel правами) имеет права системы и имеет доступ ко всему, в свою очередь user имеет доступ только к: Scratchpad, VRAM, P5 и Extended Memory (в общем ко всему, кроме kernel).
И так, для чего же нам нужны kernel права? Для того чтобы сделать изменения в прошивке. Проще говоря, создание полноценного HEN или CFW невозможно без наложения патчей на модули системы. Для этого мы и будем получать kernel права. Хочу огорчить, вам необходимы знания языка Си и ассемблера под архитектуру MIPS (чтобы изучить прочти всё про MIPS в разделе "Интересные ссылки" (см. в конец)).
|-------------------------------|------------|------------|------------|--------|--------|
| | | | Размер | Размер | Размер |
| Название блока памяти: | Адрес: | Размер: | в | в | в |
| | | | байтах | Кб | Мб |
|-------------------------------|------------|------------|------------|--------|--------|
| Scratchpad | 0x00010000 | 0x00004000 | 16'384 | 16 | 0,0156 |
| VRAM | 0x04000000 | 0x00200000 | 2'097'152 | 2'048 | 2 |
| P5 | 0x08000000 | 0x00800000 | 8'388'608 | 8'192 | 8 |
| User memory | 0x08800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
| Extended memory | 0x0A000000 | 0x02000000 | 33'554'432 | 32'768 | 32 |
|-------------------------------|------------|------------|------------|--------|--------|
| Scratchpad (uncached) | 0x40010000 | 0x00004000 | 16'384 | 16 | 0,0156 |
| VRAM (uncached) | 0x44000000 | 0x00200000 | 2'097'152 | 2'048 | 2 |
| P5 (uncached) | 0x48000000 | 0x00800000 | 8'388'608 | 8'192 | 8 |
| User memory (uncached) | 0x48800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
| Extended memory (uncached) | 0x4A000000 | 0x02000000 | 33'554'432 | 32'768 | 32 |
|-------------------------------|------------|------------|------------|--------|--------|
| Kernel memory (low) | 0x88000000 | 0x00400000 | 4'194'304 | 4'096 | 4 |
| Kernel memory (high) | 0x88800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
| Kernel memory (low uncached) | 0xA8000000 | 0x00400000 | 4'194'304 | 4'096 | 4 |
| Kernel memory (high uncached) | 0xA8800000 | 0x01800000 | 25'165'824 | 24'576 | 24 |
|-------------------------------|------------|------------|------------|--------|--------|
| Internal RAM | 0xBFC00000 | 0x00100000 | 1'048'576 | 1'024 | 1 |
|-------------------------------|------------|------------|------------|--------|--------|
В прошивке PSP модули подразделяются на 2 категории: user и kernel. Права модулей различаются соответственно. User модули грузятся в user память, kernel модули грузятся в kernel память. Ниже приведен список kernel модулей.
wlan.prx
vshbridge_msapp.prx
vshbridge.prx
videocodec_260.prx
vaudio.prx
utility.prx
usbstorms.prx
usbstormgr.prx
usbstorboot.prx
usbstor.prx
usbpspcm.prx
usbmic.prx
usbgps.prx
usbdmb.prx
usbcam.prx
usbacc.prx
usb1seg.prx
usb.prx
umdcache.prx
threadman.prx
sysmem.prx
sircs.prx
sc_sascore.prx
rtc.prx
registry.prx
pspnet_adhoc_auth.prx
psheet.prx
power_%psp_model%.prx
popsman.prx
pops_%psp_model%.prx
openpsid.prx
npdrm.prx
np_inst.prx
np_core.prx
np9660.prx
msstor.prx
msaudio.prx
mpegbase_260.prx
modulemgr.prx
mlnbridge_msapp.prx
mlnbridge.prx
mgvideo.prx
mesg_led_%psp_model%.prx
mediaman.prx
me_wrapper.prx
lowio.prx
loadexec_%psp_module%.prx
loadcore.prx
libdnas_core.prx
lfatfs.prx
isofs.prx
iofilemgr.prx
interruptman.prx
impose_%psp_module%.prx
ifhandle.prx
http_storage.prx
hpremote_%psp_module%.prx
ge.prx
g729.prx
fatms.prx
exceptionman.prx
display_%psp_module%.prx
ctrl.prx
codepage.prx
chnnlsv.prx
cert_loader.prx
avcodec.prx
audiocodec_260.prx
audio.prx
Первое, что вы должны сделать - это дизассемблировать модули из списка выше. Для этого используйте программу prxtool.
Если модуль не загружен, вы можете попробовать загрузить его функцией sceUtilityLoadModule(int module_id).
/* Net Modules */
#define PSP_MODULE_NET_COMMON 0x0100
#define PSP_MODULE_NET_ADHOC 0x0101
#define PSP_MODULE_NET_INET 0x0102
#define PSP_MODULE_NET_PARSEURI 0x0103
#define PSP_MODULE_NET_PARSEHTTP 0x0104
#define PSP_MODULE_NET_HTTP 0x0105
#define PSP_MODULE_NET_SSL 0x0106
/* USB Modules */
#define PSP_MODULE_USB_PSPCM 0x0200
#define PSP_MODULE_USB_MIC 0x0201
#define PSP_MODULE_USB_CAM 0x0202
#define PSP_MODULE_USB_GPS 0x0203
/* Audio/video Modules */
#define PSP_MODULE_AV_AVCODEC 0x0300
#define PSP_MODULE_AV_SASCORE 0x0301
#define PSP_MODULE_AV_MPEGBASE 0x0303
#define PSP_MODULE_AV_VAUDIO 0x0305
#define PSP_MODULE_AV_G729 0x0307
#define PSP_MODULE_NP_DRM 0x0500
Для того чтобы получить kernel права мы должны сделать jump (jr, jalr) из kernel модуля на свой код, который может быть в любом участке памяти. Не будем изобретать велосипед и будем использовать уже известный метод. Это использование функции sceKernelLibcTime.
Посмотрим в код модуля sysmem.prx, который и содержит функцию sceKernelLibcTime, найдите его, задав поиск по тексту. Я напишу его в двух вариациях, смотрите на второй. Первый понадобится позже.
sceKernelLibcTime:
lui $v1, 0x0 ; 0x0000F794: 0x3C030000 '...<'
lw $a1, 2244($v1) ; 0x0000F798: 0x8C6508C4 '..e.'
addiu $sp, $sp, -16 ; 0x0000F79C: 0x27BDFFF0 '...''
sw $s0, 0($sp) ; 0x0000F7A0: 0xAFB00000 '....'
sll $v1, $k1, 11 ; 0x0000F7A4: 0x001B1AC0 '....'
move $s0, $k1 ; 0x0000F7A8: 0x03608021 '!.`.'
sw $ra, 4($sp) ; 0x0000F7AC: 0xAFBF0004 '....'
beqz $a1, loc_0000F7CC ; 0x0000F7B0: 0x10A00006 '....'
move $a2, $zr ; 0x0000F7B4: 0x00003021 '!0..'
move $k1, $v1 ; 0x0000F7B8: 0x0060D821 '!.`.'
and $v1, $v1, $a0 ; 0x0000F7BC: 0x00641824 '$.d.'
bgez $v1, loc_0000F7E0 ; 0x0000F7C0: 0x04610007 '..a.'
nop ; 0x0000F7C4: 0x00000000 '....'
loc_0000F7C8: ; Refs: 0x0000F7E8
move $k1, $s0 ; 0x0000F7C8: 0x0200D821 '!...'
loc_0000F7CC: ; Refs: 0x0000F7B0
lw $ra, 4($sp) ; 0x0000F7CC: 0x8FBF0004 '....'
lw $s0, 0($sp) ; 0x0000F7D0: 0x8FB00000 '....'
move $v0, $a2 ; 0x0000F7D4: 0x00C01021 '!...'
jr $ra ; 0x0000F7D8: 0x03E00008 '....'
addiu $sp, $sp, 16 ; 0x0000F7DC: 0x27BD0010 '...''
loc_0000F7E0: ; Refs: 0x0000F7C0
jalr $a1 ; 0x0000F7E0: 0x00A0F809 '....'
nop ; 0x0000F7E4: 0x00000000 '....'
j loc_0000F7C8 ; 0x0000F7E8: 0x08003DF2 '.=..'
move $a2, $v0 ; 0x0000F7EC: 0x00403021 '!0@.'
sceKernelLibcTime(arg0, arg1) // arg1 это второй аргумент функции...
{
v1 = 0;
arg1 = mem[v1 + 2244]; // ...но здесь он перекрывается, то есть
// arg1 уже задается не из аргумента и мы
// не можем его контроллировать.
// Назовем это место "ЦЕЛЬ", запомните его и
// его адрес 0x0000F798 в sysmem.prx.
if(arg1 == 0) return 0; // Проверка второго аргумента, можно не обращать
// внимания, он всё равно не будет равен 0
if((0x80000000 & arg0) >= 0) // нам нужно пройти эту проверку,
{ // то есть коньюнкция v1 и arg0 должна
// быть положительной или равной 0, поэтому
// первый аргумент (arg0) мы сделаем 0.
// То есть: 0x80000000 & 0 = 0
jalr $arg1 // делаем jump по адресу, записаному в arg1
}
return 0;
}
Прочитав псевдокод функции sceKernelLibcTime вы видите, что мы можем сделать jump на любой адрес, но нам мешает операция "ЦЕЛЬ" по адресу 0x0000F798. Нужно её уничтожить. А уничтожать можно любой инструкцией, которая не взаимодействует с $a1. В идеале это nop (0x00000000). Проще говоря мы должны забить нулями 4 байта по адресу 0x0000F798 в модуле sysmem.prx. Но не тут то было. Мы не можем этого сделать из программы с user правами, ведь вы помните: к kernel памяти из user нет доступа.
Кстати, забивать нулями мы должны 4 байта в памяти, то есть адрес_модуля+адрес_инструкции_в_модуле. sysmem.prx всегда находится по адресу 0x88000000, поэтому реальный адрес будет 0x88000000 + 0x0000F798 = 0x8800F798.
Теперь предопределим цель следующего пункта. Мы должны зануллить 4 байта памяти по адресу 0x8800F798.
И вот опять незадача. Адрес 0x8800F798 это kernel память к которой мы не имеем доступа из user памяти.
Использовать kernel память мы можем только из kernel модулей. Для этого мы будем использовать функции из kernel модулей. Использовать можно лишь те функции, которые экспортированы в user память.
Поставим подцель. Мы должны иметь полный контроль над sw, sh или sb инструкцией в kernel функции.
sw $a0, C($a1) - эта инструкция позволяет заполнить 4 байта ($a0) в памяти по адресу $a1 + C
Пример использования:
li $a0, 0x12345678 ; значение
li $a1, 0x88000000 ; адрес
sw $a0, 0($a1)
sh $a0, C($a1) - эта инструкция позволяет заполнить 2 байта ($a0) в памяти по адресу $a1 + C
Пример использования:
li $a0, 0x1234 ; значение
li $a1, 0x88000000 ; адрес
sh $a0, 0($a1)
sb $s0, C($a1) - эта инструкция позволяет заполнить 1 байт ($a0) в памяти по адресу $a1 + C
Пример использования:
li $a0, 0x12 ; значение
li $a1, 0x88000000 ; адрес
sb $a0, 0($a1)
Допустим у нас есть функция:
sceLolFunc1:
sw $zr, 0($a0)
Мы можем напрямик записать 0x00000000 в любой адрес памяти, но такие функции это мечта и таких, увы, не бывает (по крайней мере импортированных в user). Sony хорошо защитила код от посягательств на kernel память и сейчас мы рассмотрим как.
Есть такой регистр: $k1. Изначально он равен 0x00010000.
Рассмотрим пример защищённой функции:
sceLolFunc2:
sll $k1, $k1, 11 ; k1 << 11 = 0x80000000
and $v1, $k1, $a0 ; Если один из аргументов будет = 0
; то v1 будет = 0.
bgezl $v1, loc1 ; Переход к подпрограмме loc1 если v1 >= 0.
ret:
jr $ra ; выход
loc1:
sw $zr, 0($a0) ; делаем заветное sw
j ret ; перенаправление на выход (ret)
Чтобы перейти к подпрограмме с sw нам нужно выполнить условие: v1 >= 0. Если вы подумали, что 0x8800F798 это положительное число, то это неправда. Просто число записано в типе u32. Чтобы узнать реальное число (signed) отнимите u32 число от 0xFFFF. signed=0xFFFF-u32.
Но не суть. Мы можем только сделать 1 аргумент нулем чтобы условие оказалось верно. И это будет k1, т.к. a0(аргумент функции) не может быть равен 0, так как он должен быть 0x8800F798.
Теперь сделаем k1 нулем. Это просто, но не просто найти подходящую функцию.
Допустим есть ещё 1 функция sceLolFunc3:
sceLolFunc3:
sll $k1, $k1, 11 ; k1 = 0x80000000
move $a0, $a0 ; просто чтобы было видно что аргумент
; текущей функции управляет аргументом функции sceLolFunc2
jal sceLolFunc2
nop
sceLolFunc2:
sll $k1, $k1, 11 ; k1 = 0x40000000000, но k1 вмещает
; только 32 бита, то есть k1 будет равен 0x00000000
and $v1, $k1, $a0 ; v1 будет = 0
bgezl $v1, loc1 ; и мы спокойно пройдем ЭТУ проверку
ret:
jr $ra
loc1:
sw $zr, 0($a0) ; и зарисуем нолик в нужный адрес
j ret
Теперь напишем эксплоит для наших выдуманных функций:
void clear_caches()
{
sceKernelIcacheInvalidateAll();
sceKernelDcacheWritebackInvalidateAll();
}
int kernel_permission_call()
{
// код с kernel правами
return 0;
}
void do_exploit()
{
sceLolFunc3(0x8800F798); // перетираем нулем 4 байта (u32) значение по адресу 0x8800F798
// тем самым убиваем заглушку второго аргумента в функции sceKernelLibcTime
clear_caches();
u32 intr = pspSdkDisableInterrupts();
sceKernelLibcTime(0, &kernel_permission_call); // делаем jump на kernel_permission_call
pspSdkEnableInterrupts(intr);
}
После того как эксплоит выполнен важно сделать восстановление kernel памяти.
Cнимите дамп kernel памяти до и после выполнения эксплоита.
В psplink даем команду:
savemem 0x88000000 0x400000 dumpname.bin
Назовите их before.bin и after.bin соответственно.
Положите их рядом с этой прогой: 8057.
Запустите прогу.
Получите на выходе файл output.c с функцией sysmem_recovery().
Примените эту функцию в kernel_permission_call.
А теперь соберем всё воедино:
void clear_caches()
{
sceKernelIcacheInvalidateAll();
sceKernelDcacheWritebackInvalidateAll();
}
void recovery_sysmem()
{
_sw(0x8C6508C4, 0x8800F798)
}
int kernel_permission_call()
{
recovery_sysmem();
clear_caches();
// код с kernel правами
return 0;
}
void do_exploit()
{
sceLolFunc3(0x8800F798); // перетираем нулем 4 байта (u32) значение по адресу 0x8800F798
// тем самым убиваем заглушку второго аргумента в функции sceKernelLibcTime
clear_caches();
u32 intr = pspSdkDisableInterrupts();
sceKernelLibcTime(0, &kernel_permission_call); // делаем jump на kernel_permission_call
pspSdkEnableInterrupts(intr);
}
Интересные ссылки:
Учебник по MIPS (англ.): http://chortle.ccsu.edu/assemblytutorial/tutorialcontents.html.
Описание MIPS архитектуры (рус.): http://ru.wikipedia.org/wiki/MIPS_(%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0).
Документация к процессорам архитектуры MIPS (англ.): http://www2.engr.arizona.edu/~ece369/Resources/MipsInstructionSetReference.pdf
Описание 6.20 kxploit'а от TN (англ.): http://lolhax.org/2010/12/23/arcanum/.
Описание 6.39 kxploit'а от some1 (англ.):http://wololo.net/talk/viewtopic.php?f=6&t=6612&start=0.