drozer模組的編寫及模組動態載入問題研究
drozer是MWR Labs開發的一款Android安全測試框架。是目前最好的Android安全測試工具之一。drozer提供了命令列互動式介面,使用drozer進行安全測試,使用者在自己的console端輸入命令,drozer會將命令傳送到Android裝置上的drozer agent代理程式執行。drozer採用了模組化的設計,使用者可以定製開發需要的測試模組。編寫drozer模組主要涉及python模組及dex模組。python模組在drozer console端執行,類似於metasploit中的外掛,可以擴充套件drozer console的測試功能。dex模組是java編寫的android程式碼,類似於android的dex外掛,在android手機上執行,用於擴充套件drozer agent的功能。
0x00 簡單的drozer模組demo程式碼
首先看看drozer wiki給出的demo,該模組的功能就是在android裝置上反射呼叫java.util.Random類生成一個隨機數並返回:
#!python
from drozer.modules import Module
class GetInteger(Module):
name = ""
description = ""
examples = ""
author = "Joe Bloggs (@jbloggs)"
date = "2012-12-21"
license = "BSD (3-clause)"
path = ["exp", "test"]
def execute(self, arguments):
random = self.new("java.util.Random")
integer = random.nextInt()
self.stdout.write("int: %d\n" % integer)
GetInteger類就是一個簡單的drozer模組,它繼承自drozer提供的模組基類Module。每個繼承了Module的類都對應著一個drozer模組,模組具體實現的功能則是在類中重寫excute函式,實現新的功能。在drozer console中執行以下命令就可以執行該模組了:
#!bash
dz> run exp.test.getinteger
執行效果如下:
drozer 透過Module類的metadata來配置和管理每個模組,因此模組編寫時需要包含以下 metadata資訊:
name 模組的名稱
description 模組的功能描述
examples 模組的使用示例
author 作者
date 日期
license 許可
path 描述模組命令空間
這些資訊中比較重要的就是path變數,它描述了模組在drozer namespace中的路徑,結合對應的classname可以唯一確定drozer中的模組。例如demo中的path = ["exp", "test"]
,類名為GetInteger,那麼在drozer console中該模組就以exp.test.getinteger唯一確定。需要注意的是,儘管類的名字有大小寫之分,但執行該模組的時候,drozer console中的名字都為小寫。
0x01 drozer模組倉庫的建立及模組安裝
drozer模組安裝有兩種方法,一種是直接在repository中按照python包管理的方法新建目錄結構,將python檔案放入相應目錄中,另一種是在drozer console中透過module install命令直接安裝模組。
這兩種方法都必須先在本地建立一個drozer 的repository目錄,可以直接在drozer console中透過命令建立:
#!bash
dz> module repository create [/path/to/repository]
也可以在~/.drozer_config
檔案中指定本地repository目錄
#!bash
[repositories]
/path/to/repository = /path/to/repository
建立好本地repository後就可以安裝自己的模組了。兩種安裝方法:
1) 按照python包管理的方式,在本地repository目錄下建立目錄exp,新建__int__.py空白檔案,然後將Python模組原始碼放入exp目錄即可。例如將test.py放入exp目錄下,test.py的內容如下:
#!python
from drozer.modules import Module
class GetInteger(Module):
name = ""
description = ""
examples = ""
author = "Joe Bloggs (@jbloggs)"
date = "2012-12-21"
license = "BSD (3-clause)"
path = ["exp", "test"]
def execute(self, arguments):
random = self.new("java.util.Random")
integer = random.nextInt()
self.stdout.write("int: %d\n" % integer)
安裝好模組之後即可在drozer console端透過命令run exp.test.getinteger
執行該模組了。
2) 透過drozer console中的命令module install 安裝。首先將編輯好的python模組原始檔命名為 exp.test2,檔案的內容同上。在drozer console中執行
#!bash
dz> module install [/path/to/exp.test2]
執行成功後則可以在本地repository目錄下exp目錄中看到生成了test2.py檔案,內容和原來的exp.test2檔案一致。安裝成功後及可執行該模組了。module install除了可以安裝本地倉庫的模組外,還可以遠端安裝gitbub上的模組,地址為
https://raw.github.com/mwrlabs/drozer-modules/repository/
例如執行
#!bash
dz>module install jubax.javascript
將遠端下載並安裝scanner.misc.checkjavascriptbridge模組,安裝完成後執行
#!bash
dz> run scanner.misc.checkjavascriptbridge
就可以執行該模組,該模組的功能是檢查webview中addJavascriptInterface的使用是否存在安全隱患。
0x02 利用drozer提供的API擴充套件功能
drozer封裝了android中大部分API功能,使得能夠在python中方便的使用這些API擴充套件功能,發揮drozer及python的強大威力。
1)利用反射直接與Dalvik虛擬機器互動,其實就是Python直接在寫android程式碼,非常簡單方便。drozer主要是利用了drozer agent代理實現相關功能,例項化某個類的程式碼如下:
#!python
my_object = self.new("some.package.MyClass")
例如drozer.android模組中封裝了Intent類,使用者可以透過如下方式構造需要的Intent:
#!python
someintent = android.Intent(action=act, category=cat, data_uri=data, component=comp, extras=extr, flags=flgs)
然後透過intent開啟某個activity:
#!python
self.getContext().startActivity(someintent)
2) drozer針對比較常用的功能還二次封裝了很多python的mixins工具類,提供了更簡單易用的API,這些mixins都在drozer.modules.common包中:
- Assets
- BusyBox
- ClassLoader
- FileSystem
- Filters
- PackageManager
- Provider
- ServiceBinding
- Shell
- Strings
- SuperUser
- TableFormatter
- ZipFile
例如FileSystem類提供了訪問android手機檔案系統的介面,可以方便地讀寫、建立及刪除andoid手機上的目錄和檔案。ZipFile類提供瞭解壓zip檔案的功能。 為了使用這些mixin類提供的功能,在模組中可以直接繼承這些類就可以了:
#!python
from drozer.modules import common, Module
class MyModule(Module, common.FileSystem, common.ZipFile):
……
……
self.deleteFile(“somepath”)
……
……
dex_file = self.extractFromZip("classes.dex", path, self.cacheDir())
其中,self.deleteFile來自FileSystem類,self.extractFromZip來自ZipFile類。
0x03 實現find port及find IP模組
1) app開放埠查詢模組
Android app通常會監聽某些埠進行本地IPC或者遠端網路通訊,但是這些暴露的埠卻代表了潛在的本地或遠端攻擊面,具體可以參考大牛的文章:
文章中提供了查詢開放埠及對應app的python指令碼,我們將其重寫為drozer模組,方便測試時使用:
#!python
from drozer.modules import Module,common
import re
class findport(Module,common.Shell):
name = ""
description = "find open port in android"
examples = "run exp.work.findport"
author = "[email protected]"
date = "2015-12-02"
license = "BSD (3-clause)"
path = ["exp","work"]
def toHexPort(self,port):
hexport = str(hex(int(port)))
return hexport.strip('0x').upper()
def finduid(self,protocol, entry):
if (protocol=='tcp' or protocol=='tcp6'):
uid = entry.split()[-10]
else: # udp or udp6
uid = entry.split()[-6]
try:
uid = int(uid)
except:
return -1
if (uid > 10000): # just for non-system app
return 'u0_a'+str(uid-10000)
else:
return -1
def execute(self, arguments):
proc_net = "/proc/net/"
ret = self.shellExec("netstat -anp | grep -Ei 'listen|udp*'")
list_line = ret.split('\n')
apps = []
strip_listline = []
#pattern = re.compile("^Proto") # omit the first line
for line in list_line:
if (line != ''):
socket_entry = line.split()
protocol = socket_entry[0]
port = socket_entry[3].split(':')[-1]
grep_appid = 'grep '+ self.toHexPort(port) + ' ' + proc_net + protocol
net_entry = self.shellExec(grep_appid)
uid = self.finduid(protocol, net_entry)
if (uid == -1):
continue
applist = self.shellExec('ps | grep ' + uid).split()
app = applist[8]
apps.append(app)
strip_listline.append(line)
itapp= iter(apps)
itline=iter(strip_listline)
self.stdout.write("Proto Recv-Q Send-Q Local Address Foreign Address State APP\r\n")
try:
while True:
self.stdout.write( itline.next() + ' '*10 + itapp.next() + '\n')
except StopIteration:
pass
self.stdout.write('\n')
該模組的主要功能都是在findport類中的execute函式中實現,查詢開放埠及app的方法和原來文章中的一樣,這裡主要用到了drozer提供的common.Shell類,用於在android裝置上執行shell命令:
#!python
ret = self.shellExec("netstat -anp | grep -Ei 'listen|udp*'")
在drozer console中直接執行如下命令即可:
#!bash
dz> run exp.work.findport
執行效果如下:
2)app中IP地址掃描模組
drozer的scanner.misc.weburls提供了掃描app中http及https URL地址的功能,仿照該模組的功能,我們實現了app中IP地址的掃描模組,這些收集到的IP地址可以在web滲透測試中使用:
#!python
import re
from pydiesel.reflection import ReflectionException
from drozer.modules import common, Module
class findips(Module, common.FileSystem, common.PackageManager, common.Provider, common.Strings, common.ZipFile):
name = "Find IPs specified in packages."
description = """
Find IPs in apk files
"""
examples = ""
author = "[email protected]"
date = "2015-12-9"
license = ""
path = ["exp", "server"]
permissions = ["com.mwr.dz.permissions.GET_CONTEXT"]
def add_arguments(self, parser):
parser.add_argument("-a", "--package", help="specify a package to search")
def execute(self, arguments):
self.ip_matcher = re.compile(r"((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))")
if arguments.package != None:
self.check_package(arguments.package, arguments)
else:
for package in self.packageManager().getPackages(common.PackageManager.GET_PERMISSIONS):
try:
self.check_package(package.packageName, arguments)
except Exception, e:
print str(e)
def check_package(self, package, arguments):
self.deleteFile("/".join([self.cacheDir(), "classes.dex"]))
ips = []
for path in self.packageManager().getSourcePaths(package):
strings = []
if ".apk" in path:
dex_file = self.extractFromZip("classes.dex", path, self.cacheDir())
if dex_file != None:
strings = self.getStrings(dex_file.getAbsolutePath())
dex_file.delete()
strings += self.getStrings(path.replace(".apk", ".odex"))
elif (".odex" in path):
strings = self.getStrings(path)
else:
continue
for s in strings:
m = self.ip_matcher.search(s)
if m is not None:
ips.append(s)
if len(ips) > 0:
self.stdout.write("%s\n" % str(package))
for ip in ips:
self.stdout.write(" %s\n" % ip)
if len(ips) > 0 :
self.stdout.write("\n")
add_arguments
函式是drozer提供的介面,用於新增命令列引數,這裡我們新增了--package
引數,用於指定app名稱,如果沒有指定--package
引數,那麼預設會查詢所有app中的IP地址,比較耗時。check_package
函式主要實現指定app掃描IP地址的功能,該函式首先從app相關目錄中查詢apk檔案、odex檔案,如果是apk檔案則從apk檔案中解壓出classes.dex檔案:
#!python
for path in self.packageManager().getSourcePaths(package):
strings = []
if ".apk" in path:
dex_file = self.extractFromZip("classes.dex", path, self.cacheDir())
然後從得到的dex、odex檔案中獲取到所有的strings:
#!python
strings = self.getStrings(path)
這裡的getStrings是drozer提供的API,實現了類似linux下strings命令的功能。
找到app中的所有strings後再用re匹配得到相應的IP地址:
#!python
for s in strings:
m = self.ip_matcher.search(s)
if m is not None:
ips.append(s)
ip_matcher的正規表示式為:
#!python
self.ip_matcher = re.compile(r"((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))")
最後,在drozer console中透過如下命令執行該模組:
#!bash
dz> run exp.server.findips -a com.dianping.v1
執行效果如下所示:
0x04 編寫dex外掛
除了利用drozer以python程式碼形式提供的API,使用者還可以用java程式碼編寫dex外掛。 例如下面的java程式碼就可以編譯為drozer的dex外掛:
#!python
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import android.util.Base64;
import android.util.Log;
import android.widget.Toast;
import android.net.Uri;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.content.Context;
public class dextest {
private static final int BUFFER_SIZE = 4096;
public static String test(Context c, String number) {
String name = null;
Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/" + number);
ContentResolver resolver = c.getContentResolver();
Cursor cursor = resolver.query(uri, new String[]{android.provider.ContactsContract.Data.DISPLAY_NAME}, null, null, null);
if (cursor.moveToFirst()) {
name = cursor.getString(0);
Log.d("drozer", name);
}
cursor.close();
Log.d("drozer","this is a drozer dex module!");
return "hello world! this is a test! " + number + ": " + name;
}
}
首先我們將該java檔案編譯為class檔案:
#!bash
javac -cp lib/android.jar dextest.java
然後用android sdk提供的dx工具將class檔案轉換為dex檔案:
#!bash
dx --dex --output=dextest.apk dextest*.class
最後將生成的dextest.apk檔案放到drozer的modules/common目錄下,在編寫drozer模組時可以透過以下方式呼叫該dex外掛:
#!python
dextest = self.loadClass("common/dextest.apk", "dextest")
self.stdout.write("[color red]get string from dex plugin: %s [/color]\n" % dextest.test(self.getContext(),"181" ) )
該測試外掛根據提供的部分電話號碼去匹配手機通訊錄中的聯絡人,並返回匹配到的聯絡人姓名,執行效果如下:
dex外掛是由drozer上傳到android手機上載入執行,它的作用還是為drozer模組提供更方便易用的介面,擴充套件更多的功能。由於dex外掛是Java編寫的原生android程式碼,在執行效率上比透過反射呼叫更高。drozer的modules/common目錄下包含了多個dex外掛的原始碼,有興趣的同學可以自己檢視。
0x05 drozer模組的reload及動態載入問題
編寫drozer module難免會涉及到除錯的問題,drozer console提供了debug選項,會在console中列印異常資訊,但是比較麻煩的是,修改module原始碼後必須要重啟drozer console才能生效。
檢視drozer原始碼,發現drozer在debug模式下提供了reload命令,但是測試了下,在mac下並沒有用,還是要重啟console才能生效。仔細研究drozer loader.py的相關原始碼:
#!python
def all(self, base):
"""
Loads all modules from the specified module repositories, and returns a collection of module identifiers.
"""
if(len(self.__modules) == 0):
self.__load(base)
return sorted(self.__modules.keys())
def get(self, base, key):
"""
Gets a module implementation, given its identifier.
"""
if(len(self.__modules) == 0):
self.__load(base)
return self.__modules[key]
def reload(self):
self.__modules = {}
reload命令將self.__modules
置為空,在get中按理說就會重新載入所有的drozer模組。但是在mac下始終無法實現該功能,其他平臺未做測試。這裡就涉及到python模組的import及reload機制問題,在網上查詢到python的reload機制一些解釋:
reload會重新載入已載入的模組,但原來已經使用的例項還是會使用舊的模組, 而新生產的例項會使用新的模組, reload後還是用原來的記憶體地址;不能支援from。。import。。格式的模組進行重新載入。
http://blog.csdn.net/five3/article/details/7762870
猜測可能就是這個問題,雖然用python的reload機制可以重新載入模組,但是以前使用的模組可能還是在使用中,導致修改的原始碼沒有生效。
為什麼不在執行時動態載入模組呢?這樣可以保證載入的模組原始碼是最新的。
分析了drozer相關的所有原始碼,終於在session.py中找到例項化模組類的程式碼:
#!python
def __module(self, key):
"""
Gets a module instance, by identifier, and initialises it with the
required session parameters.
"""
module = None
try:
module = self.modules.get(self.__module_name(key))
except KeyError:
pass
if module == None:
try:
module = self.modules.get(key)
except KeyError:
pass
if module == None:
raise KeyError(key)
else:
return module(self)
該函式的功能就是根據模組類的key例項化該模組,從而執行該模組。因此,我們可以在這裡實現動態載入要執行的模組類,放棄已經載入的模組:
#!python
def __module(self, key):
"""
Gets a module instance, by identifier, and initialises it with the
required session parameters.
"""
module = None
try:
module = self.modules.get(self.__module_name(key))
except KeyError:
pass
if module == None:
try:
module = self.modules.get(key)
except KeyError:
pass
if module == None:
raise KeyError(key)
else:
#reload module
mod = reload(sys.modules[module.__module__])
module_class_name = module.__name__
module_class = getattr(mod,module_class_name) #get module class object
return module_class(self)
關鍵的程式碼如下:
#!python
#reload module
mod = reload(sys.modules[module.__module__])
module_class_name = module.__name__
module_class = getattr(mod,module_class_name) #get module class object
return module_class(self)
首先使用python的reload函式重新載入指定的模組,然後再在重新載入的模組中查詢到drozer模組關聯的類,最後例項化並返回。只需新增幾行程式碼便可實現動態載入模組類,這樣除錯的時候就不用每次重啟drozer console了。這裡只是提供了一種簡單的實現動態載入模組的方法,主要是方便模組的編寫及測試。
相關文章
- nginxphp動態編譯載入模組.2017-11-14NginxPHP編譯
- 關於顯示載入動態連結庫模組及解除安裝的問題2021-02-05
- OrchardCore 如何動態載入模組?2021-04-17
- linux核心動態載入模組2015-08-23Linux
- tp5.0 的 模組配置自動載入問題2019-08-05
- SystemJS是萬能動態模組載入器2015-01-08JS
- Helloworld 驅動模組載入2022-05-21
- 如何動態匯入ECMAScript模組2022-05-23
- (十三)自動載入新模組2018-01-17
- Angular中懶載入一個模組並動態建立顯示該模組下宣告的元件2022-05-16Angular元件
- 使用typescript開發angular模組(編寫模組)2018-04-23TypeScriptAngular
- 編寫Node原生模組2019-05-14
- swoole 模組的載入2019-09-05
- 編寫軟體動態載入NT式驅動2013-07-24
- JdonJive3的登入模組問題2006-07-03
- UEFI載入程式 & 驅動模組化2018-08-13
- 模組載入器2016-04-12
- swiper 模組載入2024-08-29
- 如何編寫python模組2021-09-11Python
- 【ningoo】編寫Perl模組2008-05-19Go
- 編寫你自己的Python模組2021-09-09Python
- js模組化之自定義模組(頁面模組化載入)2017-10-16JS
- JavaScript 模組載入特性2017-03-13JavaScript
- Webpack模組載入器2017-04-18Web
- php載入memcache模組2017-11-23PHP
- AngularJs動態載入模組和依賴注入簡單介紹2017-04-12AngularJS依賴注入
- JS動態引入模組2024-08-30JS
- Nginx SPDY Pagespeed模組編譯——加速網站載入2014-09-05Nginx編譯網站
- JavaScript 模組的迴圈載入2015-11-02JavaScript
- Linux系統核心模組和驅動的編寫(轉)2007-08-09Linux
- lazyload下特性模組匯入另一個模組導致的路由問題2016-11-03路由
- 如何編寫型別安全的CSS模組2023-05-12型別CSS
- 動態頁面資料載入不全的問題2018-11-23
- Nginx 動態模組 nginx-mod-http-image-filter 載入失敗解決2022-01-22NginxHTTPFilter
- ABP - 模組載入機制2023-05-16
- Ext4 checkbox 動態載入問題2012-12-05
- js模組載入星號的作用2017-03-12JS
- Python 模組的載入順序2024-06-27Python