逆向so_记一次APP的so层算法逆向(七)

 前言:初学逆向 请多多指教 好累 感觉每天这样肝 人有点受不了了...

    学习到的内容

1、新学习到IDA的一些分析时候的小技巧

2、算法还原代码实现的练习(有个参数没有分析出来,后面知道了会补上的)

3、在Frida中使用命令行调试的方便方法

分析过程

APP登陆界面:

cf5d633b3b80401ca3b3672049563766.png

请求包:

POST /api/adult/check_guest HTTP/1.1Content-Type: application/x-www-form-urlencodedUser-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Pixel Build/OPM1.171019.011)Host: service.kuyangsh.cnConnection: Keep-AliveAccept-Encoding: gzipContent-Length: 1108a=6ih0KN8TFL5%2FQT%2FN3JY63ZovsWQyeIxHjBLHp1GGwjUNYVaGJLC%2FYZenRFKbeqsgIoI4rD1atROr%0Ajkl1p7eobNMARMel19oiGkl5hRD72vOn9zyNbERMe8Cj3b24Ru4wc2mWbbnamKVKPepkaa2mqpJl%0Akp4%2Fa5udSz0UbDR6cTwLCRWeKb60H%2Fir4vzZv1OfwQF%2FXJQsuBmxH2F6wp9CJkk9WYchx4LQU%2FS3%0AjQfpQY2iZWwsmHAiyZGVsfZIgXTvhJygpT8vH268Py5JspYZoho0RRrx4BjUfs5boif6rEMpd5PC%0AiZTLhIPovbPpoZQJC7d%2BWPFtwjPT7Ljyzgz5QxtctnZqa4qMMkfFwAIeA7lj2wJZeZBD%2F%2BoU5R45%0AMEFK6OMrMXB1M%2BC4dBt7Rd384SYhe%2BAEp0gKNHGkrpxWImFcPAaalajyijs6V4Wl4res9CWuEE5W%0Ayj5ehtmPc6uZGuo5ns6THfDwnho8BiFOnH0QoqJEeyVdTsCzOiMwfBJJldB7qbsTfUlLbSnpq4tf%0AurPbVMVKQbk4ui1XgDH5v%2FptUHirNK0IHT%2BBms8wQ%2BSX3BcMLKFiWI0OBAjUydqcJpIi0sPSWpfh%0A2k1nmMlvPnljfc7P12iF8nFHoFWRYQPVie46K%2Bhd4%2FttkyrZ4Gy3WM6zWdmnED3h6CCgZ4rEe0DB%0AN5Dj8lJJbsOAE%2FxWcYDguyj8WkUET3yLB%2FBZQx%2BsOn9otWzjwROdhfi6V7OObXZ5XoGUIDffKaFu%0ADnvTinsAh%2FvjcSDVq%2BHyD%2FzUeRceMf6uQruBhHRzikSb2Oz0Zfxld7rqmWYZ8aIBe1DMRJuXecB%2F%0AAsBu4VVuVVfQf4hlCpVNmKnX6huuMkHtCptCLaD0pkmuY7X7OEfCsudtFIco%2F7gXaQ5aXgfCs7GJ%0AzAMxfpCRm4vnF0kc8nQ4OWlexOV5t65k%2B4eDt8wY91%2BIFHcq%2BIwZPR3e41oeKriHlbsPdocNOkeg%0AeyUw%2FXlAY97IpZA%3D%0A

返回包:

{  "ret": 0,  "msg": "没有错误",  "data": {    "needs_check": "0",    "needs_bind": "0",    "needs_guest_bind": 0,    "user_is_guest": -1  }}

这里定位很简单,直接全局搜索关键词:"a" 就可以了,因为可以看到POST数据包中只有一个字段就是a,但是这里需要带上双引号,来到如下

25773b7cfad58ee02cb4dad1a4db5c18.png

可以看到最关键的加密代码就是:String a = m3385a((Map) hashMap, initFromXML.getAppKey());

initFromXML.getAppKey则正常返回一个定值

a88bb80c40539302679dd9959bd28703.png

