Модель консоли: PSP-1004
Прошивка: 6.60 PROMOD
Другие консоли: iPad 3 WiFi
Регистрация: 18.12.2006
Адрес: Москва
Возраст: 58
Сообщений: 47,419
Вы сказали Спасибо: 27,759
Поблагодарили 43,299 раз(а) в 12,358 сообщениях
Сила репутации: 10Репутация: 42490 
(репутация неоспорима)
|
Ликбез №1. Вступление. Вопрос к разработчикам ...
Тут в PrxDecrypter я заметил некорректно написанный код сжатия. Вернее он сжимает и всё подсчитывает корректно, но алгоритм сжатия у него работает не так, как надо.
В ообщем тут такое дело, что если декриптовать тот же Astinishia Story 2 и попытаться обратно зашифровать его же родным заголовком, то так не получится, потому что декриптованный ELF-файл уже не поместится по размеру в свой родной заголовок - будет ошибка: Elf is to big. И это при том, что якобы в алгоритме энкриптера используется сжатие  , причём после шифровки и последующей декриптовке явно отображается, что файл:
* DATA.PSP -> decrypted, decompressed (GZ), saved.
Аналогичная ситуация происходит со всеми файлами, например при попытке подписать файлы прошивки своими же заголовками.
Короче, я так понял, алгоритм шифрования происходит в следующем порядке:
- Сравнение по размеру ELF-файла с размером, записанном в шифрованном заголовке по адресу 0x2C минус 0x15С
- Если ELF-файл больше, то выдаётся сообщение об ошибке: Elf is to big
- Затем, если файл меньше, то он в конце забивается нулями до размера, прописанного в шифрованном заголовке по адресу 0x28 (размер декриптованного ELF)
- После применяется сжатие GZIP
- Добивается архив в конце нулями до размера, указанного в шифрованном заголовке по адресу 0xB0 (размер архива)
- И наконец файл шифруется и добавляется заголовок.
Тут требуется только поправить порядок действий на такой: - Если сжатый файл меньше, то он в конце забивается нулями до размера, прописанного в шифрованном заголовке по адресу 0x28
- Применить сжатие GZIP
- Добивается архив в конце нулями до размера, указанного в шифрованном заголовке по адресу 0xB0
- Сравнение по размеру сжатого ELF-файла с размером, записанном в шифрованном заголовке по адресу 0x2C
- Если ELF-файл больше, то выдаётся сообщение об ошибке: Elf is to big
- И наконец файл шифруется и добавляется заголовок.
Это моё видение навскидку, но возможно там происходят более масштабные преобразования и придётся не просто поменять местами порядок выполнения функций, а ещё и переписывать код.
Хотя, сам код довольно короткий и простой, но я так и не понял, каким же образом происходит порядок сжатия.
Сам код:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <zlib.h>
#include "types.h"
#include "endian.h"
#include "kirk_engine.h"
#include "psp_headers.h"
unsigned char pspHeader_name[336] = {data};
unsigned char kirkHeader_name[272] = {data};
typedef struct Header_List {
unsigned char *pspHeader;
unsigned char *kirkHeader;
} Header_List;
Header_List header_list[] = {
{ pspHeader_name, kirkHeader_name },
};
u8 in_buffer[1024*1024*12];
u8 out_buffer[1024*1024*12];
u8 kirk_raw[1024*1024*12];
u8 kirk_enc[1024*1024*12];
u8 elf[1024*1024*12];
typedef struct header_keys
{
u8 AES[16];
u8 CMAC[16];
}header_keys;
int load_elf(char *elff)
{
FILE *fp = fopen(elff, "rb");
if(fp == NULL) {
return -1;
}
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
fseek(fp, 0, SEEK_SET);
fread(elf, 1, size, fp);
fclose(fp);
return size;
}
int dumpFile(char *name, void *in, int size)
{
FILE *fp = fopen(name, "wb");
if(fp == NULL) {
return -1;
}
fwrite(in, 1, size, fp);
fclose(fp);
return 0;
}
int get_kirk_size(u8 *key_hdr)
{
int krawSize = *(u32*)(key_hdr+0x70);
if(krawSize % 0x10) {
krawSize += 0x10 - (krawSize % 0x10); // 16 bytes aligned
}
krawSize += 0x110;
return krawSize;
}
Header_List *get_header_list(int size)
{
int i;
int h_size;
for(i=0; i<sizeof(header_list)/sizeof(header_list[i]); i++) {
h_size = get_kirk_size(header_list[i].kirkHeader);
h_size -= 0x150;
if( h_size >= size ) {
return &header_list[i];
}
}
return NULL;
}
int is_compressed(u8 *psp_header)
{
if (*(u16*)(psp_header+6) == 1) {
return 1;
}
return 0;
}
int get_elf_size(u8 *psp_header)
{
return *(u32*)(psp_header+0x28);
}
int gzip_compress(u8 *dst, const u8 *src, int size)
{
int ret;
z_stream strm;
u8 *elf_compress;
const int compress_max_size = 12 * 1024 * 1024;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
elf_compress = malloc(compress_max_size);
if(elf_compress == NULL) {
return -1;
}
ret = deflateInit2(&strm, 9, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY);
if(ret != Z_OK) {
printf("%s: compress error\n", __func__);
free(elf_compress);
return -2;
}
strm.avail_in = size;
strm.next_in = (void*)src;
strm.avail_out = compress_max_size;
strm.next_out = elf_compress;
ret = deflate(&strm, Z_FINISH);
if(ret == Z_STREAM_ERROR) {
deflateEnd(&strm);
printf("%s: compress error\n", __func__);
free(elf_compress);
return -3;
}
memcpy(dst, elf_compress, strm.total_out);
deflateEnd(&strm);
free(elf_compress);
return 0;
}
int main(int argc, char **argv)
{
header_keys keys;
u8 rawkheaderBk[0x90];
if(argc < 2)
{
printf("USAGE: [exe] [prx]\n");
return 0;
}
memset(in_buffer, 0, sizeof(in_buffer));
memset(out_buffer, 0, sizeof(out_buffer));
memset(kirk_raw, 0, sizeof(kirk_raw));
memset(kirk_enc, 0, sizeof(kirk_enc));
memset(elf, 0, sizeof(elf));
kirk_init();
int elfSize = load_elf(argv[1]);
if(elfSize < 0) {
printf("Cannot open %s\n", argv[1]);
return 0;
}
Header_List *target_header = get_header_list( elfSize );
if( target_header == NULL ) {
printf("PRX SIGNER: Elf is to big\n");
return 0;
}
u8 *kirkHeader = target_header->kirkHeader;
u8 *pspHeader = target_header->pspHeader;
int krawSize = get_kirk_size(kirkHeader);
if (is_compressed(pspHeader)) {
elfSize = get_elf_size(pspHeader);
gzip_compress(elf, elf, elfSize);
}
memcpy(kirk_raw, kirkHeader, 0x110);
memcpy(rawkheaderBk, kirk_raw, sizeof(rawkheaderBk));
kirk_decrypt_keys((u8*)&keys, kirk_raw);
memcpy(kirk_raw, &keys, sizeof(header_keys));
memcpy(kirk_raw+0x110, elf, elfSize);
if(kirk_CMD0(kirk_enc, kirk_raw, sizeof(kirk_enc), 0) != 0)
{
printf("PRX SIGNER: Could not encrypt elf\n");
return 0;
}
memcpy(kirk_enc, rawkheaderBk, sizeof(rawkheaderBk));
if(kirk_forge(kirk_enc, sizeof(kirk_enc)) != 0)
{
printf("PRX SIGNER: Could not forge cmac block\n");
return 0;
}
memcpy(out_buffer, pspHeader, 0x150);
memcpy(out_buffer+0x150, kirk_enc+0x110, krawSize-0x110);
return dumpFile("./DATA.PSP", out_buffer, (krawSize-0x110)+0x150);
}
Ликбез №2 Я хотел сказать, что СЖАТИЕ здесь вообще не имеет никакого смысла и эффекта, хотя оно явно используется.
То есть, файл размером 1 Мб должен ужаться и подписаться маленьким заголовком, однако он всегда подписывается большим заголовком, чем сам файл.
Провёл такой эксперимент. За основу взял оригинал DATA.PSP - Astinishia Story 2.
Имеем такие данные:- 0x28 = 0x000EF008 = 978'952 byte - размер ELF декриптованного файла.
- 0x2C = 0x00059FA0 = 368'544 byte - размер всего файла с заголовком.
- 0xB0 = 0x00059E44 = 368'196 byte - размер архива.
Чтобы файл работал, все эти данные должны совпадать. Ну а мы пока работаем с оригинальным файлом, поэтому тут и так всё должно быть чётко.
Так как я знаю, что файл пожат в GZIP, то теперь я в PrxDecrypter отключаю декомпрессию (тупо в коде меняю сигнатуру 1F8B на любую другую, по которой программа определяет, что перед ней GZIP-архив). Это находится здесь:
// GZIP DECOMPRESSION
if (*(u16 *)&g_dataOut2[0] == 0x8B1F) {
Теперь программа будет только декриптовать шифрованный файл, но разархивировать не будет.
Декриптуем оригинальный DATA.PSP от Astinishia Story 2
В папке "enc" обнаруживаем декриптованный файл, но конечно там нет знакомой сигнатуры ELF, а видим знакомые цифры 1F8B - архив GZIP.
Смотрю его размер. Он в точности совпадает с указанным в заголовке: - 0xB0 = 0x00059E44 = 368'196 byte - размер архива.
Смотрю у архива последние 4 байта -> 0x000EF008.
Они по спецификации GZIP указывают на размер разархивированного файла, что в точности совпадает с указанным в заголовке: - 0x28 = 0x000EF008 = 978'952 byte - размер ELF декриптованного файла.
Для уверенности разархивирую с помощью 7-zip этот архив и получаю чистый ELF, как и положено ему быть.
Ликбез №3 Поехали дальше... Теперь проводим эксперимент с фейковой подписью.
Беру чистый декриптованный ELF от Astinishia Story 2 ( 978'952 byte - довольно большой  )
Подписываю через PrxEncrypter.
Получаю на выходе подписанный файл размером 0x00164000 = 1'458'176 byte заголовком от Dynasty Warriors Strikeforce
Вот те ппц, приехали, наглядный пример сжатия PrxEncrypter 
Ну это не удивительно, он всегда так подписывал, потому что сначала подбирает под декриптованный ELF подходящий заголовок, не меньше размером, забивает до нужного размера 0x28 нулями, а только потом сжимает, опять добивает уже архив всяким мусором до указанного в заголовке 0xB0 размера архива и затем шифрует.
Короче, подписанный фейковый файл опять декриптуем без декомпрессии. - Получаем на выходе архив 1F8B размером 0х00163EAE = 1'457'838 byte - собственно, как и указано в заголовке 0xB0 Dynasty Warriors Strikeforce
- Сразу разархивирую его с помощью 7-Zip и получаю декриптованный ELF размером 0x00433DBC = 4'406'716 - как и указано в заголовке 0x28.
1. По первому пункту получаем такой прикол - ищу конец архива - последние 4 байта в архиве, по уже известному размеру разархивированного файла BC3D4300 - настоящий архив кончается в 1/3 от начала и составляет 0x0005DD59 = 384'345 byte. Остальная 2/3 части забито оставшейся частью декриптованного ELF-файла и далее нулями. То есть, берётся сам декриптованный файл, архивируется в GZIP и затем архив вставляется в этот же файл в начало, и есстественно конец декриптованного файла остаётся. Но это не беда, архив GZIP имеет потоковый формат и разархивируется потоково, пока не дойдёт до своего конца, а что там после конца - ему на это пофиг. Не зря GZIP не имеет меток размера и конца, но в конце обязательно имеет последние 4 байта метки разархивированного размера.
Да, настоящий архив, без нулей и мусора от ELF, немного не дотягивает до "Astonishia Story 2" (368'544), но зато свободно бы влез в заголовок от " Everybody's Sukkiri #1" (430'176 byte).
2. По второму пункту - разархивированный файл так же забит на 2/3 нулями, чтобы подогнать его под размер заголовка в 0x28
В общем, суть в том, что энкриптер сначала подбирает заголовок под ELF-файл, потом под этот заголовок подгоняет его, а только потом архивирует и опять подгоняет.
А нужно, чтобы он сначала архивировал ELF, а только потом под архив подбирал заголовок.
Последний раз редактировалось ErikPshat; 28.04.2013 в 20:11.
|