Skip to content

Instantly share code, notes, and snippets.

@wolveix
Created September 13, 2022 20:58
Show Gist options
  • Save wolveix/af2f09c54d024e8e99736eef4c28aac5 to your computer and use it in GitHub Desktop.
Save wolveix/af2f09c54d024e8e99736eef4c28aac5 to your computer and use it in GitHub Desktop.
PCAP Decryption in Go
package pcap
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/spf13/cast"
)
const (
generateDebugLog = `tshark -n -o "tls.desegment_ssl_records: TRUE" -o "tls.desegment_ssl_application_data: TRUE" -o "tls.keys_list:SERVER_IP_HERE,SERVER_PORT_HERE,http,TLS_KEY_HERE" -o "tls.debug_file:DEBUG_PATH_HERE" -r PCAP_PATH_HERE -Y "(tcp.port eq SERVER_PORT_HERE)"`
injectSessionKey = `editcap --inject-secrets tls,SESSIONKEY_PATH_HERE PCAP_PATH_HERE OUTPUT_PATH_HERE`
parseDebugLog = `grep -A3 'ssl_save_master_key inserted (pre-)master secret for Session ID' DEBUG_PATH_HERE > TMPLOG_PATH_HERE && \
grep -A3 'hash out\[48\]' DEBUG_PATH_HERE >> TMPLOG_PATH_HERE && \
grep -A2 'client random\[32\]' DEBUG_PATH_HERE >> TMPLOG_PATH_HERE && \
grep -A3 'hash out\[48\]' DEBUG_PATH_HERE | sed -e 's/hash/hash repeat/g' >> TMPLOG_PATH_HERE && \
cat TMPLOG_PATH_HERE | sed -e 's/ //g' | awk -F'|' '$1 ~ "storedkey" {printf("RSA Session-ID:");next}
$1 ~ "hashout" {printf(" Master-Key:");next}
$1 ~ "clientrandom" {printf("\nCLIENT_RANDOM:");next}
$1 ~ "hashout" {printf(" Master-Key:");next}
$1 ~ "hashrepeatout" {printf(" ");next}
$1 == "--" {printf("\n");next} {printf("%s",$2)} END {printf("\n")}' > OUTPUT_PATH_HERE`
)
// Decrypt produces a session key file, and embeds this into the PCAP
func Decrypt(pcapPath string, tlsKeyPath string) error {
var err error
if pcapPath, err = filepath.Abs(pcapPath); err != nil {
return fmt.Errorf("failed to resolve absolute path to pcap: %v", err)
}
// check the pcap exists
if _, err = os.Stat(pcapPath); errors.Is(err, os.ErrNotExist) {
return errors.New("pcap could not be found")
}
// check the tls key exists
if _, err = os.Stat(tlsKeyPath); errors.Is(err, os.ErrNotExist) {
return errors.New("tls key could not be found")
}
// check that the PCAP hasn't already been decrypted
pcapContents, err := ioutil.ReadFile(pcapPath)
if err != nil {
return fmt.Errorf("failed to check whether the pcap has already been decrypted: %v", err)
}
if strings.Contains(string(pcapContents), "RSA Session-ID:") {
return errors.New("pcap has already been decrypted")
}
pcapFile, err := openPcap(pcapPath)
if err != nil {
return fmt.Errorf("failed to open pcap: %v", err)
}
decrypt := false
packetCount := 0
serverIp := ""
var serverPort uint16
// get external IP and port to decrypt, and verify that at least one TLS packet exists
for {
rawPacket, _, err := pcapFile.ReadPacketData()
if err != nil {
break
}
packet := gopacket.NewPacket(rawPacket, layers.LayerTypeEthernet, gopacket.Default)
packetCount++
if packetCount == 1 {
serverIp = packet.NetworkLayer().NetworkFlow().Dst().String()
}
// loop over packet layers
for _, layer := range packet.Layers() {
switch layer.LayerType() {
case layers.LayerTypeTCP:
if serverPort == 0 {
tcp, _ := layer.(*layers.TCP)
serverPort = uint16(tcp.SrcPort)
}
default:
app := packet.ApplicationLayer()
if app != nil {
var decoded []gopacket.LayerType
var tls layers.TLS
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeTLS, &tls)
if err = parser.DecodeLayers(packet.ApplicationLayer().LayerContents(), &decoded); err != nil {
// TODO: review whether these errors should be considered as fatal
// fmt.Printf("unexpected error while parsing tls packets: %v: %v\n", pcapPath, err)
continue
}
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeTLS:
decrypt = true
break
}
}
}
}
}
}
if !decrypt {
return errors.New("no tls packets could be found")
}
if serverIp == "" || serverPort == 0 {
return errors.New("could not calculate ip and port from pcap")
}
// get debug log to obtain session key
sessionKeyPath, err := generateSessionKey(serverIp, cast.ToString(serverPort), pcapPath, tlsKeyPath)
if err != nil {
return err
}
injSessionKey := strings.Replace(injectSessionKey, "SESSIONKEY_PATH_HERE", sessionKeyPath, -1)
injSessionKey = strings.Replace(injSessionKey, "PCAP_PATH_HERE", pcapPath, -1)
injSessionKey = strings.Replace(injSessionKey, "OUTPUT_PATH_HERE", pcapPath+"with-keys", -1)
if _, err = execCommand(injSessionKey); err != nil {
return fmt.Errorf("failed to inject tls session key: %v", err)
}
if err = os.Rename(pcapPath+"with-keys", pcapPath); err != nil {
return fmt.Errorf("failed to overwrite existing pcap: %v", err)
}
// delete session key file
go func(sessionKeyPath string) {
// ignore errors as it's not too important that this file gets removed
_ = os.Remove(sessionKeyPath)
}(sessionKeyPath)
return nil
}
// generateSessionKey generates the tls debug log using tshark, parses the log for the session key, and returns the path
// to the session key file
func generateSessionKey(serverIp string, serverPort string, pcapPath string, tlsKeyPath string) (string, error) {
debugLogPath := "/tmp/ppm-pcap-tls-log-" + filepath.Base(pcapPath) + ".log"
sessionKeyPath := "/tmp/ppm-pcap-session-keys-" + filepath.Base(pcapPath) + ".keys"
tmpKeyPath := "/tmp/ppm-pcap-tmp-session-keys-" + filepath.Base(pcapPath) + ".log"
genDebugLog := strings.Replace(generateDebugLog, "SERVER_IP_HERE", serverIp, -1)
genDebugLog = strings.Replace(genDebugLog, "SERVER_PORT_HERE", serverPort, -1)
genDebugLog = strings.Replace(genDebugLog, "TLS_KEY_HERE", tlsKeyPath, -1)
genDebugLog = strings.Replace(genDebugLog, "DEBUG_PATH_HERE", debugLogPath, -1)
genDebugLog = strings.Replace(genDebugLog, "PCAP_PATH_HERE", pcapPath, -1)
if _, err := execCommand(genDebugLog); err != nil {
return "", fmt.Errorf("failed to generate tls session key: %v", err)
}
prsDebugLog := strings.Replace(parseDebugLog, "DEBUG_PATH_HERE", debugLogPath, -1)
prsDebugLog = strings.Replace(prsDebugLog, "TMPLOG_PATH_HERE", tmpKeyPath, -1)
prsDebugLog = strings.Replace(prsDebugLog, "OUTPUT_PATH_HERE", sessionKeyPath, -1)
if _, err := execCommand(prsDebugLog); err != nil {
return "", fmt.Errorf("failed to parse tls session key: %v", err)
}
// delete interim log files
go func(debugLogPath string, tmpKeyPath string) {
// ignore errors as it's not too important that these logs get removed
_ = os.Remove(debugLogPath)
_ = os.Remove(tmpKeyPath)
}(debugLogPath, tmpKeyPath)
return sessionKeyPath, nil
}
func execCommand(command string) (string, error) {
var stdErr, stdOut bytes.Buffer
cmd := exec.Command("bash")
cmd.Stderr = &stdErr
cmd.Stdin = strings.NewReader(command)
cmd.Stdout = &stdOut
if err := cmd.Run(); err != nil {
var Error string
if stdErr.String() != "" {
Error = stdErr.String()
} else {
Error = stdOut.String()
}
return "", errors.New(Error)
}
return stdOut.String(), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment