动态库注入app以及在非越狱手机使用
动态库注入app以及在非越狱手机使用
1. 动态库编写
动态库编写有多种方式,可以使用Xcode创建动态库,也可以通过tweak生成动态库
- 对于越狱手机,可以直接编写tweak,将tweak打包成动态库
- 对于非越狱手机,可以使用Xcode创建动态库,在Xcode中编写hook代码,生成动态库
第一种,在越狱手机上编写tweak,打包成动态库
1. 安装Xcode,这个就不多说了,在苹果官网下载安装即可
2. tweak环境的安装及编写请看这个
第一个tweak生成后,我们来hook一下之前新建的一个demo, 可以在我的github上面下载到。
@implementation FirstTweakViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.self.view.backgroundColor = [UIColor whiteColor];self.title = @"FirstTweakDemo";
}- (void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[self showAlert];});
}- (void)showAlert {UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是原始弹窗" preferredStyle:(UIAlertControllerStyleAlert)];UIAlertAction * action = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:nil];[alert addAction:action];[self presentViewController:alert animated:YES completion:nil];
}@end
demo的主要内容是在控制器的ViewDidAppear方法中延时两秒弹出一个弹窗,我们现在来一下这个逻辑,hook住弹出弹窗的方法,然后弹出一个actionSheet.
3. 开始编写hook代码
回到我们生成的Tweak目录,发现四个文件
- control
- FirstTweak.plist
- Makefile
- Tweak.xm
hook的代码在Tweak.xm中编写,在xcode中打开Tweak.xm,logos语法参见Wiki. 编写如下代码
#import <UIKit/UIKit.h>@interface FirstTweakViewController: UIViewController- (void)showAlert;@end
%hook FirstTweakViewController- (void)showAlert {UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是hook后的提醒" preferredStyle:(UIAlertControllerStyleActionSheet)];UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"Cancel" style:(UIAlertActionStyleCancel) handler:nil];UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"Confirm" style:(UIAlertActionStyleDestructive) handler:nil];[alert addAction:cancel];[alert addAction:confirm];[self presentViewController:alert animated:YES completion:nil];
}%end
检查无误后进行配置,打开FirstTweak.plist
, 替换bundleId为我们想要hook的App的bundleId
NH.FirstTweakDemo
4. 安装tweak到手机
然后我们将22端口转发到22222
iproxy 22222 22
回到终端,cd到tweak目录, 连接我们的越狱手机
$ make package install
如果报错,缺少THEOS_DEVICE_IP
,在终端输入如下
export THEOS_DEVICE_IP=localhost:22222
再次执行make package install
结果如下:
$ make package install
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
> Making all for tweak FirstTweak…
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
make[2]: Nothing to be done for `internal-library-compile'.
> Making stage for tweak FirstTweak…
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
dm.pl: building package `com.yourcompany.firsttweak:iphoneos-arm' in `./packages/com.yourcompany.firsttweak_0.0.1-3+debug_iphoneos-arm.deb'
==> Installing…
root@localhost's password:
Selecting previously unselected package com.yourcompany.firsttweak.
(Reading database ... 2140 files and directories currently installed.)
Preparing to unpack /tmp/_theos_install.deb ...
Unpacking com.yourcompany.firsttweak (0.0.1-3+debug) ...
Setting up com.yourcompany.firsttweak (0.0.1-3+debug) ...
install.exec "killall -9 SpringBoard"
root@localhost's password:
输入两次密码后,成功安装到手机。 SpringBoard重启后打开FirstTweakDemo,点击Push,进入FirstTweakViewController控制器,等待两秒后可以看到。
成功的hook住了!!!
那么问题来了,这是给越狱设备使用的,如何给未越狱设备使用呢?
5. 将tweak生成的动态库注入app中,实现非越狱设备安装
FirstTweak.dylib依赖替换
回到tweak目录,可以看到,有个.theos
目录,找到.theos/_/Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib
文件,这是tweak打包后生成的动态库
用otool查看动态库的依赖情况
$ otool -l FirstTweak.dylib | grep namename /Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib (offset 24)name /usr/lib/libobjc.A.dylib (offset 24)name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24)name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24)name /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate (offset 24)name /usr/lib/libc++.1.dylib (offset 24)name /usr/lib/libSystem.B.dylib (offset 24)name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24)
动态库依赖了CydiaSubstrate,这个是属于越狱设备的,非越狱设备上没有,需要修改依赖
这是依赖库 name /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate
至于这个依赖 name /Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib
不用管
修改如下
install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @executable_path/Frameworks/libsubstrate.dylib FirstTweak.dylib
再次查看依赖,发现已经修改成功
name /Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib (offset 24)name /usr/lib/libobjc.A.dylib (offset 24)name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24)name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24)name @executable_path/Frameworks/libsubstrate.dylib (offset 24)name /usr/lib/libc++.1.dylib (offset 24)name /usr/lib/libSystem.B.dylib (offset 24)name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24)
FirstTweak.dylib与libsubstrate.dylib签名
下一步,需要将libsubstrate.dylib
和 FirstTweak.dylib
签名
我们先来查看下电脑上有哪些可用的证书
$ security find-identity -v -p codesigning
$ 1) DFCF477C3F593AFEF3AB261E9FD641873600EAAD "Apple Development: daye (12CD56GH78)"
找到之前用于签名FirstTweakDemo的证书,开始签名
$ codesign -fs 'Apple Development: daye (12CD56GH78)' libsubstrate.dylib
$ codesign -fs 'Apple Development: daye (12CD56GH78)' FirstTweak.dylib
签名成功后,将libsubstrate.dylib
与FirstTweak.dylib
复制到FirstTweakDemo.app/Frameworks/
下
然后将FirstTweak.dylib
注入到FirstTweakDemo
中
$ ./optool install -c load -p @executable_path/Frameworks/FirstTweak.dylib -t FirstTweakDemo.app/FirstTweakDemo
Found thin header...
Inserting a LC_LOAD_DYLIB command for architecture: arm64
Successfully inserted a LC_LOAD_DYLIB command for arm64
Writing executable to FiirstTweakDemo.app/FirstTweakDemo...
签名FirstTweak.app
生成entitlements.plist文件
将FiirstTweakDemo.app/下的 embedded.mobileprovision 拷贝一份
查看描述文件内容,寻找Entitlements节点,将Entitlements节点下的内容复制到根节点下
security cms -D -i embedded.mobileprovision > entitlements.plist
最终的entitlements.plist
文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict><key>com.apple.developer.team-identifier</key><string>12CD56GH78</string><key>get-task-allow</key><true/><key>keychain-access-groups</key><array><string>12CD56GH78.*</string><string>com.apple.token</string></array><key>application-identifier</key><string>12CD56GH78.*</string>
</dict>
</plist>
接着对FirstTweakDemo.app
签名
codesign -fs 'Apple Development: daye (12CD56GH78)' --no-strict --entitlements=entitlements.plist FirstTweakDemo.app
打包ipa包
zip -ry FirstTweakDemo.ipa Payload
现在就可以安装到未越狱的手机啦~~~
详细的信息参见install.sh
第二种,直接在Xcode中编写动态库
前面的tweak生成动态库方式,编写tweak稍显麻烦,我们直接在Xcode中编写代码。
利用Xcode,将目标APP拷贝到当前APP的目录替换现有APP,注入动态库到目标APP,重新签名,运行到设备。
1. 新建一个普通的iOS 工程, 名为InsertDemo
2. 新建一个target,选择dylib,名为InsertDemoDylib
3. 删除InsertDemoDylib类,新建InsertDemoDylib.xm与InsertDemoDylib.mm文件,工程结构如下
4. 在InsertDemoDylib.xm文件中编写hook代码,还是logos语法, 还是hook前面的FirstTweakDemo
#import <UIKit/UIKit.h>@interface FirstTweakViewController: UIViewController- (void)showAlert;@end
%hook FirstTweakViewController- (void)showAlert {UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是hook后的提醒" preferredStyle:(UIAlertControllerStyleActionSheet)];UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"Cancel" style:(UIAlertActionStyleCancel) handler:nil];UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"Confirm" style:(UIAlertActionStyleDestructive) handler:nil];[alert addAction:cancel];[alert addAction:confirm];[self presentViewController:alert animated:YES completion:nil];
}%end
5. 使用theos生成InsertDemoDylib.mm的代码,这样才能让Xcode识别并编译InsertDemoDylib.mm
/opt/theos/bin/logos.pl InsertDemoDylib.xm > InsertDemoDylib.mm
InsertDemoDylib.mm 的内容如下
#line 1 "InsertDemoDylib/InsertDemoDylib.xm"#if TARGET_OS_SIMULATOR
#error Do not support the simulator, please use the real iPhone Device.
#endif#import <UIKit/UIKit.h>@interface FirstTweakViewController: UIViewController- (void)showAlert;@end#include <substrate.h>
#if defined(__clang__)
#if __has_feature(objc_arc)
#define _LOGOS_SELF_TYPE_NORMAL __unsafe_unretained
#define _LOGOS_SELF_TYPE_INIT __attribute__((ns_consumed))
#define _LOGOS_SELF_CONST const
#define _LOGOS_RETURN_RETAINED __attribute__((ns_returns_retained))
#else
#define _LOGOS_SELF_TYPE_NORMAL
#define _LOGOS_SELF_TYPE_INIT
#define _LOGOS_SELF_CONST
#define _LOGOS_RETURN_RETAINED
#endif
#else
#define _LOGOS_SELF_TYPE_NORMAL
#define _LOGOS_SELF_TYPE_INIT
#define _LOGOS_SELF_CONST
#define _LOGOS_RETURN_RETAINED
#endif@class FirstTweakViewController;
static void (*_logos_orig$_ungrouped$FirstTweakViewController$showAlert)(_LOGOS_SELF_TYPE_NORMAL FirstTweakViewController* _LOGOS_SELF_CONST, SEL); static void _logos_method$_ungrouped$FirstTweakViewController$showAlert(_LOGOS_SELF_TYPE_NORMAL FirstTweakViewController* _LOGOS_SELF_CONST, SEL); #line 15 "InsertDemoDylib/InsertDemoDylib.xm"static void _logos_method$_ungrouped$FirstTweakViewController$showAlert(_LOGOS_SELF_TYPE_NORMAL FirstTweakViewController* _LOGOS_SELF_CONST __unused self, SEL __unused _cmd) {UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是hook后的提醒" preferredStyle:(UIAlertControllerStyleActionSheet)];UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"Cancel" style:(UIAlertActionStyleCancel) handler:nil];UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"Confirm" style:(UIAlertActionStyleDestructive) handler:nil];[alert addAction:cancel];[alert addAction:confirm];[self presentViewController:alert animated:YES completion:nil];
}static __attribute__((constructor)) void _logosLocalInit() {
{Class _logos_class$_ungrouped$FirstTweakViewController = objc_getClass("FirstTweakViewController"); MSHookMessageEx(_logos_class$_ungrouped$FirstTweakViewController, @selector(showAlert), (IMP)&_logos_method$_ungrouped$FirstTweakViewController$showAlert, (IMP*)&_logos_orig$_ungrouped$FirstTweakViewController$showAlert);} }
#line 27 "InsertDemoDylib/InsertDemoDylib.xm"
别忘了在工程中添加动态库依赖
别忘了把动态库的base SDK改为iOS,否则编译不通过
6. 编译InsertDemo,生成InsertDemoDylib.dylib
我们给工程的InsertDemoDylib target添加RunScript
dylib.sh
的任务很简单,将InsertDemoDylib这个target下的所有 .xm
后缀的文件生成对应的 .mm
,所用的工具即为 logos.pl
dylib.sh
内容如下
TARGET_NAME=${PRODUCT_NAME}echo "TARGET_NAME:${TARGET_NAME}"function panic() # args: exitCode, message...
{local exitCode=$1set +eshift[[ "$@" == "" ]] || \echo "$@" >&2exit $exitCode
}
#预处理xm、x文件
function Processor()
{local logosProcessor="$1"local currentDirectory="$2"echo "currentDirectory:$currentDirectory"if [[ $currentDirectory =~ "Build/Products" ]] || [[ $currentDirectory =~ "Build/Intermediates" ]] || [[ $currentDirectory =~ "Index/DataStore" ]] || [[ $currentDirectory =~ "/LatestBuild/" ]]; thenecho "???????"returnfifor file in `ls "$currentDirectory"`;doecho "file:${file}"extension="${file#*.}"filename="${file##*/}"if [[ -d "$currentDirectory""$file" ]]; thenProcessor "$logosProcessor" "$currentDirectory""$file"elif [[ "$extension" == "xm" ]]; thenecho "XMFile:${file}"if [[ ! -f "$currentDirectory/${file%.*}.mm" ]] || [[ `ls -l "$currentDirectory/${file%.*}.mm" | awk '{ print $5 }'` < 10 ]] || [[ `stat -f %c "$currentDirectory/$file"` > `stat -f %c "$currentDirectory/${file%.*}.mm"` ]]; thenecho "Logos Processor: $filename -> ${filename%.*}.mm..."logosStdErr=$(("$logosProcessor" "$currentDirectory""$file" > "$currentDirectory""${file%.*}.mm") 2>&1) || \panic $? "Failed Logos Processor: $logosStdErr"fielif [[ "$extension" == "x" ]]; thenif [[ ! -f "$currentDirectory/${file%.*}.m" ]] || [[ `ls -l "$currentDirectory/${file%.*}.m" | awk '{ print $5 }'` < 10 ]] || [[ `stat -f %c "$currentDirectory/$file"` > `stat -f %c "$currentDirectory/${file%.*}.m"` ]]; thenecho "Logos Processor: $filename -> ${filename%.*}.m..."logosStdErr=$(("$logosProcessor" "$currentDirectory/$file" > "$currentDirectory/${file%.*}.m") 2>&1) || \panic $? "Failed Logos Processor: $logosStdErr"fifidone
}
logosProcessor="/opt/theos/bin/logos.pl"
echo "Start to genarate xxx.mm"
Processor "$logosProcessor" "$PROJECT_DIR/${TARGET_NAME}/"
给工程APP添加RunScript
目标APP替换现有APP的主要过程如下
将现有APP下的描述文件拷贝出来
if [ -f "${BUILD_APP_PATH}/../embedded.mobileprovision" ]; thenmv "${BUILD_APP_PATH}/../embedded.mobileprovision" "${BUILD_APP_PATH}"
fi
拷贝目标APP到当前APP的目录下做替换
cp -rf "${TARGET_APP_PATH}/" "${BUILD_APP_PATH}/"
拷贝描述文件到目标APP
if [ -f "${BUILD_APP_PATH}/../embedded.mobileprovision" ]; thenmv "${BUILD_APP_PATH}/../embedded.mobileprovision" "${BUILD_APP_PATH}"
fi
将依赖的动态库与当前公吃过生成的动态库拷贝到APP的Frameworks目录下
cp -rf "${BUILT_PRODUCTS_DIR}/lib""${TARGET_NAME}""Dylib.dylib" "${TARGET_APP_FRAMEWORKS_PATH}"
cp -rf "${DYLIBS_TO_INJECT_PATH}" "${TARGET_APP_FRAMEWORKS_PATH}"
将当前工程生成的动态库注入到APP的二进制文件中
APP_BINARY=`plutil -convert xml1 -o - ${BUILD_APP_PATH}/Info.plist | grep -A1 Exec | tail -n1 | cut -f2 -d\> | cut -f1 -d\<`
"$OPTOOL" install -c load -p "@executable_path/Frameworks/lib""${TARGET_NAME}""Dylib.dylib" -t "${BUILD_APP_PATH}/${APP_BINARY}"
对APP的Frameworks目录下的所有动态库签名
code_sign "${TARGET_APP_FRAMEWORKS_PATH}"
Xcode将APP安装到设备
具体脚本信息请参见install.sh
7. 修改项目配置,安装APP到设备
编译当前项目,发现报错,找不到substrate.h
InsertDemoDylib/InsertDemoDylib.xm:16:10: fatal error: 'substrate.h' file not found
#include <substrate.h>^~~~~~~~~~~~~
1 error generated.
这是因为我们没有引入substrate.h, 在header Search Path中引入下
再次编译,报错如下
Undefined symbols for architecture armv7:"_OBJC_CLASS_$_UIAlertAction", referenced from:objc-class-ref in InsertDemoDylib.o"_OBJC_CLASS_$_UIAlertController", referenced from:objc-class-ref in InsertDemoDylib.o"_MSHookMessageEx", referenced from:_logosLocalInit() in InsertDemoDylib.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这是因为我们的代码依赖UIKit库,但是动态库的target并没有引入,引入即可
再次编译,报错如下
Undefined symbols for architecture arm64:"_MSHookMessageEx", referenced from:_logosLocalInit() in InsertDemoDylib.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这是libsubstrate库没有引入的原因
再次编译,报错如下
ld: -weak_library and -bitcode_bundle (Xcode setting ENABLE_BITCODE=YES) cannot be used together
关闭bitcode即可
再次编译,不报错了哟~~哈哈
跑个真机试试, 哟,一次成功了!!!
采用Xcode这种方式更简单,不用编写Tweak, 而且可以给Xcode添加一个template,以后就不用一步步配置啦,直接从Xcode新建项目菜单新建一个工程就行了。
剩下的就是把想要Hook的app拖入TargetAPP目录中就可以了。
动态库注入app以及在非越狱手机使用相关推荐
- iOS 逆向-非越狱手机Hook App
引子 由于对iOS逆向分析很感兴趣,所以也花了很长一段时间学习了iOS逆向相关知识,并积累了一些相关经验, 这几天又到看雪论坛逛了一下,看看最近有没有什么大牛分享心得成果的!还真让我看到一篇有意思的文 ...
- ios动态库注入把越狱手机上自制的动态库安装到普通手机上
文章目录 预备条件 导出越狱手机上的app包和自己注入的动态库 导出自己写的tweak动态库文件 查看依赖库 执行命令查看程序依赖的动态库名字 用machoview查看 安装insert_dylib ...
- iOS冰与火之歌番外篇 - 在非越狱手机上进行App Hook(转载)
作者简介:郑旻(花名蒸米),阿里巴巴移动安全部门资深安全工程师,香港中文大学移动安全(Android & iOS)方向博士,曾在腾讯.百度以及硅谷的FireEye实习.在博士期间发表了多篇移动 ...
- iOS冰与火之歌番外篇 - 在非越狱手机上进行App Hook
作者简介:郑旻(花名蒸米),阿里巴巴移动安全部门资深安全工程师,香港中文大学移动安全(Android & iOS)方向博士,曾在腾讯.百度以及硅谷的FireEye实习.在博士期间发表了多篇移动 ...
- 在非越狱手机上进行App Hook
作者简介:郑旻(花名蒸米),阿里巴巴移动安全部门资深安全工程师,香港中文大学移动安全(Android & iOS)方向博士,曾在腾讯.百度以及硅谷的FireEye实习.在博士期间发表了多篇移动 ...
- iOS安全攻防(十九):基于脚本实现动态库注入
基于脚本实现动态库注入 MobileSubstrate可以帮助我们加载自己的动态库,于是开发者们谨慎的采取了对MobileSubstrate的检索和防御措施. 那么,除了依靠MobileSubstra ...
- Android安全-SO动态库注入
2019独角兽企业重金招聘Python工程师标准>>> 关于这方面技术,网上已经有大把的实现.在此,我只是记录下自己的学习过程. 0x1 原理 所谓的SO注入就是将代码拷贝到目标进程 ...
- WIN动态库注入(远线程注入)
所谓动态库注入是指,将自己编写的动态库,通过自己的程序来注入到别的进程中去,然后运行. 原理: 在目标进程,开辟一段内存,然后写入要注入的动态库.dll .然后让目标运行加载动态库函数,将该动态库载入 ...
- 【错误记录】Android 应用配置第三方 so 动态库 ( /data/app/comxxx==/base.apk/lib/arm64-v8a]couldn‘t find “libx.so“ )
文章目录 一.报错信息 二.修改方案 一.报错信息 应用运行时报错 : 2021-11-19 16:22:58.014 13244-13244/com.example E/AndroidRuntime ...
最新文章
- linux下tar.gz、tar、bz2、zip等解压缩、压缩命令小结
- android之android.intent.category.DEFAULT的用途和使用
- [NIOS] 如何Erase EPCS flash內容
- 计算机操作系统课设总结,计算机操作系统课程设计
- 如何读emmc里的引导程序_自制操作系统学习1 引导程序
- 揭秘熊猫TV HEVC直播
- github怎么隐藏自己的pr记录_记便签的软件哪个好?怎么及时记录自己的想法
- 翁恺老师C语言学习笔记(十一)字符串_字符串常量
- 若依如何防止请求重复提交?
- Nginx源码分析 - HTTP模块篇 - HTTP Request解析过程(22)
- Nginx源码分析 - 基础数据结构篇 - 单向链表结构 ngx_list.c(06)
- learning scala 变量
- java Relative Path and absolute
- matlab画柱状图_附带调色、字体
- 如何用计算机还原魔方,初学者使用的魔方口诀 7步教你快速复原三阶魔方(附图解)...
- 英语模板作文,词组,句子,句型,开头
- java 微信转账 ca_error_CA证书出错,请登录微信支付商户平台下载证书
- 音视频同步、网络抖动
- Android仿人人客户端(v5.7.1)——人人授权访问界面
- 智能硬件可能成为网络安全事件新的“爆发点”