工具与环境

Xposed

IDA 6.8

JEB 2.2.5

Fiddler2

010Editor

NEXUS 5  Android 4.4

好久不玩逆向怕调试器生锈,拿出来磨磨!

高手莫要见笑,仅供小菜玩乐,有不对或不足的地方还请多多指教,不胜感激!

0x00: 程序大概情况分析

在我们拿到一个APP准备破解时一般得安装运行,程序运行后须要注册用户,随便注册一个用户登录,以下是APP须要购买vip才能使用的大概情况。

通过简单的查看可以知道是要通过网络支付后才能使用VIP,可以使用Fiddler进行抓包分析,但是请求体与返回值都是加密了的,目前还看不懂。

用JEB反编译app发现被加固了。

通过上面简单的分析后,该款应用为了防止被破解,主要做了以下几点防护。

利用第三方加固将app加固,网络验证是否为VIP权限。

0x01:壳反调试分析

通过第一部分的介绍,发现软件被加固了,接下来就是要脱掉壳才能更好地分析下去,挂上IDA在JNI_Onload下断点,反调试主要有,获取rtld_db_dlactivity判断是否为空,过反调试将获取到的内容清零就成了、time时间比较,将返回值清零,过/proc/self/status反调试将open函数返回0,过/proc/net/tcp也是将open函数返回0,文件监控不用管,还有一处比较隐藏的反调试raise,直接将函数改成返回。

 LOAD:75A6A038 F0  2D E9                 STMFD           SP!, {R4-R8,LR}
LOAD:75A6A03C F0 BD E8 LDMFD SP!, {R4-R8,PC};改成返回
LOAD:75A6A040 ; ---------------------------------------------------------------------------
LOAD:75A6A040 9F E5 LDR R5, =(off_75A72ED4 - 0x75A6A05C)
LOAD:75A6A044 9F E5 LDR R2, =(is_androidserver_prot - 0x75A6A058)
LOAD:75A6A048 9F E5 LDR R6, =0xE7F001F0
LOAD:75A6A04C D0 4D E2 SUB SP, SP, #0x10
LOAD:75A6A050 8F E0 ADD R2, PC, R2 ; is_androidserver_prot
LOAD:75A6A054 9F E7 LDR R5, [PC,R5] ; off_75A72ED4
LOAD:75A6A058 9F E7 LDR R3, [PC,R3] ; off_75A72ED0
LOAD:75A6A05C 8D E5 STR R5, [SP,#0x10+var_C]
LOAD:75A6A060 8D E5 STR R3, [SP,#0x10+var_8]
LOAD:75A6A064 0C 8D E5 STR R2, [SP,#0x10+var_4]
LOAD:75A6A068 8D E2 ADD R7, SP, #
LOAD:75A6A06C 0C 8D E2 ADD R8, SP, #0x10+var_4
LOAD:75A6A070
LOAD:75A6A070 loc_75A6A070 ; CODE XREF: Anti_raise+70j
LOAD:75A6A070 E2 SUB R4, R5, #
LOAD:75A6A074 E2 ADD R5, R5, #0x24
LOAD:75A6A078 EA B loc_75A6A084
LOAD:75A6A07C ; ---------------------------------------------------------------------------
LOAD:75A6A07C
LOAD:75A6A07C loc_75A6A07C ; CODE XREF: Anti_raise+54j
LOAD:75A6A07C E1 CMP R4, R5
LOAD:75A6A080 0A BEQ loc_75A6A0A0
LOAD:75A6A084
LOAD:75A6A084 loc_75A6A084 ; CODE XREF: Anti_raise+40j
LOAD:75A6A084 ; Anti_raise+64j
LOAD:75A6A084 B4 E5 LDR R3, [R4,#]!
LOAD:75A6A088 E1 CMP R3, R6
LOAD:75A6A08C FA FF FF 1A BNE loc_75A6A07C
LOAD:75A6A090 A0 E3 MOV R0, # ; sig
LOAD:75A6A094 F6 FF EB BL raise
LOAD:75A6A098 E1 CMP R4, R5
LOAD:75A6A09C F8 FF FF 1A BNE loc_75A6A084
LOAD:75A6A0A0
LOAD:75A6A0A0 loc_75A6A0A0 ; CODE XREF: Anti_raise+48j
LOAD:75A6A0A0 E1 CMP R7, R8
LOAD:75A6A0A4 B7 LDRNE R5, [R7,#0xC+var_8]!
LOAD:75A6A0A8 F0 FF FF 1A BNE loc_75A6A070
LOAD:75A6A0AC D0 8D E2 ADD SP, SP, #0x10
LOAD:75A6A0B0 F0 BD E8 LDMFD SP!, {R4-R8,PC}
LOAD:75A6A0B0 ; End of function Anti_raise
LOAD:75A6A0B0

以上就是过掉所有主要的反调试了

0x02:壳大致流程分析与Dump出解密后的dex

过掉反调试后在Case 29 与case33下好断点,代码如下:

 //case  文件偏移:9DB8
/data/app-lib/com.txy.anywhere-/libjiagu_old.so 75A66000
LOAD:75A6FDB8
LOAD:75A6FDB8 loc_75A6FDB8 ; CODE XREF: __fun_a_18(char *,uint):loc_75A6F8CCj
LOAD:75A6FDB8 ; __fun_a_18(char *,uint):loc_75A6F8DCj ...
LOAD:75A6FDB8 8C E2 ADD R3, R12, # ; jumptable 75A6F8DC case 29
LOAD:75A6FDBC D4 E7 LDRB R2, [R4,R3]
LOAD:75A6FDC0 1C 8D E5 STR R3, [SP,#0xCC+var_B0]
LOAD:75A6FDC4 E3 CMP R2, #
LOAD:75A6FDC8 C9 0A BEQ loc_75A704F4
LOAD:75A6FDCC E9 LDMIB R5, {R7,R8,R10}
LOAD:75A6FDD0 0C E0 ADD R2, R4, R12
LOAD:75A6FDD4 B0 E5 LDR R11, [R2,#]
LOAD:75A6FDD8 E5 LDR R2, [R5,#0x38]
LOAD:75A6FDDC E7 LDR R3, [R4,R3]
LOAD:75A6FDE0 C0 8C E2 ADD R12, R12, #
LOAD:75A6FDE4 1C C0 8D E5 STR R12, [SP,#0xCC+var_B0]
LOAD:75A6FDE8 B0 8B E0 ADD R11, R11, R2
LOAD:75A6FDEC 0B B0 E0 RSB R11, R3, R11
LOAD:75A6FDF0 C0 E5 LDR R12, [R5]
LOAD:75A6FDF4 0C A0 E1 MOV R0, R12
LOAD:75A6FDF8 A0 E1 MOV R1, R7
LOAD:75A6FDFC A0 E1 MOV R2, R8
LOAD:75A6FE00 0A A0 E1 MOV R3, R10
LOAD:75A6FE04 0B E0 A0 E1 MOV LR, R11
LOAD:75A6FE08 3E FF 2F E1 BLX LR
LOAD:75A6FE0C A0 E1 MOV R7, R0
LOAD:75A6FE10 C0 A0 E1 MOV R12, R1
LOAD:75A6FE14 E5 STR R7, [R5] ; jumptable 000098CC case 28
LOAD:75A6FE18 C0 E5 STR R12, [R5,#]
LOAD:75A6FE1C 1C C0 9D E5 LDR R12, [SP,#0xCC+var_B0]
LOAD:75A6FE20 A9 FE FF EA B loc_75A6F8CC
 //case  执行解压函数与执行解密后第二个so的JNI_OnLoad函数 文件偏移: 9B44
/data/app-lib/com.txy.anywhere-/libjiagu_old.so 75A66000
LOAD:75A6FB44 loc_75A6FB44 ; CODE XREF: __fun_a_18(char *,uint):loc_75A6F8CCj
LOAD:75A6FB44 ; __fun_a_18(char *,uint):loc_75A6F8DCj ...
LOAD:75A6FB44 8C E2 ADD R3, R12, # ; jumptable 75A6F8DC case 33
LOAD:75A6FB48 D4 E7 LDRB R2, [R4,R3]
LOAD:75A6FB4C 1C 8D E5 STR R3, [SP,#0xCC+var_B0]
LOAD:75A6FB50 E3 CMP R2, #arg_0
LOAD:75A6FB54 0A BEQ loc_75A704F4
LOAD:75A6FB58 8C E2 ADD R3, R12, #
LOAD:75A6FB5C E7 LDR R2, [R4,R3]
LOAD:75A6FB60 1C 8D E5 STR R3, [SP,#0xCC+var_B0]
LOAD:75A6FB64 C0 8C E2 ADD R12, R12, #
LOAD:75A6FB68 B1 E7 LDR R11, [R5,R2,LSL#]
LOAD:75A6FB6C 1C C0 8D E5 STR R12, [SP,#0xCC+var_B0]
LOAD:75A6FB70 E8 LDMIA R5, {R7,R8,R10,R12}
LOAD:75A6FB74 A0 E1 MOV R0, R7
LOAD:75A6FB78 A0 E1 MOV R1, R8
LOAD:75A6FB7C 0A A0 E1 MOV R2, R10
LOAD:75A6FB80 0C A0 E1 MOV R3, R12
LOAD:75A6FB84 0B E0 A0 E1 MOV LR, R11
LOAD:75A6FB88 3E FF 2F E1 BLX LR
LOAD:75A6FB8C C0 A0 E1 MOV R12, R0 ; jumptable 000098CC case 32
LOAD:75A6FB90 C0 E5 STR R12, [R5]
LOAD:75A6FB94 1C C0 9D E5 LDR R12, [SP,#0x50+var_34]

下好断点后一直F9会发现主是逻辑就是获取解压函数(uncompressc)解压第二个so数据,解压后就是解密第二个SO了。接下来就是在内存中加载第二个SO并获取JNI_OnLoad函数。

 LOAD:401030A8 F0  2D E9                 STMFD           SP!, {R4-R7,LR}
LOAD:401030AC C8 9F E5 LDR R4, =(off_40109EC0 - 0x401030C0)
LOAD:401030B0 9C D0 4D E2 SUB SP, SP, #0x9C
LOAD:401030B4 A0 E1 MOV R7, R0
LOAD:401030B8 9F E7 LDR R4, [PC,R4]
LOAD:401030BC E5 LDR R3, [R4]
LOAD:401030C0
LOAD:401030C0 loc_401030C0 ; DATA XREF: LOAD:4010317Co
LOAD:401030C0 A0 E1 MOV R6, R1
LOAD:401030C4 A0 E3 MOV R2, #0x94 ; '
LOAD:401030C8 A0 E3 MOV R1, #
LOAD:401030CC 0D A0 E1 MOV R0, SP
LOAD:401030D0 8D E5 STR R3, [SP,#0x94]
LOAD:401030D4 B5 ED FF EB BL memset
LOAD:401030D8 A0 9F E5 LDR R3, =0x6F732E2A
LOAD:401030DC A0 E3 MOV R2, #
LOAD:401030E0 0D A0 E1 MOV R0, SP
LOAD:401030E4 0C 8D E5 STR R3, [SP,#0xC]
LOAD:401030E8 CD E5 STRB R2, [SP,#0x10]
LOAD:401030EC 8D E5 STR R7, [SP]
LOAD:401030F0 8D E5 STR R6, [SP,#]
LOAD:401030F4 EB F2 FF EB BL sub_400FFCA8
LOAD:401030F8 9F E5 LDR R3, =(dword_4014D740 - 0x40103108)
LOAD:401030FC 9F E5 LDR R1, =(aMakekey - 0x4010310C)
LOAD: 8F E0 ADD R3, PC, R3
LOAD: 8F E0 ADD R1, PC, R1
LOAD:
LOAD: loc_40103108 ; DATA XREF: LOAD:40103184o
LOAD: E5 STR R0, [R3]
LOAD:4010310C
LOAD:4010310C loc_4010310C ; DATA XREF: LOAD:40103188o
LOAD:4010310C F2 F2 FF EB BL my_dlsym
LOAD: E2 SUBS R3, R0, #
LOAD: 0A BEQ loc_4010313C
LOAD: FF 0E C3 E3 BIC R0, R3, #0xFF0
LOAD:4010311C 0F C0 E3 BIC R0, R0, #0xF
LOAD: 1A A0 E3 MOV R1, #0x1000
LOAD: A0 E3 MOV R2, #
LOAD: 7D A0 E3 MOV R7, #0x7D ; '}'
LOAD:4010312C EF SVC
LOAD: 9F E5 LDR R2, =(loc_40102C0C - 0x4010313C)
LOAD: 8F E0 ADD R2, PC, R2
LOAD: E5 STR R2, [R3]
LOAD:4010313C
LOAD:4010313C loc_4010313C ; DATA XREF: LOAD:4010318Co
LOAD:4010313C 4C 9F E5 LDR R3, =(dword_4014D740 - 0x4010314C)
LOAD: 4C 9F E5 LDR R1, =(aJni_onload - 0x40103150)
LOAD: 8F E0 ADD R3, PC, R3
LOAD: 8F E0 ADD R1, PC, R1
LOAD:4010314C
LOAD:4010314C loc_4010314C ; DATA XREF: LOAD:40103190o
LOAD:4010314C E5 LDR R0, [R3]
LOAD:
LOAD: loc_40103150 ; DATA XREF: LOAD:40103194o
LOAD: E1 F2 FF EB BL my_dlsym ; 获取第二个SO的JNI_OnLoad函数
LOAD: 9D E5 LDR R1, [SP,#0x94]
LOAD: 9F E5 LDR R3, =(dword_4014D744 - 0x40103168)
LOAD:4010315C E5 LDR R2, [R4]
LOAD: 8F E0 ADD R3, PC, R3
LOAD: E1 CMP R1, R2
LOAD:
LOAD: loc_40103168 ; DATA XREF: LOAD:40103198o
LOAD: E5 STR R0, [R3]
LOAD:4010316C 1A BNE loc_40103178
LOAD: 9C D0 8D E2 ADD SP, SP, #0x9C
LOAD: F0 BD E8 LDMFD SP!, {R4-R7,PC}
LOAD:
LOAD: loc_40103178

获取到第二个SO的JNI_OnLoad函数地址后走到Case 33处跳到第二个SO的JNI_OnLoad去执行了,到此第一个SO的主要工作就基本完成了。

第二个SO的JNI_OnLoad主要工作就是注册壳本身的 Native函数与被Native的onCreate函数

 //注册 Native onCreate  文件偏移 C252
debug190 761DA000 R . X D . byte public CODE
debug190: loc_76180252 ; CODE XREF: debug190:7618028Aj
debug190: ; debug190:76180292j ...
debug190: D7 MOVS R3, #0xD7 ; '
debug190: LDR R0, [SP,#0x24]
debug190: 9B LSLS R3, R3, #
debug190: LDR R1, [R0]
debug190:7618025A CB LDR R3, [R1,R3]
debug190:7618025C 1C MOVS R1, R4
debug190:7618025E 9C MOV R12, R3
debug190: MOVS R3, #
debug190: E0 BLX R12 ; Native onCreate R2:存放注册信息,签名等
debug190: 5B MOV R3, R11
debug190: MOVS R2, #
debug190: 1B LDRB R3, [R3]
debug190:7618026A 1A TST R2, R3
debug190:7618026C D0 BEQ loc_76180270
debug190:7618026E A7 E1 B loc_761805C0

下面是我通过hook注册函数打印出来的对应类的Native函数,在后面SO劫持会有说:

 class-Ldalvik/system/DexFile; ->name=getClassNameList  address=75FE10F9
class-Lcom/qihoo/bugreport/javacrash/CrashReportDataFactory; ->name=interface9 address=7600917D
class-Lcom/qihoo/util/StubApp296881940; ->name=mark address=75FF75FD
class-Lcom/qihoo/util/StubApp296881940; ->name=mark address=75FFBE45
class-Lcom/qihoo/util/StubApp296881940; ->name=mark address=75FFBD91
class-Lcom/qihoo/util/StubApp296881940; ->name=interface10 address=75FFE819
class-Lcom/qihoo/util/StubApp296881940; ->name=interface5 address=75FE0375
class-Lcom/qihoo/util/StubApp296881940; ->name=interface6 address=75FDD95D
class-Lcom/qihoo/util/StubApp296881940; ->name=interface7 address=75FE1801
class-Lcom/qihoo/util/StubApp296881940; ->name=interface8 address=75FDDCFD
class-Lcom/mob/tools/MobUIShell; ->name=onCreate address=75FDF729
class-Lcom/txy/anywhere/activity/GreenHandGuideActivity; ->name=onCreate address=75FDF6F1
class-Lcom/txy/anywhere/activity/MainActivity; ->name=onCreate address=75FDF6B9
class-Lcom/txy/anywhere/activity/PanoramaActivity; ->name=onCreate address=75FDF681
class-Lcom/txy/anywhere/activity/move/MoveDetailActivity; ->name=onCreate address=75FDF649
class-Lcom/txy/anywhere/activity/move/MoveSettingsMapChoiceActivity; ->name=onCreate address=75FDF611
class-Lcom/txy/anywhere/activity/specific/SpecificProgramDetailActivity; ->name=onCreate address=75FDF5D9
class-Lcom/txy/anywhere/wxapi/WXPayEntryActivity; ->name=onCreate address=75FDF5A1
class-Lcom/common/customp/PullEntry; ->name=getAppkey address=75FE1439

接下来就是解密原始DEX了,解密逻辑如下:

 //解密dex
密钥 A0 EC E6 CC FB A6 F1 A2 EC A3 E7 AF E1 5A EA 2F
debug190:761BB0E0 ; ---------------------------------------------------------------------------
debug190:761BB0E0 F0 B5 PUSH {R4-R7,LR}
debug190:761BB0E2 MOV R7, R10
debug190:761BB0E4 4E MOV R6, R9
debug190:761BB0E6 MOV R5, R8
debug190:761BB0E8 E0 B4 PUSH {R5-R7}
debug190:761BB0EA 1C MOVS R6, R2
debug190:761BB0EC MOVS R2, #0x80 ; '€'
debug190:761BB0EE MOV R8, R0
debug190:761BB0F0 4C LDR R4, =(dword_761DCD20 - 0x761BB0FA)
debug190:761BB0F2 C2 B0 SUB SP, SP, #0x108
debug190:761BB0F4 1F 1C MOVS R7, R3
debug190:761BB0F6 7C ADD R4, PC ; dword_761DCD20
debug190:761BB0F8 4A 9B LDR R3, [SP,#0x128]
debug190:761BB0FA LDR R4, [R4]
debug190:761BB0FC AD ADD R5, SP, #
debug190:761BB0FE MOV R9, R3
debug190:761BB100 LDR R3, [R4]
debug190:761BB102 8A MOV R10, R1
debug190:761BB104 1C MOVS R0, R5
debug190:761BB106 MOVS R1, #
debug190:761BB108 LSLS R2, R2, #
debug190:761BB10A STR R3, [SP,#0x104]
debug190:761BB10C F0 4E FA BL j_j_memset_1
debug190:761BB110 MOV R3, R8
debug190:761BB112 LDR R1, [R3,#]
debug190:761BB114 CMP R1, #
debug190:761BB116 2D D0 BEQ loc_761BB174
debug190:761BB118 1C MOVS R0, R5
debug190:761BB11A MOVS R2, #0x10
debug190:761BB11C F0 F8 BL Rc4_Key_Init ; A0 EC E6 CC FB A6 F1 A2 EC A3 E7 AF E1 5A EA 2F
debug190:761BB120 3B LDR R3, [R7]
debug190:761BB122 2B CMP R3, #
debug190:761BB124 D0 BEQ loc_761BB14A
debug190:761BB126 1C MOVS R0, R5
debug190:761BB128 MOV R1, R10
debug190:761BB12A 1C MOVS R2, R6
debug190:761BB12C F0 D6 F8 BL Rc4_Dec
debug190:761BB130 4B MOV R3, R9
debug190:761BB132 MOVS R0, #
debug190:761BB134 1E STR R6, [R3]
debug190:761BB136
debug190:761BB136 loc_761BB136 ; CODE XREF: debug190:761BB172j
debug190:761BB136 ; debug190:761BB178j ...
debug190:761BB136 9A LDR R2, [SP,#0x104]
debug190:761BB138 LDR R3, [R4]
debug190:761BB13A 9A CMP R2, R3
debug190:761BB13C D1 BNE loc_761BB180
debug190:761BB13E B0 ADD SP, SP, #0x108
debug190:761BB140 1C BC POP {R2-R4}
debug190:761BB142 MOV R8, R2
debug190:761BB144 MOV R9, R3
debug190:761BB146 A2 MOV R10, R4
debug190:761BB148 F0 BD POP {R4-R7,PC}
debug190:761BB14A ; ---------------------------------------------------------------------------
debug190:761BB14A
debug190:761BB14A loc_761BB14A ; CODE XREF: debug190:761BB124j
debug190:761BB14A 1C MOVS R0, R6
debug190:761BB14C F0 FA BL malloc_0
debug190:761BB150 MOV R8, R0
debug190:761BB152 CMP R0, #
debug190:761BB154 D0 BEQ loc_761BB17A
debug190:761BB156 MOV R1, R10
debug190:761BB158 1C MOVS R2, R6
debug190:761BB15A F0 FA BL memcpy_0 ; 拷贝加密的dex内容
debug190:761BB15E 1C MOVS R0, R5
debug190:761BB160 MOV R1, R8
debug190:761BB162 1C MOVS R2, R6
debug190:761BB164 F0 BA F8 BL Rc4_Dec ; 解密Dex内容
debug190:761BB168 4B MOV R3, R9
debug190:761BB16A 1E STR R6, [R3]
debug190:761BB16C MOV R3, R8
debug190:761BB16E MOVS R0, #
debug190:761BB170 3B STR R3, [R7]
debug190:761BB172 E0 E7 B loc_761BB136
debug190:761BB174 ; ---------------------------------------------------------------------------
debug190:761BB174 //再次解密dex 文件偏移 EDF4
debug190 761DA000 R . X D . byte public CODE
debug190:76182DF4 loc_76182DF4 ; CODE XREF: debug190:76182DB6j
debug190:76182DF4 MOV R1, R10
debug190:76182DF6 F0 F3 FA BL loc_761B73E0
debug190:76182DFA CMP R0, #
debug190:76182DFC DC DB BLT loc_76182DB8
debug190:76182DFE MOV R3, R10
debug190:76182E00 1B LDR R3, [R3]
debug190:76182E02 MOV R0, R11
debug190:76182E04 9A MOV R10, R3
debug190:76182E06 5B MOV R3, R11
debug190:76182E08 1B LDR R3, [R3]
debug190:76182E0A LDR R1, [SP,#0x14]
debug190:76182E0C 9A LDR R2, [SP,#0x18]
debug190:76182E0E DB LDR R3, [R3,#0xC]
debug190:76182E10 9C MOV R12, R3
debug190:76182E12 MOV R3, R10
debug190:76182E14 E0 BLX R12 ; 又是解密
debug190:76182E16 CMP R0, #
debug190:76182E18 CE DB BLT loc_76182DB8
debug190:76182E1A LDR R0, [SP,#0x14]
debug190:76182E1C F0 0E FC BL free_0
debug190:76182E20 5B MOV R3, R11
debug190:76182E22 1B LDR R3, [R3]
debug190:76182E24 MOV R0, R11
debug190:76182E26 5B LDR R3, [R3,#]
debug190:76182E28 BLX R3
debug190:76182E2A 9B LDR R3, [SP,#0xC]
debug190:76182E2C 4D ADD R5, R9
debug190:76182E2E ADD R3, R10
debug190:76182E30 1C MOVS R0, R3
debug190:76182E32 9D CMP R5, R3
debug190:76182E34 D0 BEQ loc_76182E42 ; 解密dex
debug190:76182E36 2E CMP R6, #
debug190:76182E38 D0 BEQ loc_76182E42 ; 解密dex
debug190:76182E3A 1C MOVS R1, R5
debug190:76182E3C 1C MOVS R2, R6
debug190:76182E3E FE F7 FF BL loc_76181CD4 //解密dex 头0x70字节 文件偏移 EE42
debug190 761DA000 R . X D . byte public CODE
debug190:76182E42 loc_76182E42 ; CODE XREF: sub_76182C48+1ECj
debug190:76182E42 ; sub_76182C48+1F0j
debug190:76182E42 7A LDR R2, [R7,#0x14] ; 解密dex
debug190:76182E44 MOV R0, R10
debug190:76182E46 MOVS R1, #0x70 ; 'p'
debug190:76182E48 F0 F8 BL sub_761BAF6C ; 解密dex头70字节
debug190:76182E4C MOVS R5, #
debug190:76182E4E B9 E7 B loc_76182DC4

这时就可以将DEX数据dump出来,其实后面每个onCreate中也会出现明文的DEX。

将dump出来的dex通过JDE反编译后发现很多Activity中的onCreate函数变成了Native了,从上面打印的类与对应函数可以看得出。

0x03:被Native后的onCreate分析, 尝试修复与猜想

在上面分析到注册Native函数时就对onCreate函数下好了断点,直接F9来到onCreate断下。

 //出现解密后dex 文件偏移 A910
debug098 75DC6000 75E2C000 R . X D . byte public CODE
debug098:75DD0910 loc_75DD0910 ; CODE XREF: debug098:75DD08F2j
debug098:75DD0910 9B LDR R3, [SP,#0x14]
debug098:75DD0912 1C MOVS R0, #0x1C
debug098:75DD0914 1F LDR R7, [R3] ; 出现解密后dex
debug098:75DD0916 B3 LDR R3, [R6,#]
debug098:75DD0918 9C MOV R12, R3
debug098:75DD091A ADD R7, R12

一直单步走到定位指令的地方。

 //定位到dex中的onCreate方法在内存中的指令,获取dex中的onCreate指令并解密。 文件偏移 2F9F8
debug098 75DC6000 75E2C000 R . X D . byte public CODE
debug098:75DF59F8
debug098:75DF59F8 loc_75DF59F8 ; CODE XREF: debug098:75DF5988j
debug098:75DF59F8 ; debug098:75DF59CAj
debug098:75DF59F8 A3 LDR R3, [R4,#]
debug098:75DF59FA ADDS R3, #0x10 ; 定位到onCreate指令
debug098:75DF59FC 1E 1C MOVS R6, R3
debug098:75DF59FE 0C STR R3, [SP,#0x30]
debug098:75DF5A00
debug098:75DF5A00 loc_75DF5A00 ; CODE XREF: debug098:75DF5E54j
debug098:75DF5A00 ; debug098:75DF5EF4j ...
debug098:75DF5A00 0C 9B LDR R3, [SP,#0x30]
debug098:75DF5A02 MOVS R1, #
debug098:75DF5A04 F0 1A SUBS R0, R6, R3
debug098:75DF5A06 ASRS R3, R0, #
debug098:75DF5A08 1C MOVS R0, R6
debug098:75DF5A0A STR R3, [SP,#0x18]
debug098:75DF5A0C FE F7 FE BL GetCode ; 获取dex中的onCreate指令并解密
debug098:75DF5A10 STR R0, [SP,#0x14]
debug098:75DF5A12 LSLS R0, R0, #0x18
debug098:75DF5A14 0E LSRS R0, R0, #0x18
debug098:75DF5A16 SUBS R0, # ; switch 255 cases
debug098:75DF5A18 FE CMP R0, #0xFE ; ' ;判断是否超出表示指令范围
debug098:75DF5A1A D9 BLS loc_75DF5A20
debug098:75DF5A1C F0 8D FF BL def_75DF5A20 ; jumptable 75DF5A20 default case

比如 下面是dex中OnCreate的指令,

读取上面的指令并解密。

 //获取指令,并解密 文件偏移 2E662
debug098 75DC6000 75E2C000 R . X D . byte public CODE
debug098:75DF4662 GetCode ; CODE XREF: debug098:75DF5A0Cp
debug098:75DF4662 ; debug098:75DF5E40p ...
debug098:75DF4662 B5 PUSH {R3-R5,LR}
debug098:75DF4664 1C MOVS R5, R0
debug098:75DF4666 0C 1C MOVS R4, R1
debug098:75DF4668 FD F7 D6 FC BL loc_75DF2018
debug098:75DF466C 7E LDRB R0, [R0,#0x18] ; 密钥
debug098:75DF466E LSLS R4, R4, #
debug098:75DF4670 1C MOVS R2, R0
debug098:75DF4672 LSLS R3, R0, #
debug098:75DF4674 1A ORRS R2, R3
debug098:75DF4676 5B LDRH R3, [R4,R5] ; 取指令
debug098:75DF4678 5A EORS R2, R3 ;解密
debug098:75DF467A 1C MOVS R0, R2
debug098:75DF467C BD POP {R3-R5,PC}

解密后判断指令类型,获取执行须要的数据,每一种操作码(OPCode)都对应有一个处理逻辑(是一段代码,但不一定是函数),然后跳转到对应的处理逻辑去处理它,简单说一个正常没加壳的指令格式。

71 20  06 00  02 00  invoke-static {v2, v0}, int aurora.view.AuroraTest.Test2(int, java.lang.String)

71操作码

2  参数个数

0006 method idx

02参数V2

00参数 V0

对应Android官方指令表

但是加壳后的指令被变成了自己定义的了,我第一次想法是想通过分析加壳前与加壳后指令对应关系,只要找到足够多的指令就能将其还原,我简单加了两个apk测试,从第一个中找到了如下的指令对应关系。

真实指令    自定义指令
6f =
6E =
0c = cc
1f = 9c
= 5a
= 9c
1a =
= 4B
= 0b
=
= 5b
5b = e4
= 5c
6f =
=
0e = 8e

将第一个apk中找到的指令来修复第二个指令,不知道是免费版的原因,还是巧合,我成功修复了第二个apk,但是当我用这个关系来尝试修复要破解的程序时根本不行,后来想了想,这种方法应当不行,都自己实现了解释器,为什么还要让指令固定呢?不科学呀,放弃了这种想法。

其实壳读取并解密一条指令后主要是使用JNI接口函数来实现解析执行。

如果指令类型是调用函数的话就会做如下动作,其它的调用其它接口(简单示例)

 jclass clazz=(*env)->FindClass(env,"com/example/test");
jmethodID methodId=(*env)->GetMethodID(env,clazz,"Add","(II)I");
// jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallIntMethod(env,jobject,methodId,,);

主要流程如下:

 //获取dex中的类名、函数名、签名  文件偏移 2E648
debug098 75DC6000 75E2C000 R . X D . byte public CODE
debug098:75DF4648
debug098:75DF4648 GetString ; CODE XREF: debug098:75DF4C52p
debug098:75DF4648 ; debug098:75DF4C78p ...
debug098:75DF4648 LDR R3, [R0,#0x10]
debug098:75DF464A LDR R2, [R0]
debug098:75DF464C LSLS R1, R1, #
debug098:75DF464E DB 6B LDR R3, [R3,#0x3C]
debug098:75DF4650 C9 ADDS R1, R1, R3
debug098:75DF4652 8B LDR R3, [R1,R2]
debug098:75DF4654 D0 ADDS R0, R2, R3
debug098:75DF4656
debug098:75DF4656 loc_75DF4656 ; CODE XREF: debug098:75DF465Ej
debug098:75DF4656 ADDS R0, #
debug098:75DF4658 1E SUBS R3, R0, #
debug098:75DF465A 1B LDRB R3, [R3]
debug098:75DF465C 7F 2B CMP R3, #0x7F ; ''
debug098:75DF465E FA D8 BHI loc_75DF4656
debug098:75DF4660 BX LR FindClass 文件偏移
debug098 75DC6000 75E2C000 R . X D . byte public CODE
debug098:75DDE956 sub_75DDE956 ; CODE XREF: debug098:75DF4CBAp
debug098:75DDE956 ; debug098:75DF5092p ...
debug098:75DDE956 B5 PUSH {R3,LR}
debug098:75DDE958 LDR R3, [R0]
debug098:75DDE95A 9B LDR R3, [R3,#0x18]
debug098:75DDE95C BLX R3 ; FindClass
debug098:75DDE95E BD POP {R3,PC} ExceptionCheck 文件偏移 1802E
debug096 75DC6000 75E2C000 R . X D . byte public CODE
debug096:75DDE02E loc_75DDE02E ; CODE XREF: debug096:75DDE046p
debug096:75DDE02E ; debug096:75DF4BECp ...
debug096:75DDE02E B5 PUSH {R3,LR}
debug096:75DDE030 E4 MOVS R3, #0xE4 ; '
debug096:75DDE032 LDR R2, [R0]
debug096:75DDE034 9B LSLS R3, R3, #
debug096:75DDE036 D3 LDR R3, [R2,R3]
debug096:75DDE038 BLX R3 ; ExceptionCheck
debug096:75DDE03A BD POP {R3,PC} //GetMethodID 文件偏移
debug098 75DC6000 75E2C000 R . X D . byte public CODE
debug098:75DFB11E loc_75DFB11E ; CODE XREF: debug098:75DFB10Ej
debug098:75DFB11E LDR R0, [SP]
debug098:75DFB120 LDR R1, [SP,#0x10]
debug098:75DFB122 9A LDR R2, [SP,#]
debug098:75DFB124 B0 BLX R6 ; GetMethodID
debug098:75DFB126 1C MOVS R6, R0
debug098:75DFB128 1C MOVS R0, R5 //DeleteLocalRef 文件偏移
debug096 75DC6000 75E2C000 R . X D . byte public CODE
debug096:75DDE018 loc_75DDE018 ; CODE XREF: debug096:75DDE062p
debug096:75DDE018 ; debug096:75DF528Ap ...
debug096:75DDE018 B5 PUSH {R3,LR}
debug096:75DDE01A LDR R3, [R0]
debug096:75DDE01C DB 6D LDR R3, [R3,#0x5C]
debug096:75DDE01E BLX R3 ; DeleteLocalRef
debug096:75DDE020 BD POP {R3,PC} //CallNonvirtualVoidMethodA 文件偏移 2EFEC
debug096 75DC6000 75E2C000 R . X D . byte public CODE
debug096:75DF4FEC loc_75DF4FEC ; CODE XREF: sub_75DF4B50:loc_75DF4F0Cj
debug096:75DF4FEC LDR R3, [R4] ; jumptable 75DF4F0C case 86
debug096:75DF4FEE 9A LDR R2, [SP,#0x108+var_100]
debug096:75DF4FF0 1C MOVS R0, R4
debug096:75DF4FF2 FC ADDS R3, #0xFC ; '
debug096:75DF4FF4 STR R2, [SP,#0x108+var_108]
debug096:75DF4FF6 9D 6F LDR R5, [R3,#0x78]
debug096:75DF4FF8 LDR R1, [SP,#0x108+var_F8]
debug096:75DF4FFA 9A LDR R2, [SP,#0x108+var_A8]
debug096:75DF4FFC 9B LDR R3, [SP,#0x108+var_FC]
debug096:75DF4FFE A8 BLX R5 ; CallNonvirtualVoidMethodA
debug096:75DF5000 4D E1 B def_75DF4F0C ; jumptable 75DF4F0C default case

通过上面的流程分析后,我猜想能不能hook或者用其它方法来监控JNI接口,得到对应的执行流程与调用了那那些JNI函数,在对应着Android  Dalvik bytecode指令表来反推出真实的指令,其实就是看一条指令执行完成后调用的JNI接口能不能确定是什么指令,目前还没有试,如果大牛们有什么好办法还请多多指教,目的是破解程序,静态分析己经可以了,先不考虑还原。(^_^)

来回顾下壳的主要流程:

壳主soà反调试à解压并解密第二个soà从主so中跳到第二个so中执行à第二人so注册native函数à解密原始DEXà程序跑起来后到Native onCreateà定位到自定义的指令à读取指令并解密à解析指令格式à获取执行指令须要的参数à调用JNI接口执行。

0x04:通过SO劫持跳过烦人的反调试快速到达要分析的函数

我想要是你玩过windows上破解都知道DLL劫持吧,先伪造一个同名的DLL,提供同样的输出函数,每个输出函数中转向真正的DLL函数,不错,我也是用这种方法来实现注入SO来Hook函数,过掉反调试,会省去很多时间反调试上面,直接到你想要分析的关键函数。

分析壳java层代码就知道它主要是通过读取从资源目中把壳so拷到指定目录中加载,

我的做法是hook getAssets().open()函数,将资源中的so替换成我的so,然后将壳so改名放在app-lib目录中给我的so加载。

 XposedHelpers.findAndHookMethod(
AssetManager.class, //被Hook函数所在的类
"open", //被Hook函数的名称
String.class,
new XC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param)
throws Throwable {
// Hook函数之前执行的代码
String path = param.args[].toString();
if(path.equals("libjiagu.so")){
param.args[] = "/data/local/tmp/libjiagu.so";
}
Yfw_DebugLog.d("open beforeHookedMethod--->0 "+param.args[].toString());
} @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { File file = new File(param.args[].toString());
InputStream input = new FileInputStream(file);
param.setResult(input);
Yfw_DebugLog.d("open afterHookedMethod--->"+param.getResult().toString()); }
}); So的代码也很简单:
/*
调用原始JNI_OnLoad
*/
jint LoadSo_InitJni(char* SoFilePath, JavaVM* vm, void* reserved)
{
jint ret;
void *pHandle = NULL;
pHandle = dlopen(SoFilePath, RTLD_LAZY);
if (NULL == pHandle)
{
LOGD("dlopen %s error!", SoFilePath);
return NULL;
}
_JNI_OnLoad old_JNI_OnLoad = NULL;
old_JNI_OnLoad = (_JNI_OnLoad)dlsym(pHandle, "JNI_OnLoad");
if (NULL == old_JNI_OnLoad)
{
LOGD("old_JNI_OnLoad error!");
return NULL;
}
LOGD("old_JNI_OnLoad address=%X env...%X", old_JNI_OnLoad, g_env);
ret = old_JNI_OnLoad(vm, reserved);
LOGD("old_JNI_OnLoad end...%d", ret);
return ret; } Hook open函数过反调试
int new_open(const char *pathname, int oflag, mode_t mode)
{
if (NULL == pathname)
{
goto exitret;
} LOGD("new_open..%s", pathname);
if (strstr(pathname, "net/tcp") != NULL)
{
return ;
}
if (strstr(pathname, "status") != NULL)
{
return ;
}
if (strstr(pathname, "stat") != NULL)
{
return ;
}
if (strstr(pathname, "schedstat") != NULL)
{
return ;
} exitret:
return old_open(pathname, oflag, mode);
}
Hook time过反调试
time_t new_time(time_t * timer) {
LOGD("new_time------->");
return ;
}
Hook dvmUseJNIBridge 方便在 native下断点,打印出对应类的方法
void new_dvmUseJNIBridge(Method* method, void* func)
{
LOGD("my_dvmUseJNIBridge class-%s ->name=%s address=%08X", method->clazz->descriptor, method->name, func);
//LOGD("my_dvmUseJNIBridge nativeFunc-%s", method->nativeFunc);
if ( == strcmp(method->name, "getClassNameList"))
{
sleep(); //等待附加调试器
}
return old_dvmUseJNIBridge(method, func);
}

这样当我们的so加载时用IDA附加上就可以在new_dvmUseJNIBridge下断点,就能在想要分析的Native函数上下断点了。

上面的代码就完成了整个劫持的过程,开心地调试吧!!!!!!

0x05:静态分析APP的注册验证流程与编写Xposed插件。

通过JEB反编译该应用dump出来的的classes.dex文件,直接搜索登录时用到的网址字符串 7658/api/entrance,找到如下的字符串,双击第一个字符串进去 。

其中的a函数是返回网址的,当参数为10时是要找到网址。

 public static String a(int arg1) {
switch(arg1) {
case : {
goto label_4; //返回登录地址
}
case : {
goto label_6;
}
case : {
goto label_8;
}
case : {
goto label_10;
}
case : {
goto label_12;
}
case : {
goto label_14;
}
case : {
goto label_16;
}
case : {
goto label_18;
}
case : {
goto label_20;
}
case : {
goto label_22;
}
}

按JEB快捷键 x 找到传入参数为10调用上面函数的地方,找到如下的类:

 public class g extends j {
private static String login;
private ThreadPoolExecutor d;
private static g e; static {
g.login = i.a();
g.e = null;
}
在这个类中找到了网络登录请求并解密返回值的函数。
private String b(h arg5, String arg6) {
String v0 = null;
if(arg5 != null && (arg6 != null && (AndroidHelper.isNetworkActive(this.b)))) {
String v1 = this.a(arg6, arg5.a()); // 组合请求参数
if(v1 == null) {
return v0;
} try {
JSONObject v2 = new JSONObject(a.a(g.login_address, v1.getBytes("UTF-8"), this.c)); // 传入网址,应该就是网络请求了
if(v2.getInt("status") != ) {
return v0;
} v0 = j.b(v2.getString("data")); // 解密登录返回值
}
catch(Exception v0_1) {
d.a(((Throwable)v0_1));
v0 = "server_error";
}
} return v0;
}

分析下请求参数都有些什么东西,组合请求参数的函数实现在父类j中。

  String a(String arg9, int arg10) {
String v0 = null;
if(arg9 != null) {
String v1 = j.a(this.c(arg9)); // aes加密后再base64
if(TextUtils.isEmpty(((CharSequence)v1))) {
return v0;
} try {
StringBuffer v2 = new StringBuffer();
v2.append("body=");
v2.append(URLEncoder.encode(v1, "UTF-8"));
v2.append("&t1=");
v2.append(System.currentTimeMillis() / );
v2.append("&t2=");
v2.append(this.d(arg9));
v2.append("&type=");
v2.append(arg10);
v2.append("&flag=");
v2.append();
v0 = v2.toString();
}
catch(UnsupportedEncodingException v1_1) {
d.a(((Throwable)v1_1));
}
} return v0;
}

用Xposed hook 加函数打印参数,

     //aes加密参数
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader cl = ((Context)param.args[]).getClassLoader();
Class<?> hookclass = null;
try {
hookclass = cl.loadClass("com.txyapp.client.j");
} catch (Exception e) {
Yfw_DebugLog.d("寻找com.txyapp.client.j 报错"+ e);
return;
}
Yfw_DebugLog.d("寻找com.txyapp.client.j成功"); XposedHelpers.findAndHookMethod(
hookclass, //被Hook函数所在的类
"a", //被Hook函数的名称
String.class,
new XC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param)
throws Throwable {
// Hook函数之前执行的代码
Yfw_DebugLog.d("a_Aesenc_data beforeHookedMethod--->0 "+param.args[].toString());
} @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Yfw_DebugLog.d("a_Aesenc_data afterHookedMethod--->"+param.getResult().toString());
}
});
}
});

主要是将用户名密码,还有一些机器信息aes与base加密后请求过去,格式如下:

{"data":"{\"clientid\":\"7e7884f241c96051958cb776d6f780d9\",\"password\":\"xxxxxxxxxx\",\"username\":\"crack8888\"}","header":{"model":"Nexus 5","root":,"mcc":"","uptime":,"sysapi":,"corever":,"vername":"13.1.2","un":"","cn":"guanfang","cl":"zh-CN","brand":"google","mnc":"","uuid":"7FDC501E4B3xxxxx","cpuabi1":"armeabi-v7a","cpuabi2":"armeabi","vercode":}}

然后服务器返回一些加密后的数据,从上面的g类b函数中可以看出,判断如果请求成功,就解密返回数据,通过hook该解密函数得到解密后的数据格式为json

     XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader cl = ((Context)param.args[]).getClassLoader();
Class<?> hookclass = null;
try {
hookclass = cl.loadClass("com.txyapp.client.j");
} catch (Exception e) {
Yfw_DebugLog.d("寻找com.txyapp.client.j 报错"+ e);
return;
}
Yfw_DebugLog.d("寻找com.txyapp.client.j成功");
//解密vip
XposedHelpers.findAndHookMethod(
hookclass, //被Hook函数所在的类
"b", //被Hook函数的名称
String.class,
new XC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param)
throws Throwable {
// Hook函数之前执行的代码
Yfw_DebugLog.d("b_Aesdec_data beforeHookedMethod--->0 "+param.args[].toString());
} @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Yfw_DebugLog.d("b_Aesdec_data afterHookedMethod--->"+param.getResult().toString()); JSONObject jsonObj = new JSONObject(param.getResult().toString());
if( == (int)jsonObj.get("vip") ){
jsonObj.put("vip", );
jsonObj.put("expiretime", );// 2033-05-07 Yfw_DebugLog.d("b_Aesdec_data result--->"+jsonObj.toString());
viptag = ;
param.setResult(jsonObj.toString());
} }
});
}
});
//解密后数据
{"status":,"token":"49436a44d85f46c7e458a6b071af470a","username":"crack8888","vip":,"expiretime":}

从上面格式可以看出 vip:=0代表不是vip, expiretime:到期时间,所以我的在hook函数中将这两个参数修改如下。

{"vip":,"username":"crack8888","token":"db9ea6e12936463d722fb7bcda7e97ab","status":,"expiretime":}

这样就成功破解了,会员功能也正常使用。

还有一个地方就是显示个人信息,hook函数进行修改

     //刷新个人信息显示
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader cl = ((Context)param.args[]).getClassLoader();
Class<?> hookclass = null;
try {
hookclass = cl.loadClass("com.txy.anywhere.activity.login.PersonalInfoActivity");
} catch (Exception e) {
Yfw_DebugLog.d("寻找com.txy.anywhere.activity.login.PersonalInfoActivity报错"+ e);
return;
}
Yfw_DebugLog.d("寻找com.txy.anywhere.activity.login.PersonalInfoActivity成功");
//判断是否为vip
XposedHelpers.findAndHookMethod(
hookclass, //被Hook函数所在的类
"a", //被Hook函数的名称
String.class,
new XC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param)
throws Throwable {
// Hook函数之前执行的代码
Yfw_DebugLog.d("login beforeHookedMethod--->0 "+param.args[].toString()); JSONObject container1 = new JSONObject(); JSONObject v0_1 = new JSONObject(param.args[].toString());
JSONObject v0_2 = new JSONObject(param.args[].toString());
v0_2 = v0_1.getJSONObject("user_info");
v0_2.put("vip", );//是否为vip 0:为普通会员, 1:为vip
v0_2.put("expire_time", );//到期时间
v0_2.put("phonenumber", "");
v0_1.put("user_info", v0_2);
param.args[] = v0_1.toString();
Yfw_DebugLog.d("login beforeHookedMethod 1--->0 "+param.args[].toString()); } @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { Yfw_DebugLog.d("login afterHookedMethod--->");
}
});
}
});

到这里就算破解完成了,简单测试了会员功能,都能正常使用。

0x06:总结

由于水平有限,未能更好的分析与表达,不过,通过前面几部分的简单分析,其实我们已经知道只是Native onCreate函数是不够的,主要还是得保护好关键函数不被分析。

百度网盘 链接: https://pan.baidu.com/s/1c2KiYsg 密码: 2rft

某地理位置模拟APP从壳流程分析到破解的更多相关文章

  1. 移动应用/APP的测试流程及方法

    1. APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日),根据项目情况以及版本质量可适当缩短或延长测试时间.正式测试前先 ...

  2. APP测试基本流程以及APP测试要点

    APP测试流程梳理 APP测试要点梳理 链接:http://pan.baidu.com/s/1gfaEZ1x 密码:07yt 1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开 ...

  3. App测试基本流程详解

    1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日),根据项目情况以及版本质量可适当缩短或延长测试时间. 1.3测试资 ...

  4. Android FART脱壳机流程分析

    本文首发于安全客 链接:https://www.anquanke.com/post/id/219094 0x1 前言 在Android平台上,程序员编写的Java代码最终将被编译成字节码在Androi ...

  5. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  6. [Android]从Launcher开始启动App流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...

  7. SQL Server中CURD语句的锁流程分析

    我只在数据库选项已开启“行版本控制的已提交读”(READ_COMMITTED_SNAPSHOT为ON)中进行了观察. 因此只适用于这种环境的数据库. 该类数据库支持四种不同事务隔离级别,下面分别观察数 ...

  8. openstack之nova-api服务流程分析

    nova-api公布api服务没实用到一个些框架,基本都是从头写的.在不了解它时,以为它很复杂,难以掌握.花了两三天的时间把它分析一遍后,发现它本身的结构比較简单,主要难点在于对它所使用的一些类库不了 ...

  9. Android7.0 Phone应用源码分析(四) phone挂断流程分析

    电话挂断分为本地挂断和远程挂断,下面我们就针对这两种情况各做分析 先来看下本地挂断电话的时序图: 步骤1:点击通话界面的挂断按钮,会调用到CallCardPresenter的endCallClicke ...

随机推荐

  1. Windows多个应用程序共享全局变量,静态变量

    默认情况下exe不同实例使用copy-on-write技术避免共享数据,比如运行了两个exe,最开始它们使用的都是一份虚拟内存页,然后第一个实例修改了全局变量, 这时候COW就会复制那一页,然后将第一 ...

  2. [SDOI2009]HH的项链 树状数组 BZOJ 1878

    题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义.HH 不断地收集新的贝壳,因此,他的项链 ...

  3. Hibernate上传数据到数据库,从数据库读取数据到本地模板代码

    1.Hibernate上传数据到数据库: //创建一个session对象 Session session1=HibernateTools.getSession(); //Fenciresult数据库表 ...

  4. P1445 [Violet]樱花

    传送门 看到题目就要开始愉快地推式子 原式 $\frac{1}{x}+\frac{1}{y}=\frac{1}{n!}$ $\rightarrow \frac{x+y}{xy}=\frac{1}{n! ...

  5. 小程序自定义modal弹窗封装实现

    前言小程序官方提供了 wx.showModal 方法,但样式比较固定,不能满足多元化需求,自定义势在必行~ 老规矩先上图 点击某个按钮,弹出 modal框,里面的内容可以自定义,可以是简单的文字提示, ...

  6. 7.Hibernate 检索

    1.Hibernate检索方式 检索方式简介: 导航对象图检索方式:根据已经加载的对象,导航到其他对象.OID检索方式:按照对象的OID来检索对象.Session 的 get() 和 load() 方 ...

  7. 惠普台式机在UEFI BIOS设置通电自动开机 影响电脑自动重启关不了机设置

    设置通电自动开机 影响电脑自动重启关不了机设置   惠普台式机在UEFI BIOS中 1. 开机时不断点击F10键进入BIOS,选择Advanced(高级)然后选择Boot Options,点击回车 ...

  8. Vue.js递归组件实现动态树形菜单

    使用Vue递归组件实现动态菜单 现在很多项目的菜单都是动态生成的,之前自己做项目也是遇到这种需求,翻看了官网案例,和网上大神的案例.只有两个感觉,官网的案例太简洁,没有什么注释,看起来不太好理解,大神 ...

  9. java——极简handler机制

    handler机制要做的事情: 1.把一堆从四面八方传来的message加到一个队列中,这个队列就是MessageQueue. 2.将MessageQueue中的队头Message取出,并使用这个me ...

  10. java——红黑树 RBTree

    对于完全随机的数据,普通的二分搜索树就很好用,只是在极端情况下会退化成链表. 对于查询较多的情况,avl树很好用. 红黑树牺牲了平衡性,但是它的统计性能更优(综合增删改查所有的操作). 红黑树java ...