x
This commit is contained in:
118
internal/wechat/key/darwin/glance/glance.go
Normal file
118
internal/wechat/key/darwin/glance/glance.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package glance
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FIXME 按照 region 读取效率较低,512MB 内存读取耗时约 18s
|
||||
|
||||
type Glance struct {
|
||||
PID uint32
|
||||
MemRegions []MemRegion
|
||||
pipePath string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func NewGlance(pid uint32) *Glance {
|
||||
return &Glance{
|
||||
PID: pid,
|
||||
pipePath: filepath.Join(os.TempDir(), fmt.Sprintf("chatlog_pipe_%d", time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Glance) Read() ([]byte, error) {
|
||||
if g.data != nil {
|
||||
return g.data, nil
|
||||
}
|
||||
|
||||
regions, err := GetVmmap(g.PID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.MemRegions = MemRegionsFilter(regions)
|
||||
|
||||
if len(g.MemRegions) == 0 {
|
||||
return nil, fmt.Errorf("no memory regions found")
|
||||
}
|
||||
|
||||
region := g.MemRegions[0]
|
||||
|
||||
// 1. Create pipe file
|
||||
if err := exec.Command("mkfifo", g.pipePath).Run(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create pipe file: %w", err)
|
||||
}
|
||||
defer os.Remove(g.pipePath)
|
||||
|
||||
// Start a goroutine to read from the pipe
|
||||
dataCh := make(chan []byte, 1)
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
// Open pipe for reading
|
||||
file, err := os.OpenFile(g.pipePath, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("failed to open pipe for reading: %w", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read all data from pipe
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("failed to read from pipe: %w", err)
|
||||
return
|
||||
}
|
||||
dataCh <- data
|
||||
}()
|
||||
|
||||
// 2 & 3. Execute lldb command to read memory directly with all parameters
|
||||
size := region.End - region.Start
|
||||
lldbCmd := fmt.Sprintf("lldb -p %d -o \"memory read --binary --force --outfile %s --count %d 0x%x\" -o \"quit\"",
|
||||
g.PID, g.pipePath, size, region.Start)
|
||||
|
||||
cmd := exec.Command("bash", "-c", lldbCmd)
|
||||
|
||||
// Set up stdout pipe for monitoring (optional)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
// Start the command
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start lldb: %w", err)
|
||||
}
|
||||
|
||||
// Monitor lldb output (optional)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
// Uncomment for debugging:
|
||||
// fmt.Println(scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for data with timeout
|
||||
select {
|
||||
case data := <-dataCh:
|
||||
g.data = data
|
||||
case err := <-errCh:
|
||||
return nil, fmt.Errorf("failed to read memory: %w", err)
|
||||
case <-time.After(30 * time.Second):
|
||||
cmd.Process.Kill()
|
||||
return nil, fmt.Errorf("timeout waiting for memory data")
|
||||
}
|
||||
|
||||
// Wait for the command to finish
|
||||
if err := cmd.Wait(); err != nil {
|
||||
// We already have the data, so just log the error
|
||||
fmt.Printf("Warning: lldb process exited with error: %v\n", err)
|
||||
}
|
||||
|
||||
return g.data, nil
|
||||
}
|
||||
37
internal/wechat/key/darwin/glance/sip.go
Normal file
37
internal/wechat/key/darwin/glance/sip.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package glance
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsSIPDisabled checks if System Integrity Protection (SIP) is disabled on macOS.
|
||||
// Returns true if SIP is disabled, false if it's enabled or if the status cannot be determined.
|
||||
func IsSIPDisabled() bool {
|
||||
// Run the csrutil status command to check SIP status
|
||||
cmd := exec.Command("csrutil", "status")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
// If there's an error running the command, assume SIP is enabled
|
||||
return false
|
||||
}
|
||||
|
||||
// Convert output to string and check if SIP is disabled
|
||||
outputStr := strings.ToLower(string(output))
|
||||
|
||||
// $ csrutil status
|
||||
// System Integrity Protection status: disabled.
|
||||
|
||||
// If the output contains "disabled", SIP is disabled
|
||||
if strings.Contains(outputStr, "system integrity protection status: disabled") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for partial SIP disabling - some configurations might have specific protections disabled
|
||||
if strings.Contains(outputStr, "disabled") && strings.Contains(outputStr, "debugging") {
|
||||
return true
|
||||
}
|
||||
|
||||
// By default, assume SIP is enabled
|
||||
return false
|
||||
}
|
||||
158
internal/wechat/key/darwin/glance/vmmap.go
Normal file
158
internal/wechat/key/darwin/glance/vmmap.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package glance
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
FilterRegionType = "MALLOC_NANO"
|
||||
FilterSHRMOD = "SM=PRV"
|
||||
CommandVmmap = "vmmap"
|
||||
)
|
||||
|
||||
type MemRegion struct {
|
||||
RegionType string
|
||||
Start uint64
|
||||
End uint64
|
||||
VSize uint64 // Size in bytes
|
||||
RSDNT uint64 // Resident memory size in bytes (new field)
|
||||
SHRMOD string
|
||||
Permissions string
|
||||
RegionDetail string
|
||||
}
|
||||
|
||||
func GetVmmap(pid uint32) ([]MemRegion, error) {
|
||||
// Execute vmmap command
|
||||
cmd := exec.Command(CommandVmmap, "-wide", fmt.Sprintf("%d", pid))
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing vmmap command: %w", err)
|
||||
}
|
||||
|
||||
// Parse the output using the existing LoadVmmap function
|
||||
return LoadVmmap(string(output))
|
||||
}
|
||||
|
||||
func LoadVmmap(output string) ([]MemRegion, error) {
|
||||
var regions []MemRegion
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(output))
|
||||
|
||||
// Skip lines until we find the header
|
||||
foundHeader := false
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "==== Writable regions for") {
|
||||
foundHeader = true
|
||||
// Skip the column headers line
|
||||
scanner.Scan()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundHeader {
|
||||
return nil, nil // No vmmap data found
|
||||
}
|
||||
|
||||
// Regular expression to parse the vmmap output lines
|
||||
// Format: REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
|
||||
// Updated regex to capture RSDNT value (second value in brackets)
|
||||
re := regexp.MustCompile(`^(\S+)\s+([0-9a-f]+)-([0-9a-f]+)\s+\[\s*(\S+)\s+(\S+)(?:\s+\S+){2}\]\s+(\S+)\s+(\S+)(?:\s+\S+)?\s+(.*)$`)
|
||||
|
||||
// Parse each line
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if len(matches) >= 9 { // Updated to check for at least 9 matches
|
||||
|
||||
// Parse start and end addresses
|
||||
start, _ := strconv.ParseUint(matches[2], 16, 64)
|
||||
end, _ := strconv.ParseUint(matches[3], 16, 64)
|
||||
|
||||
// Parse VSize as numeric value
|
||||
vsize := parseSize(matches[4])
|
||||
|
||||
// Parse RSDNT as numeric value (new)
|
||||
rsdnt := parseSize(matches[5])
|
||||
|
||||
region := MemRegion{
|
||||
RegionType: strings.TrimSpace(matches[1]),
|
||||
Start: start,
|
||||
End: end,
|
||||
VSize: vsize,
|
||||
RSDNT: rsdnt, // Add the new RSDNT field
|
||||
Permissions: matches[6], // Shifted index
|
||||
SHRMOD: matches[7], // Shifted index
|
||||
RegionDetail: strings.TrimSpace(matches[8]), // Shifted index
|
||||
}
|
||||
|
||||
regions = append(regions, region)
|
||||
}
|
||||
}
|
||||
|
||||
return regions, nil
|
||||
}
|
||||
|
||||
func MemRegionsFilter(regions []MemRegion) []MemRegion {
|
||||
var filteredRegions []MemRegion
|
||||
for _, region := range regions {
|
||||
if region.RegionType == FilterRegionType {
|
||||
filteredRegions = append(filteredRegions, region)
|
||||
}
|
||||
}
|
||||
return filteredRegions
|
||||
}
|
||||
|
||||
// parseSize converts size strings like "5616K" or "128.0M" to bytes (uint64)
|
||||
func parseSize(sizeStr string) uint64 {
|
||||
// Remove any whitespace
|
||||
sizeStr = strings.TrimSpace(sizeStr)
|
||||
|
||||
// Define multipliers for different units
|
||||
multipliers := map[string]uint64{
|
||||
"B": 1,
|
||||
"K": 1024,
|
||||
"KB": 1024,
|
||||
"M": 1024 * 1024,
|
||||
"MB": 1024 * 1024,
|
||||
"G": 1024 * 1024 * 1024,
|
||||
"GB": 1024 * 1024 * 1024,
|
||||
}
|
||||
|
||||
// Regular expression to match numbers with optional decimal point and unit
|
||||
// This will match formats like: "5616K", "128.0M", "1.5G", etc.
|
||||
re := regexp.MustCompile(`^(\d+(?:\.\d+)?)([KMGB]+)?$`)
|
||||
matches := re.FindStringSubmatch(sizeStr)
|
||||
|
||||
if len(matches) < 2 {
|
||||
return 0 // No match found
|
||||
}
|
||||
|
||||
// Parse the numeric part (which may include a decimal point)
|
||||
numStr := matches[1]
|
||||
numVal, err := strconv.ParseFloat(numStr, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Determine the multiplier based on the unit
|
||||
multiplier := uint64(1) // Default if no unit specified
|
||||
if len(matches) >= 3 && matches[2] != "" {
|
||||
unit := matches[2]
|
||||
if m, ok := multipliers[unit]; ok {
|
||||
multiplier = m
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final size in bytes (rounding to nearest integer)
|
||||
return uint64(numVal*float64(multiplier) + 0.5)
|
||||
}
|
||||
Reference in New Issue
Block a user