跟进m3385a函数可以发现,定义如下:

    public static String m3385a(Map<String, String> map, String str) {        if (C1074a.f2394c || TivicloudController.f2343a) {            map.put("testing", "1");        }        try {            JSONObject jSONObject = new JSONObject(map);            Debug.m3346d("params : " + jSONObject.toString());            String encode = URLEncoder.encode(jSONObject.toString(), "UTF-8");            String encryptString = EncryptUtil.encryptString(encode + str);            JSONObject jSONObject2 = new JSONObject();            try {                jSONObject2.put("sign", encryptString);                jSONObject2.put(C0882di.C0883a.DATA, encode);            } catch (JSONException e) {                Debug.m3354w((Exception) e);            }            return new String(Base64.encode(EncryptUtil.nativeAES(jSONObject2.toString()), 0), "UTF-8");        } catch (UnsupportedEncodingException e2) {            Debug.m3354w((Exception) e2);            return null;        }    }

注意:这里发现JSONObject是处于org.json.JSONObject,系统自带的库中的类,这里也能作为一种hook的方法定位

39904e722309828f7cef8bca2cf58550.png

这里可以直接对m3385a这个函数进行观察,流程就是将传入的数据先进行一个url编码,然后拼接initFromXML.getAppKey作为参数,调用EncryptUtil.encryptString

092d7345cc292de53e29e530b5fe4d7f.png

继续跟到encryptString中去,代码如下,那么也就需要进libgavesec.so中的nativeEncrypt函数进行分析

8f3a8def2666935c7a6b66bd0637462e.png

这里遇到了一个问题,比如下面的两个函数和注入代码,一个函数会调用native中的函数,但是如果我hook encryptString这个函数,返回值写的是调用native层的nativeEncrypt函数,这样的写法就会导致程序结束,具体原因不知道

    public static String encryptString(String str) {        return nativeEncrypt(str);    }    private static native String nativeEncrypt(String str);    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        EncryptUtil.encryptString.implementation = function (a) {            send("EncryptUtil.encryptString args[0]: " + a);            var result = this.nativeEncrypt(a);            return result;        }    });

因为上面那样写就导致程序结束,所以这里就直接hook nativeEncrypt,惊奇的发现这样子就不会导致程序结束了

setImmediate(function(){    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        EncryptUtil.nativeEncrypt.implementation = function (a) {            send("EncryptUtil.nativeEncrypt args[0]: " + a);            var result = this.nativeEncrypt(a);            return result;        }    });});

这里可以对EncryptUtil.encryptString(encode + str),java层进行一次hook来获取对应的参数先

[*] EncryptUtil.encryptString args[0]: {"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","version_code":"4","version_name":"2.1.0.22277","os_lang":"zh","sdk_version":"3.1.4","package_name":"com.lm.lm","imei":"359906070277673","os_name":"Android","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}fff18b83431fa3a83b9de80c1e413bde

那么的initFromXML.getAppKey值就为fff18b83431fa3a83b9de80c1e413bde

然后接着继续跟native层进行分析,将libgavesec.so拖入到IDA中,并且找到对应的函数

ad095edfb3dfebf7ea738c6fe1851804.png

1、在通过导出library库之后,有些jni的函数无法识别参数,你可以直接右键该函数选择Force call type来进行重新分析,一般都可以成功识别参数

2、有时候ida中的强制转换类型太多,可以右键选择Show casts来隐藏强制转换,然后进行分析

主要进行了encrypt函数的调用

d69a3cb40ffdab11e07f7ca7dae35824.png

接着就是来到加密函数encrypt中进行分析

5eb3f1bb9e48ba40e373e90368c56724.png

先是进行一次sha1加密

8815c5648b0c31988b8d7070d8f231cb.png

所以这里要hook两个地方

encrypt:Base + 0x1C8C ,获取第一个参数

SHA1::Result:Base + 0x2AA4,获取该函数调用完之后的第二个参数的结果

这里分享的frida的调试方法,通过命令行注入js脚本进入到frida的命令行中进行操作

frida -RF -l hooktest.js

1、通过主动调用获取对应的类

2、调用类对应的静态/非静态方法调试数据的时候会很方便,代码如下:

function data_test() {    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        var res = EncryptUtil.nativeEncrypt('%7B%22mobile_operator%22%3A%22%22%2C%22app_versioncode%22%3A%224%22%2C%22app_version%22%3A%222.1.0.22277%22%2C%22connection_type%22%3A%22WIFI%22%2C%22os_version%22%3A%228.1.0%22%2C%22source%22%3A%22SDK%22%2C%22user_id%22%3A%2216073535%22%2C%22os_lang%22%3A%22zh%22%2C%22sdk_version%22%3A%223.1.4%22%2C%22imei%22%3A%22359906070277673%22%2C%22os_name%22%3A%22Android%22%2C%22login_token%22%3A%2285700bcdf4a41164ab7406e8445479ed%22%2C%22lang%22%3A%22zh%22%2C%22udid%22%3A%2210e1ef4509a2340eb0276bb99d186506%22%2C%22nudid%22%3A%22155c2b70-fc97-4005-abc7-6d6c4329ed23%22%2C%22app_id%22%3A%2210054%22%2C%22channel_id%22%3A%2210006%22%2C%22tdid%22%3A%2239fb282a761c17e80c069332fc6a2ffa9%22%7Dfff18b83431fa3a83b9de80c1e413bde');        console.log(res);    });}

