某物登录表单加密
之前分析过某物h5的以及小程序的搜索接口,就是一个aes,秘钥不固定,表单里把秘钥以及密文一起发过去,服务器解密后再把数据加密返回,客户端解密展示到页面上.
这期是关于app的登录,密码登录
声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!
我创建了一个js 安卓 ios逆向的技术交流群,刚兴趣的可以加一下,在最后面.
抓包
可以看到表单里有newSign,password,userName加密了,都是32位的.并且响应也是加密的.
再来看看头部,框中的那几个都是可能需要分析的,其中有一个shumeiid,这个是数美sdk里的.这个难度估计有点大,这个我们下期分析,SK和edk和ltk自行研究,后面看了下so的结构,就算是so的难度应该也不会太大.这几个貌似都没变,但是ltk是一直变化的.这个也留着后面分析.
反编译
先查下壳,没加固,总算碰到一个没加固的,前几期都是加固的,看来是对自己的产品比较有信心.
尝试搜索下字符串username
有点多,这样一个个点过去太麻烦了,尝试hook hashmap
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {if(a!=null && a.equals("userName")){console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))console.log("hashMap.put: ", a, b);}return this.put(a, b);
}
frida反调试
frida启动后以attach注入
直接断开,同时app会重启,这里用的已经是去过特征版的frida了,葫芦娃编译的,还是被检测到了.
以spawn方式启动试试
直接断开并没有重启,来看看它加载了哪些so.肯定是so的检测
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");Interceptor.attach(dlopen, {onEnter: function (args) {var path_ptr = args[0];var path = ptr(path_ptr).readCString();console.log("[dlopen:]", path);},onLeave: function (retval) {}
});
Interceptor.attach(android_dlopen_ext, {onEnter: function (args) {var path_ptr = args[0];var path = ptr(path_ptr).readCString();console.log("[dlopen_ext:]", path);},onLeave: function (retval) {}
});
libmsaoaidsec.so mobile security alliance open aid security,字面意思,看着就不是什么好东西,网上有分析libmsaoaidsec.so的文章,自行检索,我这里是直接把它删掉,有些app可以这样过,有些删了会闪退,闪退的话只能把它过掉.
删掉后frida可以正常使用,直接attach即可.
看到mobileLogin字眼,jadx中点过去看看
f.c方法点过去看看
看到了AES ECB字眼,应该是java层的,这样的话其他的几个也有可能是java层的,但是在上面图中的hashmap中没有看到newsign字眼,这个app用的okhttp3框架,极有可能是用的okhttp3.Interceptor添加的.因为有可能是java层的,有3个参数,当然优先考虑java通杀方案.
Java.perform(function () {function showStacks() {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));}var ByteString = Java.use("com.android.okhttp.okio.ByteString");function toBase64(tag, data) {//logOutPut(tag + " Base64: " + ByteString.of(data).base64());console.log(tag + " Base64: " + ByteString.of(data).base64());}function toHex(tag, data) {//logOutPut(tag + " Hex: " + ByteString.of(data).hex());console.log(tag + " Hex: " + ByteString.of(data).hex());}function toUtf8(tag, data) {//logOutPut(tag + " Utf8: " + ByteString.of(data).utf8());console.log(tag + " Utf8: " + ByteString.of(data).utf8());}var messageDigest = Java.use("java.security.MessageDigest");messageDigest.update.overload('byte').implementation = function (data) {console.log("MessageDigest.update('byte') is called!");showStacks();return this.update(data);}messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");showStacks();return this.update(data);}messageDigest.update.overload('[B').implementation = function (data) {console.log("MessageDigest.update('[B') is called!");showStacks();var algorithm = this.getAlgorithm();var tag = algorithm + " update data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log("=======================================================");return this.update(data);}messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {console.log("MessageDigest.update('[B', 'int', 'int') is called!");showStacks();var algorithm = this.getAlgorithm();var tag = algorithm + " update data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log("=======================================================", start, length);return this.update(data, start, length);}messageDigest.digest.overload().implementation = function () {console.log("MessageDigest.digest() is called!");showStacks();var result = this.digest();var algorithm = this.getAlgorithm();var tag = algorithm + " digest result";toHex(tag, result);toBase64(tag, result);console.log("=======================================================");return result;}messageDigest.digest.overload('[B').implementation = function (data) {console.log("MessageDigest.digest('[B') is called!");showStacks();var algorithm = this.getAlgorithm();var tag = algorithm + " digest data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result = this.digest(data);var tags = algorithm + " digest result";toHex(tags, result);toBase64(tags, result);console.log("=======================================================");return result;}messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {console.log("MessageDigest.digest('[B', 'int', 'int') is called!");showStacks();var algorithm = this.getAlgorithm();var tag = algorithm + " digest data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result = this.digest(data, start, length);var tags = algorithm + " digest result";toHex(tags, result);toBase64(tags, result);console.log("=======================================================", start, length);return result;}var mac = Java.use("javax.crypto.Mac");mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");return this.init(key, AlgorithmParameterSpec);}mac.init.overload('java.security.Key').implementation = function (key) {console.log("Mac.init('java.security.Key') is called!");var algorithm = this.getAlgorithm();var tag = algorithm + " init Key";var keyBytes = key.getEncoded();toUtf8(tag, keyBytes);toHex(tag, keyBytes);toBase64(tag, keyBytes);console.log("=======================================================");return this.init(key);}mac.update.overload('byte').implementation = function (data) {console.log("Mac.update('byte') is called!");return this.update(data);}mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {console.log("Mac.update('java.nio.ByteBuffer') is called!");return this.update(data);}mac.update.overload('[B').implementation = function (data) {console.log("Mac.update('[B') is called!");var algorithm = this.getAlgorithm();var tag = algorithm + " update data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log("=======================================================");return this.update(data);}mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {console.log("Mac.update('[B', 'int', 'int') is called!");var algorithm = this.getAlgorithm();var tag = algorithm + " update data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log("=======================================================", start, length);return this.update(data, start, length);}mac.doFinal.overload().implementation = function () {console.log("Mac.doFinal() is called!");var result = this.doFinal();var algorithm = this.getAlgorithm();var tag = algorithm + " doFinal result";toHex(tag, result);toBase64(tag, result);console.log("=======================================================");return result;}var cipher = Java.use("javax.crypto.Cipher");cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function () {console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function () {console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function () {console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function () {console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function () {console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function () {console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.Key').implementation = function () {console.log("Cipher.init('int', 'java.security.Key') is called!");var algorithm = this.getAlgorithm();var tag = algorithm + " init Key";var className = JSON.stringify(arguments[1]);if(className.indexOf("OpenSSLRSAPrivateKey") === -1){var keyBytes = arguments[1].getEncoded();toUtf8(tag, keyBytes);toHex(tag, keyBytes);toBase64(tag, keyBytes);}console.log("=======================================================");return this.init.apply(this, arguments);}cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () {console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");var algorithm = this.getAlgorithm();var tag = algorithm + " init Key";var keyBytes = arguments[1].getEncoded();toUtf8(tag, keyBytes);toHex(tag, keyBytes);toBase64(tag, keyBytes);var tags = algorithm + " init iv";var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec"));var ivBytes = iv.getIV();toUtf8(tags, ivBytes);toHex(tags, ivBytes);toBase64(tags, ivBytes);console.log("=======================================================");return this.init.apply(this, arguments);}cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function () {console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!");showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload('[B', 'int').implementation = function () {console.log("Cipher.doFinal('[B', 'int') is called!");showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation = function () {console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!");showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation = function () {console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!");showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload().implementation = function () {console.log("Cipher.doFinal() is called!");showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload('[B').implementation = function () {console.log("Cipher.doFinal('[B') is called!");showStacks();var algorithm = this.getAlgorithm();var tag = algorithm + " doFinal data";var data = arguments[0];toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result = this.doFinal.apply(this, arguments);var tags = algorithm + " doFinal result";toHex(tags, result);toBase64(tags, result);console.log("=======================================================");return result;}cipher.doFinal.overload('[B', 'int', 'int').implementation = function () {console.log("Cipher.doFinal('[B', 'int', 'int') is called!");showStacks();var algorithm = this.getAlgorithm();var tag = algorithm + " doFinal data";var data = arguments[0];toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result = this.doFinal.apply(this, arguments);var tags = algorithm + " doFinal result";toHex(tags, result);toBase64(tags, result);console.log("=======================================================", arguments[1], arguments[2]);return result;}var signature = Java.use("java.security.Signature");signature.update.overload('byte').implementation = function (data) {console.log("Signature.update('byte') is called!");return this.update(data);}signature.update.overload('java.nio.ByteBuffer').implementation = function (data) {console.log("Signature.update('java.nio.ByteBuffer') is called!");return this.update(data);}signature.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {console.log("Signature.update('[B', 'int', 'int') is called!");var algorithm = this.getAlgorithm();var tag = algorithm + " update data";toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log("=======================================================", start, length);return this.update(data, start, length);}signature.sign.overload('[B', 'int', 'int').implementation = function () {console.log("Signature.sign('[B', 'int', 'int') is called!");return this.sign.apply(this, arguments);}signature.sign.overload().implementation = function () {console.log("Signature.sign() is called!");var result = this.sign();var algorithm = this.getAlgorithm();var tag = algorithm + " sign result";toHex(tag, result);toBase64(tag, result);console.log("=======================================================");return result;}
});
不过我比较喜欢用算法助手,原理是一样的.先在lsp里把目标app勾上,算法助手页面把这个app打开
全部勾上后重启app,登录一下,抓包同时开着,这个包里的
{"cipherParam": "userName","countryCode": 86,"loginToken": "","newSign": "614a0f4d2ef049ff1d0145b0a96d2ef0","password": "ca8f119a27ec17f98b463807cd0b6b62","platform": "android","timestamp": "1711714586437","type": "pwd","userName": "83c7358c94e3a24e11f0fde1cbf5d559_1","v": "5.38.5"
}
userName
日志里搜一下83c7358c94e3a24e11f0fde1cbf5d559,后面的_1自己添加上去的.
有结果
aes ecb nopadding,无填充是因为已经填充过了,看明文后面那一串,直接用pkcs7就可以了.userName解决
password
直接搜ca8f119a27ec17f98b463807cd0b6b62
密码加一个盐值,没什么好说的.
newSign
搜一下614a0f4d2ef049ff1d0145b0a96d2ef0
md5签的一串base64值.搜索这个值有没有结果,搜一部分dWWoXlbR3K87j2N27Dkv4uOPUnOsh0xrJ5t
没有搜到,这个值是base64过的,尝试hook一下base64的encodeToString方法
var base64 = Java.use("android.util.Base64");
base64.encodeToString.overload('[B', 'int').implementation = function (a, b) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
console.log("base64.encodeToString: ", JSON.stringify(a));
var result = this.encodeToString(a, b);
console.log("base64.encodeToString result: ", result)
return result;}
没有hook到,估计是so base64后返回的,所以没有hook到.
先看下先前的堆栈
let i0 = Java.use("gf.i0");
i0["c"].implementation = function (map, j, str) {console.log(`i0.c is called: map=${map}, j=${j}, str=${str}`);let result = this["c"](map, j, str);console.log(`i0.c result=${result}`);return result;
};
hook一下这个c方法,入参的str是空值
中间添加了些东西,这里直接跟这个aesencrypt.encode方法,一路跟到
encodebyte方法,写这个方法的主动调用
function call2(){Java.perform(function (){let AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");var str='010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100'var bArr = [99,105,112,104,101,114,80,97,114,97,109,117,115,101,114,78,97,109,101,99,111,117,110,116,114,121,67,111,100,101,56,54,108,111,103,105,110,84,111,107,101,110,112,97,115,115,119,111,114,100,99,97,56,102,49,49,57,97,50,55,101,99,49,55,102,57,56,98,52,54,51,56,48,55,99,100,48,98,54,98,54,50,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,49,49,55,48,52,48,56,49,52,55,54,116,121,112,101,112,119,100,117,115,101,114,78,97,109,101,56,51,99,55,51,53,56,99,57,52,101,51,97,50,52,101,49,49,102,48,102,100,101,49,99,98,102,53,100,53,53,57,95,49,117,117,105,100,49,48,49,51,48,48,102,98,51,57,48,51,48,56,52,101,118,53,46,51,56,46,53]var res = AESEncrypt["encodeByte"](bArr, str)console.log(res)})
}
des 3des aes关系
第一个字符串转16进制后是24字节,写文章之前不记得前面有个aes了,以为这个是3des,3des秘钥就是24字节,3des由des进化过来的,因为des不安全,那个时候aes还没出来,aes中的a是advanced,高级的,由des进化而来,aes的原始名称是Rijndael算法,是由创造这个算法的两位作者名字组成的.因为是在des基础上改进的,所以大家都称它aes.
des秘钥8字节太短了,aes有16字节,24字节,32字节的,密钥的长度在一定程度上决定着算法的安全性,而DES密钥长度过短,也就导致了它的安全性较低;而AES则更加安全,这也是AES能够取代DES的重要原因之一.
在aes还没出之前为了解决des安全性的问题,有专家推出了3des,cyberchef中名称是TripleDES,Triple有3的意思,秘钥24字节,由3把秘钥组成,每8个字节算一把秘钥,加密流程如图所示,如果前两把秘钥相同,就相当于只有一次des加密,3des目前已被证明安全性不足,目前用的都是aes,如果没记错的话某书的x-s就是用的3des,好吧扯得有点远了,回到正文.
用3des加密一遍结果不对,只能看so了.
第一个数组转utf-8
import base64
byte_list = [99,105,112,104,101,114,80,97,114,97,109,117,115,101,114,78,97,109,101,99,111,117,110,116,114,121,67,111,100,101,56,54,108,111,103,105,110,84,111,107,101,110,112,97,115,115,119,111,114,100,99,97,56,102,49,49,57,97,50,55,101,99,49,55,102,57,56,98,52,54,51,56,48,55,99,100,48,98,54,98,54,50,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,49,49,55,48,52,48,56,49,52,55,54,116,121,112,101,112,119,100,117,115,101,114,78,97,109,101,56,51,99,55,51,53,56,99,57,52,101,51,97,50,52,101,49,49,102,48,102,100,101,49,99,98,102,53,100,53,53,57,95,49,117,117,105,100,49,48,49,51,48,48,102,98,51,57,48,51,48,56,52,101,118,53,46,51,56,46,53]
num_list = bytearray()
for item in byte_list:if item < 0:item = item + 256num_list.append(item)
utf8_string = num_list.decode()
print('utf8_string:',utf8_string)
结果就是表单里除了newsign的东西cipherParamuserNamecountryCode86loginTokenpasswordca8f119a27ec17f98b463807cd0b6b62platformandroidtimestamp1711704081476typepwduserName83c7358c94e3a24e11f0fde1cbf5d559_1uuid101300fb3903084ev5.38.5,再拼了一个uuid和版本号.
so加固
上图中so的名字是libJNIEncrypt.so,只有64位的,用ida64打开
提示文件结果被破坏,点yes
警告这个so有无意义或者无效的节.点ok.
刚进来两处爆红,上面一条都是灰色的,没有代码段,不用想,加固了.
so dump
dump内存中的so,yang神的脚本
function dump_so(so_name) {Java.perform(function () {var currentApplication = Java.use("android.app.ActivityThread").currentApplication();var dir = currentApplication.getApplicationContext().getFilesDir().getPath();var libso = Process.getModuleByName(so_name);console.log("[name]:", libso.name);console.log("[base]:", libso.base);console.log("[size]:", ptr(libso.size));console.log("[path]:", libso.path);var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";var file_handle = new File(file_path, "wb");if (file_handle && file_handle != null) {Memory.protect(ptr(libso.base), libso.size, 'rwx');var libso_buffer = ptr(libso.base).readByteArray(libso.size);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log("[dump]:", file_path);}});
}
dump_so("libJNIEncrypt.so")
dump下来拉到电脑上用sofixer修复一下.
dump下来的没有前面的警告了,上面的"进度条"也变成了蓝色,同时看到了Jnionload,动态注册,以及base64和aes128,难怪之前hook不到base64,原来是so base64的
接下来找动态注册函数.hook libart
var addrRegisterNatives = null;
var symbols = Module.enumerateSymbolsSync("libart.so");
for (var i = 0; i < symbols.length; i++) {var symbol = symbols[i];if (symbol.name.indexOf("art") >= 0 &&symbol.name.indexOf("JNI") >= 0 &&symbol.name.indexOf("RegisterNatives") >= 0 &&symbol.name.indexOf("CheckJNI") < 0) {addrRegisterNatives = symbol.address;console.log("RegisterNatives is at ", symbol.address, symbol.name);break}
}
if (addrRegisterNatives) {// RegisterNatives(env, 类型, Java和C的对应关系,个数)Interceptor.attach(addrRegisterNatives, {onEnter: function (args) {var env = args[0]; // jni对象var java_class = args[1]; // 类var class_name = Java.vm.tryGetEnv().getClassName(java_class);var taget_class = "com.duapp.aesjni.AESEncrypt"; //111 某个类中动态注册的soif (class_name === taget_class) {//只找我们自己想要类中的动态注册关系console.log("\n[RegisterNatives] method_count:", args[3]);var methods_ptr = ptr(args[2]);var method_count = parseInt(args[3]);for (var i = 0; i < method_count; i++) {// Java中函数名字的var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));// 参数和返回值类型var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));// C中的函数内存地址var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));var name = Memory.readCString(name_ptr);var sig = Memory.readCString(sig_ptr);var find_module = Process.findModuleByAddress(fnPtr_ptr);// 地址、偏移量、基地址var offset = ptr(fnPtr_ptr).sub(find_module.base);console.log("name:", name, "sig:", sig,'module_name:',find_module.name ,"offset:", offset);}}}});
}
以spawn启动
0x174c,ida中跳过去看看,然后转换一下jnienv对象
看返回值,来自v18,v18来自j_AES_128_ECB_PKCS5Padding_Encrypt,很明显了,ecb无iv 16字节秘钥,pkcs5填充,直接frida hook这个函数打印两个入参.
var soAddr = Module.findBaseAddress("libJNIEncrypt.so");
var funcAddr = soAddr.add(0x182C) //32位的话记得+1Interceptor.attach(funcAddr,{onEnter: function(args){console.log('onEnter arg[0]: ',hexdump(args[0]))console.log('onEnter arg[1]: ',hexdump(args[1]))this.arg0 = args[0]},onLeave: function(retval){// console.log('onLeave arg[]: ')console.log('onLeave result: ',retval)}});
入参1是java层传来的明文,参数2是key d245a0ba8d678a61,验证一下,和主动调用结果一致,最后md5就是newsign了,至此,newsign分析完毕,没什么难度的.
技术交流+lyaoyao__i(两个_),群聊7天有效
微信公众号
知识星球
相关文章:

某物登录表单加密
之前分析过某物h5的以及小程序的搜索接口,就是一个aes,秘钥不固定,表单里把秘钥以及密文一起发过去,服务器解密后再把数据加密返回,客户端解密展示到页面上. 这期是关于app的登录,密码登录 声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,…...
2024java面试题
题目:反转一个单链表描述:给定一个单链表的头节点,将其反转,返回反转后的链表的头节点。 题目:合并两个有序链表描述:给定两个有序链表的头节点,将它们合并成一个有序链表,返回合并…...

FebHost:什么是哈萨克斯坦.KZ域名?
哈萨克斯坦,作为中亚地区重要的一员,其国家域名”.kz”正成为这个独立国家在网络世界中的代表。作为一个经济快速发展的国家,哈萨克斯坦的互联网基础设施和网络应用也在蓬勃发展。而.kz域名正是哈萨克斯坦网络身份的重要体现。 作为注册和管理.kz域名的主要机构,哈…...

python(一)网络爬取
在爬取网页信息时,需要注意网页爬虫规范文件robots.txt eg:csdn的爬虫规范文件 csdn.net/robots.txt User-agent: 下面的Disallow规则适用于所有爬虫(即所有用户代理)。星号*是一个通配符,表示“所有”。 Disallow&…...

港大新工作 HiGPT:一个模型,任意关系类型 !
论文标题: HiGPT: Heterogeneous Graph Language Model 论文链接: https://arxiv.org/abs/2402.16024 代码链接: https://github.com/HKUDS/HiGPT 项目网站: https://higpt-hku.github.io/ 1. 导读 异质图在各种领域…...
Git版本管理使用手册 - 5 - Git的.ignore文件语法
Git的.ignore文件 1.使用 .ignore文件可以忽略指定文件的版本控制。 2.语法: (1)#开头表示注释 (2)!开头表示不忽略匹配文件 (3)* 表示除/外,任何字符串 (4)?表示除/外,任何一个字符 (5)/ 如果模式的结尾有分割符/&am…...
使用Spring Cloud Gateway构建API网关,实现路由、过滤、流量控制等功能。
使用Spring Cloud Gateway构建API网关,实现路由、过滤、流量控制等功能。 使用Spring Cloud Gateway可以轻松地构建API网关,实现路由、过滤、流量控制等功能。下面是一个简单的示例,演示如何在Spring Boot应用程序中集成Spring Cloud Gatewa…...

Matlab|电动汽车充放电V2G模型
目录 1 主要内容 1.1 模型背景 1.2 目标函数 1.3 约束条件 2 部分代码 3 效果图 4 下载链接 1 主要内容 本程序主要建立电动汽车充放电V2G模型,采用粒子群算法,在保证电动汽车用户出行需求的前提下,为了使工作区域电动汽车尽可能多的消…...

<QT基础(4)>QLabel使用笔记
Label 前面的文章里面把QLabel批量引入ScrollArea作为预览窗口,这篇把图像填充到QLable的PixelMap展示指定图像。 参数设置 设置QLabel的大小格式 QWidget* widget new QWidget; widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); widget->…...

2016年认证杯SPSSPRO杯数学建模C题(第二阶段)如何有效的抑制校园霸凌事件的发生全过程文档及程序
2016年认证杯SPSSPRO杯数学建模 C题 如何有效的抑制校园霸凌事件的发生 原题再现: 近年来,我国发生的多起校园霸凌事件在媒体的报道下引发了许多国人的关注。霸凌事件对学生身体和精神上的影响是极为严重而长远的,因此对于这些情况我们应该…...
前端理论总结(css3)——css优化的方法
1:首推的是合并css文件,如果页面加载10个css文件,每个文件1k,那么也要比只加载一个100k的css文件慢 2:减少css嵌套,最好不要套三层以上 3:不要在ID选择器前面进行嵌套,ID本来就是唯一…...
项目立项管理
目录 1.概述 2.项目建议与立项申请 3.项目可行性研究 3.1.内容 3.2.初步可行性研究 3.3.详细可行性研究 4.项目评估与决策 5.总结 1.概述 本文的目录结构参考了《信息系统项目管理师教程(第四版)》。 项目立项管理是一项全面评估准备投资工程的多…...

QT的学习
代码练习 完成一个使用qss的登陆窗口界面。 使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否…...

redis在docker安装并启动流程
1、启动server docker run -d -p 6379:6379 --name redis01 redis:7.2.4以上命令,每次启动新的Redis容器,数据会丢失。 我们需要挂载数据文件,在宿主机上面,这样就可以持久化数据. 2、挂载数据文件(可根据需求选择…...
Spring高频面试题
(一些来源于GitCode AI) 什么是Spring框架? Spring是一个开源的Java平台,它简化了企业级应用的开发。它提供了IOC(Inversion of Control)/DI(Dependency Injection)容器,…...

Qt篇——Qt无法翻译tr()里面的字符串
最近遇到使用Qt语言家翻译功能时,ui界面中的中文都能够翻译成英文,但是tr("测试")这种动态设置给控件的中文,无法翻译(lang_English.ts文件中的翻译已经正确添加了tr()字符串的翻译)。 上网搜了很多资料&am…...

农村分散式生活污水分质处理及循环利用技术指南
标准已完成意见征集: 本文件给出了农村分散式生活污水分质处理及循环利用的总则、污水收集、污水分质处理、资源化利用、利用模式、运维管理等的指导。 本文件适用于农村分散式生活污水分质处理及循环利用的设施新建、扩建和改建工程的设计、施工与运维。 注:本文件…...

深圳区块链交易所app系统开发,撮合交易系统开发
随着区块链技术的迅速发展和数字资产市场的蓬勃发展,区块链交易所成为了数字资产交易的核心场所之一。在这个快速发展的领域中,区块链交易所App系统的开发和撮合交易系统的建设至关重要。本文将探讨区块链交易所App系统开发及撮合交易系统的重要性&#…...
使用Shell脚本进行MySql权限修改
背景:原先数据配置文件中有bind-address127.0.0.1,注释掉此配置后,原数据库中默认带%root的权限,现在需要通过脚本实现白名单列表中的ip添加权限允许访问数据库,白名单之外的ip没有权限访问数据库。 以下是过程中记录的…...
项目中线程池的应用
1、首先我们需要在配置类中将线程池作为单例bean配置 Configuration public class ThreadPoolExecutorConfig {BeanExecutorService executorService(){return new ThreadPoolExecutor(2,3,0,TimeUnit.MICROSECONDS,new ArrayBlockingQueue<>(3),(r)->new Thread(r,&…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...