Last active
October 5, 2021 16:59
-
-
Save JamesHovious/1d6adb1dd6623af88735eeb5eba08eba to your computer and use it in GitHub Desktop.
Extract a Hancitor configuration using only the Go standard library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package hancitor | |
// References: | |
// https://hub.gke2.mybinder.org/user/oalabs-lab-notes-4vubrm7f/notebooks/Hancitor/hancitor.ipynb | |
// https://www.youtube.com/watch?v=OQuRwpUTBpQ | |
// https://www.binarydefense.com/analysis-of-hancitor-when-boring-begets-beacon/ | |
// https://github.com/kevoreilly/CAPEv2/blob/master/modules/processing/parsers/mwcp/Hancitor.py | |
import ( | |
"bytes" | |
"crypto/rc4" | |
"crypto/sha1" | |
"debug/pe" | |
"encoding/json" | |
"errors" | |
"fmt" | |
"net/url" | |
"strings" | |
"unicode" | |
) | |
// Config represents a parsed malware configuration | |
type Config struct { | |
Url []url.URL | |
CampaignID string | |
Family string | |
Raw json.RawMessage // any data that is not represented elsewhere in the struct can be put here | |
} | |
// Extract extracts configuration a HANCITOR PE | |
func Extract(mal *pe.File) (config.Config, error) { | |
if mal.Section(".data") == nil { | |
return config.Config{}, errors.New("invalid pointer to PE data section") | |
} | |
dataSection, err := mal.Section(".data").Data() | |
if err != nil { | |
return config.Config{}, err | |
} | |
// using hard coded offsets since debug/pe doesn't | |
// let you calculate them | |
hash := sha1.Sum(dataSection[16:24]) | |
rc4Key := hash[:5] | |
// again, hard coded offsets | |
// 24 == starting after the key material | |
// 2000 == total size of config | |
conf, err := decryptConfig(rc4Key, dataSection[24:2000]) | |
if err != nil { | |
return config.Config{}, err | |
} | |
return conf, nil | |
} | |
func decryptConfig(rc4Key, ciphertext []byte) (config.Config, error) { | |
cipher, err := rc4.NewCipher(rc4Key) | |
if err != nil { | |
return config.Config{}, err | |
} | |
// decrypt the config | |
cipher.XORKeyStream(ciphertext, ciphertext) | |
var conf config.Config | |
build := buildID(ciphertext) | |
r, err := json.Marshal(fmt.Sprintf(`{"rc4_key": "%v" }`, rc4Key)) | |
if err != nil { | |
return config.Config{}, err | |
} | |
// remove the build_id from the config | |
minusBuild := strings.TrimPrefix(string(ciphertext), build) | |
// the config is a single pipe delimited string | |
c2s := strings.Split(minusBuild, "|") | |
for _, c2 := range c2s { | |
cleaned := string(bytes.Trim([]byte(c2), "\x00")) | |
u, err := url.Parse(cleaned) | |
if err != nil { | |
continue | |
} | |
if config.IsTLDValid(u.Host) { | |
conf.Url = append(conf.Url, *u) | |
} | |
} | |
conf.CampaignID = build | |
conf.Raw = r | |
conf.Family = "hancitor" | |
return conf, nil | |
} | |
func buildID(buf []byte) string { | |
f := func(c rune) bool { | |
return !unicode.IsLetter(c) && !unicode.IsNumber(c) | |
} | |
// parse out characters from the junk | |
fields := bytes.FieldsFunc(buf, f) | |
return string(fields[0]) + "_" + string(fields[1]) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment