Notes-Android-II

NDK汇编(开发)

基础配置

https://www.jianshu.com/p/c546783ad284

idea安装cmake和NDK
1、ubuntu自动加进环境变量
2、windows要自己添加

source ~/.bashrc

1
ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk

IDA远程调试:

1
2
1、把 IDA dbgsrv目录下的文件在手机执行 android_server 
2、用32位IDA Debugger调试程序

elf文件

arm 模式:定长指令集,四个字节

thumb 模式:不定长

T标志位

clang 开发

https://developer.android.com/ndk/guides/other_build_systems?hl=zh-cn

https://www.cnblogs.com/burner/p/clang-fen-si-bu-bian-yimainc.html

clang路径在SDK/NDK下

1
2
C:\Users\admin\AppData\Local\Android\Sdk\ndk\28.0.12674087\toolchains\llvm\prebuilt\windows-x86_64\bin
C:\Users\admin\AppData\Local\Android\Sdk\ndk\28.0.12674087\toolchains\llvm\prebuilt\linux-x86_64\bin

GDB

安装GEF

GDB multiarch 远程调试,原理同IDA远程调试,需上传服务端文件

1
2
3
4
5
6
7
8
Sdk/ndk/23.1.7779620/prebuilt/android-arm/gdbserver
./gdbserver :1234 hello.o

gdb-multiarch
target remote ip:port

gdb-multiarch
gef-remote -p 3929 ip:port #雷电需要用x86_64版本,提示权限不足

ARM 案例

案例一

在 .o文件里用汇编代替原有函数(print、getchar)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#printf
#只有return 0的汇编
main:
.fnstart
@ %bb.0:
.pad #4
sub sp, sp, #4
movw r0, #0
str r0, [sp]
movw r0, #0
add sp, sp, #4
bx lr


main:
.fnstart
push {lr}

