Большое спасибо Davee и some1 за всё.
Сегодня я хочу вам рассказать про поиск kernel эксплоитов. Для начала разберемся для чего нам это нужно. PSP имеет много разделов памяти (вы можете посмотреть их ниже в спойлере), но речь пойдет только про 2: user и kernel. Различается память в уровне доступа. Код выполненный с kernel правами (запущенный из модуля с kernel правами) имеет права системы и имеет доступ ко всему, в свою очередь user имеет доступ только к: Scratchpad, VRAM, P5 и Extended Memory (в общем ко всему, кроме kernel).
И так, для чего же нам нужны kernel права? Для того чтобы сделать изменения в прошивке. Проще говоря, создание полноценного HEN или CFW невозможно без наложения патчей на модули системы. Для этого мы и будем получать kernel права. Хочу огорчить, вам необходимы знания языка Си и ассемблера под архитектуру MIPS (чтобы изучить прочти всё про MIPS в разделе "Интересные ссылки" (см. в конец)).
В прошивке PSP модули подразделяются на 2 категории: user и kernel. Права модулей различаются соответственно. User модули грузятся в user память, kernel модули грузятся в kernel память. Ниже приведен список kernel модулей.
Для того чтобы получить kernel права мы должны сделать jump (jr, jalr) из kernel модуля на свой код, который может быть в любом участке памяти. Не будем изобретать велосипед и будем использовать уже известный метод. Это использование функции sceKernelLibcTime.
Посмотрим в код модуля sysmem.prx, который и содержит функцию sceKernelLibcTime, найдите его, задав поиск по тексту. Я напишу его в двух вариациях, смотрите на второй. Первый понадобится позже.
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 соответственно.
frostegater, о, спасибо за тутор. Не знал, что память делится на множество областей с различными правами доступа, ну помимо 2-ух областей: kernel и user.
А если показать области памяти в порядке возрастания, т.е. в виде физического порядка расположения.
Думаю более наглядно будет.
Ф и з и ч е с к о е _ р а с п о л о ж е н и е _ п а м я т и
Scratchpad - высокоскоростная встроеная память. Используется для временного хранения данных. Доступна из всех режимов.
VRAM(Video RAM) - память, хранящая видеобуфер, создаваемый видеоадаптером. Может редактироваться. Получение буфера осуществляется функцией sceDisplayGetFrameBuf, установка же sceDisplaySetFrameBuf.
KERNEL память - используется системой (ядром), не может быть доступна из других областей памяти (ну мы то знаем как может ).
USER память - противоположна KERNEL памяти. В неё загружаются обычные пользовательские процессы.
P5 память - проще будет привести мой диалог с wololo:
frostegater: hi wololo.. you know what is P5 memory? Just very interesting. If I dont bother you.
wololo: sure
wololo: it is additional user memory, but very unstable
wololo: any process can use it whenever they want, I believe
wololo: it is called "volatile" memory
wololo: not sure if allocs, etc... work well in it
wololo: maybe it is reserved for savegame operations, etc...
frostegater: more thanks
Extended memory(XMS) - переводится как "дополнительная память", может использоваться для хранения данных, но не для процессорного кода.
Я немного неверно изложил суть kxploit'а в шапке.
1-ое что мы должны иметь это возможность поставить любую дрянь в памяти ядра (получить контроль над инструкцией sw (хотя бы вторым аргументом) внутри функции с k-правами).
2-ое это поставить её вот вместо этого (lui $a1, 0x8801 - это взято из функции sceKernelPowerLock):
lui $a1, 0x8801 # вот это ОБЯЗАТЕЛЬНО перетереть какойнибудь хренью, но не этой
lw $v1, 16632($a1) # тут мы загружаем адрес нашего кода, поэтому можно заметить что от 2-го аргумента
# функции sceKernelPowerLock отнимаем 16632 (0x40F8) ну или стоящее там значение.
addiu $sp, $sp, -16
sw $ra, 0($sp)
bnez $v1, 0x8800CBDC # переходим в место с меткой (!!!)
move $v0, $zr
lw $ra, 0($sp)
jr $ra
addiu $sp, $sp, 16
lw $v0, 16($v1) # !!! тут грузим загруженый адрес нашего кода (вот почему отнимаем 16 (0x10) - смотрим любой kxploit)
jalr $v0 # ну и прыгаем на шеллкод
nop
Блин, ребята. Давайте хоть какую-то движуху, а то мне одному ломать скучно. Кому я это всё говорю?! Самое сложное тут - это найти функцию, которая будет иметь контроль над sw, а это не так сложно. Это можно сказать самое главное. Yoti, давай хоть ты подсуетись. Если чё не понятно, вы спрашуйте!
lui $a1, 0x8801 # вот это ОБЯЗАТЕЛЬНО перетереть какойнибудь хренью, но не этой
В смысле, это наверное нужно совсем занулить.
Ну я могу подключиться, просто тоже своих проектов навалом. Перепрыгиваешь на новый проект, а старые незаконченные забываются, считай навсегда )))
ErikPshat, ну лишь бы не взаимодействовало с $a1, т.к. тут a1 задается аргумент инструкцией lui которая равносильна (a1 = 0x8801), а так мы можем задать a1 (второй аргумент функции) извне.