Week1#
数字筑基#
在IDA中打开文件,一眼flag。
代码金丹#
IDA打开即送 +1
网络元婴#
IDA打开即送 +2。 从上到下输入即可。
虚拟化神#
反编译程序,在main
函数中找到关键变量v13
。它的值没有明确给出,而且后续一系列的xor操作都和它有关系,所以通过动态调试找到内存中的值。实际上就是flag。
另一方面,如果不调试。按照官方wp的提示,这个程序会生成一个文件config.txt
。之后根据其内容判断是否输出v13
的内容。具体的逻辑是提取config.txt的前两个字符,转换为整数并与1
比较。这里直接修改该文件,把0改成1,之后运行程序就能拿到flag。
赛博天尊#
分析程序逻辑:do...while
部分判断输入Buffer
的长度,要求长度为44。其中最后一个字符为}
(ascii 125),接下来以-
为分隔符将flag内部32位十六进制数拆分成五部分,各部分满足方程组。
核心在于解方程组,这里尝试了两种方法。
- 用
sympy
库求解
#python
from sympy import *
v7,v8,v9,v10,v11=symbols('v7 v8 v9 v10 v11')
equations=[Eq(7 * v9 + 5 * (v8 + v11) + 2 * (v10 + 4 * v7) , 0x12021DE669FC2),
Eq( v8 + 3 * (v9 + v10 + 2 * v10 + 2 * (v11 + v7)) , 0x159BFFC17D045),
Eq(v10 + 3 * (v11 + 3 * v9) + 2 * (v8 + 4 * v7) , 0xACE320D12501),
Eq(v8 + 2 * (v7 + v11 + v9 + 2 * v10) , 0x733FFEB3A4FA),
Eq(v8 + 7 * v11 + 8 * (v9 + v10) + 5 * v7 , 0x1935EBA54EB28)]
solution = solve(equations)
solution = sorted(solution.items(), key=lambda item:int(str(item[0])[1:]))
hexs=[]
for value in solution:
hexs.append(hex(value[1])[2:])
flag='0xGame{'+'{}-{}-{}-{}-{}'.format(*hexs)+'}'
print(flag)
用
z3
模块求解z3求解出来的结果是
z3.RatNumRef
类型的,不是内置类型。需要转换为int型。此外要先用Solver.check()
判断解的情况,才能调用Solver.model()
输出解。
#python
from z3 import *
v7,v8,v9,v10,v11=Reals('v7 v8 v9 v10 v11')
s=Solver()
s.add(7 * v9 + 5 * (v8 + v11) + 2 * (v10 + 4 * v7) == 0x12021DE669FC2)
s.add(v8 + 3 * (v9 + v10 + 2 * v10 + 2 * (v11 + v7)) == 0x159BFFC17D045)
s.add(v10 + 3 * (v11 + 3 * v9) + 2 * (v8 + 4 * v7) == 0xACE320D12501)
s.add(v8 + 2 * (v7 + v11 + v9 + 2 * v10) == 0x733FFEB3A4FA)
s.add(v8 + 7 * v11 + 8 * (v9 + v10) + 5 * v7 == 0x1935EBA54EB28)
hexs=[]
if isinstance(s.check(), CheckSatResult):
result=s.model()
for var in (v7,v8,v9,v10,v11):
num = result[var].as_long()
hexs.append(hex(num)[2:])
flag='0xGame{'+'{}-{}-{}-{}-{}'.format(*hexs)+'}'
print(flag)
Week2#
符文解密师#
32位程序,IDA打开,在hex窗口找到flag
编译逆旅者#
文件后缀为.pyc可知是编译过的python程序。鉴于IDA反编译后的结果是C伪代码,并不能得到源python代码。于是考虑在线python反编译。
在main()函数中找到flag的十六进制形式。粘贴(ctrl+shift+v)到十六进制编辑器010Editor中得到flag。
码海舵师#
32位IDA打开,F5反编译代码如下:
在21行位置发现可疑的一串乱码,末尾‘=’盲猜是base64,在线解密。
注册侦探#
反编译main
函数,发现是c++程序,而且调用了很多API函数。分析程序逻辑后发现flag是在一系列判断之后生成并且输出的,所以尝试动态调试。刚开始不知道RegOpenKeyExA
,RegQueryValueExA
这两个函数的功能,也没有去查。当时思路是修改寄存器的返回值,绕过这两个函数,但在动调过程中发现无法改data的值。所以最后flag没有生成。
换一种思路,在输出时数据经过了0x33
的异或,那么数据是怎么来的?一点点往回找发现和Src变量有关,而且函数中有Src
数组的全部值。尝试了一下把Src[0]
逐字节异或0x33
,结果是0xGa
,和flag正好对应。说明就是单字节异或,写脚本解密就得到flag。
#python
'''
Src[0] = 1383353091;
Src[1] = 189290078;
Src[2] = 38864395;
Src[3] = 503515984;
Src[4] = 1364350722;
Src[5] = 1448105758;
Src[6] = 89136641;
Src[7] = 85852241;
Src[8] = 72812293;
Src[9] = 50464516;
Src[10] = 1314325334;
'''
Src=[1383353091, 189290078, 38864395, 503515984, 1364350722,
1448105758, 89136641, 85852241, 72812293, 50464516, 1314325334]
flag=bytes()
for num in Src:
byte=num.to_bytes(4,'little')
for i in byte:
flag+=(i^0x33).to_bytes(1)
print(flag.decode())
#0xGame{885b1c80-1dab-dce2-c6b3-664d77410e0d}
看了官方wp,还是从RegOpenKeyExA
,RegQueryValueExA
入手,创建注册表值,之后运行程序得到flag
壳艺大师#
在DIE里查壳发现有upx壳,先进行脱壳。
程序反编译后整体看上去有点复杂,涉及到很多未知函数。跟踪这些函数,发现大多数没有什么实际用处,感觉像是用来混淆的,暂且忽略它们。核心的判断部分也比较抽象,v7
,v8
等变量都对应寄存器的值,并不在内存中。缺少关键值,仅凭静态调试梳理不出完整的逻辑。但能看出和异或有关。
动调也显得繁琐,涉及寄存器的变量还是很多的。(不知道是不是脱壳的原因)
既然分析不出明确的逻辑,那就猜一下吧。main
函数中依然有一系列的数组赋值语句,和上一题非常相似。推测还是对这些数据异或求flag。最开始分析string的时候发现的'The0xGameKey'
应该是密钥,循环异或。
#python
from pwn import *
src=[0x64, 0x10, 0x22, 0x51, 0x15, 0x22, 0x1A, 0x0F, 0x06, 0x7C,
0x01, 0x18, 0x6C, 0x0A, 0x56, 0x1D, 0x4B, 0x7E, 0x57, 0x08,
0x48, 0x28, 0x51, 0x4C, 0x60, 0x45, 0x07, 0x53, 0x1E, 0x77,
0x4C, 0x5E, 0x5D, 0x7B, 0x53, 0x4F, 0x61, 0x59, 0x07, 0x52,
0x1C, 0x74, 0x07, 0x10]
flag=xor(bytes(src),b'The0xGameKey')
print(flag)
#b'0xGame{bc7da8b3-396e-c454-bcf0-3806651bbd3f}'
Week3#
代码启示录#
打开发现是.jar文件,在本地安装JDK来配置Java环境。用反编译器GDA打开文件,一眼flag。
旋转密码城#
同上GDA打开,Main类中定义了CaesarPlus()
函数,不难发现是变种的凯撒密码。把密码移位的范围从字母扩大到了ASCII可打印字符的范围。密文在main()
主函数中给出。编写python脚本解密:
enc="_Iv2>6L424c_4c2\\f__5\\7fec\\da32\\3ef2`cgd4b46N"
flag=''
for char in enc:
index=ord(char)
result=index-33-47
if result<0:
result+=94
flag+=chr(result+33)
print(flag)
变量迷城#
对Java,android逆向都不太熟悉,拿着反编译代码问了几次GPT。勉强理清了主要的逻辑。
程序只有一个Main
类,直接给了decryptFlag
解密函数,能看出来还是循环异或。下面的lambda$static$0
函数给出了关于x
和y
的方程组,可以解出x
和y
。brand
变量也已知是'0xGame'
#python
from z3 import *
x,y = Reals('x y')
s=Solver()
s.add(x**2+2*y**2+3*x+4*y == 7384462351178)
s.add(5*x**2+6*y**2+7*x+8*y == 22179606057658)
print(s.model())
#[y = 1919810, x = 114514]
main()
的末尾调用解密函数,能看出密钥是x
和brand
拼接的字符串。但是整个程序并没有给出原始数据,所以不能写脚本解密了。试一下运行程序,但是另一个问题是,程序从头到尾没有任何输入,我们只能等它自己生成flag然后输出。
结合上网搜索和wp,System.getenv
,System.getProperty
这两个函数比较关键,提示要创建环境变量和 JVM变量。在命令行中进行设置即可,最后运行jar程序得到flag。
System.getenv()
读取的是当前系统环境的环境变量。可以通过System.getEnv(key)
获取对应环境变量的值。通过
System.getProperty(key)
获取单个变量值,通过System.getPropertys()
获取所有 JVM 环境变量值。启动JVM时,可以通过 -D 设置JVM 环境变量值
之前一直认为逆向题的flag都是输入到程序的正确字符串,其实是陷入思维定式了。这几周的一些题不是简单地判断输入,而是检查系统的文件或者是变量,还是很打开眼界的。最后还踩了坑:参考wp在cmd敲了命令,结果报错。发现是多了不必要的空格。用powershell试了试发现这段命令不能正常运行。powershell里面set
命令不能修改环境变量,要用set-item
。
(cmd.exe)
set x=114514
set y=1919810
java -Dbrand=0xGame -jar "C:\Users\LENOVO\Desktop\变量迷城.jar"
数字幽灵城#
反编译程序,看到有一个Base58
类,说明这道题用到Base58
算法。之前对Base64
接触比较多,对于Base58
不是很了解。于是上网查了一下原理。
base58加密与其他base系列不同的是: base64是每6bit位一个映射,base32是每5bit位一个映射 base58不是根据bit位而来,而是直接模58而映射的
其算法很简单 就是将字符流转成256进制的一个超大数 然后不断的模58 最后得到的结果逆序即是结果
分析一下decode
函数,就是模58的逆运算,能确定是正常的Base58
,没有魔改。接下来分析MainActivity
类,内部对编码后的flag,也就是变量C0587R.string.encodedFlag
进行解码,再和用户输入的flag比对。我们将经过编码的flag提取出来手动解码即可。查找encodedFlag
,应该在资源文件里。
RmC442S4tDMzc3CvzoCx8toKodL8SE8GRQSmz8M84k6g9jG1vVrf3c5TECZR
复制下来拿工具解码即可。大部分在线工具的Base58
编码表都是按照比特币的标准,数字在前,之后是大写字母和小写字母。和本题程序的编码表不一样,所以在cyberchef换一下表,进行解码。
虚构核心#
用jadx反编译程序,核心的函数都在MainActivity
类里面。onClick
函数调用checkFlag
函数进行判断。继续分析checkFlag
函数,发现还需要调用FlagCheker
类的checkFlag
方法。在反编译的结果里面没找到这个类,推测应该和encrypted.dex
有关,但是需要解密出decrypted.dex
。程序最后给出了解密函数,和密钥The0xGameKey
逐字节异或。
内置的加密文件不能在jadx里直接打开,也没找到直接导出的方法。将整个.apk
文件重命名为.zip
后缀的压缩包,解压后在 assets\
目录下找到了encrypted.dex
。按照decrypt函数的逻辑手动解密。
#include<stdio.h>
#include<string.h>
int main()
{
unsigned char key[] = "The0xGameKey";
FILE *encdex = fopen("C:\\Users\\LENOVO\\Desktop\\encrypted.dex","rb+");
FILE *decdex = fopen("C:\\Users\\LENOVO\\Desktop\\decrypted.dex","wb+");
unsigned char buf[16];
while (!feof(encdex))
{
memset(buf,0,16);
fread(buf,sizeof(char),12,encdex);
for(int i=0; i<12; i++)
{
buf[i] ^= key[i];
}
fwrite(buf,sizeof(char),12,decdex);
}
fclose(encdex);
fclose(decdex);
return 0;
}
在010Editor中把解密文件最后面多出的一段删掉,之后用jadx反编译即可。在官方wp给的cmd5网站上查出3段md5的明文,最后各部分拼在一起得到flag
Week4#
二进制学徒#
在线反编译.pyc文件,直接看到flag
代码悟道者#
程序是一个.jar
文件。反编译后发现main
函数比较清晰,调用加密函数并将结果与内置的密文比较。而且给出了加密算法的具体信息即custom Base64,考虑到换表,用自定义解密工具进行解密得到flag。(这道题挺直白的没什么坑)
回过头来分析(学习)一下程序中的Base64算法:先把输入的明文字符串转换为字节,每三个字节以二进制的形式拼接,再按6bit拆成4个字节。其中对二进制数据的处理没有采用数组,而是直接进行位运算,通过移位后或运算/移位后与运算实现了拼接和截取,很简洁高效。(和NSSRound18 的那道Base64对比了一下,但那题还要交换特定的bit)
这里移位时右操作数为负,网上搜索发现结果未定义,根据整体功能猜测符号正负不影响移位结果。
(摘自oi-wiki) 移位运算中如果出现如下情况,则其行为未定义:
- 右操作数(即移位数)为负值;
- 右操作数大于等于左操作数的位数;
内存星旅者#
首先静态分析,main
函数不算复杂。可以看到比较核心的部分,其中包含两个函数 sub_7FF630D51510
和sub_7FF630D51600
。二者还有同样的int型参数v6
。
进一步分析,第二个函数可能与生成flag有关。跟踪该函数,发现其内部有点乱,调用了一大堆未知的函数,但是有两个确定的API函数(GetTempPathA
,DeleteFileA
,可以上网查到功能)。大概猜测flag被写入一个临时文件,而且最后会删除这个文件。
第一个函数也比较乱,我们的任务是让它的返回值不为0。最好同时能推出v6
的值。这个函数需要命令行参数,但是很难从程序反编译的代码中推断该输入什么。复现的时候卡在这里了很久。最后也不去管这些参数了,采用动态调试直接修改寄存器的值,让rdi
的最后一位不是零即可。v6
的值还不能完全确定,根据官方wp发现其实是函数后面的1897488
这个数(其实挺明显的,当时没想到
同样按照动态调试修改寄存器的思路,F7进入第二个函数内部,把传入参数的ecx
寄存器的值改为1897488
(0x001CF410
)。
DeleteFileA
之前打一个断点,运行后就直接去找临时文件。在 C:\Users\LENOVO\AppData\Local\Temp
里找到了刚生成的文件,名字就叫flag。用记事本打开即可。
指令神使#
反编译main
函数,发现整体逻辑还算清晰。先把unk_
一类的数据恢复为字符串,然后分析程序。输入的flag保存在Str
中,再对Str1
分别调用sub_140001154
和sub_140001118
函数,最后与Str2
比较。
跟踪这两个函数,第一个就是简单地判断flag的格式。第二个函数进行加密,是题目的关键。不难看出是针对小写字母的rot13。(看到97
,26
就猜测和字母表有关系,可能是移位密码,比如摩斯之类的,再根据84
推偏移量)最后用CyberChef解一下。把前缀补全。
还注意到题目里的一个细节,反编译后的代码没有Str1
的赋值语句,但是直接就对它调用函数。而且储存flag的Str
数组长度至少是44,但声明部分 char Str[7];
,显然会溢出。于是在栈窗口中查找这两个变量,发现它们是相连的,地址上刚好相差0x7
,也就是说Str1
储存的是Str
溢出的内容。或者说Str1
是指向Str+7
的指针,这样也能解释为什么Str1
能与Str2
字符串的格式相对应。
算法祭司#
程序放到DIE里面查看是.NET
32位程序,用dnSpy进行反编译,分析Main
类。最开始对encryptedKey
进行逐字节异或解密。之后读取用户输入,利用异或后的key
做密钥,encryptedKey
作为初始向量对其进行DES加密。
Main
类中直接给出了DES加密后的Base64编码结果。在资源文件中可以找到encryptedKey
,进行异或得到密钥。最后写脚本解密DES即可。
#python
import base64
from Crypto.Cipher import DES
encryptedkey="STV>!'+#"
key=''
result='s7/e+JnJbGEdE9j2g3XHxgym+G6Fu/PjJuW80NeMKgemdqaWG9KVM8Tfcc0eRfaA'
for c in encryptedkey:
key+=chr(ord(c)^ord('f'))
des=DES.new(key.encode(),DES.MODE_CBC,iv=encryptedkey.encode())
enc=base64.b64decode(result)
flag=des.decrypt(enc)
print(flag)
#b'0xGame{8edf2e65-1cb3-2e1a-b2d1-b54d3d4bddc5}\x04\x04\x04\x04