Files
shuffle-cracked/backend/go-app/shuffle-shared/textClassifier.go
T
Marat Kharitonov 4f3f07d4dd Crack: bypass license check - force all limits unlimited
- Vendor shuffle-shared v1.2.51 as backend/go-app/shuffle-shared
- Add replace directive in go.mod to use the local moduled copy
- In HandleCheckLicense, force org.Licensed=true and set every
  SyncFeatures limit to 1e9, skipping all license-key logic
- Update Dockerfile to ADD the local shuffle-shared before go build
- Verified: backend image builds successfully via docker
2026-08-12 03:14:24 +03:00

271 lines
8.5 KiB
Go

package shuffle
// Basic classifier that tries to look for similarities without requiring a lot of resources.
// FIXME: Not doing loops at all yet.
import (
//"bytes"
"context"
"encoding/json"
"fmt"
"log"
"regexp"
"strings"
"github.com/adrg/strutil"
"github.com/adrg/strutil/metrics"
//"github.com/rcrowley/go-metrics"
"reflect"
)
func findSimilarity(blob1, blob2 string, onlyItems []string) (int64, []string) {
var blob1Map map[string]interface{}
var blob2Map map[string]interface{}
err := json.Unmarshal([]byte(blob1), &blob1Map)
if err != nil {
//log.Printf("[WARNING] Something went wrong for blob1: %s", err)
blob1Map = map[string]interface{}{
"default": blob1,
}
blob2Map = map[string]interface{}{
"default": blob2,
}
} else {
err = json.Unmarshal([]byte(blob2), &blob2Map)
if err != nil {
//log.Printf("[WARNING] Something went wrong for blob2: %s", err)
blob2Map = map[string]interface{}{
"default": blob2,
}
}
}
allValues, skippedValues := findSimilarityInterface("", blob1Map, blob2Map, onlyItems)
log.Printf("Allvalues: %#v", allValues)
log.Printf("SkippedValues: %#v", skippedValues)
if len(allValues) == 0 {
return 0, skippedValues
}
avg := int64(0)
for _, value := range allValues {
avg = avg + value
}
return avg / int64(len(allValues)), skippedValues
}
func cleanupText(input string) string {
typesToRemove := []string{",", ".", "\n"}
for _, val := range typesToRemove {
input = strings.Replace(input, val, "", -1)
}
input = strings.ToLower(input)
input = strings.TrimSpace(input)
return input
}
func findSimilarityInterface(rootNode string, blob1Map, blob2Map map[string]interface{}, onlyItems []string) ([]int64, []string) {
// clean up data first: stopwords, dots - halvor :)
log.Printf("[DEBUG] Root: %s", rootNode)
// "reflect"
badKeys := []string{"id"}
avgValues := []int64{}
skippedValues := []string{}
for key, value := range blob1Map {
if fmt.Sprintf("%s", reflect.TypeOf(value)) == "string" {
if len(onlyItems) > 0 && !ArrayContains(onlyItems, strings.ToLower(key)) {
skippedValues = append(skippedValues, key)
continue
}
if ArrayContains(badKeys, key) {
skippedValues = append(skippedValues, key)
continue
}
newValue1 := cleanupText(fmt.Sprintf("%#v", blob1Map[key]))
newValue2 := cleanupText(fmt.Sprintf("%#v", blob2Map[key]))
//log.Printf("Val1: %#v, Val2: %#v", newValue1, newValue2)
//fmt.Printf("%.2f\n", similarity) // Output: 0.43
similarity := strutil.Similarity(newValue1, newValue2, metrics.NewLevenshtein())
avgValues = append(avgValues, int64(similarity*100))
} else if fmt.Sprintf("%s", reflect.TypeOf(value)) == "map[string]interface {}" {
var mappedValue2 map[string]interface{}
if val, ok := blob2Map[key]; ok {
mappedValue2 = val.(map[string]interface{})
} else {
continue
}
mappedValue1 := value.(map[string]interface{})
newRootnode := fmt.Sprintf("%s/%s", rootNode, key)
parentValue, parentSkipped := findSimilarityInterface(newRootnode, mappedValue1, mappedValue2, onlyItems)
avgValues = append(avgValues, parentValue...)
skippedValues = append(skippedValues, parentSkipped...)
} else {
log.Printf("%s: %s", key, reflect.TypeOf(value))
}
}
return avgValues, skippedValues
}
// Checks same workflow's executions if it has had something similar happening in the last 10 workflows
func RunTextClassifier(ctx context.Context, workflowExecution WorkflowExecution) {
// Onlyitems is here in case we JUST want to look for specific keys. Could be per action, app or workflow
onlyItems := []string{}
maxCheck := 10
workflowExecutions, err := GetAllWorkflowExecutions(ctx, workflowExecution.Workflow.ID, 50)
if err != nil {
log.Printf("[WARNING] Failed getting executions for %s in text classifier: %s", workflowExecution.Workflow.ID, err)
return
}
// Compare with at most 10 previous
if len(workflowExecutions) > maxCheck {
workflowExecutions = workflowExecutions[0 : maxCheck-1]
}
updatedExecutions := []string{}
for mainResultKey, mainResult := range workflowExecution.Results {
if len(mainResult.Result) == 0 {
continue
}
for executionKey, execution := range workflowExecutions {
if execution.ExecutionId == mainResult.ExecutionId {
continue
}
// Need to be same length
if len(execution.Results) != len(workflowExecution.Results) {
continue
}
executionAdded := false
for subResultKey, result := range execution.Results {
if mainResult.Action.ID != result.Action.ID {
continue
}
// FIXME: 100% match for this action
//log.Printf("[DEBUG] Checking action %s (%s)", mainResult.Action.Name, mainResult.Action.ID)
similarity := int64(0)
if mainResult.Result == result.Result {
//log.Printf("[DEBUG] They are exactly equal\n")
// Skip exactly equal for now
//similarity = 100
} else {
similarity, skippedItems := findSimilarity(mainResult.Result, result.Result, onlyItems)
log.Printf("[DEBUG] Similarity: %d, Skipped: %#v\n", similarity, skippedItems)
}
if similarity > 0 {
workflowExecution.Results[mainResultKey].SimilarActions = append(workflowExecution.Results[mainResultKey].SimilarActions, SimilarAction{
ExecutionId: execution.ExecutionId,
Similarity: similarity,
})
workflowExecutions[executionKey].Results[subResultKey].SimilarActions = append(workflowExecutions[executionKey].Results[subResultKey].SimilarActions, SimilarAction{
ExecutionId: workflowExecution.ExecutionId,
Similarity: similarity,
})
if !executionAdded {
executionAdded = true
updatedExecutions = append(updatedExecutions, execution.ExecutionId)
}
}
}
}
}
for _, execution := range workflowExecutions {
if execution.ExecutionId == workflowExecution.ExecutionId {
continue
}
if !ArrayContains(updatedExecutions, execution.ExecutionId) {
continue
}
//log.Printf("Should update %s", execution.ExecutionId)
err := SetWorkflowExecution(ctx, execution, true)
if err != nil {
log.Printf("[WARNING] Failed to update execution %s", execution.ExecutionId)
}
}
// Means main one is also updated
if len(updatedExecutions) > 0 {
//log.Printf("Should current: %s", execution.ExecutionId)
err := SetWorkflowExecution(ctx, workflowExecution, true)
if err != nil {
log.Printf("[WARNING] Failed to update main execution %s", workflowExecution.ExecutionId)
}
}
}
func runDedup(inputArr []string) []string {
newarr := []string{}
for _, value := range inputArr {
if !ArrayContains(newarr, value) {
newarr = append(newarr, value)
}
}
return newarr
}
// Finds IPs, domains and hashes
// Point is to test out how we can create a structured database of these, correlate with, and store them
func RunIOCFinder(ctx context.Context, workflowExecution WorkflowExecution) {
numBlock := "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
regexPattern := numBlock + "\\." + numBlock + "\\." + numBlock + "\\." + numBlock
ips := regexp.MustCompile(regexPattern)
domains := regexp.MustCompile(`^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$`)
//urls := regexp.MustCompile(`(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})`)
//urls := regexp.MustCompile(`(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})`)
md5s := regexp.MustCompile(`[a-f0-9]{32}`)
sha256s := regexp.MustCompile(`[A-Fa-f0-9]{64}`)
foundIps := []string{}
foundDomains := []string{}
foundMd5s := []string{}
foundSha256s := []string{}
for _, result := range workflowExecution.Results {
// Too big?
//if len(result.Result) > 1000000 {
// continue
//}
foundIps = append(foundIps, ips.FindAllString(result.Result, -1)...)
foundDomains = append(foundDomains, domains.FindAllString(result.Result, -1)...)
foundMd5s = append(foundMd5s, md5s.FindAllString(result.Result, -1)...)
foundSha256s = append(foundSha256s, sha256s.FindAllString(result.Result, -1)...)
}
foundIps = runDedup(foundIps)
foundDomains = runDedup(foundDomains)
foundMd5s = runDedup(foundMd5s)
foundSha256s = runDedup(foundSha256s)
//fmt.Printf("[DEBUG][%s] IPS: %#v, Domains: %#v, Md5s: %#v, Sha256s: %#v", workflowExecution.ExecutionId, foundIps, foundDomains, foundMd5s, foundSha256s)
}