一、框架
1. 测试用例管理
首先,VulnerabilityOrganizer类用于包含该所有的测试用例,使用VTS框架来进行漏洞检测时,所有的测试用例需要在这个类中注册。
public static List<VulnerabilityTest> getTests(Context ctx){
List<VulnerabilityTest> allTests = new ArrayList<>();
allTests.add(new ZipBug9950697());
allTests.add(new ZipBug8219321());
allTests.add(new ZipBug9695860());
// allTests.add(new JarBug13678484());
allTests.add(new CVE_2013_6282());
allTests.add(new CVE_2011_1149());
allTests.add(new CVE_2014_3153());
allTests.add(new CVE_2014_4943());
//tests.add(new StumpRoot());
allTests.add(new WeakSauce());
allTests.add(new GraphicBufferTest());
allTests.addAll(StageFright.getTests(ctx));
allTests.add(new CVE_2015_6602());
allTests.add(new OpenSSLTransientBug());
allTests.add(new CVE_2015_3636());
//tests.add(new ZergRush()); // Hide super old bugs?
allTests.add(new SamsungCREDzip());
allTests.add(new CVE_2015_6608());
allTests.add(new CVE20151528());
allTests.add(new CVE_2015_6616());
allTests.add(new CVE20153860());
allTests.add(new CVE_2016_0807());
List<VulnerabilityTest> filteredTest = new ArrayList<>();
String cpuArch1 = SystemUtils.propertyGet(ctx, "ro.product.cpu.abi");
String cpuArch2 = SystemUtils.propertyGet(ctx, "ro.product.cpu.abi2");
/*
The logic here is:
The test must support every architecture that the device lists
*/
for(VulnerabilityTest vt : allTests){
if(vt.getSupportedArchitectures() == null) {
Log.d(TAG, "architectures is null for : " + vt.getCVEorID());
}
if(vt.getSupportedArchitectures().contains(CPUArch.ALL)){
filteredTest.add(vt);
} else {
if(isArchitectureSupported(vt, cpuArch1) &&
isArchitectureSupported(vt, cpuArch2)){
filteredTest.add(vt);
}
}
}
return filteredTest;
}
所有的测试用例采用接口式设计,即所有的测试用例都需要实现VulnerabilityTest接口,这样便于框架的管理。这个类其实只有几个接口方法,其中isVulnerable()方法用于确定漏洞是否存在。
public interface VulnerabilityTest extends Serializable {
public String getCVEorID();
public boolean isVulnerable(Context context) throws Exception;
public List<CPUArch> getSupportedArchitectures();
}
2. 测试执行
测试用例的执行由VulnerabilityTestRunner管理,由于测试用例执行可能是个耗时过程,所以采用了异步任务的方式。
@Override
protected List<VulnerabilityTestResult> doInBackground(Void... params) {
Log.d(TAG, "Async execute called!!!!");
List<VulnerabilityTestResult> results = new ArrayList<>();
for (int i = 0; i < tests.size(); i++) {
mProgressDialog.setProgress(i);
VulnerabilityTest test = tests.get(i);
Log.d(TAG, "Running test: " + test.getCVEorID());
Exception x = null;
boolean isVuln = false;
try {
isVuln = test.isVulnerable(mCtx);
Log.d(TAG, test.getCVEorID() + " isVulnerable: " + isVuln);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, test.getCVEorID() + " failed with: " + e.getMessage());
x = e;
}
results.add(new VulnerabilityTestResult(test, isVuln, x));
}
return results;
}
3. 测试结果管理
测试结果由VulnerabilityTestResult类管理,其实就是定义了一个数据结构。
public class VulnerabilityTestResult implements Serializable {
private final VulnerabilityTest mTest;
private final boolean mIsVuln;
private final Exception mE;
public VulnerabilityTestResult(VulnerabilityTest test, boolean isVuln, Exception e){
mIsVuln = isVuln;
mE = e;
mTest = test;
}
public String getCVEorID(){
return mTest.getCVEorID();
}
public Boolean getResult(){
return mIsVuln;
}
public Exception getException(){
return mE;
}
public boolean isVulnerable() { return mIsVuln; }
}
二、 测试用例检测原理
1. 纯静态检测
直接检测patch中的特征,例如字符串。比如CVE-2016-0807的检测。
此外,CVE-2015-3860也是静态检测。
//patch code
if (nhdr.n_type == NT_GNU_BUILD_ID) {
// Skip the name (which is the owner and should be "GNU").
addr += NOTE_ALIGN(nhdr.n_namesz);
- uint8_t build_id_data[128];
- if (nhdr.n_namesz > sizeof(build_id_data)) {
- ALOGE("Possible corrupted note, name size value is too large: %u",
- nhdr.n_namesz);
+ uint8_t build_id_data[160];
+ if (nhdr.n_descsz > sizeof(build_id_data)) {
+ ALOGE("Possible corrupted note, desc size value is too large: %u",
+ nhdr.n_descsz);
return false;
}
if (backtrace->Read(addr, build_id_data, nhdr.n_descsz) != nhdr.n_descsz) {
//check code
public boolean isVulnerable(Context context) throws Exception {
File debuggerd = new File("/system/bin/debuggerd");
if(!debuggerd.exists() || !debuggerd.isFile()){
throw new Exception("debuggerd doesn't exist or is not a file");
}
String patchedString = "Possible corrupted note, desc size value is too large: %u";
String unpatchedString = "Possible corrupted note, name size value is too large: %u";
ByteArrayOutputStream debuggerdBAOS = new ByteArrayOutputStream((int)debuggerd.length());
BinaryAssets.copy(new FileInputStream(debuggerd), debuggerdBAOS);
byte[] debuggerdBin = debuggerdBAOS.toByteArray();
KMPMatch binMatcher = new KMPMatch();
int indexOf = binMatcher.indexOf(debuggerdBin, patchedString.getBytes());
boolean hasPatchedString = indexOf == -1;
indexOf = binMatcher.indexOf(debuggerdBin, unpatchedString.getBytes());
boolean hasUnpatchedString = indexOf == -1;
return hasPatchedString && !hasUnpatchedString;
}
2. 动态方式
通过触发漏洞等方式来检测漏洞存在,利用通过对so中的函数调用,根据崩溃或者返回值来确定漏洞是否存在。CVE-2015-1528就是这种检测方式。
int Check_CVE_2015_1528()
{
const char *libname = "libcutils.so";
size_t * ( *native_handle_create )( int numFds, int numInts ) = NULL;
void *handle = dlopen( libname, RTLD_NOW | RTLD_GLOBAL );
if( !handle )
{
printf( "error opening %s: %s\n", libname, dlerror() );
return -1;
}
native_handle_create = dlsym( handle, "native_handle_create" );
if( !native_handle_create )
{
printf( "missing native_handle_create\n" );
return -2;
}
int ret = -3;
int numFds = 1025;
int numInts = 1;
size_t *bla = native_handle_create( numFds, numInts );
if( !bla )
{
// fixed
printf( "looks fixed to me\n" );
ret = 0;
goto done;
}
// sanity checks
switch(bla[0])// version
{
case 12://android wear 5.0.2 LWX49K
if( bla[1] != numFds || bla[2] != numInts )
{
LOG_D( "got back unexpected values\n" );
}
else
{
LOG_D( "its vulnerable\n" );
return 1;
}
break;
default:
LOG_D( "failed. version %d %d %d\n", bla[0], bla[1], bla[2] );
break;
}
done:
// done with this
dlclose( handle );
// should be allocated with malloc
//! if its already null, then free does nothing
free( bla );
return ret;
}
三、漏洞检测Testcase添加
- 继承VulnerabilityTest接口,实现接口方法;
- 向VulnerabilityOrganizer类注册。