Android 5.0及以上,select调用会检查fd大小,是否超过1024,如果超过就会提示:
FORTIFY_SOURCE: FD_SET: file descriptor >= FD_SETSIZE. Calling abort().
然后崩溃,检查的代码在:
/bionic/libc/bionic/__FD_chk.cpp
extern "C" int __FD_ISSET_chk(int fd, fd_set* set, size_t set_size) {
if (__predict_false(fd < 0)) {
__fortify_chk_fail("FD_ISSET: file descriptor < 0", 0);
}
if (__predict_false(fd >= FD_SETSIZE)) {
__fortify_chk_fail("FD_ISSET: file descriptor >= FD_SETSIZE", 0);
}
if (__predict_false(set_size < sizeof(fd_set))) {
__fortify_chk_fail("FD_ISSET: set is too small", 0);
}
return FD_ISSET(fd, set);
}
如果是因为自己的代码调用select提示fd超标,可以通过编译时覆盖__FD_ISSET_chk函数的方式,略过检查,
但是如果是从其它第三方库调到select,或者libc.so自己调用select,编译覆盖的方法就行不通。
尝试了一下直接修改汇编指令的方法,把1024调到4096,步骤如下:
readelf -s libc.so | grep __FD_ISSET_chk
// 找到__FD_ISSET_chk在libc.so中的地址,我的手机上为 0004d3d9
objdump -d libc.so > output
// 把so dump出来,查看汇编代码,定位到地址为0004d3d9,发现了__FD_ISSET_chk的函数体,明显能看到一行:
4d3e4: f5b0 6f80 cmp.w r0, #1024 ; 0x400
// 这里就是比较fd和1024的地方,所以只要这条指令中的1024改为更大的数字就可以了
// 在这里我偷了个懒,本来需要查找cmp.w指令格式,再修改立即数1024,再构造一条指令的,我是通过查找cmp.w r0的方法,
发现了一条指令相同,立即数更大的指令:
4e606: f5b0 5f80 cmp.w r0, #4096 ; 0x1000
// 完全相同,只是6f变成5f,数字就变成了4096,最终的修改目标就是,找到"6f"的地址,修改为"5f"
修改方法如下:
使用hex编译器,例如vi的:%!xxd,定位到地址为4d3e0 这一行,从左到右就是4c3e0, 4c3e1, 4c3e2, ...每一个地址对应一个字节,
4c3e7就是"6f"的地址
然后,借助mprotect系统调用,把4c3e7对应Page的权限修改为PROT_WRITE | PROT_EXEC | PROT_READ,就可以直接修改了。
在这里发现把权限修改为PROT_WRITE | PROT_EXEC或PROT_EXEC会出现permission denied错误。
最后测试一下,dlopen打开libc.so,dlsym找到__FD_ISSET_chk,直接传入一个大于1024的FD,并没有崩溃,成功!
FORTIFY_SOURCE: FD_SET: file descriptor >= FD_SETSIZE. Calling abort().
然后崩溃,检查的代码在:
/bionic/libc/bionic/__FD_chk.cpp
extern "C" int __FD_ISSET_chk(int fd, fd_set* set, size_t set_size) {
if (__predict_false(fd < 0)) {
__fortify_chk_fail("FD_ISSET: file descriptor < 0", 0);
}
if (__predict_false(fd >= FD_SETSIZE)) {
__fortify_chk_fail("FD_ISSET: file descriptor >= FD_SETSIZE", 0);
}
if (__predict_false(set_size < sizeof(fd_set))) {
__fortify_chk_fail("FD_ISSET: set is too small", 0);
}
return FD_ISSET(fd, set);
}
如果是因为自己的代码调用select提示fd超标,可以通过编译时覆盖__FD_ISSET_chk函数的方式,略过检查,
但是如果是从其它第三方库调到select,或者libc.so自己调用select,编译覆盖的方法就行不通。
尝试了一下直接修改汇编指令的方法,把1024调到4096,步骤如下:
readelf -s libc.so | grep __FD_ISSET_chk
// 找到__FD_ISSET_chk在libc.so中的地址,我的手机上为 0004d3d9
objdump -d libc.so > output
// 把so dump出来,查看汇编代码,定位到地址为0004d3d9,发现了__FD_ISSET_chk的函数体,明显能看到一行:
4d3e4: f5b0 6f80 cmp.w r0, #1024 ; 0x400
// 这里就是比较fd和1024的地方,所以只要这条指令中的1024改为更大的数字就可以了
// 在这里我偷了个懒,本来需要查找cmp.w指令格式,再修改立即数1024,再构造一条指令的,我是通过查找cmp.w r0的方法,
发现了一条指令相同,立即数更大的指令:
4e606: f5b0 5f80 cmp.w r0, #4096 ; 0x1000
// 完全相同,只是6f变成5f,数字就变成了4096,最终的修改目标就是,找到"6f"的地址,修改为"5f"
修改方法如下:
使用hex编译器,例如vi的:%!xxd,定位到地址为4d3e0 这一行,从左到右就是4c3e0, 4c3e1, 4c3e2, ...每一个地址对应一个字节,
4c3e7就是"6f"的地址
然后,借助mprotect系统调用,把4c3e7对应Page的权限修改为PROT_WRITE | PROT_EXEC | PROT_READ,就可以直接修改了。
在这里发现把权限修改为PROT_WRITE | PROT_EXEC或PROT_EXEC会出现permission denied错误。
最后测试一下,dlopen打开libc.so,dlsym找到__FD_ISSET_chk,直接传入一个大于1024的FD,并没有崩溃,成功!