# 失效提醒

Chrome Blog
大约在 2024.7 附近,谷歌增强了对 Cookie 的加密,现非 Chrome 的进程无法进行解密,故此文章的做法只能适用于老版本的 Chrome,敬请注意。

# 写在前面

哔哩哔哩的动态接口突然变得需要 Cookie 了,Cookie 这种东西变的很快,如果每次失效都手写一遍岂不是很麻烦,还是自动采集浏览器的 Cookie 吧
代码抄自 Gist,改编为了 C# 版本

# AES 解密

.Net 自从.NetCore3.0 之后,在 System.Security.Cryptography 内内置了相关的算法,对于不支持的版本需要类似于 BouncyCastle 的加密算法库

# >= .NetCore3.0

1
2
3
4
5
6
7
8
using System.Security.Cryptography;
public static string Decrypt(byte[] cipher, byte[] key, byte[] nonce, byte[] tag)
{
byte[] plain = new byte[cipher.Length];
using var aes = new AesGcm(key);
aes.Decrypt(nonce, cipher, tag, plain);
return Encoding.UTF8.GetString(plain);
}

# < .NetCore3.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Org.BouncyCastle.Crypto.Parameters;
private static string Decrypt(byte[] ciphertext, byte[] key, byte[] nonce, byte[] tag)
{
var plaintextBytes = new byte[ciphertext.Length];
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), tag.Length * 8, nonce);
cipher.Init(false, parameters);

var bcCiphertext = ciphertext.Concat(tag).ToArray();

var offset = cipher.ProcessBytes(bcCiphertext, 0, bcCiphertext.Length, plaintextBytes, 0);
cipher.DoFinal(plaintextBytes, offset);

return Encoding.UTF8.GetString(plaintextBytes);
}

# 代码

# 调用 CryptUnprotectData

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
private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
private const int ERROR_SUCCESS = 0;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DATA_BLOB
{
public int cbData;
public IntPtr pbData;
}

[DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool CryptUnprotectData(ref DATA_BLOB pCipherText, string pszDescription, IntPtr pEntropy, IntPtr pReserved, IntPtr pPromptStruct, int dwFlags, ref DATA_BLOB pPlainText);

private static byte[] DecryptProtectedData(byte[] ciphertext)
{
DATA_BLOB cipherBlob = new DATA_BLOB();
cipherBlob.cbData = ciphertext.Length;
cipherBlob.pbData = Marshal.AllocHGlobal(ciphertext.Length);
Marshal.Copy(ciphertext, 0, cipherBlob.pbData, ciphertext.Length);

DATA_BLOB plainBlob = new DATA_BLOB();

bool success = CryptUnprotectData(ref cipherBlob, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, ref plainBlob);
if (!success)
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}

byte[] plainBytes = new byte[plainBlob.cbData];
Marshal.Copy(plainBlob.pbData, plainBytes, 0, plainBlob.cbData);

Marshal.FreeHGlobal(cipherBlob.pbData);
Marshal.FreeHGlobal(plainBlob.pbData);

return plainBytes;
}

# SQLite 支持

使用了 SQLSugar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using SqlSugar;
[SugarTable("cookies")]
private class Cookie
{
public string host_key { get; set; } = "";
public string name { get; set; } = "";
public byte[] encrypted_value { get; set; }
}

private static SqlSugarClient GetInstance()
{
SqlSugarClient db = new(new ConnectionConfig()
{
ConnectionString = $"data source=Cookies.db",
DbType = DbType.Sqlite,
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute,
});
return db;
}

# 主体部分

进行解密时,Chrome 应当处于关闭状态,否则 Cookie 文件无法读取

  1. 获取 Cookie 数据库路径,并复制出来
  2. 读取 Chrome 的 Key,并使用 CryptUnprotectData API 解密
  3. 读取数据库文件,查询需要的 Cookie 并解密
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
public static Dictionary<string, string> QueryCookies(string domain)
{
Dictionary<string, string> dict = new();
string path = Environment.GetEnvironmentVariable("APPDATA");
Console.WriteLine(path);
dynamic stateJson = JObject.Parse(File.ReadAllText(path + "/../Local/Google/Chrome/User Data/Local State"));
string key = stateJson.os_crypt.encrypted_key;
byte[] decryptKey = DecryptProtectedData(Convert.FromBase64String(key).Skip(5).ToArray());
if (decryptKey == null || decryptKey.Length < 5)
{
return dict;
}

File.Copy(path + "/../Local/Google/Chrome/User Data/Default/Network/Cookies", "Cookies.db", true);
var r = GetInstance().Queryable<Cookie>().Where(x => x.host_key == domain).ToList();
foreach (var cookie in r.OrderBy(x => x.name))
{
string decryptText = "";
decryptText = Decrypt(cookie.encrypted_value.Skip(15).Take(cookie.encrypted_value.Length - 15 - 16).ToArray()
, decryptKey, cookie.encrypted_value.Skip(3).Take(12).ToArray()
, cookie.encrypted_value.Skip(cookie.encrypted_value.Length - 16).Take(16).ToArray());
dict.Add(cookie.name, decryptText);
}

return dict;
}

# 结果

file

更新于 阅读次数