刚刚拿到这道题的时候发现这道题目的内存镜像非常特殊,Misc 取证常用的软件诸如 取证大师 和 vol 都搞不定这种内存镜像。(当然因为我电脑不存在 python2 环境,所以导致我手上的 vol 实际是 vol3, 这还是蛮多区别的,所以我一开始以为我的 vol 锅了。后来问了问同队的师傅, 确实是 vol 和 取证都爆炸了 找不到 Profile 也不能确定类型)
所以面对如此棘手的一个问题,我想起了万年前看到一个神器,尸解 (Autopsy)。
当时我的师傅找了官方的 training 课程的优惠券 (其实就是因为疫情打折直接白送) 不过这是题外话了
官网为 https://www.autopsy.com 先进行一个装
windows 的版本更加阳间一点 但是我这边用的是 web 界面版本的
macOS 直接 brew install autopsy 就行
直接运行 autopsy 然后根据命令行输出打开 https://localhost:9999/autopsy
当然不出意外,就算是 “尸解” 确实也识别不出来镜像类型,如果可以识别和处理镜像类型, autopsy 将会变成一个更为强大的工具,某种意义上是真正的取证大师。(没有碰瓷的意思)
因此最后只能使用最基础的关键词搜索功能,当然这都是后话了。
题目附件快捷方式: https://github.com/wm-team/WMCTF2022
内存不是下来就能直接打开的 所以我们先 wireshark 看看流量了,可以看到前面是一堆 quic 流量,看起来是加了密的,可以留意一下。接着是明显的 HTTP 流量 GET 了一个 flag 同时升级协议到了 websocket。
接下来 都是客户机发往服务端的 WebSocket 流量,ws 第一个包里内容是 flag,然后接下来的包似乎不是明文,可以猜测是某种特殊的加密或者编码的内容,没那么简单。不过可以先保存一下。
把过滤条件设置为 websocket 进行分组导出 可以直接导出成 json 格式
因为我更加熟悉命令行操作所以进行如下的剪切
此外 命令 jq 是一款 json jq 解析器
cat tc.json|jq '.[]._source.layers."data-text-lines"' | awk "NR % 3 == 2"|cut -d "\"" -f 2 # 这时候得到的就是下面这样的密文 flag SlAZT80ZTIXZTIcZSl9ZSlTZT80ZTIXZTIwC Sx0ZTf1ZTIuZSx0ZSluZSthZTf1ZTIuZSxnC SthZStQZSt1ZT81ZT8HZSlXZStQZSt1ZT8/C Sl0ZSlQZSx6ZT8HZS46ZSlTZSlQZSx6ZT8gC SxuZT86ZTIcZTfQZTfQZTfQZT86ZTIcZTfPC TI0ZT81ZSx6ZT8HZSxcZSlhZT81ZSx6ZT8gC SxXZTIXZTIhZSl0ZSxLZT80ZTIXZTIhZSlnC SxHZT8HZSxkZSt1ZT8QZSt1ZT8HZSxkZSt/C SxXZTIBZSl0ZSlBZSlQZT8XZTIBZSl0ZSlzC StHZStQZSluZTIuZSl9ZSxkZStQZSluZTINC SlXZSlkZSxrZS40ZSthZTIBZSlkZSxrZS4nC TIuZT8QZS40ZT81ZTIXZTIXZT8QZS40ZT8/C Sl6ZSx6ZSl9ZSthZT8QZTI9ZSx6ZSl9ZStGC SxTZTIBZTIXZTf1ZT8XZSlkZTIBZTIXZTf/C SlkZSlBZS4LZS46ZTI9ZSlQZSlBZS4LZS45C T8AZSlAZSlhZSxhZSlTZS40ZSlAZSlhZSxGC TIcZSxrZSlAZSl9ZSlhZSxhZSxrZSlAZSl3C StHZStTZT8XZTI6ZSxkZS46ZStTZT8XZTI5C SxTZT8QZT8XZS46ZSlhZSlQZT8QZT8XZS45C TIcZSxrZS4LZStQZSlBZS4XZSxrZS4LZStPC StHZSx6ZSluZT8TZSlQZSx0ZSx6ZSluZT8SC SxuZTIXZSxcZTf1ZSluZSxkZTIXZSxcZTf/C StAZSx6ZSl0ZSluZSl9ZT86ZSx6ZSl0ZSlNC T81ZS40ZS4XZTIcZSt1ZTIBZS40ZS4XZTIwC T8LZTIcZSxcZT86ZSxrZSl6ZTIcZSxcZT85C StHZS4XZSlkZSluZT8HZS46ZS4XZSlkZSlNC StQZTIuZSxhZSlAZSxcZSxBZTIuZSxhZSlbC SlLZSt1ZSthZS4XZSl6ZTI9ZSt1ZSthZS4WC Sl0ZT8TZStQZTfQZSxrZT86ZT8TZStQZTfPC
流量里基本就只有这些信息了
跟着网上教程 直接创建案件 case
然后编写受害者信息 host
然后以链接方式导入 内存镜像
这样一个初始化的分析就完成了
既然他说内存里存在假 flag 那么我们大可搜索 WMCTF{
这类字符串, 假 FLAG 多半是在测试的时候留下的内容,因此可以想到这类内容边上可能就是我们可以找的代码段,确实 我们可以在多处找到这个字符串,可以发现在 flag 出现的同时伴随着不少 Go 源码。我们可以找到相关的服务端收信逻辑。
如下
package main import ( "github.com/gin-gonic/gin" "github.com/gorila/websocket" "net/http" ) var f1ags = "WMCTF{WebSOcket_And" var upGrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } //...flag func flag (c *gin. Context) { //......get.........webSocket...... ws,err := upGrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return } defer ws.Close () //.........flags......... for { //......ws....... mt, message, err := ws.ReadMessage() if err ! = nil { break } if string (message) == "flag" { //.....flags for i:= 0; i< len (flags) ; i++ { ch := string(flags[i]) err : = ws. WriteMessage (mt, []byte (ch)) //sleep...... //time. Sleep (time. Second) if err != nil { break } } } } func main() { bindAddress := "localhost:2303" r := gin. Default () r.GET("/flag", flag) r.Run(bindAddress) }
不过只有一半的 FLAG 而且程序逻辑似乎和我们需要的逻辑并不相符。(这里看起来是服务端,而且没有我们需要的加密解密算法) 不过就这些地方已经可以看到一些端倪了
接下来有两条思路
websocket
关键字尝试寻找那段 web 通讯中的具体信息 肯定可以找到对应代码key
关键字 尝试破解 QUIC 流量拿到其他的 key在 autopsy 的 数据分析中的关键词搜索可以顺带获取对应字符串在镜像中的位置
联合使用关键字搜索(Keyword Search) 和基于 Offset (Data Unit)的搜索
你先会发现可能被破坏的内存区块,但是没关系,肯定有完整的。
有些会有 RgU.... 这种坏字符
接下来我们会大量看到 (除了上面 go 部分的代码) 诸如下面代码块的内容
关键字 key
"fmt" "github.com/lucas-clemente/quic-go/http3" "log" "net/http" "os" ) func HelloHTTP3Server(w http.ResponseWriter, req *http.Request) { fmt.Printf("client from : %s\n", req.RemoteAddr) fmt.Fprintf(w, "_HTTP3_1s_C000L}\n") } func main() { mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(HelloHTTP3Server)) w := os.Stdout server := http3.Server{ Addr: "127.0.0.1:18443", TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, KeyLogWriter: w, }, Handler: mux, } err := server.ListenAndServeTLS("./my-tls.pem", "./my-tls-key.pem") if err != nil { log.Fatal(err) } } ..........................L...............L...i............................my-tls.pem......-----BEGIN CERTIFICATE----- MIIDazCCAlOgAwIBAgIUAuwgrK8T+kosTHW9KW11AvscB88wDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA3MTkwODUyNTFaFw0yNTA3 MTgwODUyNTFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQD62iNNKuGH54IDYqbg00gD/gbO9wq+UwmiYBXzYqnn K9lTWvOEqlNvYNLhAoALcRrCkpqhw3ks/dhKqPCbDI3bxbQT3vZrvaRkP/DO1SnX jmCt5yExDYXhPxNF+lWHs8TP7SjDE6sC6h+lEhYaQsKd/wYhw54NW/USrUR685r5 M1MfVg0+VOu5fqhwbOkn9lmwJaEOAtTIBAyG1jPFlt5LsBshe+2CXEG1cbaCDInB 6Jz6IZ7zN9KQ0YrWY8y2iw0toVODuNZnU7pSeKdWRwX6eYU3NA+QaTYl2zpl939b jVtNKWlY+DiUFroTucph9W4jWvzu9Yp9uGEO46VvCV+tAgMBAAGjUzBRMB0GA1Ud DgQWBBSzrkF13VZfMqO3s/1K1gogeETXdTAfBgNVHSMEGDAWgBSzrkF13VZfMqO3 s/1K1gogeETXdTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQD0 /EZXhjszp2KHnekh8Ktz66pIkxRa9ErZCbQt/7os4jCdj77OhcFYQ4O/mhdOeQb4 zvKlb0sAxsLcJiK1WB9cIcG+j4Kmrp6vJ8nRlI2YMBi8dX/MNDgBgXw/DdeuISyU K05t26oQJxYfZ36zT2k2NVUdnvqAXTbk4IGxnfGRJJXZ/70iBWJYXEaB8UKeTXrn VSefJKbO9v0CmuxWQxP363nB/e5f+l73ELTO3bs7qqyz9FHqZuR8cCo5YJ05c8G+ CLuL4JtyOX+7Cd+pGtadc54XtNYWw35CbBkHzhKwtU/+c24eM+SXV1AakzrSHHE3 1p80nnkmmO4f9yG5CZ8/ -----END CERTIFICATE----- ..........................L...............L.@..............................my-tls-key.pem......-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA+tojTSrhh+eCA2Km4NNIA/4GzvcKvlMJomAV82Kp5yvZU1rz hKpTb2DS4QKAC3EawpKaocN5LP3YSqjwmwyN28W0E972a72kZD/wztUp145grech MQ2F4T8TRfpVh7PEz+0owxOrAuofpRIWGkLCnf8GIcOeDVv1Eq1EevOa+TNTH1YN PlTruX6ocGzpJ/ZZsCWhDgLUyAQMhtYzxZbeS7AbIXvtglxBtXG2ggyJweic+iGe 8zfSkNGK1mPMtosNLaFTg7jWZ1O6UninVkcF+nmFNzQPkGk2Jds6Zfd/W41bTSlp WPg4lBa6E7nKYfVuI1r87vWKfbhhDuOlbwlfrQIDAQABAoIBAQD16xPgesFOcm7K 0tO2ZGqdP1N9YkJuAwnW3UunpnnZ3urXBLrmu/O/pLQXUlQk42TQith87RzGNrTr vGLkHZKUeWTodhQt22RlwylYGzFB2Jp+4a9wX0l4YFWMrLVcq6euD1l+pLFp0gvj z69LX1dbfL+OKi+v+Q5wmNwhjN/Im89qAxTHAKUlGQGy7cZq0aewVkF7qPrV44tA 4uUk2h36k+MFELUeDBAhegH6todAnjI+Ec72OzhtDDEF5hHM+1e3fsngz2RdfMQM gnHm5fdb4yVGOV5K1HqVpDqKyCLIr0JvKNf/5HktJ/+lSlliL/mrx3KQCRt1DWN9 O8EBaMgBAoGBAP7GYPtzwXwn5bDmkD+//ejZm5SDq1EZ6ZSIttHl2OfbFwU45X0N cMyuBXcHkiaVuD2GXiKmy5W4xh3WRPF4o7qMLe4dcUbTqqwc6nnY+2fLY71TMM9a MjRQuQHnwQsMrCVYiv0/50eKwglc61ogsv+WmFxfYZtjnGMJP6M6xtlBAoGBAPwO 7iAQTNAZhrTefwDXSGirc1BVg0FBB05woVm/Gn0hkZqrl9VJd6g7gCAHh4tndR54 j5IR67eROoPY+tZSF8Gc/ne66BH2yq3Xbh3E281ajft+RLUFuD6k+Rlq2l91J72x H46mKl/toB9ukPxl0P/8vOViXMYVlFsPHGnAEB9tAoGBAJ/Op36SOUc7b2Pq+4hB UW8BMAmUHZ2dd1pn9uTqG4gzcNkhuzEZgSuh7GOhKBdzykEtS1bI8OJVKFAG2u/s ECcvTpARf8BBfMjAyoLri6areUCEMhWeKeeOyr1bNUdNB53VUDlSICxL6TIeSrIZ 2K1hNOicG4lwjePBJV2pvJkBAoGAVERRi9qnM3M1O8aewxM2G/glxxevl+M7pBe3 eZ+QJYFRgloXmrDDFjU+MncR86MU3qkDppvjKC2fWHDz+y7azlnEIRcVetv9Cn1Z TQ6BRXgeu5ONOM++twLEXKECfKNYM+zBVhlrVULGI3v9cMRBSTOfmzh1N6wDOyYk I56YRUkCgYBhPvpgPohOzEukToo64UtbmOK25pKJBHfhMH/jGglfFUnzAURXPC4b 7HIxMSGd5A0EjX34M7CA4rRHXcF7Sxc6X4eP43FoZabey6di+rIvm6F/pQFRTT/u uSNIJWzf9lF5rbqKMxPlHVJLvlnIVZNSjUV2FIdCe4LR55qzByOOvQ== -----END RSA PRIVATE KEY-----
这里有 key 也能看到似乎是后半个 flag 的变量
var ws = new WebSocket("ws://localhost:2303/flag"); ws.onopen = function(evt) { console.log("Connection open ..."); ws.send("flag"); }; ws.onmessage = function(evt) { var rstr = randomString(5) n = evt.data res = n.padEnd(9,rstr) s1= encrypto(res,15,25) f1 = b1.encode(s1) ws.send(f1) console.log('Connection Send:'+f1) }; ws.onclose = function(evt) { console.log("Connection closed."); }; function Encode() { _keyStr = "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC";
这里基本已经可以看到 加密的逻辑了 基本可以猜测我们需要的内容就在这里
找到完整的代码可以试试用关键字
_keyStr
接下来摸到相关的内存 Unit 号之后 在该数字后面稍微减少 10 左右
然后使用 Data Unit 分析附近内存号 ± 20 个内存单元的内容
接着我们可以摸到如下的代码 大约在 内存编号 987382 处
<script> function randomString(e) { e = e || 32 var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", a = t.length, n = ""; for (i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a)); return n } function encrypto( str, xor, hex ) { if ( typeof str !== 'string' || typeof xor !== 'number' || typeof hex !== 'number') { return; } let resultList = []; hex = hex <= 25 ? hex : hex % 25; for ( let i=0; i<str.length; i++ ) { let charCode = str.charCodeAt(i); charCode = (charCode * 1) ^ xor; charCode = charCode.toString(hex); resultList.push(charCode); } let splitStr = String.fromCharCode(hex + 97); let resultStr = resultList.join( splitStr ); return resultStr; } var b1 = new Encode() var ws = new WebSocket("ws://localhost:2303/flag"); ws.onopen = function(evt) { console.log("Connection open ..."); ws.send("flag"); }; ws.onmessage = function(evt) { var rstr = randomString(5) n = evt.data res = n.padEnd(9,rstr) s1= encrypto(res,15,25) f1 = b1.encode(s1) ws.send(f1) console.log('Connection Send:'+f1) }; ws.onclose = function(evt) { console.log("Connection closed."); }; function Encode() { _keyStr = "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC"; this.encode = function (input) { var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = _utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); } return output; } _utf8_encode = function (string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; } } </script>
这里我写了一个小小的 Decode 就交给队里的逆向大师傅了
我的 Decode
// encrypto rev function decrypto(str ,xor ,hex) { console.log("decrypto_get:","str: " + str, xor,hex) let splitStr = String.fromCharCode(hex + 97); resultStr = str.split(splitStr); console.log(resultStr) resultList = [] for(let i = 0; i<resultStr.length; i++) { charCode = resultStr[i]; char = parseInt(charCode,hex); char2 = (char ^ xor); str = String.fromCharCode(char2); resultList.push(str); } text = resultList.join("") console.log("decrypto_return: " + text) return text }
大师傅给出来的结果
this.decode = function(input) { // input = String(input) // .replace(REGEX_SPACE_CHARACTERS, ''); var length = input.length; if (length % 4 == 0) { input = input.replace(/==?$/, ''); length = input.length; } if ( length % 4 == 1 || // http://whatwg.org/C#alphanumeric-ascii-characters /[^+a-zA-Z0-9/]/.test(input) ) { error( 'Invalid character: the string to be decoded is not correctly encoded.' ); } var bitCounter = 0; var bitStorage; var buffer; var output = ''; var position = -1; while (++position < length) { buffer = _keyStr.indexOf(input.charAt(position)); bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer; // Unless this is the first of a group of 4 characters… if (bitCounter++ % 4) { // …convert the first 8 bits to a single ASCII character. output += String.fromCharCode( 0xFF & bitStorage >> (-2 * bitCounter & 6) ); } } return output; };
合作非常愉快
队里的 RE 大师傅用了一会儿就搞定了,搞定之后直接顺手就解出结果了。
什么叫术业有专攻啊 战术后仰 (x
WdzsPXdzs
MrtMmCrtM
CBDkfSBDk
TYKf4XYKf
Fbspppbsp
{kKfEZkKf
LzaTNdzaT
OfGDiDfGD
LxTQYcxTQ
_BmtPGBmt
SnH6CxnH6
ti6kzzi6k
RKPCiwKPC
1xzrcnxzr
nQ74wYQ74
gWZ3X6WZ3
sHWPZ3HWP
_AcyG4Acy
1ic4ZYic4
sH7BQ5H7B
_KmhYMKmh
FzErmGzEr
@KTmPbKTm
k65sDx65s
esEbHRsEb
_5nmf45nm
Bt3WEJt3W
UDC5RwDC5
ThBpHbhBp
==> WMCTF{[email protected]_BUT
BUT 后面明显还有后半句话 猜想是 一半 flag 的藏头
想到之前还存在后半段 Flag 在内存中 进行一个拼接后提交
WMCTF{[email protected]_BUT_HTTP3_1s_C000L}
虽然说有一点点脑洞但是基本题目逻辑是清晰的。可以说是出的很好的一道 misc。
misc 不是套娃捏 misc == 套娃 的坏毛病建议改改
赛后在和队友和其他队伍的成员交流的时候, 发现大家都基本在使用 VOL
取证大师
strings
或者一些二进制编辑器,甚至听说有用 WinHex 嗯做来解这道题的。相反对于一些国外的优秀工具了解并不是很多,于是我一合计就有了这篇文章。
在 Autopsy 的帮助下,我基本不需要担心搜素字符串不全或者很慢的问题,这些内容都交给工具自动处理了。Autopsy 不仅非常的贴心的在关键字的搜索中有专门的正则搜索,而且还会额外帮你匹配大小写不敏感的搜索,也可以匹配到诸如 S\x00T\x00R\x00I\x00N\x00G\x00
的字符串。所以我的脑袋基本聚集于根据获取的结果进一步推理相关内容去解题的过程中。
此外 Windows 版本使用流程和 web 版本的基本一致,如果你是使用该版本来解决这道题,你可以直接提取出来镜像里所有的文件。关键字搜索也很方便,他可以直接识别出来 go 文件。但是定位代码相关上下文就非常的困难,就是 data unit 相关的功能有一点点欠缺,不过问题不大。