好友
阅读权限30
听众
最后登录1970-1-1
|
本帖最后由 ThomasKing 于 2014-11-10 21:45 编辑
源码已放,大家尽情玩耍吧。
----------------------------------------------------------------------------------------------------------------
本题的重点不是在算法本身,壳子和校验算法都比较简单。主要看攻击组是否被fake到吧,下面简单分析下原理。libverify.so作为简单的壳子,仅自加密了On_load函数。 校验算法分为两个,一个是real.c,一个是fake.c。fake.c生成libmodule.so文件,而real.c生成的libreal.so被隐藏在了文件末尾。APK运行时,linker调用自解密函数,解密On_load。之后调用On_load函数将libreal.so在内存中加载起来,即需要实现linker加载的一些机制,主要是重定位。可以看到libverify.so其实是作为简单的SO加载器的,其中包含了读取进程状态的反调试。攻击组已经调试看到,校验函数并不存在libverify.so的地址范围,是因为libreal.so是通过匿名内存释放出来的。
//-------------------------
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <elf.h>
#include <sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
typedef struct _funcInfo{
Elf32_Addr st_value;
Elf32_Word st_size;
}funcInfo;
void init_verify() __attribute__((constructor));
static void print_debug(const char *msg){
#ifdef DEBUG
__android_log_print(ANDROID_LOG_INFO, "JNITag", "%s", msg);
#endif
}
static Elf32_Sym *g_dSymtab;
static char *g_dStrtab;
static unsigned g_dStrsz;
static unsigned g_nbucket, g_nchain, *g_bucket, *g_chain;
static unsigned g_base, g_filesz;
static Elf32_Rel *g_reldyn, *g_relplt;
static unsigned g_reldynsz, g_relpltsz;
static int g_fd;
void (*__eabi_Unwind_cpp_ptr384)(JNIEnv*, jobject, jstring, jstring);
static const char g_szStackChkGuard[] = "__stack_chk_guard";
static unsigned g_guard;
static const char g_szMemcpy[] = "memcpy";
static unsigned g_memcpy;
static unsigned elfhash(const char *_name)
{
const unsigned char *name = (const unsigned char *) _name;
unsigned h = 0, g;
while(*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static unsigned int getLibAddr(char *name){
unsigned int ret = 0;
char buf[4096], *temp;
FILE *fp;
sprintf(buf, "/proc/self/maps");
fp = fopen(buf, "r");
if(fp == NULL)
{
print_debug("open failed");
goto _error;
}
while(fgets(buf, sizeof(buf), fp)){
if(strstr(buf, name)){
temp = strtok(buf, "-");
ret = strtoul(temp, NULL, 16);
break;
}
}
_error:
fclose(fp);
return ret;
}
static char getTargetFuncInfo(unsigned long base, const char *funcName, funcInfo *info){
char flag = -1, *dynstr;
int i;
Elf32_Ehdr *ehdr;
Elf32_Phdr *phdr;
Elf32_Off dyn_vaddr;
Elf32_Word dyn_size, dyn_strsz;
Elf32_Dyn *dyn;
Elf32_Addr dyn_symtab, dyn_strtab, dyn_hash;
Elf32_Sym *funSym;
unsigned funHash, nbucket;
unsigned *bucket, *chain;
ehdr = (Elf32_Ehdr *)base;
phdr = (Elf32_Phdr *)(base + ehdr->e_phoff);
for (i = 0; i < ehdr->e_phnum; ++i) {
if(phdr->p_type == PT_DYNAMIC){
flag = 0;
break;
}
phdr ++;
}
if(flag)
goto _error;
dyn_vaddr = phdr->p_vaddr + base;
dyn_size = phdr->p_filesz;
flag = 0;
for (i = 0; i < dyn_size / sizeof(Elf32_Dyn); ++i) {
dyn = (Elf32_Dyn *)(dyn_vaddr + i * sizeof(Elf32_Dyn));
if(dyn->d_tag == DT_SYMTAB){
dyn_symtab = (dyn->d_un).d_ptr;
flag += 1;
}
if(dyn->d_tag == DT_HASH){
dyn_hash = (dyn->d_un).d_ptr;
flag += 2;
}
if(dyn->d_tag == DT_STRTAB){
dyn_strtab = (dyn->d_un).d_ptr;
flag += 4;
}
if(dyn->d_tag == DT_STRSZ){
dyn_strsz = (dyn->d_un).d_val;
flag += 8;
}
}
if((flag & 0x0f) != 0x0f){
goto _error;
}
dyn_symtab += base;
dyn_hash += base;
dyn_strtab += base;
dyn_strsz += base;
funHash = elfhash(funcName);
funSym = (Elf32_Sym *) dyn_symtab;
dynstr = (char*) dyn_strtab;
nbucket = *((int *) dyn_hash);
bucket = (int *)(dyn_hash + 8);
chain = (unsigned int *)(dyn_hash + 4 * (2 + nbucket));
flag = -1;
for(i = bucket[funHash % nbucket]; i != 0; i = chain){
if(strcmp(dynstr + (funSym + i)->st_name, funcName) == 0){
flag = 0;
break;
}
}
if(flag) goto _error;
info->st_value = (funSym + i)->st_value;
info->st_size = (funSym + i)->st_size;
return 0;
_error:
return -1;
}
static void decrypt_verify(unsigned int base, const char target_fun[]){
funcInfo info;
int i;
unsigned int npage;
if(getTargetFuncInfo(base, target_fun, &info) == -1){
return ;
}
npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == 0) ? 0 : 1);
if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), npage, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
}
for(i=0;i< info.st_size - 1; i++){
char *addr = (char*)(base + info.st_value + i);
(*addr)--;
*addr = ~(*addr);
}
if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), npage, PROT_READ | PROT_EXEC) != 0){
}
clearcache((char*)(base + info.st_value - 1), (char*)(base + info.st_value -1 + info.st_size -1));
}
static char is_traced(){
FILE *fp = NULL;
char flag = 1;
char buf[100], *ptr;
int ppid;
memset(buf, 0, sizeof(buf));
sprintf(buf, "/proc/%d/status", getpid());
fp = fopen(buf, "r");
if(fp == NULL)
return flag;
while(fgets(buf, sizeof(buf)-1, fp)){
if(ptr = strstr(buf, "TracerPid:")){
sscanf(ptr + strlen("TracerPid:"), "%d", &ppid);
if(!ppid)
flag = 0;
break;
}
}
fclose(fp);
return flag;
}
static void release(unsigned base, char* addr){
int i, soLen = 0;
char *so_base = NULL;
Elf32_Ehdr *ehdr = NULL;
Elf32_Phdr *phdr = NULL;
ehdr = (Elf32_Ehdr*)base;
phdr = (Elf32_Phdr *)(base + ehdr->e_phoff);
for(i=0;i<ehdr->e_phnum;i++){
if(phdr->p_type == PT_LOAD){
break;
}
phdr ++;
}
if(i == ehdr->e_phnum){
return;
}
phdr ++;
soLen = *((int*)(base + phdr->p_vaddr + phdr->p_filesz - 4));
so_base = (char*)(base + phdr->p_vaddr + phdr->p_filesz - 4 - soLen);
memcpy(addr, so_base, sizeof(Elf32_Ehdr) + 10 * sizeof(Elf_Phdr));
for (i = 0; i < sizeof(Elf32_Ehdr) + 10 * sizeof(Elf_Phdr); ++i) {
addr = addr - 3;
addr = ~addr;
}
ehdr = (Elf32_Ehdr*)addr;
phdr = (Elf32_Phdr *)(addr + ehdr->e_phoff);
for(i=0;i<ehdr->e_phnum;i++){
if(phdr->p_type == PT_LOAD){
break;
}
phdr ++;
}
memcpy(addr, so_base, phdr->p_filesz);
for (i = 0; i < phdr->p_filesz; ++i) {
addr = addr - 3;
addr = ~addr;
}
phdr ++;
memcpy(addr + phdr->p_vaddr, so_base + phdr->p_offset, phdr->p_filesz);
for (i = 0; i < phdr->p_filesz; ++i) {
(addr + phdr->p_vaddr) = (addr + phdr->p_vaddr) - 3;
(addr + phdr->p_vaddr) = ~(addr + phdr->p_vaddr);
}
}
static void init(){
Elf32_Ehdr *ehdr = NULL;
Elf32_Phdr *phdr = NULL;
Elf32_Dyn *dyn;
unsigned *dHash;
int i;
unsigned e_phnum = 0;
ehdr = (Elf32_Ehdr*) g_base;
e_phnum = ehdr->e_phnum;
phdr = (Elf32_Phdr*)(g_base + ehdr->e_phoff);
for (i = 0; i < e_phnum; ++i) {
if(phdr->p_type == PT_DYNAMIC){
break;
}
phdr ++;
}
for (i = 0; i < phdr->p_filesz / sizeof(Elf32_Dyn); ++i) {
dyn = (Elf32_Dyn *)(phdr->p_vaddr + g_base + i * sizeof(Elf32_Dyn));
if(dyn->d_tag == DT_SYMTAB){
g_dSymtab = (void*)(g_base + (dyn->d_un).d_ptr);
}
if(dyn->d_tag == DT_HASH){
dHash = (void*)(g_base + (dyn->d_un).d_ptr);
}
if(dyn->d_tag == DT_STRTAB){
g_dStrtab = (void*)(g_base + (dyn->d_un).d_ptr);
}
if(dyn->d_tag == DT_STRSZ){
g_dStrsz = (dyn->d_un).d_val;
}
if(dyn->d_tag == DT_REL){
g_reldyn = (void*)(g_base + (dyn->d_un).d_ptr);
}
if(dyn->d_tag == DT_RELSZ){
g_reldynsz = (dyn->d_un).d_val;
}
if(dyn->d_tag == DT_JMPREL){
g_relplt = (void*)(g_base + (dyn->d_un).d_ptr);
}
if(dyn->d_tag == DT_PLTRELSZ){
g_relpltsz = (dyn->d_un).d_val;
}
}
g_nbucket = *dHash;
g_nchain = *(dHash + 1);
g_bucket = dHash + 2;
g_chain = g_bucket + g_nbucket;
}
static unsigned findExportedSym(const char name[]){
int i;
unsigned hashval = elfhash(name);
for(i = g_bucket[hashval % g_nbucket]; i != 0; i = g_chain){
if(strcmp(g_dStrtab + (g_dSymtab + i)->st_name, name) == 0){
return i;
}
}
return 0;
}
static unsigned findAddrInGOT(Elf32_Rel *rel, unsigned relsz, unsigned index){
unsigned i, nRel;
nRel = relsz / sizeof(Elf32_Rel);
for(i=0;i<nRel;i++){
if(ELF32_R_SYM((rel + i)->r_info) == index)
{
return (rel + i)->r_offset;
}
}
return 0;
}
void init_verify(){
unsigned base;
unsigned offset;
if(is_traced())
return;
base = getLibAddr("libverify.so");
decrypt_verify(base, "JNI_OnLoad");
g_base = base;
init();
offset = findExportedSym(g_szStackChkGuard);
offset = findAddrInGOT(g_reldyn, g_reldynsz, offset);
g_guard = *((unsigned*)(base + offset));
offset = findExportedSym(g_szMemcpy);
offset = findAddrInGOT(g_relplt, g_relpltsz, offset);
g_memcpy = *((unsigned*)(base + offset));
}
void verify(JNIEnv* env, jobject obj, jstring username, jstring regcode){
if(is_traced())
return;
__eabi_Unwind_cpp_ptr384(env, obj, username, regcode);
}
#define JNIREG_CLASS "com/example/crackme52/MainActivity"
static JNINativeMethod gMethods[] = {
{"verify", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)verify}
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
unsigned idx;
unsigned base;
struct stat file_stat;
void* addr;
if(is_traced())
return result;
base = getLibAddr("libverify.so");
stat("/data/data/com.example.crackme52/lib/libmodule.so", &file_stat);
g_filesz = 4096 * (file_stat.st_size / 4096 + (file_stat.st_size % 4096 ? 1 : 0));
g_filesz += 0x1000;
g_fd = open("/data/data/com.example.crackme52/lib/libmodule.so", O_RDONLY); //障眼法。。。
if(g_fd == -1){
__android_log_print(ANDROID_LOG_INFO, "JNITag", "Open libmodule.so error\n");
return result;
}
addr = mmap(NULL, g_filesz, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); //匿名映射,关键点
if(addr == (void*)-1){
__android_log_print(ANDROID_LOG_INFO, "JNITag", "Map load error\n");
return result;
}
if(is_traced())
return result;
release(base, addr);
g_base = (unsigned)addr;
init();
idx = findExportedSym("begin_verify");
__eabi_Unwind_cpp_ptr384 = (void (*)(JNIEnv*, jobject, jstring, jstring))(g_base + (g_dSymtab + idx)->st_value);
idx = findExportedSym(g_szStackChkGuard);
idx = findAddrInGOT(g_reldyn, g_reldynsz, idx);
*((unsigned*)(g_base + idx)) = g_guard;
idx = findExportedSym(g_szMemcpy);
idx = findAddrInGOT(g_relplt, g_relpltsz, idx);
*((unsigned*)(g_base + idx)) = g_memcpy;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
if(env == NULL)
return -1;
if (!registerNatives(env)) {
return -1;
}
result = JNI_VERSION_1_4;
return result;
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved){
JNIEnv* env = NULL;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return;
}
(*env) -> UnregisterNatives(env, JNIREG_CLASS);
close(g_fd);
munmap((void*)g_base, g_filesz);
}
//real.c 算法校验源码:
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <elf.h>
#include <sys/mman.h>
static is_legal(jint length){
if(length >= 8 && length <= 20)
return 1;
else
return 0;
}
void begin_verify(JNIEnv* env, jobject thiz, jstring username, jstring regcode)
{
jclass c_toast;
jmethodID m_make, m_show;
jobject iToast;
jint length, i;
const char dic[] = "0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; //length = 62
const char code[] = ",.?!";
char msg1[12];
const char *temp;
int sum = 0;
length = (*env) -> GetStringUTFLength(env, regcode);
if(length != 12)
return;
if(!is_legal((*env) -> GetStringUTFLength(env, username))){
return;
}
length = (*env) -> GetStringUTFLength(env, username);
temp = (*env) -> GetStringUTFChars(env, username, JNI_FALSE);
if(length > 12){
for (i = 0; i < 12; ++i) {
msg1 = temp;
}
}
else{
for (i = 0; i < length; ++i) {
msg1 = temp;
}
for(i = length; i < 12; ++i){
msg1 = code[i - length];
}
}
temp = (*env) -> GetStringUTFChars(env, regcode, JNI_FALSE);
for (i = 11; i>=0; --i) {
sum += msg1;
if(dic[sum % 62] != temp)
{
return ;
}
}
c_toast = (*env) -> FindClass(env, "android/widget/Toast");
if(c_toast == NULL){
goto _error;
}
m_make = (*env) -> GetStaticMethodID(env, c_toast, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
if(m_make == NULL){
goto _error;
}
iToast = (*env) -> CallStaticObjectMethod(env, c_toast, m_make, thiz, (*env)->NewStringUTF(env, "Congratulation! You crack it!"), 0);
if(iToast == NULL){
goto _error;
}
m_show = (*env) -> GetMethodID(env, c_toast, "show", "()V");
if(m_make == NULL){
goto _error;
}
(*env) -> CallVoidMethod(env, iToast, m_show);
_error:
return ;
}
//fake.c源码,就是libmodule.so中看到的。。
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <elf.h>
#include <sys/mman.h>
static is_legal(jint length){
if(length >= 8 && length <= 20)
return 1;
else
return 0;
}
void begin_verify(JNIEnv* env, jobject thiz, jstring username, jstring regcode)
{
jclass c_toast;
jmethodID m_make, m_show;
jobject iToast;
jint length, i;
const char dic[] = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789"; //length = 62
const char code[] = ",.?!";
char msg1[12];
const char *temp;
int sum = 0;
length = (*env) -> GetStringUTFLength(env, regcode);
if(length != 10)
return ;
if(!is_legal((*env) -> GetStringUTFLength(env, username))){
return ;
}
length = (*env) -> GetStringUTFLength(env, username);
temp = (*env) -> GetStringUTFChars(env, username, JNI_FALSE);
if(length > 12){
for (i = length - 12; i < length; ++i) {
msg1 = temp;
}
}
else{
for (i = 0; i < length; ++i) {
msg1 = temp;
}
for(i = length; i < 12; ++i){
msg1 = code[i - length];
}
}
temp = (*env) -> GetStringUTFChars(env, regcode, JNI_FALSE);
for (i = 0; i < 12; ++i) {
sum += msg1;
if(dic[sum % 62] != temp)
{
return ;
}
}
c_toast = (*env) -> FindClass(env, "android/widget/Toast");
if(c_toast == NULL){
goto _error;
}
m_make = (*env) -> GetStaticMethodID(env, c_toast, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
if(m_make == NULL){
goto _error;
}
iToast = (*env) -> CallStaticObjectMethod(env, c_toast, m_make, thiz, (*env)->NewStringUTF(env, "You got it!"), 0);
if(iToast == NULL){
goto _error;
}
m_show = (*env) -> GetMethodID(env, c_toast, "show", "()V");
if(m_make == NULL){
goto _error;
}
if(username)
goto _error;
(*env) -> CallVoidMethod(env, iToast, m_show);
_error:
return ;
}
可以看到,自加密算法和校验算法都很easy吧。 小心慢慢调试就没问题的。
当然,如果把section加密,再加点反调试,同时加密反调试代码,ELF来点DIY,搞个section移动什么的,校验算法再搞复杂点,可能调试起来就有点恶心了。
-----------------------------------------------------------------------------------------------------------------
如果对elf格式及其机制不太熟悉,可以参看我在看雪的几篇文章,主要看这两篇吧
http://bbs.pediy.com/showthread.php?t=191649
http://bbs.pediy.com/showthread.php?t=193279
源码审核@Hmily
Hmily
|
免费评分
-
查看全部评分
|