Broadcast Recevier 廣播接收器是一個專注于接收廣播通知信息,并做出對應處理的組件。很多廣播是源自于系統代碼的──比如,通知時區改變、電池電量低、拍攝了一張照片或者用戶改變了語言選項。應用程序也可以進行廣播──比如說,通知其它應用程序一些數據下載完成并處于可用狀態。 應用程序可以擁有任意數量的廣播接收器以對所有它感興趣的通知信息予以響應。所有的接收器均繼承自BroadcastReceiver基類。 廣播接收器沒有用戶界面。然而,它們可以啟動一個activity來響應它們收到的信息,或者用NotificationManager來通知用戶。通知可以用很多種方式來吸引用戶的注意力──閃動背燈、震動、播放聲音等等。一般來說是在狀態欄上放一個持久的圖標,用戶可以打開它并獲取消息。
注冊形式:動態or靜態
(靜態與動態注冊廣播接收器區別)
回調方法
廣播接收器只有一個回調方法:
#!java
void onReceive(Context curContext, Intent broadcastMsg)
當廣播消息抵達接收器時,Android調用它的onReceive() 方法并將包含消息的Intent對象傳遞給它。廣播接收器僅在它執行這個方法時處于活躍狀態。當onReceive()返回后,它即為失活狀態。 擁有一個活躍狀態的廣播接收器的進程被保護起來而不會被殺死。但僅擁有失活狀態組件的進程則會在其它進程需要它所占有的內存的時候隨時被殺掉。 這種方式引出了一個問題:如果響應一個廣播信息需要很長的一段時間,我們一般會將其納入一個衍生的線程中去完成,而不是在主線程內完成它,從而保證用戶交互過程的流暢。如果onReceive()衍生了一個線程并且返回,則包涵新線程在內的整個進程都被會判為失活狀態(除非進程內的其它應用程序組件仍處于活躍狀態),于是它就有可能被殺掉。這個問題的解決方法是令onReceive()啟動一個新服務,并用其完成任務,于是系統就會知道進程中仍然在處理著工作。
權限
設置接收app
#!java
Intent setPackage(String packageName)
(Usually optional) Set an explicit application package name that limits the components this Intent will resolve to.
設置接收權限
#!java
abstract void sendBroadcast(Intent intent, String receiverPermission)
Broadcast the given intent to all interested BroadcastReceivers, allowing an optional required permission to be enforced.
protectionLevel
normal:默認值。低風險權限,只要申請了就可以使用,安裝時不需要用戶確認。
dangerous:像WRITE_SETTING和SEND_SMS等權限是有風險的,因為這些權限能夠用來重新配置設備或者導致話費。使用此protectionLevel來標識用戶可能關注的一些權限。Android將會在安裝程序時,警示用戶關于這些權限的需求,具體的行為可能依據Android版本或者所安裝的移動設備而有所變化。
signature:這些權限僅授予那些和本程序應用了相同密鑰來簽名的程序。
signatureOrSystem:與signature類似,除了一點,系統中的程序也需要有資格來訪問。這樣允許定制Android系統應用也能獲得權限,這種保護等級有助于集成系統編譯過程。
廣播類型
系統廣播:像開機啟動、接收到短信、電池電量低這類事件發生的時候系統都會發出特定的廣播去通知應用,應用接收到廣播后會以某種形式再轉告用戶。
自定義廣播:不同于系統廣播事件,應用可以為自己的廣播接收器自定義出一條廣播事件。
Ordered Broadcast
OrderedBroadcast-有序廣播,Broadcast-普通廣播,他們的區別是有序廣播發出后能夠適配的廣播接收者按照一定的權限順序接收這個廣播,并且前面的接收者可以對廣播的內容進行修改,修改的結果被后面接收者接收,優先級高的接收者還可以結束這個廣播,那么后面優先級低的接收者就接收不到這個廣播了。而普通廣播發出后,能夠是適配的接收者沒有一定順序接收廣播,也不能終止廣播。
sticky broadcast
有這么一種broadcast,在發送并經過AMS(ActivityManagerService)分發給對應的receiver后,這個broadcast并不會被丟棄,而是保存在AMS中,當有新的需要動態注冊的receiver請求AMS注冊時,如果這個receiver能夠接收這個broadcast,那么AMS會將在receiver注冊成功之后,馬上向receiver發送這個broadcast。這種broadcast我們稱之為stickybroadcast。
sendStickyBroadcast()字面意思是發送粘性的廣播,使用這個api需要權限android.Manifest.permission.BROADCAST_STICKY,粘性廣播的特點是Intent會一直保留到廣播事件結束,而這種廣播也沒有所謂的10秒限制,10秒限制是指普通的廣播如果onReceive方法執行時間太長,超過10秒的時候系統會將這個廣播置為可以干掉的candidate,一旦系統資源不夠的時候,就會干掉這個廣播而讓它不執行。
(幾種廣播的特性)
變動
android3.1以及之后版本廣播接收器不能在啟動應用前注冊。可以通過設置intent的flag為Intent.FLAG_INCLUDE_STOPPED_PACKAGES將廣播發送給未啟動應用的廣播接收器。
關鍵方法
安全建議
intent-filter節點與exported屬性設置組合建議
私有廣播接收器設置exported='false',并且不配置intent-filter。(私有廣播接收器依然能接收到同UID的廣播)
<receiver android:name=".PrivateReceiver" android:exported="false" />
對接收來的廣播進行驗證
內部app之間的廣播使用protectionLevel='signature'驗證其是否真是內部app
返回結果時需注意接收app是否會泄露信息
發送的廣播包含敏感信息時需指定廣播接收器,使用顯示意圖或者
setPackage(String packageName)
sticky broadcast粘性廣播中不應包含敏感信息
Ordered Broadcast建議設置接收權限receiverPermission,避免惡意應用設置高優先級搶收此廣播后并執行abortBroadcast()方法。
1、查找動態廣播接收器:反編譯后檢索registerReceiver(),
dz> run app.broadcast.info -a android -i
2、查找靜態廣播接收器:反編譯后查看配置文件查找廣播接收器組件
3、查找發送廣播內的信息檢索sendBroadcast與sendOrderedBroadcast,注意setPackage方法于receiverPermission變量。
發送測試廣播
#!java
adb shell:
am broadcast -a MyBroadcast -n com.isi.vul_broadcastreceiver/.MyBroadCastReceiver
am broadcast -a MyBroadcast -n com.isi.vul_broadcastreceiver/.MyBroadCastReceiver –es number 5556.
drozer:
dz> run app.broadcast.send --component com.package.name --action android.intent.action.XXX
code:
Intent i = new Intent();
ComponentName componetName = new ComponentName(packagename, componet);
i.setComponent(componetName);
sendBroadcast(i);
接收指定廣播
#!java
public class Receiver extends BroadcastReceiver {
private final String ACCOUNT_NAME = "account_name";
private final String ACCOUNT_PWD = "account_password";
private final String ACCOUNT_TYPE = "account_type";
private void doLog(Context paramContext, Intent paramIntent)
{
String name;
String password;
String type;
do
{
name = paramIntent.getExtras().getString(ACCOUNT_NAME);
password = paramIntent.getExtras().getString(ACCOUNT_PWD);
type = paramIntent.getExtras().getString(ACCOUNT_TYPE);
}
while ((TextUtils.isEmpty(name)) || (TextUtils.isEmpty(password)) || (TextUtils.isEmpty(type)) || ((!type.equals("email")) && (!type.equals("cellphone"))));
Log.i("name", name);
Log.i("password", password);
Log.i("type", type);
}
public void onReceive(Context paramContext, Intent paramIntent)
{
if (TextUtils.equals(paramIntent.getAction(), "account"))
doLog(paramContext, paramIntent);
}
}
案例1:偽造消息代碼執行
WooYun: 百度云盤手機版釣魚、信息泄露和代碼執行高危漏洞三合一
案例2:拒絕服務
嘗試向廣播接收器發送不完整的intent比如空action或者空extra。
案例3:敏感信息泄漏
某應用利用廣播傳輸用戶賬號密碼
隱式意圖發送敏感信息
#!java
public class ServerService extends Service {
??// ...
??private void d() {
????// ...
????Intent v1 = new Intent();
????v1.setAction("com.sample.action.server_running");
????v1.putExtra("local_ip", v0.h);
????v1.putExtra("port", v0.i);
????v1.putExtra("code", v0.g);
????v1.putExtra("connected", v0.s);
????v1.putExtra("pwd_predefined", v0.r);
????if (!TextUtils.isEmpty(v0.t)) {
??????v1.putExtra("connected_usr", v0.t);
????}
??}
??this.sendBroadcast(v1);
}
接收POC
#!java
public class BcReceiv extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){
String s = null;
if (intent.getAction().equals("com.sample.action.server_running")){
String pwd = intent.getStringExtra("connected");
s = "Airdroid => [" + pwd + "]/" + intent.getExtras();
}
Toast.makeText(context, String.format("%s Received", s),
Toast.LENGTH_SHORT).show();
}
}
修復后代碼,使用 LocalBroadcastManager.sendBroadcast() 發出的廣播只能被app自身廣播接收器接收。
#!java
Intent intent = new Intent("my-sensitive-event");
intent.putExtra("event", "this is a test event");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
案例4:權限繞過
http://www.jssec.org/dl/android_securecoding_en.pdf