zoukankan      html  css  js  c++  java
  • [转载]手把手用C++解密Chrome80版本数据库

    谷歌浏览器Google Chrome 80正式版例行更新详细版本80.0.3987.163。Google Chrome浏览器又称谷歌浏览器采用Chromium内核全球最受欢迎的免费网页浏览器追求速度、隐私安全的网络浏览器。

    先说下吧。chrome80以前的版本是直接可以通过DPAPI来进行解密的。关于DPAPI 大家可以 看这里的介绍

    DPAPI是Windows系统级对数据进行加解密的一种接口无需自实现加解密代码微软已经提供了经过验证的高质量加解密算法提供了用户态的接口对密钥的推导存储数据加解密实现透明并提供较高的安全保证

    DPAPI提供了两个用户态接口`CryptProtectData`加密数据`CryptUnprotectData`解密数据加密后的数据由应用程序负责安全存储应用无需解析加密后的数据格式。但是加密后的数据存储需要一定的机制因为该数据可以被其他任何进程用来解密当然`CryptProtectData`也提供了用户输入额外`数据`来参与对用户数据进行加密的参数但依然无法放于暴力破解。

    总体来说程序可以使用DPAPI来对自己敏感的数据进行加解密也可持久化存储程序或系统重启后可解密密文获取原文。如果应用程序对此敏感数据只是暂存于内存为了防止被黑客dump内存后进行破解也对此数据无需进行持久化存储微软还提供了加解密内存的接口`CryptProtectMemory`和`CryptUnprotectMemory`。加解密内存的接口并可指定`Flag`对此内存加解密的声明周期做控制详细见`Memory加密及优缺点`章节

    废话不多说我们且来看看新版的Chrome是怎么一个加密流程。首先。我们需要大致清楚新版chrome用到的加密。无非就是2个 划重点

    DPAPI

    AES-GCM

    先给大家看一用python写的解密吧

    aes.py
    import os
    import sys
    import sqlite3
    from urllib.parse import urlencode
    import json, base64
    import aesgcm
    import binascii
    def dpapi_decrypt(encrypted):
        import ctypes
        import ctypes.wintypes
    
        class DATA_BLOB(ctypes.Structure):
            _fields_ = [('cbData', ctypes.wintypes.DWORD),
                        ('pbData', ctypes.POINTER(ctypes.c_char))]
        p = ctypes.create_string_buffer(encrypted, len(encrypted))
        blobin = DATA_BLOB(ctypes.sizeof(p), p)
        blobout = DATA_BLOB()
        retval = ctypes.windll.crypt32.CryptUnprotectData(
            ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout))
        if not retval:
            raise ctypes.WinError()
        result = ctypes.string_at(blobout.pbData, blobout.cbData)
        ctypes.windll.kernel32.LocalFree(blobout.pbData)
        return result
    def aes_decrypt(encrypted_txt):
        encrypted_txt = binascii.unhexlify(encrypted_txt)
        encoded_key = "RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAAFBcVfgeqrR6TWICu+11nQAAAAAAIAAAAAABBmAAAAAQAAIAAAADGFDG3ftjedfJDzI98JL+tPfbE3tgNumX5v+PGs9eEgAAAAAA6AAAAAAgAAIAAAAHMoKUPxu+eC153jdAcreqzjPCvccip33ZQPvnOZstQBMAAAAFCQh824CftlmS+gbu8NK1Gev4EVvODPwV6T33S9AXilInJ26Z09nTULJE3pF+9XtEAAAACndz8ZGF2V7IMxQDK6kFAk6wOUv/Bx9hZhZtiyu2urYfKYbCPvMSWg4e9+/oQrEL2NEG+fFjX/EP6SrLzE8Xqy"
        encrypted_key = base64.b64decode(encoded_key)
        print("encrypted_key="+encrypted_key.hex()+"  Len="+str(len(encrypted_key))+"
    ");
        encrypted_key = encrypted_key[5:]
        print("encrypted_key="+encrypted_key.hex()+"
    ");
        key = dpapi_decrypt(encrypted_key)
        print("key="+key.hex()+"
    "); 
        nonce = encrypted_txt[3:15]
        print("nonce="+nonce.hex()+"
    ");
        cipher = aesgcm.get_cipher(key)
        ##print("cipher="+cipher.hex()+"
    ");
        print("encrypted_txt="+encrypted_txt[15:].hex()+"
    ");
        return aesgcm.decrypt(cipher,encrypted_txt[15:],nonce)
    print(aes_decrypt("76313068C3E4957EC879AD4483CBFA7476E7B77C035D8355A5D73FCFA9A87007D908896061DDD79471"))
    

    然后是aes-gcm

    import os
    import sys
    
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.ciphers import (
        Cipher, algorithms, modes
    )
    
    NONCE_BYTE_SIZE = 12
    
    def encrypt(cipher, plaintext, nonce):
        cipher.mode = modes.GCM(nonce)
        encryptor = cipher.encryptor()
        ciphertext = encryptor.update(plaintext)
        return (cipher, ciphertext, nonce)
    
    def decrypt(cipher, ciphertext, nonce):
        cipher.mode = modes.GCM(nonce)
        decryptor = cipher.decryptor()
        return decryptor.update(ciphertext)
    
    def get_cipher(key):
        cipher = Cipher(
            algorithms.AES(key),
            None,
            backend=default_backend()
        )
        return cipher

    如此即可解密。说下简单的流程吧。

    大致流程从C:UsersopsAppDataLocalGoogleChromeUserDataLocalState这个Json中读取一个值os_crypt下的encrypted_key

    然后取解密秘钥(encrypted_key)去除前5个字符再通过对其dpapi解密出这个值保存为key.并且截取15位去除前3位字符保存为Nonce.

    然后使用asegcm进行解密key最终使用aesgcm解密。

    大致就是如此。为了证明我的屁眼代码可以用。上一个图。稍等。。我去安装下chrome80。。。影子系统还原了。。。我安装好了

    取下encrypted_key和被加密的value的HEX。在这之前 我们先看下加密的内容

    包含V10和V11的是chrme80的加密。好了 我们来找找freebuf的值

    把加密值HEX[v10mC1^ĻI~\`ql>t^c+EO0bJKp1YRn˭F$O]一下得到7631306D43A786939231E0A4D6DC5E**BB497E5C60716CFEFDDB3E74A7ABE2E5F1BAF45EF5F163BC2BB**54F9D30624A4B708D310C168894FFEC189C8959526ECBAD46EF1D7FD224B6868FA64F83CD

    然后我们用python解密一下

    可看到了解密成功。下一篇用C++来实现自动化解密

    几个注意点

    Cookie位于User Data/Default下的Cookies文件 改名为Cookies.db即可用sqllite进行查询和查看

    上一篇实现了python的简单解密。这一次我们来用C++实现自动化。在这之前 我们需要用到两个C++库

    repaidjson
    cryptopp

    编译环境为VS2013.这两个库不多做介绍,rapidjson是腾讯的一个开源json解析库。发挥的作用不大,就是解析个json。另外就是cryptopp。嗯。。很牛逼。

    解析下大致流程:

    1:获取local state文件位置

    2:获取加密的key(base64编码)

    3:解析sqllite文件

    4:DPAPI解密

    5:ase-gcm解密

    关于Aes-gcm 需要用到 KEY IV 以及被加密的字符串 梳理下这几个参数的流程:

    KEY = local state = > os_crypt => Encrypted_key => Base64Decode(encrypted_key) => 去除首位5个字符 => DPAPI解密

    IV = 被加密的字符串掐头去尾

    chiper = 被加密的字符串去头

    一般来说 安装的这些配置文件都在LOCAL_APPDATA下。可以使用SHGetSpecialFolderPath(NULL, szBuffer, CSIDL_LOCAL_APPDATA, FALSE);

    来获取这个路径。然后starcat组合一下字符串得到路径 部分代码如下:

            char szBuffer[MAX_PATH];
    	if (EncryptBaseKey == "")
    	{
    		string jsonstr;
    		SHGetSpecialFolderPath(NULL, szBuffer, CSIDL_LOCAL_APPDATA, FALSE);
    		strcat(szBuffer, "\Google\Chrome\User Data\Local State");
    		jsonstr = readfile(szBuffer);
    		Document root;
    		root.Parse(jsonstr.c_str());
    		Value& infoArray = root["os_crypt"];
    		EncryptBaseKey = infoArray["encrypted_key"].GetString();
    	}
    	return EncryptBaseKey;

    这里就获取了加密的秘钥。但是有一点。如果是非80版本 是不存在os_crypt的,这里使用的rapidjson就会抛出异常。但不影响。只需要在使用sqllite查询的时候 接管一下字符串,看看是不是包含v10或者v11即可。如果你使用的和我一样代码。请注意大小写V10和v10的区别。

                            string e_str = argv[i];
    			if (strstr(e_str.c_str(), "v10") != NULL || strstr(e_str.c_str(), "v11") != NULL)
    			{
    				string DecryptVaule=NewDecrypt(argv[i]);
    				strcpy(enc_value_a, DecryptVaule.c_str());
    			}
    			else{
    				DecryptPass(argv[i], enc_value, 2048);
    				_snprintf_s(enc_value_a, sizeof(enc_value_a), _TRUNCATE, "%S", enc_value);
    			}

    紧接着就是对他进行base64解密。这里我用的是cryptopp  先放一下新版解密函数

    std::string static NewDecrypt(CHAR *cryptData)
    {
    	string EncryptValue = cryptData;
    	string Encoded,Decoded;
    	string key,iv,chiper;
    	string recovered;//也就是解密的KEY
    	WCHAR enc_value[2048];
    	char enc_value_a[2048];
    
    	ZeroMemory(enc_value, sizeof(enc_value));
    	ZeroMemory(enc_value_a, sizeof(enc_value_a));
    	//-----------------------初始化几个要用到加密字符串的变量----------------------------------//
    	iv = EncryptValue;
    	chiper = EncryptValue;
    	//---------------------------------------------------------//
    	StringSource((BYTE*)EncryptValue.c_str(), EncryptValue.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	EncryptValue = Encoded;
    	Encoded.clear();
    	//---------------------------------------------------------//
    	key = GetEncryptKEY();
    	StringSource((BYTE*)key.c_str(), key.size(), true,
    		new Base64Decoder(
    		new StringSink(Decoded)));
    	key = Decoded;
    	key = key.substr(5);//去除首位5个字符
    	Decoded.clear();
    	DecryptPass((char*)key.c_str(), enc_value, 2048);
    	_snprintf_s(enc_value_a, sizeof(enc_value_a), _TRUNCATE, "%S", enc_value);
    	key = enc_value_a;
    	StringSource((BYTE*)key.c_str(),key.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	key = Encoded;
    	Encoded.clear();
    	//KEY解密完毕 开始处理Nonce 也就是IV
    	iv =iv.substr(3,12);
    	StringSource((BYTE*)iv.c_str(), iv.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	iv = Encoded;
    	Encoded.clear();
    	//---------------------------------------------------------//
    	//开始处理chiper
    	if (chiper.size() < 30){ return "wu xiao zi fu chuan....."; }
    	StringSource((BYTE*)chiper.c_str(), chiper.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	chiper = Encoded;
    	Encoded.clear();
    	chiper = chiper.substr(30);//因为是HEX 占了2个字节
    	//---------------------------------------------------------//
    	//进行AES_GCM
    	try
    	{
    		StringSource((BYTE*)iv.c_str(), iv.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		iv = Decoded;
    		Decoded.clear();
    		StringSource((BYTE*)key.c_str(), key.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		key = Decoded;
    		Decoded.clear();
    		StringSource((BYTE*)chiper.c_str(), chiper.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		chiper = Decoded;
    		Decoded.clear();
    		cout << chiper << endl;
    		GCM< AES >::Decryption d;
    		d.SetKeyWithIV((BYTE*)key.c_str(), key.size(), (BYTE*)iv.c_str(), iv.size());
    		StringSource s(chiper, true,
    			new AuthenticatedDecryptionFilter(d,
    			new StringSink(recovered)
    			) // StreamTransformationFilter
    			); // StringSource
    		cout << "recovered text: " << recovered << endl;
    	}
    	catch (const CryptoPP::Exception& e)
    	{
    		cerr << e.what() << endl;
    		//exit(1);
    	}
    	return recovered;
    }

    先base64解码一下

    key = GetEncryptKEY();
    	StringSource((BYTE*)key.c_str(), key.size(), true,
    		new Base64Decoder(
    		new StringSink(Decoded)));
    	key = Decoded;
    	key = key.substr(5);//去除首位5个字符
    	Decoded.clear();

    如此可以得到这一样一个字符串

    这是没有去除字符的情况下,这个时候去除之后 即祛除了首位的DPAPI 如此便获得了一个初步解密的KEY。但在这之后,我们还需要对这个KEY做一次解密,因为这个时候的KEY还不能真正算是解密的KEY 他还需要进行一次DPAPI解密

    DPAPI的解密函数部分代码如下:

    DATA_BLOB input;
        input.pbData = (BYTE*)(cryptData);
    	DATA_BLOB output;
    	DWORD blen;
    
    	for(blen=128; blen<=2048; blen+=16) {
            input.cbData = blen;
    		if (CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output))
    			break;
    	}
    	if (blen>=2048)
    		return 0;
    
    	CHAR *decrypted = (CHAR *)malloc(clearSize);
    	if (!decrypted) {
    		LocalFree(output.pbData);
    		return 0;
    	}
    
    	memset(decrypted, 0, clearSize);
    	memcpy(decrypted, output.pbData, (clearSize < output.cbData) ? clearSize - 1 : output.cbData);
    
    	_snwprintf_s(clearData, clearSize, _TRUNCATE, L"%S", decrypted);
    
    	free(decrypted);
    	LocalFree(output.pbData);
    
    	return 1;

    在解密之后我们可以得到:

    然后我们对加密字符串进行处理,取出iv和chiper。再使用aes-gcm解密即可。

    iv =iv.substr(3,12);
    	StringSource((BYTE*)iv.c_str(), iv.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	iv = Encoded;
    	Encoded.clear();
    	//---------------------------------------------------------//
    	//开始处理chiper
    	if (chiper.size() < 30){ return "wu xiao zi fu chuan....."; }
    	StringSource((BYTE*)chiper.c_str(), chiper.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	chiper = Encoded;
    	Encoded.clear();
    	chiper = chiper.substr(30);//因为是HEX 占了2个字节

    解密

    try
    	{
    		StringSource((BYTE*)iv.c_str(), iv.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		iv = Decoded;
    		Decoded.clear();
    		StringSource((BYTE*)key.c_str(), key.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		key = Decoded;
    		Decoded.clear();
    		StringSource((BYTE*)chiper.c_str(), chiper.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		chiper = Decoded;
    		Decoded.clear();
    		cout << chiper << endl;
    		GCM< AES >::Decryption d;
    		d.SetKeyWithIV((BYTE*)key.c_str(), key.size(), (BYTE*)iv.c_str(), iv.size());
    		StringSource s(chiper, true,
    			new AuthenticatedDecryptionFilter(d,
    			new StringSink(recovered)
    			) // StreamTransformationFilter
    			); // StringSource
    		cout << "recovered text: " << recovered << endl;
    	}
    	catch (const CryptoPP::Exception& e)
    	{
    		cerr << e.what() << endl;
    		//exit(1);
    	}
    	return recovered;

    最终献上Demo源码

    // Chrome80解密Demo.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <string>
    #include <fstream>
    #include <iostream>
    /*********************************
    加密库头存放在这
    *********************************/
    #include "cryptoppase64.h"
    using CryptoPP::Base64Decoder;
    using CryptoPP::Base64Encoder;
    #include "cryptopp/hex.h"
    using CryptoPP::HexEncoder;
    using CryptoPP::HexDecoder;
    #include "cryptopp/filters.h"
    using CryptoPP::StringSink;
    using CryptoPP::StringSource;
    using CryptoPP::AuthenticatedEncryptionFilter;
    using CryptoPP::AuthenticatedDecryptionFilter;
    #include "cryptopp/aes.h"
    using CryptoPP::AES;
    #include "cryptopp/gcm.h"
    using CryptoPP::GCM;
    #include "cryptopp/secblock.h"
    using CryptoPP::SecByteBlock;
    /*********************************
    加密库头加载完毕
    *********************************/
    using namespace std;
    #pragma comment(lib,"userenv.lib")
    #pragma comment(lib,"cryptlib.lib")
    #pragma comment(lib,"Crypt32.lib")
    //RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAAFBcVfgeqrR6TWICu+11nQAAAAAAIAAAAAABBmAAAAAQAAIAAAAJxLse8lqGAP4o493iTyljEUUF9y76AAoprRgHJwesCyAAAAAA6AAAAAAgAAIAAAAFtTd4B22Ky/x2LVgQUSaKku2rCvsv+FiMFj+lGN8LmZMAAAANBlkfPhV/zVaMALHr0gK6dM7nFsfNTv6bfFKCyKbIorgbBnjfKp+K5MVz9iizYVs0AAAACihmRGBIQ6oDkgjzCk+9AhePof4eUhB98pb7UlbGgssV2fnGRrBYQHW8Gyyp9W4pojyn9J7GQixtdCIPBwEW92
    //763130954DBA6D89BBAB2FF4A4460AEA7B823BA5BAF01B2B5E2CECDED5855F6E1E7B57946599C6ACD7D60F4B03FC11D5F7C6A39FA59FBF33D7
    int  DecryptPass(CHAR *cryptData, WCHAR *clearData, UINT clearSize)
    {
    	DATA_BLOB input;
    	input.pbData = (BYTE*)(cryptData);
    	DATA_BLOB output;
    	DWORD blen;
    
    	for (blen = 128; blen <= 2048; blen += 16) {
    		input.cbData = blen;
    		if (CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output))
    			break;
    	}
    	if (blen >= 2048)
    		return 0;
    
    	CHAR *decrypted = (CHAR *)malloc(clearSize);
    	if (!decrypted) {
    		LocalFree(output.pbData);
    		return 0;
    	}
    
    	memset(decrypted, 0, clearSize);
    	memcpy(decrypted, output.pbData, (clearSize < output.cbData) ? clearSize - 1 : output.cbData);
    
    	_snwprintf_s(clearData, clearSize, _TRUNCATE, L"%S", decrypted);
    
    	free(decrypted);
    	LocalFree(output.pbData);
    
    	return 1;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
    	string EncryptValue;
    	string key, iv, chiper, recovered;
    	string Decoded, Encoded;
    	WCHAR enc_value[2048];
    	char enc_value_a[2048];
    	ZeroMemory(enc_value, sizeof(enc_value));
    	ZeroMemory(enc_value_a, sizeof(enc_value_a));
    	cout << "请输入EncryptKEY[BASE64]:" <<  endl;
    	cin >> key;
    	cout << "请输入EncryptValue[HEX]:" << endl;
    	cin >> EncryptValue;
    	cout << "<---------------开始解密流程--------------->
    " << endl;
    	//开始赋值
    	iv = EncryptValue;
    	chiper = EncryptValue;
    	StringSource((BYTE*)key.c_str(), key.size(), true,
    		new Base64Decoder(
    		new StringSink(Decoded)));
    	key = Decoded;
    	Decoded.clear();
    	cout << "1:EncryptKEY 进行Base64解密:
    " << key << "
    " << endl;
    	key = key.substr(5);
    	cout << "2:EncryptKEY 去除首5个字符:
    " << key << "
    " << endl;
    	DecryptPass((char*)key.c_str(), enc_value, 2048);
    	_snprintf_s(enc_value_a, sizeof(enc_value_a), _TRUNCATE, "%S", enc_value);
    	key = enc_value_a;
    	cout << "3:EncryptKEY 进行DPAPI解密:
    " << key << "
    " << endl;
    	StringSource((BYTE*)key.c_str(), key.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	key = Encoded;
    	Encoded.clear();
    	cout << "4:对已经通过DPAPI的EncryptKEY 进行HEX编码:
    " << key << "
    " << endl;
    	StringSource((BYTE*)iv.c_str(), iv.size(), true,
    		new HexDecoder(
    		new StringSink(Decoded)));
    	iv = Decoded;
    	Decoded.clear();
    	iv=iv.substr(3, 15);
    	StringSource((BYTE*)iv.c_str(), iv.size(), true,
    		new HexEncoder(
    		new StringSink(Encoded)));
    	iv = Encoded;
    	Encoded.clear();
    	iv = iv.substr(0,iv.size()-6);
    	cout << "5:对要解密的字符串进行反HEX编码 也就是解码 并且截取之后再次 进行HEX编码 赋值给iv:
    " << iv << "
    " << endl;
    	chiper = chiper.substr(30);
    	cout << "6:对要解密的字符串进行截取末尾15:
    " << chiper << "
    " << endl;
    	try
    	{
    		StringSource((BYTE*)iv.c_str(), iv.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		iv = Decoded;
    		Decoded.clear();
    		StringSource((BYTE*)key.c_str(), key.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		key = Decoded;
    		Decoded.clear();
    		StringSource((BYTE*)chiper.c_str(), chiper.size(), true,
    			new HexDecoder(
    			new StringSink(Decoded)
    			) // HexEncoder
    			); // StringSource
    		chiper = Decoded;
    		Decoded.clear();
    		cout << chiper << endl;
    		GCM< AES >::Decryption d;
    		d.SetKeyWithIV((BYTE*)key.c_str(), key.size(), (BYTE*)iv.c_str(), iv.size());
    		StringSource s(chiper, true,
    			new AuthenticatedDecryptionFilter(d,
    			new StringSink(recovered)
    			) // StreamTransformationFilter
    			); // StringSource
    		cout << "7:最终解密文本为:
    " << recovered << "
    " << endl;
    	}
    	catch (const CryptoPP::Exception& e)
    	{
    		cerr << e.what() << endl;
    		//exit(1);
    	}
    	system("pause");
    	return 0;
    }
    

    附上一张解密靓照

    核对下解密的密文是否正确

    欢迎的加入群:480257002

    *本文作者:hijacking,转载请注明来自FreeBuf.COM

  • 相关阅读:
    mybatis 从数据库查询的信息不完整解决办法
    Mybatis关联查询,查询出的记录数量与数据库直接查询不一致,如何解决?
    UltraEdit快捷键大全-UltraEdit常用快捷键大全
    使用UltraEdit 替换解决---文字中含有逗号的文件,如何把逗号自动转换成为:回车换行呢?
    在对文件进行随机读写,RandomAccessFile类,如何提高其效率
    雷军:曾经干掉山寨机,现在干掉山寨店(将性价比进行到底)
    tbox的项目:vm86(汇编语言虚拟机),tbox(类似dlib),gbox(c语言实现的多平台图形库)
    Delphi XE8 iOS与Android移动应用开发(APP开发)教程[完整中文版]
    人和动物的最大区别,在于能否控制自己
    CWnd和HWND的区别(hWnd只是CWnd对象的一个成员变量,代表与这个对象绑定的窗口)
  • 原文地址:https://www.cnblogs.com/h2zZhou/p/12843739.html
Copyright © 2011-2022 走看看