基于Android的ELF PLT/GOT符号重定向过程及ELF Hook实现(by 低端码农 2014.10.27)
引言
写这篇技术文的原因,主要有两个:
- 其一是发现网上大部分描写叙述PLT/GOT符号重定向过程的文章都是针对x86的。比方《Redirecting functions in shared ELF libraries》就写得很不错。
尽管其过程跟ARM很相似,但由于CPU体系不同,指令实现差异很大。
- 其二是网上大部分关于ELF文件格式的介绍。都是基于链接视图(Linking View),链接视图是基于节(Section)对ELF进行解析的。
然而动态链接库在载入的过程中,linker仅仅关注ELF中的段(Segment)信息。
因此ELF中的节信息被全然篡改或者甚至删除掉,并不会影响linker的载入过程。这样做能够防止静态分析工具(比方IDA,readelf等)对其进行分析,一般加过壳的ELF文件都会有这方面的处理。对于这样的ELF文件,假设要实现hook功能,则必须要基于运行视图(Execution View)进行符号解析。
准备
在往下阅读之前,请先确保对ELF文件格式和ARM汇编有个大概了解,參考指引:
准备工具:
- readelf(NDK包括)
- objdump(NDK包括)
- IDA Pro 6.4或以上
- Android真机或者模拟器
符号重定向
在ARM上,常见的重定向类型,主要有三种。各自是R_ARM_JUMP_SLOT、R_ARM_ABS32和R_ARM_GLOB_DAT,而我们要hook elf函数,则须要同一时候处理好这三种重定向类型。
样例
先看演示样例代码
typedef int (*strlen_fun)(const char *);
strlen_fun global_strlen1 = (strlen_fun)strlen;
strlen_fun global_strlen2 = (strlen_fun)strlen; #define SHOW(x) LOGI("%s is %d", #x, x) extern "C" jint Java_com_example_allhookinone_HookUtils_elfhook(JNIEnv *env, jobject thiz){
const char *str = "helloworld"; strlen_fun local_strlen1 = (strlen_fun)strlen;
strlen_fun local_strlen2 = (strlen_fun)strlen; int len0 = global_strlen1(str);
int len1 = global_strlen2(str);
int len2 = local_strlen1(str);
int len3 = local_strlen2(str);
int len4 = strlen(str);
int len5 = strlen(str); SHOW(len0);
SHOW(len1);
SHOW(len2);
SHOW(len3);
SHOW(len4);
SHOW(len5); return 0;
}
这段代码分别以三种不同的方式调用strlen,各自是全局函数指针、局部函数指针以及直接调用,下而我们针对这个样例。分别对三种调用分析进行分析。
先通过readelf,我们查看一下重定向表。例如以下所看到的:
Relocation section '.rel.dyn' at offset 0x2a48 contains 17 entries:
Offset Info Type Sym.Value Sym. Name
0000ade0 00000017 R_ARM_RELATIVE
0000af00 00000017 R_ARM_RELATIVE
0000af0c 00000017 R_ARM_RELATIVE
0000af10 00000017 R_ARM_RELATIVE
0000af18 00000017 R_ARM_RELATIVE
0000af1c 00000017 R_ARM_RELATIVE
0000af20 00000017 R_ARM_RELATIVE
0000af24 00000017 R_ARM_RELATIVE
0000af28 00000017 R_ARM_RELATIVE
0000af30 00000017 R_ARM_RELATIVE
0000aefc 00003215 R_ARM_GLOB_DAT 00000000 __stack_chk_guard
0000af04 00003715 R_ARM_GLOB_DAT 00000000 __page_size
0000af08 00004e15 R_ARM_GLOB_DAT 00000000 strlen
0000b004 00004e02 R_ARM_ABS32 00000000 strlen
0000b008 00004e02 R_ARM_ABS32 00000000 strlen
0000af14 00006615 R_ARM_GLOB_DAT 00000000 __gnu_Unwind_Find_exid
0000af2c 00007415 R_ARM_GLOB_DAT 00000000 __cxa_call_unexpected ...
... Relocation section '.rel.plt' at offset 0x2ad0 contains 48 entries:
Offset Info Type Sym.Value Sym. Name
0000af40 00000216 R_ARM_JUMP_SLOT 00000000 __cxa_atexit
0000af44 00000116 R_ARM_JUMP_SLOT 00000000 __cxa_finalize
0000af48 00001716 R_ARM_JUMP_SLOT 00000000 memcpy
...
0000afd4 00004c16 R_ARM_JUMP_SLOT 00000000 fgets
0000afd8 00004d16 R_ARM_JUMP_SLOT 00000000 fclose
0000afdc 00004e16 R_ARM_JUMP_SLOT 00000000 strlen
0000afe0 00004f16 R_ARM_JUMP_SLOT 00000000 strncmp
...
...
在.rel.plt和.rel.dyn两个section中。我们发现一共出现了4个strlen,我们先把它们的关键信息记录下来,后面分析会很实用。它们各自是
.rel.dyn 0000AF08 R_ARM_GLOB_DAT
.rel.dyn 0000B004 R_ARM_ABS32.rel.dyn 0000B008 R_ARM_ABS32.rel.plt 0000AFDC R_ARM_JUMP_SLOT
在代码中。我们一共调用了6次strlen。但为什么仅仅出现了4次呢?另外,它们之间又是怎样相应的呢。带着这些问题去分析汇编代码。把编译出来的so拖到IDA,我们看到演示样例代码的指令:
.text:000050BC EXPORT Java_com_example_allhookinone_HookUtils_elfhook
.text:000050BC Java_com_example_allhookinone_HookUtils_elfhook
.text:000050BC
.text:000050BC var_40 = -0x40
.text:000050BC var_38 = -0x38
.text:000050BC var_34 = -0x34
.text:000050BC s = -0x2C
.text:000050BC var_28 = -0x28
.text:000050BC var_24 = -0x24
.text:000050BC var_20 = -0x20
.text:000050BC var_1C = -0x1C
.text:000050BC var_18 = -0x18
.text:000050BC var_14 = -0x14
.text:000050BC var_10 = -0x10
.text:000050BC var_C = -0xC
.text:000050BC
.text:000050BC PUSH {R4,LR}
.text:000050BE SUB SP, SP, #0x38
.text:000050C0 STR R0, [SP,#0x40+var_34]
.text:000050C2 STR R1, [SP,#0x40+var_38]
.text:000050C4 LDR R4, =(_GLOBAL_OFFSET_TABLE_ - 0x50CA)
.text:000050C6 ADD R4, PC ; _GLOBAL_OFFSET_TABLE_
.text:000050C8 LDR R3, =(aHelloworld - 0x50CE)
.text:000050CA ADD R3, PC ; "helloworld"
.text:000050CC STR R3, [SP,#0x40+s]
.text:000050CE LDR R3, =(strlen_ptr - 0xAF34)
.text:000050D0 LDR R3, [R4,R3] ; __imp_strlen
.text:000050D2 STR R3, [SP,#0x40+var_28]
.text:000050D4 LDR R3, =(strlen_ptr - 0xAF34)
.text:000050D6 LDR R3, [R4,R3] ; __imp_strlen
.text:000050D8 STR R3, [SP,#0x40+var_24]
.text:000050DA LDR R3, =(global_strlen1_ptr - 0xAF34)
.text:000050DC LDR R3, [R4,R3] ; global_strlen1
.text:000050DE LDR R3, [R3]
.text:000050E0 LDR R2, [SP,#0x40+s]
.text:000050E2 MOVS R0, R2
.text:000050E4 BLX R3
.text:000050E6 MOVS R3, R0
.text:000050E8 STR R3, [SP,#0x40+var_20]
.text:000050EA LDR R3, =(global_strlen2_ptr - 0xAF34)
.text:000050EC LDR R3, [R4,R3] ; global_strlen2
.text:000050EE LDR R3, [R3]
.text:000050F0 LDR R2, [SP,#0x40+s]
.text:000050F2 MOVS R0, R2
.text:000050F4 BLX R3
.text:000050F6 MOVS R3, R0
.text:000050F8 STR R3, [SP,#0x40+var_1C]
.text:000050FA LDR R2, [SP,#0x40+s]
.text:000050FC LDR R3, [SP,#0x40+var_28]
.text:000050FE MOVS R0, R2
.text:00005100 BLX R3
.text:00005102 MOVS R3, R0
.text:00005104 STR R3, [SP,#0x40+var_18]
.text:00005106 LDR R2, [SP,#0x40+s]
.text:00005108 LDR R3, [SP,#0x40+var_24]
.text:0000510A MOVS R0, R2
.text:0000510C BLX R3
.text:0000510E MOVS R3, R0
.text:00005110 STR R3, [SP,#0x40+var_14]
.text:00005112 LDR R3, [SP,#0x40+s]
.text:00005114 MOVS R0, R3 ; s
.text:00005116 BLX strlen
.text:0000511A MOVS R3, R0
.text:0000511C STR R3, [SP,#0x40+var_10]
.text:0000511E LDR R3, [SP,#0x40+s]
.text:00005120 MOVS R0, R3 ; s
.text:00005122 BLX strlen
.text:00005126 MOVS R3, R0
...
...
.text:000051CA ADD SP, SP, #0x38
.text:000051CC POP {R4,PC}
.text:000051CC ; End of function Java_com_example_allhookinone_HookUtils_elfhook
先把几个重要的地址找出来,它们各自是
- GLOBAL_OFFSET_TABLE: 0x0000AF34
- strlen_ptr: 0x0000AF08
- __imp_strlen: 0x0000B0C8
- global_strlen1_ptr: 0x0000AF0C
- global_strlen1: 0x0000B004
- global_strlen2_ptr: 0x0000AF10
- global_strlen2: 0x0000B008
全局函数指针调用外部函数
global_strlen1和global_strlen2的调用。相应0x000050E4和0x000050F4两处的BLX指令,通过计算终于R3的值各自是*global_strlen1和*global_strlen2,而global_strlen1和global_strlen2的值正好相应位于.rel.dyn的两个R_ARM_ABS32的重定位项,因此我们得出结论:通过全局函数指针的方式调用外部函数,它的重定位类型是R_ARM_ABS32,而且位于.rel.dyn节区。
我们仅仅分析global_strlen1的调用过程,首先定位到global_strlen1_ptr(0x0000AF0C),该地址位于.got节区,GLOBAL_OFFSET_TABLE的上方。然后再通过global_strlen1_ptr定位到0x0000B004(位于.data节区),最后再通过0x0000B004定位到终于的函数地址。因此R_ARM_ABS32重定位项的Offset指向终于调用函数地址的地址(也就是函数指针的指针),整个重定位过程是先位到.got,再从.got定位到.date。以下是.got段区的16进制表示片段:
...
0000AF0C 04 B0 00 00 08 B0 00 00 DC B0 00 00 B4 87 00 00
0000AF1C F4 84 00 00 60 5B 00 00 58 5B 00 00 50 5B 00 00
0000AF2C EC B0 00 00 FC 8C 00 00 00 00 00 00 00 00 00 00
...
0000B004 C8 B0 00 00 C8 B0 00 00 ?? ?? ?? ? ? ?? ?? ? ? ? ? 0000B014 ?? ? ? ? ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ??
0000B024 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
0000B0C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000B0D8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
最后发现0x0000B0C8地址片的指令全为0,当动态链接时,linker会覆盖0x0000B004地址的值。指向strlen的真正地址(而不是如今的0x0000B0C8。有点绕)。
局部函数指针调用外部函数
local_strlen1和local_strlen2的调用,相应0x00005100和0x0000510C两处的BLX指令,通过计算终于R3的值都是*strlen_prt,即0x0000AF08。正好相应位于.rel.dyn中的R_ARM_GLOB_DAT重定位项。因此我们得出结论:通过局部函数指针方式调用外部函数,它的重定位类型是R_ARM_GLOB_DAT,而且位于.re.dyn节区。
我们仅仅分析local_strlen1的调用过程。首先是定位到strlen_prt(0x0000AF08)。该地址位于.got节区。GLOBAL_OFFSET_TABLE的上方,然后再通过strlen_prt,定位到0x0000B0C8,跟上面分析的结果竟然一样。因此R_ARM_GLOB_DAT的重定项Offset指向终于调用函数地址的地址(也就是函数指针的指针)。以下是.got段区的16进制表示片段:
0000AF08 C8 B0 00 00 04 B0 00 00 08 B0 00 00 DC B0 00 00
0000AF18 B4 87 00 00 F4 84 00 00 60 5B 00 00 58 5B 00 00
0000AF28 50 5B 00 00 EC B0 00 00 FC 8C 00 00 00 00 00 00
...
0000B0C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000B0D8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
须要注意的是,0x000050D8的指令“STR R3, [SP,#0x40+var_24]”,这里已经把函数的真实地址保存到堆栈了,因此哪怕我们改动了GOT表也不会影响堆栈的值,因此这样的重定位类型无法通过改动地址进行hook。
直接调用外部函数
最后看看strlen的直接调用,相应0x0000511A和0x00005122两处的BLX指令,最后它们都指向.plt节区指令,例如以下所看到的:
.plt:00002E38 ADR R12, 0x2E40
.plt:00002E3C ADD R12, R12, #0x8000
.plt:00002E40 LDR PC, [R12,#(strlen_ptr_0 - 0xAE40)]! ; __imp_strlen
...
0000AFDC C8 B0 00 00 CC B0 00 00 D0 B0 00 00 D4 B0 00 00
0000AFEC D8 B0 00 00 DC B0 00 00 E0 B0 00 00 E4 B0 00 00
0000AFFC E8 B0 00 00 00 00 00 00 C8 B0 00 00 C8 B0 00 00
...
最后。PC指向*strlen_ptr_0,即strlen_ptr_0的地址0x0000AFDC,该地址位于.got节区,而0x0000AFDC地址值的正好是0x0000B0C8,多么熟悉的身影。因此得到结论,直接调用外部函数。它的重定位类型是R_ARM_JUMP_SLOT,而且位于.re.plt节区。其Offset指向终于调用函数地址的地址(也就是函数指针的指针)。整个过程是先到.plt。再到.got,最后才定位到真正的函数地址。
关于这部分的分析,发现IDA和objdump的反编译结果有些差异,以下是通过objdump到的汇编指令:
00002e38 <strlen@plt>:
2e38: e28fc600 add ip, pc, #0, 12
2e3c: e28cca08 add ip, ip, #8, 20 ; 0x8000
2e40: e5bcf19c ldr pc, [ip, #412]! ; 0x19c
...
...
afd8: 00002c50 andeq r2, r0, r0, asr ip
afdc: 00002c50 andeq r2, r0, r0, asr ip
afe0: 00002c50 andeq r2, r0, r0, asr ip
afe4: 00002c50 andeq r2, r0, r0, asr ip
见到afdc处的地址,指向的是0x00002c50,而0x00002c50正好是PLT[0],指令例如以下:
00002c50 <__cxa_atexit@plt-0x14>:
2c50: e52de004 push {lr} ; (str lr, [sp, #-4]!)
2c54: e59fe004 ldr lr, [pc, #4] ; 2c60 <__cxa_atexit@plt-0x4>
2c58: e08fe00e add lr, pc, lr
2c5c: e5bef008 ldr pc, [lr, #8]!
2c60: 000082d4 ldrdeq r8, [r0], -r4
运行2c5c处指令后,终于pc指向0x0000af3c,正好是GLOBAL_OFFSET_TABLE + 8。即GOT[2],我们看到0x0000af3c处:
0000AF3C 00 00 00 00 28 B0 00 00 24 B0 00 00 2C B0 00 00
0000AF4C 30 B0 00 00 34 B0 00 00 38 B0 00 00 3C B0 00 00
结果发现GOT[2]里指向的函数地址竟然是0,这是由于android上的符号绑定并不支持lazy绑定。所以当so被载入时,linker会预先把GOT[n](n>=2)的所相应的函数都提前找出来,因此这里GOT[2]的代码实际上不会被运行,因此在眼下的Android上,并不存在完整的PLT/GOT链接过程。猜想这主要是出于稳定性考虑的。
总结
尽管IDA和obudump两个工具反编译得出的指令在PLT\GOT过程中有些区别,但对于Android而言。事实上这个差异不会造成影响,由于Android上不支持lazy绑定。同一时候我们得出一个很重要的结论:R_ARM_ABS32、R_ARM_GLOB_DAT和R_ARM_JUMP_SLOT的重定位项尽管在代码中使用方法不一样,但其offset都是指向一个函数的指针的指针,这个对于我们以下进行elfhook很实用。
基于运行视图解析ELF
《Redirecting functions in shared ELF libraries》这篇文章所提供的样例,就是基于链接视图对ELF进行解析的,与基于运行视图进行解析相比。后面的逻辑基本是一样的,关键是要通过segment找到.dynsym、.dynstr、.rel.plt和rel.dyn,以及它们的项数。
首次通过Program Header Table找到类型为PT_DYNAMIC的段,该的内容事实上相应.dynamic,这段的内容相应Elf32_Dyn类型的数组。其结构体例如以下所看到的:
/* Dynamic structure */
typedef struct {
Elf32_Sword d_tag; /* controls meaning of d_val */
union {
Elf32_Word d_val; /* Multiple meanings - see d_tag */
Elf32_Addr d_ptr; /* program virtual address */
} d_un;
} Elf32_Dyn;
通过遍历这个数组,我们能够找到全部的须要的信息,我把它们的相应关系列出来:
- DT_HASH -> .hash
- DT_SYMTAB & DT_SYMENT -> .dynsym
- DT_STRTAB & DT_STRSZ -> .dynstr
- PLTREL(决定REL还是RELA) &(DT_REL | DT_RELA) & (DT_RELSZ | DT_RELASZ ) & (DT_RELENT | DT_RELAENT ) -> .rel.dyn
- DT_JMPREL & DT_PLTRELSZ & (DT_RELENT | DT_RELAENT) -> .rel.plt
- FINI_ARRAY & FINI_ARRAYSZ -> .fini_array
- INIT_ARRAY & INIT_ARRAYSZ -> .init_array
这是查找的相关代码:
void getElfInfoBySegmentView(ElfInfo &info, const ElfHandle *handle){ info.handle = handle;
info.elf_base = (uint8_t *) handle->base;
info.ehdr = reinterpret_cast<Elf32_Ehdr *>(info.elf_base); // may be wrong
info.shdr = reinterpret_cast<Elf32_Shdr *>(info.elf_base + info.ehdr->e_shoff);
info.phdr = reinterpret_cast<Elf32_Phdr *>(info.elf_base + info.ehdr->e_phoff); info.shstr = NULL; Elf32_Phdr *dynamic = NULL;
Elf32_Word size = 0; getSegmentInfo(info, PT_DYNAMIC, &dynamic, &size, &info.dyn);
if(!dynamic){
LOGE("[-] could't find PT_DYNAMIC segment");
exit(-1);
}
info.dynsz = size / sizeof(Elf32_Dyn); Elf32_Dyn *dyn = info.dyn;
for(int i=0; i<info.dynsz; i++, dyn++){ switch(dyn->d_tag){ case DT_SYMTAB:
info.sym = reinterpret_cast<Elf32_Sym *>(info.elf_base + dyn->d_un.d_ptr);
break; case DT_STRTAB:
info.symstr = reinterpret_cast<const char *>(info.elf_base + dyn->d_un.d_ptr);
break; case DT_REL:
info.reldyn = reinterpret_cast<Elf32_Rel *>(info.elf_base + dyn->d_un.d_ptr);
break; case DT_RELSZ:
info.reldynsz = dyn->d_un.d_val / sizeof(Elf32_Rel);
break; case DT_JMPREL:
info.relplt = reinterpret_cast<Elf32_Rel *>(info.elf_base + dyn->d_un.d_ptr);
break; case DT_PLTRELSZ:
info.relpltsz = dyn->d_un.d_val / sizeof(Elf32_Rel);
break; case DT_HASH:
uint32_t *rawdata = reinterpret_cast<uint32_t *>(info.elf_base + dyn->d_un.d_ptr);
info.nbucket = rawdata[0];
info.nchain = rawdata[1];
info.bucket = rawdata + 2;
info.chain = info.bucket + info.nbucket;
break;
}
} //because .dynsym is next to .dynstr, so we can caculate the symsz simply
info.symsz = ((uint32_t)info.symstr - (uint32_t)info.sym)/sizeof(Elf32_Sym);
}
然而,有一个值我无法通过通过PT_DYNAMIC段得到的,那就是.dynsym的项数,我最后通过变通的方法得到的。由于.dynsym和.dynstr两个节区是相邻的,因此它们两个地址相减,就可以得到的.dynsym总长度,再除了sizeof(Elf32_Sym)就可以得到.dynsym的项数,假设你有更好的方法。请跟我说说。
ELF Hook
有了上面的介绍之后。写个ELF Hook就很easy的,我把关键代码贴出来:
#define R_ARM_ABS32 0x02
#define R_ARM_GLOB_DAT 0x15
#define R_ARM_JUMP_SLOT 0x16 int elfHook(const char *soname, const char *symbol, void *replace_func, void **old_func){
assert(old_func);
assert(replace_func);
assert(symbol); ElfHandle* handle = openElfBySoname(soname);
ElfInfo info; getElfInfoBySegmentView(info, handle); Elf32_Sym *sym = NULL;
int symidx = 0; findSymByName(info, symbol, &sym, &symidx); if(!sym){
LOGE("[-] Could not find symbol %s", symbol);
goto fails;
}else{
LOGI("[+] sym %p, symidx %d.", sym, symidx);
} for (int i = 0; i < info.relpltsz; i++) {
Elf32_Rel& rel = info.relplt[i];
if (ELF32_R_SYM(rel.r_info) == symidx && ELF32_R_TYPE(rel.r_info) == R_ARM_JUMP_SLOT) { void *addr = (void *) (info.elf_base + rel.r_offset);
if (replaceFunc(addr, replace_func, old_func))
goto fails; //only once
break;
}
} for (int i = 0; i < info.reldynsz; i++) {
Elf32_Rel& rel = info.reldyn[i];
if (ELF32_R_SYM(rel.r_info) == symidx &&
(ELF32_R_TYPE(rel.r_info) == R_ARM_ABS32
|| ELF32_R_TYPE(rel.r_info) == R_ARM_GLOB_DAT)) { void *addr = (void *) (info.elf_base + rel.r_offset);
if (replaceFunc(addr, replace_func, old_func))
goto fails;
}
} fails:
closeElfBySoname(handle);
return 0;
}
最后是測试的代码:
typedef int (*strlen_fun)(const char *);
strlen_fun old_strlen = NULL; size_t my_strlen(const char *str){
LOGI("strlen was called.");
int len = old_strlen(str);
return len * 2;
} strlen_fun global_strlen1 = (strlen_fun)strlen;
strlen_fun global_strlen2 = (strlen_fun)strlen; #define SHOW(x) LOGI("%s is %d", #x, x) extern "C" jint Java_com_example_allhookinone_HookUtils_elfhook(JNIEnv *env, jobject thiz){
const char *str = "helloworld"; strlen_fun local_strlen1 = (strlen_fun)strlen;
strlen_fun local_strlen2 = (strlen_fun)strlen; int len0 = global_strlen1(str);
int len1 = global_strlen2(str);
int len2 = local_strlen1(str);
int len3 = local_strlen2(str);
int len4 = strlen(str);
int len5 = strlen(str); LOGI("hook before:");
SHOW(len0);
SHOW(len1);
SHOW(len2);
SHOW(len3);
SHOW(len4);
SHOW(len5); elfHook("libonehook.so", "strlen", (void *)my_strlen, (void **)&old_strlen); len0 = global_strlen1(str);
len1 = global_strlen2(str);
len2 = local_strlen1(str);
len3 = local_strlen2(str);
len4 = strlen(str);
len5 = strlen(str); LOGI("hook after:");
SHOW(len0);
SHOW(len1);
SHOW(len2);
SHOW(len3);
SHOW(len4);
SHOW(len5); return 0;
}
从打印结果能够发现,local_strlen1和local_strlen2正所上面所说,并没有受影响。但假设函数再次被调用,则生效了。原因不解析了。測试结果就不发了,留给你们试吧。
GitHup地址
完整代码,见https://github.com/boyliang/AllHookInOne.git
基于Android的ELF PLT/GOT符号重定向过程及ELF Hook实现(by 低端码农 2014.10.27)的更多相关文章
- 基于Android的ELF PLT/GOT符号和重定向过程ELF Hook实现(by 低端农业代码 2014.10.27)
介绍 技术原因写这篇文章,有两种: 一个是在大多数在线叙述性说明发现PLT/GOT第二十符号重定向过程定向x86的,例<Redirecting functions in shared ELF l ...
- 【转】基于 Android NDK 的学习之旅-----数据传输(引用数据类型)
原文网址:http://www.cnblogs.com/luxiaofeng54/archive/2011/08/20/2147086.html 基于 Android NDK 的学习之旅-----数据 ...
- 【基于Android的ARM汇编语言系列】之三:ARM汇编语言程序结构
作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell [ ...
- ELF格式文件符号表全解析及readelf命令使用方法
http://blog.csdn.net/edonlii/article/details/8779075 1. 读取ELF文件头: $ readelf -h signELF Header: Magi ...
- 【基于Android的ARM汇编语言系列】之五:ARM指令集与Thumb指令集
作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell [ ...
- 基于android平台的斗地主AI
本软件是基于android平台的斗地主AI,我们在源代码的基础之上,旨在改进AI的算法,使玩家具有更丰富的体验感,让NPC可以更为智能. (一)玩法解析: (1)发牌和叫牌:一副扑克54张,先为每个人 ...
- 基于Android Studio搭建hello world工程
基于Android Studio搭建hello world工程 版本:ANDROID STUDIO V0.4.6 This download includes: · Android St ...
- 基于Android 平台简易即时通讯的研究与设计[转]
摘要:论文简单介绍Android 平台的特性,主要阐述了基于Android 平台简易即时通讯(IM)的作用和功能以及实现方法.(复杂的通讯如引入视频音频等可以考虑AnyChat SDK~)关键词:An ...
- 基于Android 4.4 开发的多窗体系统 开放源代码
Hi, 这是我基于Android 4.4开发的多窗体系统,还有非常多不足,还请多多不吝赐教啊,代码已经所有开源. 视频地址 源代码地址 Done: 1. APP以窗体化显示 在 PhoneWindow ...
随机推荐
- SQL group 分组查询
1.使用group by进行分组查询 在使用group by关键字时,在select列表中可以指定的项目是有限制的,select语句中仅许以下几项: 被分组的列 为每个分组返回一个值得表达式,例如 ...
- Azkaban(一)Azkaban的基础介绍
一.为什么需要工作流调度器 1.一个完整的数据分析系统通常都是由大量任务单元组成: shell 脚本程序,java 程序,mapreduce 程序.hive 脚本等 2.各任务单元之间存在时间先后及前 ...
- 30分钟LINQ教程的学习笔记
原文章 : http://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html 一.11个与LINQ有关的语言特性: [隐式类型.匿名类型.对象 ...
- 02:实现Singleton模式
Java实现单例模式有很多种实现方法,其中我们应根据需要选择线程安全的与非线程安全的两种方式,根据对象实现的方式又分为饱汉与饿汉方式. 这里使用java中的volatile关键字与synchroniz ...
- 《Android源码设计模式》--工厂方法模式
No1: 对于一个应用程序来说,其真正的入口是在ActivityThread类中,ActivityThread中含有我们熟悉的main方法.ActivityThread是一个final类,不能被继承. ...
- awk 基本函数用法
gsub函数有点类似于sed查找和替换.它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行.第一个函数作用于记录$0,第二个gsub函数允许指定目标,然而,如果未指定目标,缺省为 ...
- 掩码计算工具netmask
掩码计算工具netmask 在网络扫描和防火墙配置中,经常需要计算IP地址范围和对应的掩码.为了简化这个过程,Kali Linux预置了一个掩码计算工具netmask.该工具不仅可以根据IP地址范 ...
- 支撑大规模公有云的Kubernetes改进与优化 (3)
这一篇我们来讲网易为支撑大规模公有云对于Kubernetes的定制化. 一.总体架构 网易的Kubernetes集群是基于网易云IaaS平台OpenStack上面进行部署的,在外面封装了一个容器平台的 ...
- 深入理解ajax系列第四篇
前面的话 现代Web应用中频繁使用的一项功能就是表单数据的序列化,XMLHttpRequest 2级为此定义了FormData类型.FormData为序列化表单以及创建与表单格式相同的数据提供了便利. ...
- [Luogu4724][模板]三维凸包(增量构造法)
1.向量点积同二维,x1y1+x2y2+x3y3.向量叉积是行列式形式,(y1z2-z1y2,z1x2-x1z2,x1y2-y1x2). 2.增量构造法: 1)首先定义,一个平面由三个点唯一确定.一个 ...