ldr r0,[r1,#4]
bl printf

movw r0, #0
pop {lr}
bx lr

案例二

根据汇编还原为C代码

1、IDA打开v7a的 SO文件

2、找到导出 check

3、修改第一个默认参数为 JNIEnv,这样就能关联到内置的函数

4、算法还原

这些R0 、R1指代第一第二个参数吗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
TestDec                                 ; CODE XREF: j_TestDec+8↑j
.text:00001062 ; DATA XREF: LOAD:00000250↑o ...
.text:00001062 ; __unwind {
.text:00001062 PUSH {R4,R5,R7,LR}
.text:00001064 ADD R7, SP, #8
.text:00001066 MOV R4, R0
.text:00001068 BLX strlen
.text:0000106C CMP R0, #2
.text:0000106E BCC loc_108A
.text:00001070 MOVS R5, #0
.text:00001072
.text:00001072 loc_1072 ; CODE XREF: TestDec+26↓j
.text:00001072 ADDS R1, R4, R5
.text:00001074 LDRB R0, [R4,R5]
.text:00001076 LDRB R2, [R1,#0x10]
.text:00001078 STRB R2, [R4,R5]
.text:0000107A ADDS R5, #1
.text:0000107C STRB R0, [R1,#0x10]
.text:0000107E MOV R0, R4 ; s
.text:00001080 BLX strlen
.text:00001084 CMP.W R5, R0,LSR#1
.text:00001088 BCC loc_1072
.text:0000108A
.text:0000108A loc_108A ; CODE XREF: TestDec+C↑j
.text:0000108A LDRB R0, [R4]
.text:0000108C CBZ R0, locret_10B8
.text:0000108E LDRB R1, [R4,#1]
.text:00001090 STRB R1, [R4]
.text:00001092 STRB R0, [R4,#1]
.text:00001094 MOV R0, R4 ; s
.text:00001096 BLX strlen
.text:0000109A CMP R0, #3
.text:0000109C BCC locret_10B8
.text:0000109E MOVS R5, #0
.text:000010A0
.text:000010A0 loc_10A0 ; CODE XREF: TestDec+54↓j
.text:000010A0 ADDS R0, R4, R5
.text:000010A2 LDRB R1, [R0,#2]
.text:000010A4 LDRB R2, [R0,#3]
.text:000010A6 STRB R2, [R0,#2]
.text:000010A8 STRB R1, [R0,#3]
.text:000010AA MOV R0, R4 ; s
.text:000010AC BLX strlen
.text:000010B0 ADDS R1, R5, #4
.text:000010B2 ADDS R5, #2
.text:000010B4 CMP R1, R0
.text:000010B6 BCC loc_10A0
.text:000010B8
.text:000010B8 locret_10B8 ; CODE XREF: TestDec+2A↑j
.text:000010B8 ; TestDec+3A↑j
.text:000010B8 POP {R4,R5,R7,PC}
.text:000010B8 ; End of function TestDec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int TestDec(char *str){
char *r4 = str;
int lenstr = strlen(str);
if(lenstr>=2){
for(int r5=0;r5< lenstr>>1;r5++){
char * r1 = r4+r5;
char r0 = *(r4+r5);
char r2 = *(r1+16);
*(r4+r5) = r2;
*(r1+16) = r0;
}
}
char r0 = *(r4);
if(r0){
char r1 = *(r4+1);
*(r4) = r1;
*(r4+1) = r0;
if(lenstr >= 3){
for(int r5=0; r5+4 < lenstr;r5+=2){
char *rr0 = r4+r5;
char rr1 = *(rr0+2);
char rr2 = *(rr0+3);
*(rr0+2) = rr2;
*(rr0+3) = rr1;
}
}
}
printf("%s\n",r4);
return 0;
}

int main(){
char *s = "9007b55be5bf95adf72c5a365694182a";
int lenstr = strlen(s);
char *arg = (char*)malloc(lenstr+1);
memset(arg,0,lenstr+1);
memcpy(arg,s,lenstr);
TestDec(arg);
return 0;
}

可引入IDA 提供的 defs.h 后直接运行伪代码

https://www.52pojie.cn/forum.php?mod=viewthread&tid=1584115&highlight=ida

IDA快捷键&常用方法

  • 汇编 & 伪代码的跳转(空格、Tab)
  • 强制当做代码/数据(C、D)
  • 数据 convert
  • 重命名
  • 查找调用链,Xref graph to
  • 插件使用

https://blog.csdn.net/qq_41028985/article/details/119407917

熟悉算法

用于识别魔改算法或分析算法

输入信息/输出信息要hex编码

md5算法

SHA1算法

SHA2/256/512等后续一堆

HMAC(salt)

  • 自行根据字符串计算hmac对照

https://space.bilibili.com/253413704

1
2
3
message:加密消息
key:加密密钥(需要补充)
opad 和 ipad 都是固定

DES

1
2
3
4
5
https://www.bilibili.com/video/BV1134y1Y71j
https://www.bilibili.com/video/BV1QW411B7A4
https://www.bilibili.com/video/BV1KQ4y127AT
https://juejin.cn/post/7084242293816983589
https://cloud.tencent.com/developer/article/1497864

https://www.cnblogs.com/jikexianfeng/p/10192024.html

CLion

分组加密

https://blog.csdn.net/a745233700/article/details/102311776

https://segmentfault.com/a/1190000040964999

https://www.bilibili.com/video/BV1U8411f74f/

AES

https://www.bilibili.com/video/BV1824y1y7gc

12345678,8个字节,64个位

APK未抹去符号表,MD5算法是否魔改分析

1、提取 so,用IDA分析

2、根据原MD5的几个关键步骤,分析汇编。搜索固定的表内容,没被魔改可以搜到

3、Hook SO地址

MD5常见魔改点:

1、明文加盐或填充

2、初始魔数的修改

3、常量表K

4、左移次数

5、F,G,H,I四个非线性变换函数等的逻辑

unidbg

IDA快捷键:H、Y、Tab

IDA 动态调试之反调试

遗留问题:虚拟机debuggable=1

1、getprop ro.debuggable = 0 的话 IDA 动态调试看不到进程名,部分功能被屏蔽

真机用:agiskHide Props Config 修改成功

虚拟机暂未成功

2、查看线程名要用新版的IDA,实测7.0的看不到,7.7的就看得到

1
2
3
4
5
6
1.Attach模式 附加 so动态调试。过掉反调试的方法
2.Spwan模式 过掉反调试的方法:这里讲原理

1. nop掉
2. 挂起线程
3. frida hook

IDA调试tips:

1
2
3
4
5
6
1、真机无网络通过USB调试,需配置端口转发才能IDA远程调试
adb forward tcp:23946 tcp:23946 本机->设备
adb reverse tcp:23946 tcp:23946 设备->本机
adb forward --remove tcp:<port>

2、静态调试和动态调试,IDA自动化分析的注释不同

步骤:

1
2
3
4
1、进入IDA动态调试,点击运行崩溃
2、在Threads窗口,找和包名一样的线程,Suspended 挂起运行正常
3、在Modules窗口,搜索包名/运行关键词/check/security,找到检测/校验模块,双击进入对应代码,下断点
4、点击触发,Tab切换伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var ByPassTracerPid = function () {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = Memory.readUtf8String(buffer);
if (bufstr.indexOf("TracerPid:") > -1) {
Memory.writeUtf8String(buffer, "TracerPid:\t0");
console.log("tracerpid replaced: " + Memory.readUtf8String(buffer));
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']));
};
setImmediate(ByPassTracerPid);

参考文章:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Android修改ro.debuggable 的四种方法
https://blog.csdn.net/jinmie0193/article/details/111355867

IDA动态调试破解AliCrackme与反调试对抗
https://blog.csdn.net/weixin_39190897/article/details/120808079

[原创]2015年AliCrackMe第二题的分析之人肉过反调试
https://bbs.pediy.com/thread-258595.htm

常用命令:
adb shell am start -D -n com.yaotong.crackme/.MainActivity
adb shell ps | findstr crackme
jdwp协议端口转发:adb forward tcp:8700 jdwp:2494
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

APP过ROOT检测

安装面具:
https://www.bilibili.com/video/BV1UN4y137Z5/?vd_source=43c2c404de6d798650d44c856ee1e992
https://bbs.kanxue.com/thread-263203.htm

修改固有面具(修改源码)

https://blog.csdn.net/qq_41155858/article/details/127885048

root检测:
1.在代码中检测 -> hook/修改重打包(代码固有API检测是否root?)
2.检测系统属性(检测app安装的系统环境参数)
3.查找字符串(查找root关键词,比如su)

对抗方法:
1.hook
2.最新版magisk(配置排除列表、随机文件名) + shamiko(可有可无)+随机magisk
3.需要重新编译面具 改su名称(这个有点麻烦,最新版gsyh过不去就这个)

1
2
3
4
小米6重装最新版Magisk就可以了,直接过三个(新版的gs只开还是过不了)
adb reboot bootloader
https://magiskcn.com/
https://blog.csdn.net/qq_42663692/article/details/135704736

绕过的具体操作
1、最新版面具
2、修改面具名称
3、用自带的隐藏root模块
4、修改su名称

Tips:
1、面具安装在真机,需要提取真机boot,通过面具修复后生成.img文件,再重新刷回手机
2、面具安装在模拟器,要安装修改的magisk-delta版本,有直接刷在系统选项(https://magisk-delta.com/)

自编译面具

环境:win10

1
2
3
4
5
6
7
8
1、用AS自带的 java11,设置为环境变量
2、全程挂梯子
3、windows 用旧版 python,如python3.8

git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git
python build.py ndk
python build.py clean
python build.py -v all

需要修改成自定义指令的地方

1
2
val isActive = versionCode > 0 追踪逻辑
"su","--mount-master"

Apktoolbox 重打包

windwos坑太多,用ubuntu试试,用ubuntu挂梯子

参考链接:

1
2
https://blog.csdn.net/qq_41155858/article/details/127885048?spm=1001.2014.3001.5506
https://zhuanlan.zhihu.com/p/385255256

frida 反调试

1
2
3
4
5
6
7
8
9
10
11
12
13
参考文章: [翻译]多种特征检测 Frida ->https://bbs.pediy.com/thread-217482.htm

从inlinehook角度检测frida->https://blog.csdn.net/u010559109/article/details/120846740
9
Android逆向——过frida检测+so层算法逆向https://blog.csdn.net/weixin_43889136/article/details/127713563


原理:当被调试器附加之后 不为0:https://wiki.hackjie.com/2728.html

ptrace占坑:
https://bbs.pediy.com/thread-268155.htm#msg_header_h2_16

adb shell dumpsys window w |grep \/ |grep name=

fridacheck app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 为什么这样就可以hook,不输入脚本?还是原先有脚本?只是绕过-UF?
frida -U -f xxx
%resume

ps -e | grep xxx
adb shell dumpsys window w | grep \/ | grep name=

# 修改端口、修改文件名(进程名)
./frida-server-pass -l 0.0.0.0:1234
adb forward tcp:1234 tcp:1234
frida -H 手机ip:1234 -F

hook_RegisterNatives.js
https://github.com/zhkl0228/unidbg
# 获取的偏移值可直接在IDA g搜索,此时是加载状态吗
修改第一个参数为 JNIEnv*

# 线程检测
通过IDA分析所用检测的方法,根据检测逻辑去hook fgets、fopen,为什么直接hook整个检测函数

function hook_readlink() {}
function main(){
hook_readlink()
}
setImmediate(main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// code.js
function hook_strstr() {
// char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符
var strstr = Module.findExportByName(null, "strstr");
if (null !== strstr) {
Interceptor.attach(strstr, {
onEnter: function (args) {
this.frida = Boolean(0);
this.haystack = args[0];
this.needle = args[1];
if (this.haystack.readCString() !== null && this.needle.readCString() !== null) {
if (this.haystack.readCString().indexOf("frida") !== -1 ||
this.needle.readCString().indexOf("frida") !== -1 ||
this.haystack.readCString().indexOf("gum-js-loop") !== -1 ||
this.needle.readCString().indexOf("gum-js-loop") !== -1 ||
this.haystack.readCString().indexOf("gmain") !== -1 ||
this.needle.readCString().indexOf("gmain") !== -1 ||
this.haystack.readCString().indexOf("linjector") !== -1 ||
this.needle.readCString().indexOf("linjector") !== -1) {
this.frida = Boolean(1);
}
console.log("this.haystack.readCString():",this.haystack.readCString())
console.log("this.needle.readCString():",this.needle.readCString())
}
},
onLeave: function (retval) {
if (this.frida) {
retval.replace(ptr("0x0"));
}

}
})
console.log("anti anti-frida");
}

console.log("hook strstr over")
}

function main(){
hook_strstr();
}

setImmediate(main);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function bypass_fgets() {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
console.log(fgetsPtr,fgets);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = buffer.readCString();
if (bufstr.indexOf("TracerPid:") > -1) {
Memory.writeUtf8String(buffer, "TracerPid:\t0");
}

if (bufstr.indexOf("Name:\tgmain") > -1 || bufstr.indexOf("Name:\tgdbus") > -1 || bufstr.indexOf("Name:\tpool-frida") > -1 || bufstr.indexOf("Name:\tgum-js-loop") > -1 || bufstr.indexOf("SigBlk:\tffffffe0fffbfaff") > -1) {
Memory.writeUtf8String(buffer, "TName:\t1");
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']))
}

function hook_fopen() {
var open_addr = Module.findExportByName("libc.so", "fopen")
var io_map = Memory.allocUtf8String("/proc/13585/maps");
Interceptor.attach(open_addr, {
onEnter: function (args) {
if (args[0].readCString().indexOf("/maps") != -1) {
args[0] = io_map
}
this.pathname = args[0]
this.mode = args[1]
},
onLeave: function (retval) {
console.log("fopen pathname=" + this.pathname.readCString() + "---mode=" + this.mode)
}
})
}


function hook_readlink() {
var aaa, bbb, ccc;
Interceptor.attach(Module.findExportByName(null, "readlink"), {
onEnter: function (args) {
aaa = args[0];
bbb = args[1];
ccc = args[2];
},
onLeave: function (retval) {
// console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
if (bbb.readCString().indexOf("frida") !== -1 ||
bbb.readCString().indexOf("gum-js-loop") !== -1 ||
bbb.readCString().indexOf("gmain") !== -1 ||
bbb.readCString().indexOf("tmp") !== -1 ||
bbb.readCString().indexOf("linjector") !== -1) {
console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
bbb.writeUtf8String("/system/framework/boot.art")
console.log("replce with: "+bbb.readCString())
retval.replace(0x1A)
}
}
});
}

function main(){
bypass_fgets()
hook_fopen()
hook_readlink()
}

setImmediate(main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// hook_anti-thread.js
function hook_pthread_create() {
var pthread_create_addr = Module.findExportByName(null, "pthread_create");
const __android_log_print_ptr = Module.findExportByName(null, '__android_log_print')
const cm = new CModule(`
#include <gum/guminterceptor.h>
extern void onMessageStr (const gchar * message);
extern void onMessagePtr (void * message);
extern void onMessageInt (int a);
extern int __android_log_print(int prio, const char* tag, const char* fmt, ...);
void hello(){
}
void onEnter (GumInvocationContext * ic)
{
// void* arg2,arg3;
if((((int)gum_invocation_context_get_nth_argument(ic, 2))&0xfff)==0x599){
onMessageStr("replace success");
gum_invocation_context_replace_nth_argument(ic,2,(gpointer)hello);
}
// arg2 = gum_invocation_context_get_nth_argument (ic, 2);
onMessagePtr(gum_invocation_context_get_nth_argument (ic, 2));
// arg0 = (int)gum_invocation_context_get_nth_argument (ic, 2);
// gum_invocation_context_replace_nth_argument(ic,2,(gpointer)100);
// arg1 = (int)gum_invocation_context_get_nth_argument (ic, 3);
// log ("function add arg0=%d arg1=%d ", arg0,arg1);
// __android_log_print(3,"isDebug","arg0=%d,arg1=%d",arg0,arg1);
}
void
onLeave (GumInvocationContext * ic)
{
int result;
result = (int) gum_invocation_context_get_return_value (ic);
onMessageInt(result);
// gum_invocation_context_replace_return_value (ic,(gpointer)100);
}`, {
__android_log_print: __android_log_print_ptr,
onMessageStr: new NativeCallback(messagePtr => {
const message = messagePtr.readUtf8String();
console.log('onMessageStr:', message);
}, 'void', ['pointer']),
onMessagePtr: new NativeCallback(messagePtr => {
console.log('onMessagePtr:', messagePtr ,hexdump(messagePtr));
}, 'void', ['pointer']),
onMessageInt: new NativeCallback(messageInt => {
console.log('onMessageInt:', messageInt);
}, 'void', ['int']),
});
Interceptor.attach(pthread_create_addr, cm);
}

function replace_thread() {
var pthread_create_addr = Module.findExportByName(null, "pthread_create");
var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
Interceptor.replace(pthread_create_addr, new NativeCallback((parg0, parg1, parg2, parg3) => {
var so_name = Process.findModuleByAddress(parg2).name;
var so_base = Module.getBaseAddress(so_name);
var offset = (parg2 - so_base);
var PC = 0;
console.log("normal find thread func offset", so_name, parg2,offset, offset.toString(16));
// i加密
if(
(so_name.indexOf("libexec.so")>-1 && offset===197069)||
(so_name.indexOf("libexec.so")>-1 && offset===196137)
){

}else if((so_name.indexOf("libDexHelper.so")>-1 && offset===684452)||
(so_name.indexOf("libDexHelper.so")>-1 && offset===724380)){

}
else{
PC = pthread_create(parg0, parg1, parg2, parg3);
}
return PC;
}, "int", ["pointer", "pointer", "pointer", "pointer"]));
}

setImmediate(replace_thread)

// frida -U -f com.darvin.security -l hook_anti-thread.js --no-pause -o out.log
// frida -UF -l hook_anti-thread.js


//frida -U -f com.gzlex.hui.guoziwei.travel --no-pause -l hook_anti-thread.js
//frida -U -f tv.danmaku.bili -l hook_anti-thread.js --no-pause

1、hook app调用的各关键内置函数的调用 so和偏移值

2、针对每次调用进行功能hook,多个进行hook,筛选关键功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
https://www.jianshu.com/p/c546783ad284
https://developer.android.com/ndk/guides/other_build_systems?hl=zh-cn
https://www.cnblogs.com/burner/p/clang-fen-si-bu-bian-yimainc.html 编译博客也不错
https://blog.csdn.net/qq_31865983/article/details/89402100 这个更加完整一点
https://github.com/hugsy/gef
https://www.likecs.com/show-204245381.html#sc=400
Cortex-A7 常用汇编指令资料:
https://www.cnblogs.com/iron2222/p/15640269.html

gdb调试器快速上手教程
https://blog.csdn.net/qq_45953886/article/details/127678876

ARM官方汇编指令
https://blog.csdn.net/oqqHuTu12345678/article/details/125683244

ARM汇编基础
https://www.cnblogs.com/hilfloser/p/10516610.html

ARM关于标志位影响详解
https://blog.csdn.net/tabactivity/article/details/90407858

ARM的九种寻址方式
https://blog.csdn.net/qq_43743762/article/details/105056991

### 5.编写第一个汇编程序

> ​ https://www.w3cschool.cn/c/c-file-io.html

参考资料:https://softool.cn/read/arm_assembly_basic_aye/21010203.html

https://blog.csdn.net/qq_41028985/article/details/119407917

### 6.逆向C程序-数据类型

> 计算器:
> https://www.bangnishouji.com/tools/224457.html

##### 1.32位浮点数表示方法:

> 二进制数怎么转化为浮点数?阶码又是怎么算出来的?
> https://zhidao.baidu.com/question/750285508423217012.html

算法篇:ios和安卓通用

参考连接:
https://www.cnblogs.com/xiaxveliang/p/15004954.html
https://blog.csdn.net/sinat_27933301/article/details/79538169
https://www.cnblogs.com/DeeLMind/p/7581423.html
https://www.bilibili.com/video/BV1aP411M7ug:强推

https://www.bilibili.com/video/BV1V14y1T7LM
https://blog.csdn.net/ZCShouCSDN/article/details/84675235