注:这是我第一次参加CTF,初来乍到,当时的许多表达、做法放在现在看都是不专业甚至可笑的,就当做黑历史吧…
赛题轮次:Week1
已解出的题目:
题目类型 | 题目名称 |
---|---|
Pwn | Real Login、Game、overwrite、gdb |
Reverse | base64、ezAndroidStudy、Simple_encryption、ez_debug、begin |
Web | headach3、会赢吗、智械危机 |
Crypto | xor、Base、一眼秒了、Strange King |
Misc | WhereIsFlag、Labyrinth、decompress、pleasingMusic、兑换码 |
Pwn方向:
Real Login:
反编译观察函数,发现要输入密码,找到密码是NewStar!!!,观察发现没有位数溢出保护机制,在虚拟机中nc地址后输入NewStar!!!+大量额外字符弹出祝贺,此时已经成功,用ls指令查看文件发现有一个flag,再用cat查看得到flag{6a532e27-a281-4c15-b965-c030de9c47df}.
Game:
反编译观察函数,发现是要求在5s内输入0-9之间的数,总和超出999之后可以获得控制权限,于是在nc地址之后以形如“9 换行 9 换行 9…”的形式同时输入一百余次复制粘贴执行,获得控制权,用ls查看发现有flag,再查看发现flag{4a045fea-9b70-4637-8561-cf5bece7e814}.
Overwrite:
反编译观察函数,第一个要求填入读取的字节数,不能超过48字节(但为什么容器里超过40就不行了?),第二个要求填数,填入的数被存在nbytes里,但接下来程序却会用nptr和114514进行比较,结合题目名称猜测应该是要输入超长的字节来覆盖同栈上的nptr,于是将第一个空填入超大的数,发现第二空能正常填入,经过动态调试发现输入54个9时nptr会被覆盖为999999\n,大于等于114514,所以在第一空输入一大堆9,第二空输入54个9,返回flag{48d03781-f157-4869-8041-5ba44bef070a}.
gdb:
反编译观察函数,注意到是把字符串0d000721以key = mysecretkey1234567890abcdefghijk用AVX指令集进行了复杂的加密,再与输入的值进行比对,观察加密函数,经过IDA+linux远程动态调试和把原函数丢给AI验证出需要输入的字符对应的十六进制是5D 1D 43 55 53 45 57 45,对应的字符是]_x001D_CUSEWE(第二个字符是不可打印字符),尝试把不可打印字符输入进命令行中,发现进入程序并不能正常转换,于是使用命令来代替输入:printf ‘\x5D\x1D\x43\x55\x53\x45\x57\x45’ | nc 容器地址,返回祝贺和flag{205f9ae3-9d66-4523-b4b7-283cb5e1e381}.
Reverse方向:
Base64:
用IDA打开base64.exe然后在string里面发现以下几行:
1 | .rdata:0000000140011003 00000011 C Enter the flag: |
由最后一行和题目名称猜测该行为换了表的base64加密,g84Gg6m2ATtVeYqUZ9xRnaBpBvOVZYtj+Tc=应该是用换表base64加密的flag,由此构建Python代码:
1 | import base64 |
解得flag为flag{y0u_kn0w_base64_well}.
ezAndroidStudy:
在模拟器上安装并运行apk,进入后提示用jadx进行反编译,并提示第一段flag在Androidmanifest.xml的activity里,反编译后在资源文件里找到Androidmanifest.xml,发现有两个activity,其中一个<activity android:name="work.pangbai.ezandroidstudy.Homo"/>
点开后发现第一部分的flag:flag{Y0u,进入二部分,得知flag在resources.arsc/res/value/string.xml
中,寻找得到第二部分的flag:_@r4,进入第三部分,根据提示在/res/layout/activity\_main.xml
中找到第三部分的flag:_900d,进入第四部分,根据提示在/res/raw/
找到flag4.txt
,获得第四部分的flag:_andr01d,进入第五部分,根据其提示在/lib/x86\_64/
中找到libezandroidstudy.so
和/libpl\_droidsonroids\_gif.so
,导出后在IDA查看libezandroidstudy.so
,在函数中找到Java\_work\_pangbai\_ezandroidstudy\_MainActivity\_getflag5
,反编译发现第五部分的flag:_r4V4rs4r},拼合得到flag{Y0u_@r4_900d_andr01d_r4V4rs4r}.
Simple_encryption:
在IDA中反编译并观察代码,首先要求输入的flag会逐个字符填入input数组中,直到达到30个字符,之后对于其中每个字符,根据其在数组中的位置分别进行不同的转换:
1 | for ( j = 0; j < len; ++j ) |
接下来是将字符ASCII转为十六进制并与buffer中的值进行比较:
1 | for ( k = 0; k < len; ++k ) |
其中buffer为{0x47, 0x95, 0x34, 0x48, 0xA4, 0x1C, 0x35, 0x88, 0x64, 0x16, 0x88,0x07, 0x14, 0x6A, 0x39, 0x12, 0xA2, 0x0A, 0x37, 0x5C, 0x07, 0x5A,0x56, 0x60, 0x12, 0x76, 0x25, 0x12, 0x8E, 0x28, 0x00, 0x00},倒推得到{102, 108, 97, 103, 123, 73, 84, 95, 49, 53, 95, 82, 51, 65, 108, 49, 121, 95, 86, 51, 82, 121, 45, 53, 49, 77, 112, 49, 101, 125, 31, -41}(末尾两个数据排除),结合ASCII得知flag为:flag{IT_15_R3Al1y_V3Ry-51Mp1e},代码如下:
1 | a = [0x47, 0x95, 0x34, 0x48, 0xA4, 0x1C, 0x35, 0x88, 0x64, 0x16, 0x88, 0x07, 0x14, 0x6A, 0x39, 0x12, 0xA2, 0x0A, 0x37, 0x5C, 0x07, 0x5A,0x56, 0x60, 0x12, 0x76, 0x25, 0x12, 0x8E, 0x28, 0x00, 0x00] |
ez_debug:
将程序用x64dbg打开,将断点打在输出Decrypted flag:所对应的行,进行动态调试,输入随便的字符,到断点位置时发现断点的周围变量中含有flag:flag{y0u_ar3_g0od_@_Debu9}.
Begin:
运行exe程序,发现需要用IDA进行反编译,打开IDA进行反编译,按Fn+F5
反编译main函数得到第八行为:strcpy((char *)&flag_part1, "OK,You can click on this variable to discover the first part\n");
点击其中的变量得到乱码:6B614D7B67616C66h,按a转化为字符串得到flag的第一部分:flag{Mak3_aN_(这里在看十六进制的时候也找到了对应行:0000000000403010 66 6C 61 67 7B 4D 61 6B 33 5F 61 4E 5F 00 00 00
flag{Mak3_aN_…),再按Fn+Shift+F12
转到.rdata:0000000000404000 00000086 C this is flag part2: 3Ff0rt\_tO\_5eArcH\_ ,You can press' x 'on the variable name of this string to view the function that references it
,得到第二部分的flag:3Ff0rt_tO_5eArcH_,注意到其下方两行还有提示:.rdata:0000000000404088 0000007A C And you can press 'x' key on the function name to see which function references this function,Flag Part 3 is right there.
和.rdata:0000000000404108 00000045 C the function name is flag part3,Don't forget to add a '}' at the end
,按提示双击左侧.rdata:0000000000404108
跳转到IDA_view,找到第三部分的flag函数为:F0r_th3_f14g_C0Rpse,组合得到flag{Mak3_aN_3Ff0rt_tO_5eArcH_F0r_th3_f14g_C0Rpse}.
Web方向:
Headach3:
打开网页后发现是head(er) ache,header是标头,开启开发者模式,在网络页面刷新网页,得到一个文档,在响应标头里找到属性fl3g:flag{You_Ar3_R3Ally_A_9ooD_d0ctor}.
会赢吗:
第一个页面在源代码中发现flag第一部分ZmxhZ3tXQTB3以及第二个页面的后缀/4cqu1siti0n,第二个页面发现给出了一个getflag的方法,于是结合上面给出的课程名称,在控制台调用并输出第二部分的 flag: IV95NF9yM2Fs和第三个页面的后缀/s34l,第三个页面检查源代码后发现要把已封印的文本改成解封,发现flag的第三个部分MXlfR3I0c1B和第四个页面的后缀/Ap3x,进入第四个页面,发现有一个被禁用的noscript按钮无量空处,挪出来后发现不显示,于是在前面将class为s的改为显示,按下得到flag的第四部分fSkpKcyF9,合并起来之后得到ZmxhZ3tXQTB3IV95NF9yM2FsMXlfR3I0c1BfSkpKcyF9,进行base64后解密得到flag{WA0w!_y4_r3al1y_Gr4sP_JJJs!}.
智械危机:
询问ai后在网页后加入后缀/robots.txt,访问得到两行数据:User-agent: \*
和Disallow: /backd0or.php
,查询百科得知robots.txt
的作用,并访问后缀/backd0or.php
,访问后查询得知这是一种后门示例,需要提供cmd和key,观察代码得知key会被base64解码为decoded_key,cmd会被先翻转字母再取MD5,然后判定两者是否相等,如果不等则以不合法的密钥来结束,否则将cmd也base64解码,执行被解码的cmd,若访问后门时候提供了cmd和key就会执行decrypt_request后再带进execute_cmd执行得到系统返回的cmd,否则就展示后门的源代码.
首先尝试列出所有文件(ls指令),尝试Linux输入命令:curl -X POST -d "cmd=bHM=&key=N2FiZThiMjRiZDAxMzc0NDZmZDMzNmMyMjk4ZmNjYTA=" http://eci-2zeimkpaddax8w71qsw9.cloudeci1.ichunqiu.com/backd0or.php
,只返回了backd0or.php、index.php、robots.txt,并没有所需的flag,于是进一步搜寻所有带flag的文件(find / -name "flag\*" 2>/dev/null
):curl -X POST -d "cmd=ZmluZCAvIC1uYW1lICJmbGFnKiIgMj4vZGV2L251bGw=&key=Y2ZlNDg1MjNjNTY4NmI5YzBjNmIwM2M3YmQyN2JiMjg=" http://eci-2zeimkpaddax8w71qsw9.cloudeci1.ichunqiu.com/backd0or.php
,返回了如下文件:
1 | /sys/devices/pnp0/00:04/tty/ttyS0/flags |
再查看flag:curl -X POST -d "cmd=Y2F0IC9mbGFn&key=ODc5YTU5MWM2Nzg1YTRlMTM5OGI5NmE5YTFiYzY3ZWI=" http://eci-2zeimkpaddax8w71qsw9.cloudeci1.ichunqiu.com/backd0or.php
,得到flag{03b6f3f8-8dfa-40e3-b077-59d9009d520e}.
Crypto方向:
Xor:
题目给了一个xor的python程序,将两个量执行后得到了c1的long整数和c2的byte字符串,并给出了key = b'New\_Star\_CTF'
,现将c2=b';:\x1c1<\x03>\*\x10\x11u;'
转换为long整数18329866411473355926313989435
,用题目所给库pycryptodome
进行逆向还原,把key也从byte字符串转换为long整数,并分别用c1和c2与key进行异或运算,将得到的long整数再次转换为byte字符串,并用utf-8编码得到前半段flag{0ops!_yo和后半段u_know_XOR!},最终flag{0ops!_you_know_XOR!}.
Python代码:
1 | from Crypto.Util.number import long_to_bytes, bytes_to_long |
Base:
将4C4A575851324332474E324547554B494A5A4446513653434E564D444154545A4B354D45454D434E4959345536544B474D5134513D3D3D3D
扔进cyberchef,经过16进制+base32+base64转换为flag{B@sE_0f_CrYpt0_N0W}.
一眼秒了:
观察所给py文件发现是RSA加密算法,给了两个308位的整数,测试getprime(512)函数发现生成的是154-155位的质数,这样得到的n可以是308位的整数,将第一个数(n)因数分解(分解网站https://factordb.com/
)得到
1 | p=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421 |
计算φ(n) = (p-1)*(q-1):
1 | phin=52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674858130758088899044472900099582762041084679725101595564288259840921657497158600332295551583526683560422708098048258479197276963841017255615653572783474986640 |
私钥d 使用 gmpy2.invert(e,φ(n))求解得到:
1 | d=50815830946428726535614046353626937023197652348927437511761939372692971763376865348702551167390521792222849904014468110435221645117001665727526339627128906414128425003411450284449391237239907716037717446619737025290761260576445929041941546093509744402441915711334879221133938020030558707487954892232538055553 |
结合
1 | c=48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069 |
而m = pow(c, d, n),得到
1 | m=56006392793407635344141327873443751833955250118605130634434259084012318581267186801727168881696715389 |
把m从long转化为bytes得到b’flag{9cd4b35a-affc-422a-9862-58e1cc3ff8d2}‘,即包含flag.
Python代码:
1 | from Crypto.Util.number import * |
Strange King:
观察发现ksjr{EcxvpdErSvcDgdgEzxqjql}的前四位是偏移量分别为5,7,9,11的凯撒加密,以此类推得到初步结果flag{RngcugFqPqvUvqrNgctkpi},被包裹部分再进行偏移量为2的凯撒解密得到flag{PleaseDoNotStopLearing}.
Misc方向:
WhereIsFlag:
下载vmware和linux Ubuntu并安装虚拟机,在指令行里面nc指令进入所给地址,经过测试用ls指令在proc/self里找到三个文件,其中environ用cat指令查看得到flag{a3aacfc0-a14c-4331-9cc1-0f70dc678d4b}.
Labyrinth:
查看图片的LSB(lsb.daidr.me
),在RGB plane 0
模式下得到一张二维码,扫描得到flag{e33bb7a1-ac94-4d15-8ff7-fd8c88547b43}.
decompress:
将16个zip压缩分卷合并解压得到flag{e6468aaf-7ddc-4eb2-b6db-bce9f1ffb52f_oOo0hhh_U_r3411y_K0nw_h0w_2_d3c0mpr355_Multi-part_archive_5e35fcde-28e1-487d-9659-ba2dab629bb3_&_Th1s_f1ag_15_s0O0oO_l0oOoOng999_b433dc9b-db28-4a1d-965b-dc0a95d3a53a},整体32位小写MD5加密得到c6edaba55bac51875a215e2de11c35dd,即为flag.
pleasingMusic:
将音乐文件中出现的摩斯电码倒置解读得到EZ_MORSE_CODE,即为flag.
兑换码:
用winhex修改图片高度,在下方发现flag{La_vaguelette}.
赛题轮次:Week2
已解出的题目:
题目类型 | 题目名称 |
---|---|
Reverse | UPX、drink_TEA、Ptrace、ezencrypt、PangBai 泰拉记(1)、Dirty flowers |
Web | 你能在一秒内打出八句英文吗 |
Crypto | 这是几次方?疑惑!、Since you konw something、 茶里茶气、Just one and more than two |
Misc | wireshark_checkin、wireshark_secret、热心助人的小明同学、 用溯流仪见证伏特台风、你也玩原神吗、字里行间的秘密、Herta’s Study |
Reverse方向:
UPX:
用exeinfope查看发现加了upx壳,用UPX程序对原文件进行脱壳,用IDA反编译得到main主函数:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
观察发现是RC4算法,通过IDA反编译找到key为NewStar,在Linux Ubuntu上进行动态调试,打断点后得到data内储存的数据为:{0xC4, 0x60, 0xAF, 0xB9, 0xE3, 0xFF, 0x2E, 0x9B, 0xF5, 0x10, 0x56, 0x51, 0x6E, 0xEE, 0x5F, 0x7D, 0x7D, 0x6E, 0x2B, 0x9C, 0x75, 0xB5},询问AI得到RC4解密代码:
1 |
|
解出flag为:flag{Do_you_know_UPX?}.
drink_TEA:
反编译观察主函数,发现首先是长度检测,要求输入的flag应当是32位(20h),然后将输入的字符每8字节执行一次以key = ‘WelcomeToNewStar’的魔改TEA加密:
1 | for ( i = 0; i < dword\_7FF6E5E74078; i += 8 ) |
经过转换:
1 | import struct |
得到key = [0x636C6557, 0x54656D6F, 0x77654E6F, 0x72617453],动态调试找到加密后的32字节为:[0x78, 0x20, 0xF7, 0xB3, 0xC5, 0x42, 0x0C, 0xE, 0xDA, 0x85, 0x59, 0x21, 0x1A, 0x26, 0x56, 0x5A, 0x59, 0x29, 0x02, 0x0D, 0xED, 0x07, 0xA8, 0xB9, 0xEE, 0x36, 0x59, 0x11, 0x87, 0xFD, 0x5C, 0x23, 0x24],那么按8字节进行合并得到[0x7820F7B3C542CEDA, 0x8559211A26565A59, 0x29020DED07A8B9EE, 0x36591187FD5C2324],接下来观察被魔改的TEA加密部分:
1 | __int64 __fastcall sub_7FF6E5E71180(unsigned int *a1, _DWORD *a2) |
由于TEA要把原数据拆分成两个部分,所以实际上每次大循环产生的v3和v4是:
1 | v3 v4 |
并且这里的v5也与标准的TEA不同,是从-1640531527
开始,每次逐步减小直到-32*1640531527
的,基于此构建逆向函数后却发现不论是正向加密还是逆向解密,v3、v4的位数都会逐渐扩大,再次进行动态调试,代入测试值进行检查,发现key(a2)的值居然是变动的,并且v3、v4也一直维持八位数,再次详细观察推断后发现v3、v4、v5等均为32位整型,在计算过程中因为超出了32位上限而全部被 & 0xFFFFFFFF,而a2的值的变动并不影响v3、v4在不变情况下的计算值(也就是说相当于没有变动),所以真正的加密函数是:
1 | for i in range(32): |
基于此编写逆向代码:
1 | a2 = [0x636C6557, 0x54656D6F, 0x77654E6F, 0x72617453] |
得到:
1 | v3 v4 |
但是并无法转换成可读字符,再观察寄存器RCX后发现四个字节是倒着填进去的(也就是说假设对计算结果37 5B AC 75,实际填入的是75 AC 5B 37,这叫做小端序),所以与原式不同,我们将原式每四个字节按小端序填入:
1 | v3 = [0xB3F72078, 0x1A215985, 0xED0D0229, 0x87115936] |
再次带入逆向脚本,得到:
1 | v3 v4 |
十六进制转字符串得到:galfehT{R_erAET_ETX_na_AXX_d}AET,转换端序得到:flag{There_R_TEA_XTEA_and_XXTEA}.
Ptrace:
发现有两个文件father和son,反编译观察得知父程序检测输入的32个字符并创建子进程(也就是子程序),在子进程中完成检验,父程序会在ptrace(PTRACE_POKEDATA, addr, addr, 3)中将子程序的dword_60004040从其初始化设置的4改为3,子程序以start
函数为起始,以sub_600011AD作为main函数,函数中的
1 | for ( i = 0; i < v6; ++i ) |
部分会将输入的每个字符右移dword_60004040位和左移8 - dword_60004040位的两个值进行按位或运算(即byte_60004080=(dword_60004040≫3)∣(dword_60004040≪5)),并将结果储存在byte_60004080中,之后会将byte_60004080与byte_60004020进行比较,全部相符即为正确,查看得知byte_60004020的值为[0xCC, 0x8D, 0x2C, 0xEC, 0x6F, 0x88, 0xED, 0xEB, 0x2F, 0xED, 0xAE, 0xEB, 0x4E, 0xAC, 0x2C, 0x8D, 0x8D, 0x2F, 0xEB, 0x6D, 0xCD, 0xED, 0xEE, 0xEB, 0x0E, 0x8E, 0x4E, 0x2C, 0x6C, 0xAC, 0xE7, 0xAF],编写python代码解得flag为flag{Do_you_really_know_ptrace?}.
附:python代码:
1 | def reverse(x): |
ezencrypt:
下载后发现是安卓应用程序,先用jadx打开分析主activity,发现是要输入值,然后把输入的东西交给enc
类去处理,然后再拿enc
处理过的输入进行检验,观察enc类发现其主要有以下几个功能:1.检验:调用本地方法 doEncCheck
来检验 this.enc
是否正确。2.生成密钥(stringToKey):将字符串 keyStr
转换为字节数组,生成 SecretKeySpec
作为密钥,其中this.enc
与MainActivity.title
有关(MainActivity.title = "IamEzEncryptGame"
)。3.加密:将输入的字符串转换为字节数组,用 secretKey
进行加密,再用 Base64 编码转换为字符串。4.解密:先将 Base64 编码的字符串解码为字节数组,再使用 secretKey 进行解密转换为字符串。发现不知道doEncCheck
是什么,于是提取.so文件,用IDA反编译,找到起始的Java_work_pangbai_ezencrypt_Enc_doEncCheck检验函数:
1 | __int64 __fastcall Java_work_pangbai_ezencrypt_Enc_doEncCheck(__int64 a1, __int64 a2, __int64 a3) |
观察发现是将被enc处理后的s数组(输入的数组)与mm进行比较来得到结果,双击mm数组得到其值[0xC2, 0x6C, 0x73, 0xF4, 0x3A, 0x45, 0x0E, 0xBA, 0x47, 0x81, 0x2A, 0x26, 0xF6, 0x79, 0x60, 0x78, 0xB3, 0x64, 0x6D, 0xDC, 0xC9, 0x04, 0x32, 0x3B, 0x9F, 0x32, 0x95, 0x60, 0xEE, 0x82, 0x97, 0xE7, 0xCA, 0x3D, 0xAA, 0x95, 0x76, 0xC5, 0x9B, 0x1D, 0x89, 0xDB, 0x98, 0x5D],再观察enc
函数:
1 | __int64 __fastcall enc(char *a1) |
观察发现enc
函数为输入值与xork
字符串的循环异或,之后找到xork
数组是"meow"
,又找到encc
函数如下:
1 | __int64 __fastcall encc(char *a1, char *a2) |
其中a1是xork
,a2是循环异或后的输入值,接下来又进行了类似RC4的加密。
分析得出总的逻辑链条:输入值会被带进enc的生成方法(作为string v),之后this.enc = encrypt(输入值, stringTokey(”IamEzEncryptGame”)),而encrypt函数则是先将1与key进行AES加密,然后再把输入值带进去加密,之后this.enc会被带进check进行检验,而这部分在.so文件内完成,this.enc会被存入s数组并在.so文件内执行文件内的enc函数,先与xork(也就是”meow”)进行循环异或,再返回xork和a1带入encc函数的值,而encc函数则会进行可逆的RC4加密,在i大于等于v2(也就是a2的长度,enc中a1的长度,检验函数中s的长度(应当与mm相等,是44))之后跳出循环,返回循环的次数(也就是应当输入字符串的长度44,是定值),同理enc也会返回44,虽然enc(s)的运算结果并没有对任何变量进行赋值,但是在此过程中this.enc已经被改变,进行了循环异或又进行了RC4加密,才与mm(也就是[0xC2, 0x6C, 0x73, 0xF4, 0x3A, 0x45, 0x0E, 0xBA, 0x47, 0x81, 0x2A, 0x26, 0xF6, 0x79, 0x60, 0x78, 0xB3, 0x64, 0x6D, 0xDC, 0xC9, 0x04, 0x32, 0x3B, 0x9F, 0x32, 0x95, 0x60, 0xEE, 0x82, 0x97, 0xE7, 0xCA, 0x3D, 0xAA, 0x95, 0x76, 0xC5, 0x9B, 0x1D, 0x89, 0xDB, 0x98, 0x5D])进行比较,基于此进行逐步逆向,先将mm逆向回到RC4之前:
1 | def init_sbox(key): |
计算得mm_beforeRC4_after_xor = [0x5F, 0x27, 0x2D, 0x5C, 0x2A, 0x34, 0x0E, 0x1A, 0x1D, 0x2E, 0x02, 0x04, 0x1F, 0x03, 0x2B, 0x30, 0x55, 0x50, 0x44, 0x47, 0x2C, 0x52, 0x01, 0x46, 0x55, 0x28, 0x44, 0x1C,0x39, 0x57, 0x15, 0x35, 0x29, 0x0C, 0x35, 0x24, 0x22, 0x57, 0x57, 0x3E, 0x0E, 0x0D, 0x5B, 0x4A],再逆向回到异或之前:
1 | mm = [0x5F, 0x27, 0x2D, 0x5C, 0x2A, 0x34, 0x0E, 0x1A, 0x1D, 0x2E, 0x02, |
得到this.enc =[0x32, 0x42, 0x42, 0x2B, 0x47, 0x51, 0x61, 0x6D, 0x70, 0x4B, 0x6D, 0x73, 0x72, 0x66, 0x44, 0x47, 0x38, 0x35, 0x2B, 0x30, 0x41, 0x37, 0x6E, 0x31, 0x38, 0x4D, 0x2B, 0x6B, 0x54, 0x32, 0x7A, 0x42, 0x44, 0x69, 0x5A, 0x53, 0x4F, 0x32, 0x38, 0x49, 0x63, 0x68, 0x34, 0x3D],转换得2BB+GQampKmsrfDG85+0A7n18M+kT2zBDiZSO28Ich4=,再带回AES解密(key=IamEzEncryptGame)得flag{0hh_U_kn0w_7h15_5ki11}.
PangBai 泰拉记(1):
反编译后静态分析发现main函数内说Use your debugger to discover the hidden flag!
和Click on the flag to obtain?
但经过检查,发现加密的步骤居然只有for ( i = 0; i < 32; ++i ){flag[i] ^= key[i];}
一步,猜测题目并没有这么简单,点开查看flag和key之后输入进python进行异或操作发现输出的是乱码,而flag本身对应的是can you find me can you find me?
,提示还需要再找,于是尝试进行动态调试,运行程序发现报错,缺少四个dll文件:VCRUNTIME140D.dll、VCRUNTIME140_1D.dll、ucrtbased.dll和MSVCP140D.dll,去网络上搜索这些dll文件并下载放在同目录中,发现程序不再报错,进行动态调试,在退出前设置断点,在暂停时得到了一串新的key和flag,带入运行却发现输出flag{I_m_wrong_flag_dont_submit},说明flag还是不对,再次详细观察发现还有一个main0
函数,在运行过程中多次检测了是否正处于调试状态(如j___CheckForDebuggerJustMyCode、CheckRemoteDebuggerPresent、IsDebuggerPresent等等),得知应当绕过这些反调试机制,总的程序流程应该是j___CheckForDebuggerJustMyCode→main0
上半段→main0
(j___CheckForDebuggerJustMyCode)→main0
(CheckRemoteDebuggerPresent(m1a0, &miao);)→if ( miao | IsDebuggerPresent() )如果检测到正在调试,就修改aaa
并将key与aaa进行错误的异或,否则修改v5和v6并将key与v5进行正确的异或,再将flag和key进行异或得到结果,基于此要么在所有CheckRemoteDebuggerPresent执行时将&miao
改为0(false)和在所有IsDebuggerPresent函数执行时将其返回值patch为0,要么找到对应的反汇编代码并改动跳转条件:
1 | .text:00007FF670C22B8A call cs:__imp_CheckRemoteDebuggerPresent |
将jz改为jnz(右键patch修改byte将机器码0F 84改为0F 85),观察发现接下来是key与
1 | .text:00007FF670C22C6A ; --------------------------------------------------------------------------- |
进行异或,即条件判定已修改完成,绕过了反调试机制,接着进行动态调试,退出前发现key已经变为[0x05, 0x0D, 0x0F, 0x47, 0x02, 0x02, 0x0C, 0x7F, 0x22, 0x5A, 0x0C,0x11, 0x47, 0x0A, 0x56, 0x52, 0x3C, 0x0C, 0x0F, 0x59, 0x26, 0x5E, 0x06, 0x7F, 0x04, 0x08, 0x00, 0x0A, 0x45, 0x09, 0x5A, 0x42],将flag与key进行异或,得到flag{my_D3bugg3r_may_1s_banned?}.代码:
1 | for i in range(32): |
Dirty flowers:
反编译后发现程序内有花指令混淆了某些基本函数,找到程序内部有以下提示:
1 | .rdata:00B83118 Format db 'Please first master basic x86 assembly knowledge',0Ah,0 |
接下来就是检测flag,反编译main会发现main执行后会自动跳转到这里(用汇编语言继续执行?),根据提示将对应的函数全部改成nop(无操作),再进行断点调试运行,发现机制是先检测位数是否符合要求,如果不符合要求直接以错误的长度退出,长度符合要求再进行字符上的比较,在反汇编代码中找到检测输入部分:
1 | .text:00B81321 lea eax, [ebp-1004h] ; (用户输入存储在 [ebp-1004h] 中) |
和比较长度部分:
1 | .text:00B8138C cmp dword ptr [ebp-1010h], 24h ; '$' |
发现所需长度是0x24(也就是36位),再观察下方符合长度要求时进一步的检测:
1 | .text:00B813A6 ; --------------------------------------------------------------------------- |
子函数sub_B81100反编译:
1 | void sub_B81100() |
0xB81177部分:
1 | .text:00B81177 |
0xB81189周围的循环:
1 | .text:00B81180 ; --------------------------------------------------------------------------- |
子函数sub_B81100结束后:
1 | .text:00B813B9 add esp, 8 |
在此基础上反编译sub_B811D0函数,如下:
1 | int __cdecl sub_B811D0(int a1, int a2) |
即输入与dirty_flower循环异或后得到的结果与v3逐字符比对,若有不符的直接返回0,倒推得到flag:flag{A5s3mB1y_1s_r3ally_funDAm3nta1}.
代码:
1 | v3 = [2, 5, 19, 19, 2, 30, 83, 31, 92, 26, 39, 67, 29, 54, 67, 7, 38, 45, 85, 13, 3, 27, 28, 45, 2, 28, 28, 48, 56, 50, 85, 2, 27, 22, 84, 15] |
Web方向:
你能在一秒内打出八句英文吗:
进入页面,发现右键、开发者菜单、复制粘贴等全部被禁用,下方有一个计时器,超出一秒就无法成功,再观察源代码,发现源代码第41行就是要输入的文本内容,形如<p id="text">xxx</p>
,于是选择使用JavaScript在控制台提交:
1 | const textElement = document.getElementById('text'); |
将代码提前复制粘贴到控制台,在按下按钮的瞬间迅速按enter即可在一秒内提交答案,得到flag:flag{70c3123f-1368-4b50-a6de-e10468333471}.
Crypto方向:
这是几次方?疑惑!:
^在计算机中并不表示次方(Pow)而是表示异或(谐音疑惑),下载py文件后得到另一个RSA加密,其中有e = 65537,hint = p^e + 10086,而异或的优先级低于加号,所以由提示的
1 | hint=12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531673390 |
得到
1 | p=hint^75623=12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531598921 |
从而计算出
1 | q=9894080171409167477731048775117450997716595135307245061889351408996079284609420327696692120762586015707305237750670080746600707139163744385937564246995541 |
用之前写的python工具得出
1 | φ=124455847177872829086850368685666872009698526875425204001499218854100257535484730033567552600005229013042351828575037023159889870271253559515001300645102547272582607556946597809819206498583700587629821896578436377540232328729792993693280980361351684844458558344121591584147825245121367422062684374122308816800 |
解得flag{yihuo_yuan_lai_xian_ji_suan_liang_bian_de2333}.
Since you konw something:
发现仍然是异或,但是这次key不知道,不过提示了非常小,于是按上次的方式遍历key,最后得到key是b’ns’,flag为:flag{Y0u_kn0w_th3_X0r_b3tt3r}.代码如下:
1 | from pwn import xor |
茶里茶气:
观察代码并结合题目名称得知该算法是魔改的TEA加密,flag长度为25(内部为19),a是将 flag 中每个字符的 ASCII 值转换为十六进制字符串,去掉0x并连接起来形成一个长的字符串,当flag长度为25时l的值恰为199,v0是a 的前 99 位,v1是a的后99位,接下来随机了一个p=446302455051275584229157195942211
,又给出了其他几个数据:
1 | v2 = 0 |
之后进行32次变换:
1 | for i in range(32): |
变换又可以写作:
1 | for i in range(32): |
注意到v2初始值是0,且derta远小于p(p/derta≈964),所以v2在每次执行完后其实是(i+1)*derta,从来没有被取过余,代入可转化为:
1 | for i in range(32): |
而v0和v1是一定会超出p的,所以被取过余。又∵若a+b ≡ c (mod p),则a ≡ c - b (mod p),构建逆向算法:
1 | from Crypto.Util.number import * |
解得flag为:flag{f14gg9_te2_1i_7ea_7}.
Just one and more than two:
观察发现是变种的RSA,N为3个大质数的成绩,flag被拆成大小相等的两段,m1和m2所取的mod也不一样,但依旧可以按照之前的方法来解:
1 | from sympy import mod_inverse |
拼合得到flag:flag{Y0u_re4lly_kn0w_Euler_4nd_N3xt_Eu1er_is_Y0u!}.
Misc方向:
wireshark_checkin:
打开pcapng,根据题目flag应该在文件传输中留有痕迹,在左上角导出对象HTTP中发现有一个flag.txt,找到flag:flag{ez_traffic_analyze_isn’t_it}.
wireshark_secret:
依旧是导出对象,发现有一张png,导出后在图片上得到flag:flag{you_are_gooddddd}.
热心助人的小明同学:
根据提示文件是一个内存镜像,下载文件后将格式改为.vmem,用Passware Password Recovery Kit破解得到密码为:ZDFyVDlfdTNlUl9wNHNTdzByRF9IQUNLRVIh
,即为flag.
用溯流仪见证伏特台风:
搜索相关报导,在http://news.china.com.cn/2024-07/08/content_117297304.shtml
找到原图清晰版本得到域名为:powerj7kmpzkdhjg4szvcxxgktgk36ezpjxvtosylrpey7svpmrjyuyd.onion,转写16位小写md5得到6c3ea51b6f9d4f5e
,即为flag.
你也玩原神吗:
发现gif中包含一帧特殊图像:
拆解发现其中正中央是经典的乱数假文:Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
但是末尾出现了四个额外字符quis,左上角是flag is a sentence,左下角是do you know fence,考虑栅栏加密,右上角是iiaaelgtsfkfa
,解密得到itisafakeflag
,右下角是mesioaabgnhnsggogmyeiade
,解密得到maybegenshinisagogdoagem
,转换一下得到maybegenshinisagoodgame
,即为flag.
字里行间的秘密:
打开txt发现藏的有两部分零宽度隐写,破解得到隐写字符分别为i和t_is_k3y,合并得到it_is_k3y,打开docx文档,发现有一行虚空文字,复制粘贴得到flag:flag{you_h4ve_4nyth1n9}.
Herta’s Study:
追踪流,发现上传了一个木马,还有fake{this_is_fake_falg}和名为flag
、fla
、f
的txt记录,导出全部对象后发现以下内容:
1 | -----------------------------350282821911984478142300364923 |
1 |
|
1 | -----------------------------350282821911984478142300364923 |
解码中间的构建函数部分后得到:
1 |
|
得知所有输入值的偶数位都经过了rot13变换,而f、flag等文档中都含有类似base64加密的内容:
1 | f ZzxuZ3tmSQNsaGRsUmBsNzVOdKQkZaVZLa0tCt== |
代入0721
对应的值进行检验,变换为MDcyMQo=
,恰好是0721
的base64,验证完成,对上述f、flag、fake进行变换,得到其中一组为:ZmxhZ3tzSDNfaTRfUzBfNmVBdXQxZnVMLn0gCg==
,经过转换得到flag{sH3_i4_S0_6eAut1fuL.}.
赛题轮次:Week3
已解出的题目:
题目类型 | 题目名称 |
---|---|
Reverse | PangBai过家家(3)、SMc_Math、取啥名好呢?、011vm、flowering_shrubs |
Crypto | 故事新编1、没e这能玩?、故事新编2 |
Misc | BGM坏了吗?、ez_jail、AmazingGame、OSINT-MASTER |
Reverse方向:
PangBai过家家(3):
下载程序发现是被打包成exe的pyc文件,使用pyinstxtractor
进行拆解,得到很多文件,再用pycdc
进行反编译,反编译主pyc得到enc = [40,9,22,52,15,56,66,71,111,121,90,33,18,40,3,13,80,28,65,68,83,88,34,86,5,12,35,82,67,3,17,79]和key = ‘NewStar2024’,一眼看出前面的数组是加密后的flag,接下来寻找是如何加密的,用pycdas再次反汇编发现是将输入值与key进行循环异或,于是再次异或得到flag{Y0u_Know_Py1nstall3r_W311!}.
SMc_math:
反编译主函数发现flag长度应当为28,但是最关键的encrypt函数却无法反编译,查询得知使用了动态代码加密技术,将encrypt逐字节用0x3Eu进行了异或操作,进行动态调试,在数据异或后、函数执行前下断点,再在汇编界面选中整个函数按u转回机器码,再按p汇编,按f5反编译,得到以下代码:
1 | _BOOL8 __fastcall encrypt(unsigned int *a1) |
观察encrypt函数不难猜出v4~v10是输入值按小端序转为dword的结果,而且输入的a1是32位无符号整数(如输入1234则为0x34333231u),然后构建了一大堆式子使得输入值经过某些变化满足所给常数才能通过:
编写python z3-solver脚本来解出flag:
1 | from z3 import * |
输出flag{D0_Y0u_Kn0w_sMC_4nD_Z3}.
取啥名好呢?:
打开发现main函数无法被反编译,而多出来一个mian函数,内容如下:
1 | int mian() |
推测程序会在触发这些信号时调用对应的函数,再观察后面的handler,发现只有func1可能是应当被调用的函数:
1 | void handler_func1() |
func1会将数组byte_4150的第dword_4168个值与dword_4168的值进行异或,当dword_4168的值为22时,调用函数sub_12E9,再将其本身加上1,而sub_12E9就是最终的比较函数:
1 | void __noreturn sub_12E9() |
sub_12E9
会将加密的输入值与加密的flag的前23个字节(也就是flag的长度)进行比较,而加密的flag为[0x4F, 0x54, 0x48, 0x53, 0x60, 0x45, 0x37, 0x1A, 0x28, 0x41, 0x26, 0x16, 0x3B, 0x45, 0x14, 0x47, 0x0E, 0x0C, 0x70, 0x3B, 0x3C, 0x3D, 0x70],不过到此为止我们只找到了一个简单的异或加密和最终的比较部分,main函数和读取输入的部分并未发现,于是转到_start函数再点击main,发现main会维持汇编代码,尝试按u将main全部转为机器码再按p,发现仍然不能转变,进行手动分析:
1 | .text:0000000000001983 ; --------------------------------------------------------------------------- |
手动转为大致的C语言代码:
1 | int main() { |
那么程序的运行逻辑已经很明确了,只是简单的读取了输入的23个字符,然后每个字符值加上了dword_4068,接着除以0引发信号8转入func1
,再将数组byte_4150的第dword_4168个值与dword_4168的值进行异或,而dword_4168的值的波动则在main
函数的switch case中:
1 | switch (dword_4168) { |
和func1中的++dword_4168体现,当dword_4168的值为22时,调用函数sub_12E9进行比较,接下来进行动态调试,发现dword_4068是定值0xE9
,相当于每个字符的十六进制ASCII值加上0xE9强制转换byte,接下来在第二次异或的时候输入值的1-23个字符每次异或的值分别是0-22,倒推得到原来的十六进制字符[0x66, 0x6C, 0x61, 0x67, 0x7B, 0x57, 0x48, 0x34, 0x37, 0x5F, 0x43, 0x34, 0x4E, 0x5F, 0x31, 0x5F, 0x35, 0x34, 0x79, 0x3F, 0x3F, 0x3F, 0x7D]和flag{WH47_C4N_1_54y???}.
附Python代码:
1 | enc_flag = [0x4F, 0x54, 0x48, 0x53, 0x60, 0x45, 0x37, 0x1A, 0x28, 0x41, 0x26, 0x16, 0x3B, 0x45, 0x14, 0x47, 0x0E, 0x0C, 0x70, 0x3B, 0x3C, 0x3D, 0x70] |
011vm:
反编译发现代码结构非常混乱,结合题目名称和介绍得知是ollvm
混淆,使用D-810
插件反混淆后再次反编译,先简要进行动态调试,发现被加密的输入内容和被加密的真正的flag进行比对的步骤在transform
中的v11 = memcmp(s1, s2, 0x20uLL);
这一行,s1是加密后的输入内容,s2是被加密的真正的flag,观察发现到比较s1与s2这一步时,s1的值恰好是上文提到的在t3被printf的值,若s1只输入一种字符(如32个2),则能体现出s1是被循环加密的(并且循环周期恰好是8个字符,也就是上文提到的2组),s1与s2紧邻,s2的值为定值[0x28, 0x7E, 0xB9, 0x38, 0xC1, 0x10, 0xE5, 0xB7, 0xAE, 0x9F, 0xB2, 0xB4, 0xD7, 0xBB, 0x93, 0x55, 0x9E, 0x9B, 0x2E, 0x3C, 0x37, 0xC6, 0x71, 0x16, 0xB5, 0x8C, 0x3A, 0x8F, 0x15, 0xE5, 0x16, 0x51],那么可以验证s2就是被加密后的flag,经过观察发现栈上这些数据之间的关系为:
s1(修改的输入值) 32个字符
S2(修改的flag值) 32个字符
猜测是循环加密的秘钥,值为[0x14, 0x13, 0x12, 0x11, 0x25, 0x24, 0x23, 0x22, 0x36, 0x35, 0x34, 0x33, 0x44, 0x43, 0x42, 0x41] 16个字符
未知 176个字符
输入的字符 32个字符
(以下内容包含流程解释,因此以图片形式展示):
进行正向测试(这里输入了12345678作为前8个字符的测试):
1 | v6 = 0x34333231 |
输出结果为:0x6e9bbeb4
0x443b7474
再看看IDA动态调试的结果,与输出结果完全一致,由此找到测试程序与实际输出的对应关系:v6对应前四个字符的小端序dword,v4对应后四个字符的小端序dword,输出结果的v6是加密后的前四个字符的小端序dword,v4也是加密后的后四个字符的小端序dword,至此T170函数的主要部分(TEA加密)已经解析完成。
接着解析T170函数最后的剩余部分:
1 | v9 = (__int64)v14; |
发现只是简单的把加密后的结果挪回去了,接着分析transform
:
1 | v15 = *v24; --> 513353247 |
…跳过很多break…
1 | if ( v15 != 513353247 ) |
观察发现在执行完多次T170后程序会跳到T160函数,而T160函数仅仅是一个简单的打印加密结果,而剩下的transform
只用来返回比较结果:
1 | } |
剩下的main只用来输出对错:
1 | *(v11 - 16) = v3 != 0; |
至此程序解构完毕,仅有一个简单的TEA加密算法,用week2的python文件做同样处理:
1 | # flag |
输出:
1 | 0x67616c66 0x3131307b |
转化得到galf110{1_mvZe_stuB_M0C_XE1P}!!_,转换端序得到flag{011vm_1s_eZ_But_C0MP1EX_!!}.
flowering_shrubs:
拖进ida惊讶地发现main函数居然是空的,考虑花指令把main混淆了,进入汇编观察代码,找到以下内容:
1 | .text:00000000000014D9 call $+5 |
和
1 | .text:00000000000014D9 call $+5 |
是典型的call型花指令,nop掉后再次反编译main成功:
1 | void __fastcall main(int a1, char **a2, char **a3) |
继续观察发现几乎所有函数都又被花指令干扰了,发现很多call $+5
和retn
的花指令,全部nop掉之后,观察sub_14F0:
1 | __int64 __fastcall sub_14F0(unsigned int a1) |
是一个简单的读取输入函数,再看其中的sub_1548:
1 | __int64 __fastcall sub_1548(__int64 a1, __int64 a2) |
是最终比较flag的函数,先找到定值unk_4020(加密后的flag):[54h, F4h, 20h, 47h, FCh, C4h, 93h, E6h, 39h, E0h, 6Eh, 00h, A5h, 6Eh, AAh, 9Fh, 7Ah, A1h, 66h, 39h, 76h, B7h, 67h, 57h, 3Dh, 95h, 61h, 22h, 55h, C9h, 3Bh, 4Eh, 4Fh, E8h, 66h, 08h, 3Dh, 50h, 43h, 3Eh],然后分析sub_1313:
1 | __int64 __fastcall sub_1313(_BYTE *a1, char a2) |
发现是将a1的当前字节先与a2进行异或,再将a1的下一个字节与a1的当前字节进行异或,再看sub_137B:
1 | __int64 __fastcall sub_137B() |
sub_137B也进行了一些加密操作:
① 将 v0 指向的字节与 v2 - 28 指向的字节相加,结果存储在 v2 - 24 指向的地址加 1 的位置。
② 将 v2 - 24 指向的地址加 2 的位置的字节与 v2 - 24 指向的地址加 1 的位置的字节进行异或操作。
③ 将 v2 - 24 指向的地址加 2 的位置的字节减去 v2 - 28 指向的字节。
④ 将 v2 - 24 指向的地址加 3 的位置的字节与 v2 - 24 指向的地址加 2 的位置的字节进行异或操作。
接着看sub_1410:
1 | __int64 __fastcall sub_1410() |
发现只是简单地转到了sub_1434:
1 | void __fastcall sub_1434(__int64 a1, char **a2, char **a3) |
发现只是简单地执行了sub_1453:
1 | void __fastcall sub_1453(__int64 a1, char **a2, char **a3) |
再查看sub_11C9:
1 | __int64 sub_11C9() |
发现sub_11C9生成了一个疑似没用的随机数并返回了sub_1216的值,观察sub_1216:
1 | __int64 __fastcall sub_1216(__int64 a1, __int64 a2, char a3, char a4) |
sub_1216计算了a4 - 8 \* (a3 + v4)
并存储在v6 - 1
指向的字节中,然后生成了一个随机数并存储在v7
中,接着调用了sub_1257,分析sub_1257:
1 | __int64 __fastcall sub_1257(__int64 a1, int a2) |
发现只是简单地返回了sub_1285的值,查看sub_1285:
1 | void __fastcall sub_1285() |
发现只是简单地返回了sub_12A2的值,查看sub_12A2:
1 | __int64 __fastcall sub_12A2(__int64 a1, __int64 a2, char a3) |
sub_12A2将a3
存入v4-1
对应的字节,并使v4-1
与0xFC
进行按位与操作,然后返回sub_12CA的值,查看sub_12CA:
1 | void __fastcall sub_12CA() |
发现只是简单地返回了sub_12EC的值,查看sub_12EC:
1 | __int64 __fastcall sub_12EC(__int64 a1, __int64 a2, __int64 a3) |
sub_12EC检查v3 + a3
指向的字节是否为0,若是,将byte_4080数组中v4 - 1
指向的字节值作为索引的位置设置1,并返回v4 - 1
指向的字节值,sub_12EC应为该支链上最末端的函数.
至此所有自定义函数均已分析完毕,经动态调试发现输入值是以四个字符为单位进行加密的,但却不是逐组加密,而是按第1 2 9 4 3 7 5 6 8 10组的顺序加密,a2是固定数组[0x75, 0x61, 0x72, 0x65, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2E],每组中的加密为:
① sub_1313:
1 | *a1 ^= a2;//第一个字符变为异或a2后的字符 |
② sub_137B:
1 | *(_BYTE *)(*(_QWORD *)(v2 - 24) + 1LL) = *v0 + *(_BYTE *)(v2 - 28);//新第二个字符 = 第二个字符+a2强制转换成byte |
③ sub_1410:
1 | return sub_62C568C23434(a1, a2, *(_BYTE *)(v3 + 3) ^ a3);//注意到这里的最后一个参数,v3+3是第四个字符,a3是第一个字符,以输入flag为例,①②完成后第一个字符变为13,第四个字符变为47,但最后第一个字符会变成54,而47^13正是54,所以这一步是让第一个字符成为第四个字符和第一个字符的异或值. |
结合密文[54h, F4h, 20h, 47h, FCh, C4h, 93h, E6h, 39h, E0h, 6Eh, 00h, A5h, 6Eh, AAh, 9Fh, 7Ah, A1h, 66h, 39h, 76h, B7h, 67h, 57h, 3Dh, 95h, 61h, 22h, 55h, C9h, 3Bh, 4Eh, 4Fh, E8h, 66h, 08h, 3Dh, 50h, 43h, 3Eh]编写python代码进行解密:
1 | enc_flag = [[0x54, 0xF4, 0x20, 0x47], [0xFC, 0xC4, 0x93, 0xE6], |
输出[[102, 108, 97, 103], [123, 121, 48, 117], [95, 67, 52, 110], [95, 51, 97, 53], [49, 108, 121, 95], [82, 101, 109, 48], [118, 51, 95, 67], [111, 78, 102, 117], [53, 49, 48, 110], [45, 33, 33, 125]],转化得到
1 | [[0x66, 0x6c, 0x61, 0x67], |
十六进制转字符得到flag{y0u_C4n_3a51ly_Rem0v3_CoNfu510n-!!}.
Crypto方向:
故事新编1:
观察被加密后的文本和加密方式得知是维尼吉亚密码,代入求解器,计算得key为subtitution
,解得zen1
为:
1 | BEAUTIFUL IS BETTER THAN UGLY. |
将明文的结尾多加上一个换行符带回原python文件执行,发现输出与所给相同,返回flag{bda2bcf1eaeff7754a6483e74e70a937}.
没e这能玩?:
观察发现是类RSA加密,给了三个大质数pqr的相关方程组,由于每组的值均已知,那么pqr构成的三元一次方程组是可以直接解的:
解得:
1 | p=10183677214652023044474780341271224480860741865271128462400237861954731862671046572481587003643951915319746511716779405224320633957652210335830783097185731 |
接下来又给出了大质数的e次方除以2的512次方的余数hint,即prime ^ e ≡ hint (mod 2^512)
,发现e用python极其难以计算,在linux上下载专业求解器PARI/GP
,编写.gp脚本:
1 | h=1117823254118009923270987314972815939020676918543320218102525712576467969401820234222225849595448982263008967497960941694470967789623418862506421153355571; |
输出e = 18344052974846453963
,带回原文件,使用之前的解密文件,结合phin = (p-1)\*(q-1)\*(r-1)
,d = gmpy2.invert(e,phin)
,得到
1 | d=353990565818625470203369777239314743702215915925233196685710922971035136745962920650978745767080817978533931486524755066350185911891300410592045053635868129756936833542534389569636562575622589318159685096070360444918245742399076155280089425194564103920810567326139574530309013616431270046381080112401477101628065595664594982640241027864708389381440505030265366815955626373592957956775853576195599726111052060073122365749364757456811923493380577927720503392159587 |
结合原文件的
1 | c=999238457633695875390868312148578206874085180328729864031502769160746939370358067645058746087858200698064715590068454781908941878234704745231616472500544299489072907525181954130042610756999951629214871917553371147513692253221476798612645630242018686268404850587754814930425513225710788525640827779311258012457828152843350882248473911459816471101547263923065978812349463656784597759143314955463199850172786928389414560476327593199154879575312027425152329247656310 |
得到m = powmod(c, d, n)
= 2761328357324640838041048482823820617928011152855994221385645606528893,结合python脚本:
1 | m2 = long_to_bytes(m) |
得到flag{th1s_2s_A_rea119_f34ggg}.
故事新编2:
发现同样是维尼吉亚密码,代入求解器选择autokey
模式解得key为supersubtitution
,zen2
为:
1 | IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS. |
是python之禅的另一部分,但这次中间没有夹杂额外的句子,将明文的结尾多加上一个换行符,返回flag{8bc383165248f2e45a6910960a61e6a8}.
Misc方向:
BGM坏了吗?:
观察音乐频谱发现最后几秒钟的左声道为杂音,右声道有整齐的波形,截取右声道的波形如下:
经过对比分析得知这是经典的DTMF拨号音,经过比对听出信息为2024093020241103,即为flag.
ez_jail:
下载后观察文件发现题目配置了一个用Python编写的服务器,需要上传被base64编码的C++代码,并在不允许使用#include、#define、不包含{}、长度不超过100字符的情况下执行,若成功输出”Hello, World!”,则会返回flag,查找各种信息后发现二元字符<%和%>可以替代{},将void user_code() <% std::cout << “Hello, World!”; %>转换成base64:dm9pZCB1c2VyX2NvZGUoKSA8JSBzdGQ6OmNvdXQgPDwgIkhlbGxvLCBXb3JsZCEiOyAlPg==并输入,得到flag{cabac835-dabb-406e-8047-c83a95868834}.
AmazingGame:
把游戏至少打通一关,安装MT管理器,找到安装包提取,找到com.pangbai.projectm
,发现数据目录/data/user/0/com.pangbai.projectm
,找到shared_prefs
,发现一个文件net.osaris.turbofly.JumpyBall.xml
,点击查看发现是存档,代码如下:
1 |
|
将前两个值修改为21
,第四个值改成5
(解锁最强战机)再次启动游戏发现文档已经被改回去,但是多了个bak文件,点击bak文件恢复备份(将bak文件与现在的文档互换后缀),再次修改成同样的值,并等待一会,发现关卡已解锁,把第二十关打通,得到flag{U_W1n!!_7he_g@m4}.
OSINT-MASTER:
打开图像发现是在飞机上拍的照片,右侧机翼上有飞机编号B-2419
,点击图像详细信息,发现拍摄时间2024年8月18日14:30
,在飞常准软件中搜索,得到航班编号MU5156
,跟踪得到14:30时位于山东省济宁市,组合得到flag{MU5156_济宁市}.
赛题轮次:Week4
已解出的题目:
题目类型 | 题目名称 |
---|---|
Pwn | Maze_Rust |
Reverse | Easygui、ezrust、MazE、洞OVO |
Crypto | 欧拉欧拉!!、圣石匕首 |
Misc | 扫码领取flag、ezblockchain、Alt |
Pwn方向:
Maze_Rust:
把迷宫走完就出来了,卡墙了就刷新地图,最后:
Say something: cat /flag
返回flag{bf1bc81d-3f19-46a5-9962-15367aa71e06}.
Reverse方向:
easygui:
反编译找到WinMain,发现整个主函数里面只有一个子函数sub_140001490,查看发现前面声明了一大串变量,再往下就是程序的输入框和验证框,在第140行找到:
1 | if ( WindowTextW <= 44 ) |
之后是大量的字符比较和最终结果的打印,可以证明这里就是给输入的字符串进行加密和验证的地方,加密的flag值为[-33, -57, 77, 20, -63, -20, 8, -28, 95, 63, 3, -76, -112, 74, -71, -113, -113, -6, 113, 67, -57, -15, -99, -35, 79, -64, 18, 68, 92, -99, -120, 54, 45, 22, 29, -19, -68, -17, -69, 91, -97, 119,-21, 88],再观察加密函数sub_140001000(String, (unsigned int)WindowTextW, v93);:string
是输入的字符串(但是每个字符之间有一个间隔),windowtextw
是输入字符串长度,v93
是输入的字符串的每个字符,函数将输入的字符串移动到Src
数组,然后将v29
数组加载上8个常量:
1 | xmmword_140003390: 17122A41293C7A6463251526050E3B7Fh |
注意到xmmword
都是从右往左填入的(仍然是小端序),也即
1 | v29 = [ |
之后进行第一次加密:
1 | if ( (int)v4 > 0 ) //输入值长度 |
然后改变v29:
1 | v16 = 256i64; |
最后再进行一次加密:
1 | v25 = 0; |
观察发现最后一次是以easy_GUI
为key的RC4,第一次则是置换与位运算,进行动态调试发现失败,观察发现第94行有if ( !IsDebuggerPresent() )反调试,按照惯例将跳转的jz修改为jnz(74改75)处理后能正常运行,却发现输入框只能输入42个字符,于是在调试的时候修改数据补上两个,调试过程中发现的规律已经在上面的函数处进行注释.
下面先逆向第一次加密:由于第一次加密分两个小部分,先是把每个字符的值按10周期的循环加上[8, 25, A, 32, FE, E, 35, 37, E, FE]再强制转换为byte,那么这部分的逆向就很简单了,减回去就行,第二部分的位运算就比较复杂,先对反编译出来的函数进行化简(以4个字符为一个单元):
1 | def encrypt_step_1(Src): |
也就是说正向过程为:
新的第一个=旧的第四个的低三位在前+旧的第一个的高五位在后:new[0] = ((old[3] << 5) | (old[0] >> 3)) & 0xFF
新的第二个=旧的第一个的低三位在前+旧的第二个的高五位在后:new[1] = ((old[0] << 5) | (old[1] >> 3)) & 0xFF
新的第三个=旧的第二个的低三位在前+旧的第三个的高五位在后:new[2] = ((old[1] << 5) | (old[2] >> 3)) & 0xFF
新的第四个=旧的第三个的低三位在前+旧的第四个的高五位在后:new[3] = ((old[2] << 5) | (old[3] >> 3)) & 0xFF
那么逆向过程就是:
旧的第一个=新的第一个的低五位在前+新的第二个的高三位在后:old[0] = ((new[0] << 3) | (new[1] >> 5)) & 0xFF
旧的第二个=新的第二个的低五位在前+新的第三个的高三位在后:old[1] = ((new[1] << 3) | (new[2] >> 5)) & 0xFF
旧的第三个=新的第三个的低五位在前+新的第四个的高三位在后:old[2] = ((new[2] << 3) | (new[3] >> 5)) & 0xFF
旧的第四个=新的第四个的低五位在前+新的第一个的高三位在后:old[3] = ((new[3] << 3) | (new[0] >> 5)) & 0xFF
1 | def decrypt_step_or(Src): |
再把最后一步进行逆向:
1 | import struct |
以上两个就是倒数第二、第一步的逆向(正向加密的第二、一步),后面的就是典型的以“easy_GUI”为key的RC4了,带入经过前两次加密的字符串C72AE7ACE6688D8DE8E5C72AE7ACE6688D8DE8E5C72AE7ACE6688D8DE8E5C72AE7ACE6688D8DE8E5C72A(1234567890*4+12)进行验证,在线网站和IDA上的输出相同,均为776c0c7d4428ceae38f3433a504cf98e4d50374a27f59dd8c02fb12c9b5ca9738e3d520c7e44149ff35c,验证通过,将原先的加密值(DFC74D14C1EC08E45F3F03B4904AB98F8FFA7143C7F19DDD4FC012445C9D88362D161DEDBCEFBB5B9F77EB58)带入,解得[0x6f, 0x81, 0xa6, 0xc5, 0x63, 0xac, 0x4b, 0xc7, 0x8f, 0x29, 0x87, 0xa4, 0x27, 0xaa, 0xa6, 0x69, 0x4f, 0x27, 0xae, 0xec, 0x27, 0x2e, 0xe7, 0xa9, 0x69, 0x87, 0x2e, 0xe5, 0x2f, 0x24, 0xe6, 0x6f, 0x44, 0x87, 0xa9, 0x89, 0x4f, 0x26, 0x47, 0x21, 0xab, 0x01, 0xa7, 0xae],再代入上面的两个逆向函数的倒数第二步,解得[0x7C, 0x0D, 0x36, 0x2B, 0x1D, 0x62, 0x5E, 0x3B, 0x79, 0x4C, 0x3D, 0x24, 0x3D, 0x55, 0x33, 0x49, 0x79, 0x3D, 0x77, 0x62, 0x39, 0x77, 0x3D, 0x49, 0x4C, 0x39, 0x77, 0x2B, 0x79, 0x27, 0x33, 0x79, 0x24, 0x3D, 0x4C, 0x4A, 0x79, 0x32, 0x39, 0x0A, 0x58, 0x0D, 0x3D, 0x75],再代入最后一步,解得[0x66, 0x6C, 0x61, 0x67, 0x7B, 0x47, 0x55, 0x21, 0x5F, 0x72, 0x33, 0x76, 0x33, 0x52, 0x35, 0x65, 0x5F, 0x33, 0x6E, 0x47, 0x31, 0x6E, 0x33, 0x65, 0x72, 0x31, 0x6E, 0x67, 0x5F, 0x69, 0x35, 0x5F, 0x76, 0x33, 0x72, 0x79, 0x5F, 0x73, 0x31, 0x6D, 0x70, 0x6C, 0x33, 0x7D],即为flag{GU!_r3v3R5e_3nG1n3er1ng_i5_v3ry_s1mpl3}.
ezrust:
发现函数多达543个,好在是windows程序,结合动态调试单步调试观察主要函数,首先是只有一行的main,返回的是std::rt::lang_start::h55428494e2afd6bb((__int64)sub_7FF790BE4D20, argc, (__int64)argv, 0);,看起来很奇怪,main并没有直接包含程序基本运行逻辑,而是用·std::rt::lang_start::h55428494e2afd6bb来执行,并且这个函数的第一个参数又是另一个函数sub_7FF790BE4D20
的返回值,观察外部函数的反编译:
1 | v2 = a1; //这里v2、a1就是sub_7FF790BE4D20的返回值 |
发现又嵌套了一层,而&unk_7FF790BFF548
是一个除了第九项为8之外其他项全为0的24项的数组,再看这个函数:
1 | __int64 __fastcall std::rt::lang_start_internal::hc7b8e748e4342164(__int64 a1, __int64 a2) |
发现了很多名称很长又极其丑陋的函数,过于复杂,而且貌似和核心程序并没有什么关系(毕竟正常运行时弹出的“请输入要比较的字符串:”和经典的比较函数以及加密的flag字符串貌似在这里都找不到),转换角度观察其第一个参数sub_7FF790BE4D20
这个函数:
1 | __int64 sub_7FF790BE4D20() |
可以看到v6是一个26个字节的数组,可能是被加密的flag,值为[18, 31, 20, 21, 30, 15, 95, 57, 43, 51, 7, 65, 58, 79, 95, 3, 16, 44, 53, 6, 58, 4, 26, 31, 0, 14],而还有一些其他的变量v7、v8等作用未知,在v15 = _$LT$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::flush::h6c721deb5b75b2b0(&v9);
这一行打印了“请输入要比较的字符串:”,在v17 = std::io::stdio::Stdin::read_line::h381832b7ef3a4ed2(&v11, v10);
这一行读取了输入,v1 = sub_7FF790BE4480(v10);
负责存储输入到v1,真正重要的是sub_7FF790BE4BA0((unsigned int)v12, v3, v4, (unsigned int)&unk_7FF790BFFDF8, 8i64);
这个函数,其中的第四个参数&unk_7FF790BFFDF8
是一个字符串“loverust”([0x6C, 0x6F, 0x76, 0x65, 0x72, 0x75, 0x73, 0x74]),
观察这个函数:
1 | __int64 __fastcall sub_7FF790BE4BA0(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5) //a1是v12,a2是v3,a3是v4,a4是字符串,a5是8 |
其中sub_7FF790BE2B30(v13, v7, v5);
有以下主要五行:
1 | result = a1; //地址2163AE3BEF0 |
其中sub_7FF790BE29D0(v12, v13, v10, &v11);
有以下主要七行:
1 | result = a1; //初始化??? |
其中sub_7FF790BE2AD0(a1, v12);
有以下主要两行:
1 | sub_7FF790BE4940(); |
其中sub_7FF790BE4940();
有以下主要两行:
1 | sub_7FF790BE4380(v4); |
其中v4是个40个字符的char,是各种地址的集合,观察sub_7FF790BE4380
:
1 | memcpy_0(a1, a2, 0x28ui64); |
这里将a2的前40个字节复制给了a1,再观察sub_7FF790BE4B00
:
1 | sub_7FF790BE14B0(); |
再看sub_7FF790BE14B0
,发现结构非常复杂,返回的result
是输入字符的长度.
回到“主函数”sub_7FF790BE4D20
,黑蓝色的部分返回的值只会是0或者1(也就是布尔值),并对应着不同的结果,猜测这个部分是加密和比较部分,查看sub_7FF790BE3050
的主要部分(两个参数分别是a1和a2):
1 | v7 = sub_7FF790BE46C0(a1, &off_7FF790BFFD18); |
先查看sub_7FF790BE46C0
(第二个参数是一个library):
1 | v4 = *(_QWORD *)(a1 + 8); |
发现v4指向地址2163AE3DB60
(是一个很奇怪的字符串),v5是输入的长度,再查看sub_7FF790BE2CC0
,发现主要是错误检查,最后返回的还是输入长度,最后result sub函数返回的result还是a1,回到sub_7FF790BE3050
函数,发现最终的sub函数内部居然是比较函数memcmp:
1 | return a2 == a4 && memcmp_0(a1, a3, a2) == 0; |
其中a2是输入长度,a4应该是flag的长度(实际上为26,0x1A),而a1是E开头的字符串,a3就是上文的v6([0x12, 0x1f, 0x14, 0x15, 0x1e, 0x0f, 0x5f, 0x39, 0x2b, 0x33, 0x07, 0x41, 0x3a, 0x4f, 0x5f, 0x03, 0x10, 0x2c, 0x35, 0x06, 0x3a, 0x04, 0x1a, 0x1f, 0x00, 0x0e]),结合“主函数”后面的if和else,可知这就是用于比较的部分,到此为止,输入检测、存放输入的数组、加密的flag、flag长度、比较函数均已知,带入不同flag进行测试,发现是8个一组,对于其中每个位置的字符,假设每组第一个为t,则为[t, t+1, t-1, t+2, t-17, t-2, t-11, t-8](十进制,这里的数字不代表实际数字,而是在这个列表中的偏移量,程序把这些数据进行了某种混淆),其中t在某种程度上分多部分线性增加,考虑到“key”loverust的ASCII码小端序排列恰好符合这个要求,将加密的flag与loverust字符串倒序循环异或,得到数组[102, 108, 97, 103, 123, 121, 48, 85, 95, 64, 114, 51, 95, 57, 48, 111, 100, 95, 64, 116, 95, 114, 117, 115, 116, 125],转换得到flag{y0U_@r3_90od_@t_rust}.
附python代码:
1 | flag = [0x12, 0x1f, 0x14, 0x15, 0x1e, 0x0f, 0x5f, 0x39, 0x2b, 0x33, 0x07, 0x41, 0x3a, |
MazE:
发现是经典的迷宫游戏,开局提示用wsad
来进行上下左右
移动,要从左上角走到右下角,且只能看见周围九宫格的区域,flag是最短路径的md5数值,并且路径长度长达936.
先对main函数进行分析,发现有sub_56EBE386F469
(下称mapgen
)和sub_56EBE386F68F
(下称move_detect
)两个函数,根据上方mapgen
先运行,之后进入死循环的mapgen
以及move_detect
可以推断出mapgen
负责初始化以及后续生成迷宫视图,move_detect
用于读取输入的方向并改变位置.
观察视图生成函数,发现a2和a3用于记录坐标,而初始代入值是(1,1)
,当坐标为(97,97)
时判定胜利,还有三个执行三次的循环,则是生成第一二三行的迷宫视图,char a1的0-2(i)、4-6(j)、8-10(k)用于存储和显示当前的9个格子的地图,对于其中的一个循环有:
1 | for ( i = 0; i <= 2; ++i ) |
可以看出sub_56EBE386F41B
(下称map_encryptor
)用于结合加密地图数据&unk_56EBE38720A0
(下称encmap
)和要显示的位置(后两个参数)来现实对应的字符(墙或者路),注意到其中有两个dword,分别记为d2和d3,有:
1 | d2 = -1-1-1 0 0 0 0 0 1 1 1 0 0 0 0 0(-1为等效) |
而对应绘制的ijk则为0 1 2 | 4 5 6 | 8 9 10
,不妨设(a2,a3)
为原点,则绘制的三行九宫格简化坐标为:
1 | (-1, -1) (-1, 0) (-1, 1) |
可以看出a2控制的是行,a3控制的是列,迷宫坐标范围至少为(0, 0)
到 (98, 98)
,即大小至少为99*99
,观察加密函数map_encryptor
:
1 | __int64 __fastcall sub_56EBE386F41B(__int64 a1, unsigned int a2, unsigned int a3) |
可以看出,v4根据地图和坐标位置返回两种特殊的非零状态或者零状态,如果为零就返回0x48(0的ASCII码),普通情况下的非零状态(1)返回0x49(1的ASCII码),特殊情况返回0x101(e的ASCII码),很明显,返回0时是路,返回1时是墙,返回e时大概是当前位置或者终点.
再查看内部的sub_56EBE386F3A1
,发现返回的是((int)(unsigned __int8)sub_56EBE386F329(a1, (unsigned int)((99 * a2 + a3) / 8)) >> (7 - (99 * a2 + a3) % 8)) & 1;
,也就是说当 sub_56EBE386F329(a1, ((99 * a2 + a3) / 8)) >> (7 - (99 * a2 + a3) % 8))
返回的值是偶数时对应的值是0
,奇数时对应1
.
再看sub_56EBE386F329
,发现会返回encmap
的第((99 * a2 + a3) // 8) + 1
个字节异或字符串 tgrddf55
的第 ((99 * a2 + a3) // 8) + 1
个字符的ASCII值,根据这个值的奇偶性来决定是墙是路.
接下来首先提取encmap
:
1 | encmap = [ |
然后遍历0-99的行和列:
1 | tgrddf55 = [0x74, 0x67, 0x72, 0x64, 0x64, 0x66, 0x35, 0x35] |
得到迷宫地图如下图所示:
则路径为:
1 | ddddssssddssaassssddddssssddssssssssddssaaaawwaawwwwwwaaaawwddwwaaaassssssssddssddssssddddssddwwddssddwwddwwaawwwwwwaawwddddssddddddwwaaaawwwwwwddddddddddssssddssssssaassssaassssddddddddssaassddddssssddwwwwddssssssddddwwddwwaawwaawwwwaawwddwwaawwaawwddddssddwwddddssddwwddwwaaaawwwwddwwwwaaaawwddddddddwwwwaawwaawwddddddddddddssaassssssssaassssssaaaassssssaassddddwwddwwddssssddddssssssssssssssssddwwwwwwwwwwwwwwwwwwaawwddwwddwwddssssssssaassddddwwwwwwddwwwwwwddssssddwwwwwwddddddddddssaaaaaassddddssssaassaaaawwaassaassddssssddwwddwwddddssddddwwwwddddwwwwaaaassaawwwwddwwwwwwaawwwwwwaassssssaaaawwwwaawwwwddwwddssddwwddddddddssaassddssssssddssssssssssssssaassssaassssaassssddssaassssssddddddssssssaassssaaaaaawwwwddddwwwwaassaaaaaaaawwwwaaaassddssssssssssddddssssssssaaaassssaaaassssssssssddddddwwwwwwaaaawwddddwwddwwddwwddddddddddssssaassssaassddssssaaaaaassaaaaaassddddddssddwwddssssaaaaaassddssssaawwaaaaaassssssddwwddddddddddddssdd |
转换得到flag{4ed5a17ee7aeb95fcf12a3b96a9d4e6f}.
洞OVO:
发现题目是让寻找特定版本的winrar
中的重大漏洞,先打开应用程序点击帮助>关于
,发现版本为6.22
,在搜索引擎查询6.22
版本的winrar
的高危漏洞,查询到两种,CVE-2023-38831和CVE-2023-40477,其中前者与恶意文件相关,看起来很复杂,根据提示只修复了三条汇编,不太可能是题目所给的漏洞,先看后者,找到网页,发现漏洞类型是简单的溢出,且原文所述在6.23版本的更新中发现添加了与0xFF大小的检测来检测和避免溢出,还给了一张图片:
发现所做的修改确实很简单,那么这应该就是要找的函数,不过这个函数地址和IDA反编译的以及题目所给示例(大约是0000000140XXXXXX
)对不上号(因为这里是unrar.exe
子程序的改动,不是主程序的,提示里面也说了要向上溯源),于是得手动找6.23版本的winrar,不过官网已经不提供该版本了,在其他地方的同一个网站上找到6.22和6.23版本以保证同源性(因为不同地址下载的不同版本不一定经过了相同的操作,比如破解、汉化等,所以要保证应用程序同源,否则函数地址不一定一样),安装后进行反编译,发现6.22版本下与题目所给版本的WinMain函数地址一样(0000000140102524
),那么后续分析地址就会变得略微简单,再反编译6.23版本,发现地址变成了0000000140102774
,多出了0x250(592)
行,打包i64文件,用BinDiff
对比(主要找两个版本共有但内容不同的,因为根据提示,3行汇编不太可能成为一个单独的函数),发现在5400多个函数中有少数(14-15个)独有差异,另有一些在共有函数中的重合率不为100%,首先查看重合率不为100%的:00000001400E5630
、00000001400EF508、…发现标红的恰好符合新增三行,提交flag发现正确.(即flag为flag{00000001400EF508})
Crypto方向:
欧拉欧拉!!:
发现p和q有以下关系:q = ((pow(2,512) - 1) ^ p) - 3
,n无法分解,求n的开平方后假设p = √n,发现n - p * q
随着p的增加而减少,用二分法测试得:
1 | p=1523365295049608852447528761114036169506740936697335552555682720997896170562682523843468707748630306876516559528314335864164090878934605899152321495370201 |
附测试大致代码(纯手算,一位一位逼近的):
1 | import sympy |
则
1 | φ(n)=18104347461003907895610914021247683508445228187648940019610703551961828343286923443588324205257353157349226965840638901792059481287140055747874675375786188374454317608065999358326594993253669257155936880793765183990054804582101959834465441403647401623484701548572956283476063411128778877557225686531832078000, |
解得flag{y0u_really_kn0w_the_phi}.
圣石匕首:
发现是sagemath
程序源代码,懒得下载安装了,在在线大型数学计算服务器Cocalc
上注册账号并创建项目,新建sagemath工作表
并转换为.ipynb
文件,在服务器上运行,片刻后得到flag{small_dp_is_not_secure_adhfaiuhaph}.
Misc方向:
扫码领取flag:
发现有一个hint图片和四个奇奇怪怪的flag文件,查看hint的属性提示图像base64,下方有一个UXVldHphbGNvYXRsJkt1a3VsY2Fu
,解码得到羽蛇神库库尔坎,然后用winhex查看四个文件发现全都是png,改格式后发现没有正常显示,结合压缩包名称猜测提示为图像CRC校验码,发现校验码全都是AD8705E1,计算得到图像的正确宽高应该是250(FA),修改后得到四张像是二维码的图片,拼合如下:
查询得知这种二维码叫做阿兹台克码
(Aztec),扫描得到flag{Then_d0_you_kn0w_w6at_Hanx1n_cod3_1s?}.
附:CRC计算宽高python代码:
1 | import os |
ezblockchain:
运行nc地址,发现返回如下内容:
1 | Can you make the isSolved variable true? |
发现目标是要让isSolved
变量变为true
,完成任务之后才能获取flag,先按1注册合约账户,返回:
1 | [+] deployer account: 0xc9fC913037776e0a01a9918db880FCFeeEb3d2aE |
发现需要获得ETH测试币,找到题目所给的faucet,输入账户进行获取,返回了一长串交易哈希值,问ai用python的web3库查看当前的余额:
1 | from web3 import Web3 |
发现已经有1测试ETH了,再在nc地址中按2,提交刚刚生成的token
,返回:
1 | [+] contract address: 0x34831cFa6b845A9abBdA9cBd7f097103a52De5Ef |
也就是部署的合约地址和交易哈希值,再次在nc地址中按4查看合约源代码,返回:
1 | contracts/Question0.sol |
接下来使用remix IDE
编译和部署合约,先加入注释:
1 | /** |
再进行compile,获得abi:
1 | [ |
再问ai用python构建交易代码,但是运行发现需要私钥,而用nc地址创建时并没有给出私钥,于是注册Metamask
电子钱包账户并手动添加网络rpc url,链ID虽然不知道是什么但只要输入的不对就会提示正确的是28848,货币符号为TCH,添加成功后用创建的账户账号在faucet请求货币,发现能顺利请求,并且metamask也变成了1TCH,此时再结合账户的私钥执行脚本,补全一大堆东西之后运行:
1 | from web3 import Web3 |
返回如下内容:
1 | Transaction hash: 0xa4f4e4b9731fe27fc26fe7f5ac5b2d5a06e7685830ac0b6d792a6603ffb989bf |
此时已经交易成功,返回nc地址获取flag:flag{b10ckch@1n_15_r3@lly_1nt3r35t1ng!}.
Alt:
发现格式为.pcapng
,用wireshark打开,发现是USB流量
,其中比特长度为35的就是关键内容,过滤选择只看长度为35的,按时间排序,其中HID数据里面第三个字节的内容就是击键码,下载UsbKeyboardDataHacker程序直接获取按键内容,在powershell中输入:
1 | PS 路径> python 路径\UsbKeyboardDataHacker.py --input 路径\keyboard.pcapng |
输出:
1 | 1021089710312338190<DEL>30424<DEL>27969<DEL>37327<DEL>9511910511610495971081169521644<DEL>31383<DEL>25143<DEL>9549539553111487979481119537239<DEL>125 |
结合题目名称Alt,这应该是用了Alt+小键盘数字的输入方式,进行手动拆分和输入:
1 | 102 108 97 103 123 = flag{ |
组合得到flag{键盘流量_with_alt_和窗户_15_5o0OO0o_酷}.
赛题轮次:Week5
已解出的题目:
题目类型 | 题目名称 |
---|---|
Reverse | MY_ARM、Lock |
Crypto | RSA?cmd5!、easy_ecc、没e也能玩 |
Reverse方向:
MY_ARM:
用Ghidra反编译,先找到entry函数:
1 | void processEntry entry(undefined4 param_1,undefined4 param_2) |
这个结构很像week4的ezrust,其中的函数FUN_000110a4
大概就是主要的函数了,双击查看:
1 | undefined4 FUN_000110a4(undefined4 param_1,undefined4 param_2,undefined4 param_3) |
接着查看外层加密函数FUN\_00010fbc
:
1 | int FUN_00010fbc(int param_1,undefined4 param_2) |
查看内层加密函数FUN\_00010e78
:
1 | int * FUN_00010e78(int *param_1,int *param_2) |
一看就是典型的TEA加密
,再查看其中的关键数值:local_10
为sum
,起始值为0,每次加上DAT_000a6260
(0x9E3779B9
,经典的魔数);DAT_000a6264
是循环次数,值为0x20
(32次),也是经典的值;local_18
是第一部分的小端序,local_14
是第二部分的小端序;param_2
就是key(DAT_000a62b8
),加密的flag(DAT_000a6268
起始)和它一样,值都是变动的,而且在调用的时候还是以指针方式进行调用,分别在初始化、1055c、10630、10704、107d8、108ac、10980、10a54、10b28、10bfc、10cd0、10da4中赋值,共计12次,记录其数值为:
1 | 1. [0x12345678, 0x00456789, 0x00ABC123, 0x33445511] |
加密部分至此已经解构完成,接下来看被加密的flag,发现其数值也是动态的,赋值函数同上,并且也是以指针方式调用,记录其数值为:
1 | 1. [ 0xEC890F55, 0x0EE050C4, 0x9EEA10AB, 0xD5520BA3, 0xC26E2934, 0x46733FF2, 0x7FCE42D7 , 0x2A66A5DC , 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 ] |
但是其中没有任何一组的密文与密钥能对上解密出可读的flag,考虑动态调试,使用qemu+gdbserver+ida9.0
进行调试,发现第一组进行加密时起始密钥和密文都是第五组的值,但在32轮加密的过程中用于TEA的上半组和下半组的值的十六进制前四位却会发生多次额外的变化,并且没有明显规律,只与输入的值有关,考虑有一些其他的函数对此进行了干扰,以0x34333231
和0x38373635
为初始数据,第三轮进行汇编级单步调试:
1 | .text:00010E78 |
观察发现以下行对应前半部分(v2)的加密的以下步骤,且密钥的数值从未发生变化(也就是说不需要考虑密钥变动的因素):
1 | .text:00010ED0 MOV R2, R3,LSL#4 ; v3 << 4 |
第二轮产生的v2和v3为:0x011e2192
, 0xb238320d
,第三轮代入的sum为:0xdaa66d2b
,那么在第三轮计算过程中:
①处的值应当为0x34a55414
(0xb34a55414 & 0xFFFFFFFF
),符合结果,②处的值应当为0x8cde9f38
(0x18cde9f38 & 0xFFFFFFFF
),符合结果,
③处的值应当为0xb87bcb2c
(0xab87bcb2c & 0xFFFFFFFF
),符合结果,④处的值应当为0x5af83918
,带入检验,不符合结果!
观察④处,v3的值为0xb238320d
,r1应当是0x0591c190
,这与实测结果不符,仔细观察操作符之后发现是算术右移(补全高位均为1)而非逻辑右移(高位保持均为0),算术右移结果为0xfd91c190
,符合结果,修改python的tea加密脚本,代入0x34333231
和0x38373635
进行检验,发现也不对,综合分析得到结果:若最高位为1(大于等于0x80000000),补全前面的位数(算术右移),否则保持前五位为0(逻辑右移),代入检验发现符合调试结果:
编写正向加密脚本:
1 | m1 = ? |
经过动态调试发现key为定值,永远位第五组的[0x11223344, 0x55667788, 0x9900AABB, 0xCCDDEEFF],那么相应地,密文也应当是第五组的密文[0xA0F8CB44, 0xF82F83CF, 0xA55E48C2, 0x7A26E00A, 0xF1E354C9, 0x687D9915, 0xF88816E8, 0x90878E86, 0x3AB06298, 0xCBCFE78B, 0x578F0F50, 0xC39E3C65, 0xBBE92B84, 0x128A2CA2, 0xDB8F03F5, 0x8482F8E2],编写逆向脚本:
1 | import struct |
输出为
1 | 666c61677b41524d5f5f4072636831743363747552655f2d6e336564735f2d74305f5f62655f2d64654275676765645f5f7573316e672d5f51454d555f5f217d |
十六进制转文本得flag{ARM__@rch1t3ctuRe_-n3eds_-t0__be_-deBugged__us1ng-_QEMU__!}.
Lock:
发现是python文件,还给了一个pyd动态链接库,发现pyd文件很难动态调试,直接运行程序,发现会返回输入字符串中有几个字符在正确的位置,于是进行逐位测试,最终得到flag{d6cf50e2736849b4ba21}.
Crypto方向:
RSA?cmd5!:
由于m比较短,先试着求m的md5:
1 | from Crypto.Util.number import long_to_bytes |
求得md5为86133884de98baada58a8c4de66e15b8,在线解密得到m为adm0n12,得到flag: flag{th1s_1s_my_k3y:adm0n120xbfab06114aa460b85135659e359fe443f9d91950ca95cbb2cbd6f88453e2b08b}.
easy_ecc:
发现是椭圆曲线加密,由于知道私钥k,所以可以直接求m = c1 + (-k) \* c2
,得到m的坐标为(42473705116462428406202294102847935773542820132768934248776117467532517063207, 28673652398806211089618369285265886256488311031977716002800065471137170688215)
,但是发现xm和ym均远大于所给的c_left和c_right,考虑模逆元,得到flag_left = 531812496965563174412251588431148136
和 flag_right = 526357398425538015765092604513836925
,long_to_bytes
转换得到flag{This_is_the_last_crypto_}.
Sagemath代码:
1 | from Crypto.Util.number import long_to_bytes, inverse |
没e也能玩:
真的没给e吗?e其实还是65537,直接带进去之前的脚本就好了,得出flag{No_course_e_can_play}.
结语
这是我第一次参加CTF,开始时我确实是什么都不懂,完全零基础参赛的,在比赛过程中学习积累了很多知识和技巧,也交到了很多朋友,顺道奠定了我之后选择逆向方向的基础(毕竟打得最好的就这个方向),了解CTF前,我对网络安全方向的认知一直都是靠偶尔在B站刷出来的所谓“黑客教程”视频,这次比赛是我对网安这个领域的初探,也很奇幻般地立刻成为了我的兴趣爱好之一。
NewStar CTF作为一个“招新赛”,无疑是属于设计的非常完美的那一档:前几周的题目从最基本的知识和方法考起,零基础也能通过现学而掌握这些知识而不会因为门槛太高而直接放弃,难度循序渐进(对于我来说就是确保了恰好每周做一道题的时间都差不多,没有因出现重复的前几周学过的内容而变得简单,也很少有因出现跨越性的知识而导致新手完全看不懂或者不想看导致半途而废,甚至直到现在我评价题目的难度都是用“week x”来评价,已经潜移默化地成为了我的难度评判标准),题目也都很有意思,解题时的趣味和解出flag的瞬间带来的成就感双重推进,使得整个参赛过程都是充满做题动力的。
总的来说,我很庆幸当时尝试参加了这次比赛,如果没有参与的话,或许我在大学的四年生活不会缺乏色彩,但也不会像如今这样丰富充实,比赛过程中认识的朋友们也都成为了我生活中不可或缺的一部分,为我的生活带来能够共享欢声笑语、随心所欲畅谈以及互相学习进步的对象(毕竟不管是网安还是我的其他兴趣爱好比如天文,质数之类的都比较小众,我小学初中高中十二年也没找到有跟我的兴趣爱好有共同语言的人),感谢这次比赛提供的机会,也让我对人生的选择多了一种全新的可能。