2 Commits

Author SHA1 Message Date
Sarv
ba3563ad4e fix v4 chatroom display name (#26) 2025-04-10 16:25:09 +08:00
Sarv
4983d27054 support multi key pattern matching (#25) 2025-04-10 14:53:17 +08:00
9 changed files with 163 additions and 84 deletions

View File

@@ -22,6 +22,8 @@ func initLog(cmd *cobra.Command, args []string) {
if Debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
}
func initTuiLog(cmd *cobra.Command, args []string) {

View File

@@ -20,9 +20,17 @@ func (c *ChatRoomV4) Wrap() *ChatRoom {
users = ParseRoomData(c.ExtBuffer)
}
user2DisplayName := make(map[string]string, len(users))
for _, user := range users {
if user.DisplayName != "" {
user2DisplayName[user.UserName] = user.DisplayName
}
}
return &ChatRoom{
Name: c.UserName,
Owner: c.Owner,
Users: users,
Name: c.UserName,
Owner: c.Owner,
Users: users,
User2DisplayName: user2DisplayName,
}
}

View File

@@ -15,13 +15,17 @@ type Validator struct {
}
// NewValidator 创建一个仅用于验证的验证器
func NewValidator(dataDir string, platform string, version int) (*Validator, error) {
func NewValidator(platform string, version int, dataDir string) (*Validator, error) {
dbFile := GetSimpleDBFile(platform, version)
dbPath := filepath.Join(dataDir + "/" + dbFile)
return NewValidatorWithFile(platform, version, dbPath)
}
func NewValidatorWithFile(platform string, version int, dbPath string) (*Validator, error) {
decryptor, err := NewDecryptor(platform, version)
if err != nil {
return nil, err
}
dbFile := GetSimpleDBFile(platform, version)
dbPath := filepath.Join(dataDir + "/" + dbFile)
d, err := common.OpenDBFile(dbPath, decryptor.GetPageSize())
if err != nil {
return nil, err

View File

@@ -19,12 +19,22 @@ const (
MaxWorkersV3 = 8
)
var V3KeyPatterns = []KeyPatternInfo{
{
Pattern: []byte{0x72, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x33, 0x32},
Offset: 24,
},
}
type V3Extractor struct {
validator *decrypt.Validator
validator *decrypt.Validator
keyPatterns []KeyPatternInfo
}
func NewV3Extractor() *V3Extractor {
return &V3Extractor{}
return &V3Extractor{
keyPatterns: V3KeyPatterns,
}
}
func (e *V3Extractor) Extract(ctx context.Context, proc *model.Process) (string, error) {
@@ -127,7 +137,6 @@ func (e *V3Extractor) findMemory(ctx context.Context, pid uint32, memoryChannel
// worker processes memory regions to find V3 version key
func (e *V3Extractor) worker(ctx context.Context, memoryChannel <-chan []byte, resultChannel chan<- string) {
keyPattern := []byte{0x72, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x33, 0x32}
for {
select {
case <-ctx.Done():
@@ -137,51 +146,60 @@ func (e *V3Extractor) worker(ctx context.Context, memoryChannel <-chan []byte, r
return
}
index := len(memory)
for {
if key, ok := e.SearchKey(ctx, memory); ok {
select {
case <-ctx.Done():
return // Exit if context cancelled
case resultChannel <- key:
default:
}
log.Debug().Msgf("Searching for V3 key in memory region, size: %d bytes", len(memory))
// Find pattern from end to beginning
index = bytes.LastIndex(memory[:index], keyPattern)
if index == -1 {
break // No more matches found
}
log.Debug().Msgf("Found potential V3 key pattern in memory region, index: %d", index)
// For V3, the key is 32 bytes and starts right after the pattern
if index+24+32 > len(memory) {
index -= 1
continue
}
// Extract the key data, which is right after the pattern and 32 bytes long
keyOffset := index + 24
keyData := memory[keyOffset : keyOffset+32]
// Validate key against database header
if e.validator.Validate(keyData) {
select {
case resultChannel <- hex.EncodeToString(keyData):
log.Debug().Msg("Key found: " + hex.EncodeToString(keyData))
return
default:
}
}
index -= 1
}
}
}
}
func (e *V3Extractor) SearchKey(ctx context.Context, memory []byte) (string, bool) {
for _, keyPattern := range e.keyPatterns {
index := len(memory)
for {
select {
case <-ctx.Done():
return "", false
default:
}
// Find pattern from end to beginning
index = bytes.LastIndex(memory[:index], keyPattern.Pattern)
if index == -1 {
break // No more matches found
}
// Check if we have enough space for the key
keyOffset := index + keyPattern.Offset
if keyOffset < 0 || keyOffset+32 > len(memory) {
index -= 1
continue
}
// Extract the key data, which is 32 bytes long
keyData := memory[keyOffset : keyOffset+32]
// Validate key against database header
if e.validator.Validate(keyData) {
log.Debug().
Str("pattern", hex.EncodeToString(keyPattern.Pattern)).
Int("offset", keyPattern.Offset).
Str("key", hex.EncodeToString(keyData)).
Msg("Key found")
return hex.EncodeToString(keyData), true
}
index -= 1
}
}
return "", false
}
func (e *V3Extractor) SetValidate(validator *decrypt.Validator) {
e.validator = validator
}

View File

@@ -19,12 +19,26 @@ const (
MaxWorkers = 8
)
var V4KeyPatterns = []KeyPatternInfo{
{
Pattern: []byte{0x20, 0x66, 0x74, 0x73, 0x35, 0x28, 0x25, 0x00},
Offset: 16,
},
{
Pattern: []byte{0x20, 0x66, 0x74, 0x73, 0x35, 0x28, 0x25, 0x00},
Offset: -80,
},
}
type V4Extractor struct {
validator *decrypt.Validator
validator *decrypt.Validator
keyPatterns []KeyPatternInfo
}
func NewV4Extractor() *V4Extractor {
return &V4Extractor{}
return &V4Extractor{
keyPatterns: V4KeyPatterns,
}
}
func (e *V4Extractor) Extract(ctx context.Context, proc *model.Process) (string, error) {
@@ -127,8 +141,6 @@ func (e *V4Extractor) findMemory(ctx context.Context, pid uint32, memoryChannel
// worker processes memory regions to find V4 version key
func (e *V4Extractor) worker(ctx context.Context, memoryChannel <-chan []byte, resultChannel chan<- string) {
keyPattern := []byte{0x20, 0x66, 0x74, 0x73, 0x35, 0x28, 0x25, 0x00}
for {
select {
case <-ctx.Done():
@@ -138,47 +150,65 @@ func (e *V4Extractor) worker(ctx context.Context, memoryChannel <-chan []byte, r
return
}
index := len(memory)
for {
if key, ok := e.SearchKey(ctx, memory); ok {
select {
case <-ctx.Done():
return // Exit if context cancelled
case resultChannel <- key:
default:
}
// Find pattern from end to beginning
index = bytes.LastIndex(memory[:index], keyPattern)
if index == -1 {
break // No more matches found
}
// Check if we have enough space for the key
if index+16+32 > len(memory) {
index -= 1
continue
}
// Extract the key data, which is 16 bytes after the pattern and 32 bytes long
keyOffset := index + 16
keyData := memory[keyOffset : keyOffset+32]
// Validate key against database header
if e.validator.Validate(keyData) {
select {
case resultChannel <- hex.EncodeToString(keyData):
log.Debug().Msg("Key found: " + hex.EncodeToString(keyData))
return
default:
}
}
index -= 1
}
}
}
}
func (e *V4Extractor) SearchKey(ctx context.Context, memory []byte) (string, bool) {
for _, keyPattern := range e.keyPatterns {
index := len(memory)
for {
select {
case <-ctx.Done():
return "", false
default:
}
// Find pattern from end to beginning
index = bytes.LastIndex(memory[:index], keyPattern.Pattern)
if index == -1 {
break // No more matches found
}
// Check if we have enough space for the key
keyOffset := index + keyPattern.Offset
if keyOffset < 0 || keyOffset+32 > len(memory) {
index -= 1
continue
}
// Extract the key data, which is 16 bytes after the pattern and 32 bytes long
keyData := memory[keyOffset : keyOffset+32]
// Validate key against database header
if e.validator.Validate(keyData) {
log.Debug().
Str("pattern", hex.EncodeToString(keyPattern.Pattern)).
Int("offset", keyPattern.Offset).
Str("key", hex.EncodeToString(keyData)).
Msg("Key found")
return hex.EncodeToString(keyData), true
}
index -= 1
}
}
return "", false
}
func (e *V4Extractor) SetValidate(validator *decrypt.Validator) {
e.validator = validator
}
type KeyPatternInfo struct {
Pattern []byte
Offset int
}

View File

@@ -15,6 +15,9 @@ type Extractor interface {
// Extract 从进程中提取密钥
Extract(ctx context.Context, proc *model.Process) (string, error)
// SearchKey 在内存中搜索密钥
SearchKey(ctx context.Context, memory []byte) (string, bool)
SetValidate(validator *decrypt.Validator)
}

View File

@@ -1,6 +1,8 @@
package windows
import (
"context"
"github.com/sjzar/chatlog/internal/wechat/decrypt"
)
@@ -12,6 +14,11 @@ func NewV3Extractor() *V3Extractor {
return &V3Extractor{}
}
func (e *V3Extractor) SearchKey(ctx context.Context, memory []byte) (string, bool) {
// TODO : Implement the key search logic for V3
return "", false
}
func (e *V3Extractor) SetValidate(validator *decrypt.Validator) {
e.validator = validator
}

View File

@@ -1,6 +1,8 @@
package windows
import (
"context"
"github.com/sjzar/chatlog/internal/wechat/decrypt"
)
@@ -12,6 +14,11 @@ func NewV4Extractor() *V4Extractor {
return &V4Extractor{}
}
func (e *V4Extractor) SearchKey(ctx context.Context, memory []byte) (string, bool) {
// TODO : Implement the key search logic for V4
return "", false
}
func (e *V4Extractor) SetValidate(validator *decrypt.Validator) {
e.validator = validator
}

View File

@@ -90,7 +90,7 @@ func (a *Account) GetKey(ctx context.Context) (string, error) {
return "", err
}
validator, err := decrypt.NewValidator(process.DataDir, process.Platform, process.Version)
validator, err := decrypt.NewValidator(process.Platform, process.Version, process.DataDir)
if err != nil {
return "", err
}