|
本帖最后由 luozhenni 于 2022-9-16 13:51 编辑
不常见的内存与流量取证 -- WMCTF 2022 1!5!
原文链接:不常见的内存与流量取证 -- WMCTF 2022 1!5!
e*h 衡阳信安 2022-09-16 06:00 发表于山东
start
刚刚拿到这道题的时候发现这道题目的内存镜像非常特殊,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
复制代码 流量里基本就只有这些信息了
还是看看远处的内存吧 家人们
autopsy
跟着网上教程 直接创建案件 case
然后编写受害者信息 host
然后以链接方式导入 内存镜像
这样一个初始化的分析就完成了
内存 fake flag
既然他说内存里存在假 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 和 key
接下来有两条思路
- 搜索 websocket 关键字尝试寻找那段 web 通讯中的具体信息 肯定可以找到对应代码
- 搜索 key 关键字 尝试破解 QUIC 流量拿到其他的 key
在 autopsy 的 数据分析中的关键词搜索可以顺带获取对应字符串在镜像中的位置
联合使用关键字搜索(Keyword Search) 和基于 Offset (Data Unit)的搜索
你先会发现可能被破坏的内存区块,但是没关系,肯定有完整的。
有些会有 RgU.... 这种坏字符
接下来我们会大量看到 (除了上面 go 部分的代码) 诸如下面代码块的内容
another 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 的变量
JS
- 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{LOL_StR1ngs_1s_F@ke_BUT
复制代码
BUT 后面明显还有后半句话 猜想是 一半 flag 的藏头
想到之前还存在后半段 Flag 在内存中 进行一个拼接后提交
- WMCTF{LOL_StR1ngs_1s_F@ke_BUT_HTTP3_1s_C000L}
复制代码
虽然说有一点点脑洞但是基本题目逻辑是清晰的。可以说是出的很好的一道 misc。
misc 不是套娃捏 misc == 套娃 的坏毛病建议改改
说说 Autopsy 工具
赛后在和队友和其他队伍的成员交流的时候, 发现大家都基本在使用 VOL 取证大师 strings或者一些二进制编辑器,甚至听说有用 WinHex 嗯做来解这道题的。相反对于一些国外的优秀工具了解并不是很多,于是我一合计就有了这篇文章。
在 Autopsy 的帮助下,我基本不需要担心搜素字符串不全或者很慢的问题,这些内容都交给工具自动处理了。Autopsy 不仅非常的贴心的在关键字的搜索中有专门的正则搜索,而且还会额外帮你匹配大小写不敏感的搜索,也可以匹配到诸如 S\x00T\x00R\x00I\x00N\x00G\x00 的字符串。所以我的脑袋基本聚集于根据获取的结果进一步推理相关内容去解题的过程中。
此外 Windows 版本使用流程和 web 版本的基本一致,如果你是使用该版本来解决这道题,你可以直接提取出来镜像里所有的文件。关键字搜索也很方便,他可以直接识别出来 go 文件。但是定位代码相关上下文就非常的困难,就是 data unit 相关的功能有一点点欠缺,不过问题不大。
来源:先知(https://xz.aliyun.com/t/11699)
注:如有侵权请联系删除
|
|