68b3b9f63b1ea95521878750210212fa.png

接着上面的分析,打印了sha1加密过后的数据v12变量和最终的Sign值(这里也就是nativeEncrypt的返回值),你会发现结果是不一样的

3b25f21ab8a7f771931853777cf286a0.png

接着你会看到下面的循环的操作,所以sha1加密的结果应该被二次修改了,这里有两个大循环的操作

79ead75f1ab7ce3f98e4c7ca02357d46.png

首先第一段循环:

SHA1::Result((SHA1 *)&v13, (unsigned int *)v12);// sha1加密的结果v4 = 0LL; // long long v5 = 7;do    // 进行循环操作  {    v6 = *(_DWORD *)&v12[v4]; // 0X0C    v7 = &dest_cstr_1[v5];    while ( 1 )    {      v8 = v6 & 0xF; // 0X0C      v6 >>= 4; // 0X00      *v7-- = hexDigits[v8]; //       if ( !(v5 & 7) )        break;      --v5;    }    *(_DWORD *)&v12[v4] = v6;    // 赋值操作    v4 += 4LL;    v5 += 15;  }  while ( v4 != 20 );

你会发现hexDigits是data段的一段数据:

ef58c9f838dabcafdc11517513323ecf.png

最后分析,其实就是将其原封不动的转换为16进制的字符,然后一起拼接起来,分析注释如下:

ccd2aa9de9b9e436d8721fbc593e3c4e.png

然后就是第二段循环了,循环次数也可以看出来会对每个16进制字符进行处理,循环次数为40次

28b069aede524dfe075ed980a76a9e6a.png

hook了char2hexInt处理过后的数据会发现,char2hexInt这个函数才是二次处理的函数

9ac0a176dcbe4cddbcab6f9d516b76b0.png

  do  {    v10 = char2hexInt(dest_cstr_1[v9]);    dest_cstr_1[v9] = hexDigits[(signed int)((unsigned __int64)char2hexInt(a211034f8af4e6b[v9]) ^ v10)]; //异或的值为v10    ++v9;  }

还需要char2hexInt的参数来观察,该地址为0x1B34,HOOK结果如下

4344ff78b82653dee049b2e7311038cc.png

其实就是两个返回值进行异或处理,比如0x2^0x2就为0,最后还会通过hexDigits数组转成对应的16进制在内存中保存为0x30,因为字符 '0' 对应的ASCII 十六进制就是 0x30 是相等的!

f1176a5cca0f0d781b7de8df75b43e1d.png

最终的sign值分析的注释:

e0ed7024ea1e4757a2b2d2912a8bb005.png

sign这里也分析完了,还有个整体加密的分析,发现整体会进行一次Base64.encode,但是nativeAES可以跟进去观察

ed3fcfef7706b9804c42722acdca26e2.png

发现该函数依旧是native层的函数,所以这里继续去看分析

67337fb4efef81be73902ffe4bfcd452.png

来到如下进行分析,看到名字就知道是AES算法加密了,这里的话用findcrypt插件通过特征码也可以进行识别

de563dd392d3eecae5d7fbcdfeed3c24.png

99386d8131de5942f868af00f6327a38.png

然后接着就是先对传入的数据进行字符串复制

ed95ae8e357575ae904865877994009b.png

然后就是AES对象和密钥的初始化,这里的AES需要hook,来确认v19的值,还有该函数最终的返回值dest_array_bytes的地址来进行打印

0d2c09e57c14f2437f6029d4939d393b.png

AES::AES:0x3274,Cipher:0x397C,然后hook到的数据如下,一个是AES密钥,最终要进行AES字符串加密的字符串

4d2072d84e85410296e839e9ebf5f604.png

最终hook的代码:

function hook_test() {    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        EncryptUtil.nativeEncrypt.implementation = function (a) {            console.log("=============================")            // console.log("EncryptUtil.nativeEncrypt args[0] 被加密的字符串: ", a);            var result = this.nativeEncrypt(a);            console.log("Sign的结果:", result);            return result;        }        var b64 = Java.use("android.util.Base64");        var str = Java.use("java.lang.String");        EncryptUtil.nativeAES.implementation = function (a) {            console.log("=============================")            // console.log("EncryptUtil.nativeEncrypt args[0] 被加密的字符串: ", a);            var result = this.nativeAES(a);            console.log("base64数据", str.$new(b64.encode(result, 0)));            return result;        }        var libgavesec = Module.findBaseAddress("libgavesec.so");        // encrypt        Interceptor.attach(libgavesec.add(0x1C8C), {            onEnter: function (args) {                console.log("encrypt args[0] 被加密的字符串: ", Memory.readCString(args[0]));                this.args1 = args[1];            },            onLeave: function (retVal) {                console.log("encrypt args[1] 处理过后的数据:", hexdump(this.args1, {                    offset: 0,                    length: 32,                    header: true,                    ansi: false                }));            }        })        // SHA1::Result        Interceptor.attach(libgavesec.add(0x2AA4), {            onEnter: function (args) {                // console.log("SHA1::Result args[1] 加密前的数据", hexdump(args[1], {                //     offset: 0,                //     length: 64,                //     header: true,                //     ansi: false                // }));                this.args1 = args[1];            },            onLeave: function (retVal) {                console.log("SHA1::Result args[1] 加密后的数据", hexdump(this.args1, {                    offset: 0,                    length: 32,                    header: true,                    ansi: false                }));            }        })        // char2hexInt        Interceptor.attach(libgavesec.add(0x1B34), {            onEnter: function (args) {                // console.log("char2hexInt 参数:", args[0]);                this.args1 = args[1];            },            onLeave: function (retVal) {                // console.log("char2hexInt 返回值:", retVal);            }        })        // AES::AES        Interceptor.attach(libgavesec.add(0x3274), {            onEnter: function (args) {                console.log("AES密钥:", hexdump(args[1], {                    offset: 0,                    length: 32,                    header: true,                    ansi: false                }));            },            onLeave: function (retVal) {            }        })        // AES::Cipher        Interceptor.attach(libgavesec.add(0x397C), {            onEnter: function (args) {                console.log("AES::Cipher args[1]:", Memory.readCString(args[1]));                this.args1 = args[1];            },            onLeave: function (retVal) {            }        });    });};setImmediate(function () {    Java.perform(function () {        hook_test();    });});

简单的加密代码的实现:

from Crypto.Cipher import AESfrom urllib import parsefrom binascii import b2a_hex, a2b_hex, b2a_base64import hashlib"""aes加密算法ECB模式"""class Aes128_(object):    def __init__(self):        self.key = b"14ca829f017c0357"        self.mode = AES.MODE_ECB    def add_to_16(self, text):        if len(text.encode('utf-8')) % 16:            add = 16 - len(text.encode('utf-8')) % 16        else:            add = 0        text = text + ("\0"*add)  # 明文 + \00填充        return text.encode('utf-8')    def encrypt(self, text):        text = self.add_to_16(text)        cryptos = AES.new(self.key, self.mode)        cipher_text = cryptos.encrypt(text)        # return b2a_hex(cipher_text)        return b2a_base64(cipher_text)    def decrypto(self, text):        cryptor = AES.new(self.key, self.mode)        plain_text = cryptor.decrypt(a2b_hex(text))        return bytes.decode(plain_text).rstrip('\0')def getSign(data):    sign = ''    a211034f8af4e6b = '211034f8af4e6b9546c19ae13ed099553319b6c3'    for i in range(40):        print(hex(int(data[i], 16) ^ int(a211034f8af4e6b[i], 16)))        sign += str(hex(int(data[i], 16) ^ int(a211034f8af4e6b[i], 16)))[2:3]    return signif __name__ == "__main__":    data = bytes(parse.quote('{"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","source":"SDK","user_id":"16073535","os_lang":"zh","sdk_version":"3.1.4","imei":"359906070277673","os_name":"Android","login_token":"9f6037c931db9a9cfcbb991966fad614","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}fff18b83431fa3a83b9de80c1e413bde'), encoding="utf-8")    sha1 = hashlib.sha1(data)    sha1_data = sha1.hexdigest()    sign = getSign(sha1_data)    aes_data = str(Aes128_().encrypt('{"sign":"'+sign+'","data": "' + parse.quote('{"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","source":"SDK","user_id":"16073535","os_lang":"zh","sdk_version":"3.1.4","imei":"359906070277673","os_name":"Android","login_token":"9f6037c931db9a9cfcbb991966fad614","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}"}')), encoding='utf8').replace("\n", "")    print(aes_data)

0f11da1e95f0c903d9ea8ab00169ba88.png

这个其实不是最终的代码,因为login_token没有分析出来,感觉有点难,我继续试试,可以的话再写一篇!

3d9a3c2f0243cfda7366940fc5ff25b1.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311063.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++实现Huffman树

代码如下&#xff1a; #include <iostream> using namespace std; int s1, s2;typedef struct {int weight;int parent, lch, rch; } HTNode, *HuffmanTree;void Select(HuffmanTree &HT, int n, int &s1, int &s2) {int minv;//定义一个临时变量存储最小值…

.NET Core开发实战(第25课:路由与终结点:如何规划好你的Web API)--学习笔记(下)...

25 | 路由与终结点&#xff1a;如何规划好你的Web API自定义约束实现了路由约束接口&#xff0c;它只有一个 Match 方法&#xff0c;这个方法传入了 Http 当前的 httpContext&#xff0c;route&#xff0c;routeKey这个 routeKey 就是我们要验证的 key 值后面两个参数 RouteVal…

微软 Visual Studio 2019 16.5 发布:.NET 移动开发、生产力

微软最新发布了 Visual Studio 2019 16.5 版本&#xff0c;下面来看看主要更新内容&#xff1a;.NET 移动开发首先要讨论的特性是 XAML Hot Reload for Xamarin.Forms。此功能可加快开发速度&#xff0c;并使开发者可以更轻松地在移动应用的用户界面上进行构建、实验和迭代。且…

chrome主题_谷歌Chrome将很快允许用户创建自定义主题

站长之家(ChinaZ.com) 7月31日 消息:据9to5google报道&#xff0c;虽然用户可以通过Chrome Web Store定制主题&#xff0c;但用户要根据自己的独特喜好定制主题却不是一个简单的事。谷歌正寻求通过在Chrome内置一个自定义主题生成器来解决这个问题。Chrome Web Store中有许多传…

使用Magicodes.IE.Excel完成Excel图片的导入和导出

说明本章教程主要说明如何使用Magicodes.IE.Excel进行图片的导入导出。要点配置DTO进行Excel图片导出配置DTO进行Excel图片导入图片导入导出特性说明ExportImageFieldAttributeHeight&#xff1a;高度(默认15)Width&#xff1a;宽度(默认50)Alt&#xff1a;图片不存在时替换文本…

C++未定义行为-数组越界

我们先来看看下面的代码&#xff1a; #include <iostream> using namespace std; const int N 100010; int a[N]; int main() {for (int i 1;i<N;i) a[i] 2;return 0; }当我们写这段代码的时候&#xff0c;编译器就会发生这样的问题。 这是为什么呢&#xff1f;&a…

SuperBenchmarker一个用.NET编写的压测工具

0x01 前言在这之前想必大家对ab(http)与abs(https)也有一些了解,我们今天不去看ab和abs,SuperBenchmarker(sb.exe)是一个压测工具,他是一个受Apache Benchmark的启发,他会在终端窗口为我们显示最终的结果,同时也会在web界面生成一个动态结果。SuperBenchmarker(sb.exe)可以在Wi…

mysql文献综述_文献综述随笔(二十)

一、基本信息标题&#xff1a;中小型酒店管理系统的设计与实现时间&#xff1a;2013来源&#xff1a;厦门大学关键词&#xff1a;MVC;B/S;JAVA EE;JSP;MySQL;瀑布开发模型二、研究内容1.主要内容&#xff1a;系统业务需求、功能需求、系统架构设计、数据库设计1.1功能模块设计&…

五分钟完成 ABP vNext 通讯录 App 开发

ABP vNext&#xff08;后文简称Abp&#xff09;是 Volo 公司堪称艺术品级的应用开发框架&#xff0c;它基于领域驱动设计&#xff08;DDD&#xff09;的思维&#xff0c;创新地采用了模块化的设计。Abp 目前无疑是 ASP.NET Core 开发框架中最先进和最优雅的存在。笔者认为&…

mysql 5.74安装教程_MySQL安装、基本账户安全(5.0以后版本)

-----------MySQL 5.0以后版本的安装-----------MySQL安装安装包学习的必杀绝技——就是阅读包的安装说明(readme & install)文档。----------# rm /etc/my.cnf (安装前执行一下)----------1.Mysql-5.0.40.tar.gz1.1.Source Installation Overview(lines 74 of …

使用GUI工具Portainer.io管控Docker容器

背景5年前容器技术扑面而来&#xff0c;如今已经成为面向云原生开发的基础架构&#xff0c;基于微服务的设计需要部署大量容器&#xff0c;同时强调了友好快速的管理容器。是时候推荐一个轮子Portainer.io&#xff1a;提供GUI界面的容器管理工具&#xff0c;给开发者的工具箱又…

【项目升级】集成Quartz.Net Job实现(一)

这两天的新闻也是越来越多了&#xff0c;不仅Github接手了NPM&#xff0c;还有.NET 5也要新鲜出炉了&#xff08;11月正式发布&#xff09;&#xff0c;当然还有MVP峰会也正在如火如荼的展开&#xff0c;会有哪些好的东西被碰撞出来&#xff0c;也是很期待的。这些天我也简单的…

DevC++如何安装自定义头文件并使用

首先我们打开DevC&#xff0c;然后点击新建。 新建一个控制台应用程序 取一个喜欢的文件名。 然后会出现如下界面。 点击新建单元 将头文件源码放入 找到空白位置&#xff0c;右键&#xff0c;然后点关闭并保存 保存的文件名要为头文件的名字 使用这个头文件时&#xff0c;只…

论ORM之EFCore初篇(快速基于本地数据库实现数据操作)

欢迎大家阅读《朝夕Net社区技术专刊》第6期我们致力于.NetCore的推广和落地&#xff0c;为更好的帮助大家学习&#xff0c;方便分享干货&#xff0c;特创此刊&#xff01;很高兴你能成为忠实读者&#xff0c;文末福利不要错过哦&#xff01;前言&#xff1a;今天准备带大家一站…

C++变量的初始化问题及列表初始化

在C中&#xff0c;初始化是一个异常复杂的问题&#xff0c;很多人认为初始化是赋值的一种&#xff0c;事实上&#xff0c;初始化和赋值是两个完全不同的操作。 列表初始化 要定义一个名为haif的int变量并初始化为0&#xff0c;以下4条语句都可以做到。 int haif 0; int hai…

c++ set 遍历_47. Set 是如何工作的(3) 遍历顺序是如何确定的?

Set 是无序容器&#xff0c;它的插入顺序与迭代&#xff08;或者 print&#xff09;输出的顺序不保证与插入顺序一致&#xff0c;与 Dict 类似的问题&#xff0c;Set 的输出顺序是如何决定的呢&#xff1f;首先我们从 Set 的输出开始寻找蛛丝马迹&#xff0c;在 Dict 的研究中&…

跟着“土牛”学架构知识

这里的土牛是指abp的作者&#xff0c;土耳其人&#xff0c;简称“土牛”&#xff0c;前两天看了他分享的ppt&#xff0c;这里做个小笔记。架构分层图一&#xff08;abp作者&#xff09;图二&#xff08;clean架构&#xff09;图三&#xff08;在朋友圈看到的&#xff09;每种架…

《C++ Primer》2.6.1节练习

练习2.39&#xff1a; 在类体花括号后加一个分号就好了。 练习2.40&#xff1a; 代码如下&#xff1a; struct Sales_data{std::string bookNo;//书籍编号unsigned units_sold 0;//销售量double sellingprice 0.0;//零售价double saleprice 0.0//实售价double discount 0…

Cef mysql.exe_CEF3.2623使用记录:windows编译

CEF3.2623使用记录&#xff1a;windows编译1&#xff1a;cef3.2623下载地址2623是cef3最后一个支持xp系统的版本&#xff0c;且可以支持html的audio标签&#xff0c;可以用作对html音频的处理下载地址为 https://bitbucket.org/chromiumembedded/cef/branch/2623。下载win32版本…

Asp.Net Core 中IdentityServer4 授权流程及刷新Token

一、前言上面分享了IdentityServer4 两篇系列文章&#xff0c;核心主题主要是密码授权模式及自定义授权模式&#xff0c;但是仅仅是分享了这两种模式的使用&#xff0c;这篇文章进一步来分享IdentityServer4的授权流程及refreshtoken。系列文章目录(没看过的先看这几篇文章再来…