starCTF2021
StarCTF2021
自己的程序分析能力还要加强啊!!!!chacha20的解密不知道问题出在哪里??? qemu-riscv64也没能配置好环境,调试不了程序,所以 Favourite Architecture flag0 只解了一半,希望有大佬能指导指导咋搞啊???
Stram
这是一个rust编写的elf文件
程序流程分析
找到main函数,进入第一个函数之中
读取输入数据
打开flag文件,读取其中的数据
对输入的加密过程
这里的加密过程调用了rustcrypto_api
init_chacha: 利用输入生成随机数种子
refill_wide:流密钥加密的过程
因为密钥的生成和输入显然是有关系的,这样的流密钥加密我们并不能够直接解密,所以使用爆破的方法
加密结果的输出
加密的结果会被写入到一个名为output的文件之中
所以该程序的输入,我们可以通过写入flag文件输入;该程序输出的获取,我们可以通过读取ouput文件得知
python和程序的交互(subprocess模块)
subprocess.Popen 类:创建并返回一个子进程,并在这个进程中执行指定的程序。
```
subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)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
* p.wait() : 等待子进程 p 终止,返回 p.returncode 属性(wait() 立即阻塞父进程,直到子进程结束!)
* p.communicate(input=None): 和子进程 p 交流,将参数 *input* (字符串)中的数据发送到子进程的 stdin,同时从子进程的 stdout 和 stderr 读取数据,直到EOF。(只能通过**管道**和**子进程通信**,也就是说,只有调用 **Popen()** 创建子进程的时候参数 **stdin=subprocess.PIPE**,才能通过 **p.communicate(input)** 向子进程的 stdin **发送数据**;只有参数 **stout 和 stderr** 也都为 **subprocess.PIPE** ,才能通过**p.communicate()** 从子进程**接收数据**,否则接收到的二元组中,对应的位置是None。)
* subprocess.PIPE: 调用本模块提供的若干函数时,作为 std* 参数的值,为标准流文件打开一个管道。
例:
![image-20220407175812946](/images/starCTF/image-20220407175812946.png)
### 脚本
脚本的执行流程:
* 由加密的字符的顺序来爆破每一个元素,以防前面的加密会对后面的加密过程产生影响(index数组的遍历)
* 向flag文件之中写入数据(初始化的数组,并且将对应的爆破对象的数据修改)
* 运行程序
* 读取output之中文件的数据
* 将读取的数据之中对应的数据和目标的数据对比,如果相同,则爆破成功该元素,跳出循环
* 再一次循环
脚本中需要书写的函数:
* 向flag文件之中写入数据
* 从ouput文件之中读取数据,并且和目标的数据进行对比
* 将数组转换成str形式的数据并返回
* 运行该程序
对比的数据(从提供的output文件之中得到)
```data
0x9A,0x7A,0x42,0x92,0x3C,0xE3,0x1B,0xA4,0xB8,0x72,0x96,0x2F,0x01,0x98,0x5E,0x29,0x22,0xF5,0x9A,0x14,0x0D,0x4C,0x18,0x2A,0x38,0x41,0xD4,0xF3,0xA9,0xE8,0x60,0xEE,0xDA,0x03,0xDC,0x1C,0xA1,0x86,0x12,0xFE,0x18,0x42,0x89,0x80,0x91,0xDC
读取文件的顺序:
1 | 4,11,18,25,32,39,0,7,14,21,28,35,42,3,10,17,24,31,38,45,6,13,20,27,34,41,2,9,16,23,30,37,44,5,12,19,26,33,40,1,8,15,22,29,36,43 |
因为要运行程序,所以需要先配置环境rust环境(我是在ubuntu里面配置的环境)
这里爆破一定要使用递归的方法(因为相同的计算结果可能有多种输入都满足,用递归的方法排除不满足的结果)
爆破脚本:
1 | import subprocess |
flag:*ctf{EbXZCOD56vEHNSofFvRHG7XtgFJXcUXUGnaaaaaa}
wherekey
知识点:静态编译文件的符号表恢复、sage的线性方程求解
导入签名文件
这是一个静态编译的elf文件
ldd命令是Linux查看动态链接库的命令,执行该命令我们可以查看到,所以这个是静态编译
所以需要恢复一下库函数的符号
FLIRT:IDA之中所提供的,库文件快速识别与鉴定技术,能通过一个静态链接库的签名文件,来快速识别被去符号的程序中的函数,从而为函数找到符号
将sig文件导入到ida之中,网上有大佬把各个平台各个版本的sig都放到github上面,我们只需要下载下来导入就可以了(但是如果有特殊需要可以按照上面提供的文章制作SIG文件)
相关sig文件的下载地址:
https://github.com/Maktm/FLIRTDB
https://github.com/push0ebp/sig-database
大致的挑选sig的版本,可以通过用file命令和strings命令去找版本信息,从而挑选sig,但是这样也很难准确找到,使用下面脚本找
使用iscan的脚本可以匹配测试出最佳的sig:https://github.com/maroueneboubakri/lscan
上面这个脚本之中指定的-S 参数要是一个只有SIG文件的文件夹
我找了一个Ubuntu20.版本的SIG文件(签名文件),导入之后识别了500多个函数
程序逻辑分析
查看字符串,通过Please enter key 定位到一段代码,这个地方有花指令(去花指令)
main函数
这个地方是jmp jnz型的花指令,nop掉jmp和jnz那个地方的花指令就可以(逐个进行jmp,因为该指令字节之中可能会有效的字节)
又通过 /dev/tty 定位到一段代码(也就是主函数之中,字符串上面的那个函数)
- bsd_signal() 函数为使用 BSD 形式的 signal() 函数编写的程序提供了部分兼容的接口。(所以这里有信号的传送)
- _libc_open64()函数读取文件,获得输入(可能就是输入的额flag)
bsd_signal()的函数联系到一个函数:(这个地方能够返回一些值)
再在主函数之中向下翻看函数,找到第二个有效函数sub_402072();
在这个函数的开头我们发现了读取文件的操作的函数,并且这里读取的内容和之前 “/dev/tty ”输入数据相关联
在最开始接受信号的函数之中,通过下面两个数据,跟进到内存之中,发现了一个字符串
通过字符串找到对应的函数
这个函数本身内部只有简单的操作,我交叉引用了一下,找到了下面这个函数
再向上xref 找到一个含有_libc_recvfrom()的函数,dword_4C8520关联到socket接口(接受输入的数据)
- recvfrom()函数:经socket接收数据
- sys_socket():创建插口
所以上面的逻辑是,程序执行socket产生,tty文件中的数据被recvfrom()函数接收(5个数),接收之后进入到这个加密函数之中,最后对比的结果是:0x4C5150位置的数据(我们在主函数之中最后unk_4C5130数据能够定位到这段数据的周围)后面的分析也可以知道输入的字符串的长度应该是25字节
1 | 0x38, 0x6D, 0x4B, 0x4B, 0xB9, |
线性方程求解
分析加密的过程:
初始化数组:
每轮初始化的结果依次是
1 | "faori" |
输入的元素和上面初始化的结果(每轮和一组字符串)进行逐byte的相乘之后相加则得到最后的结果(要模257)
所以这个地方就是线性方程组求解问题(一共有五组线性方程组,每组之中五个变量,5个方程,每个方程对应在模257的运算之中得到相应的结果)
1 | [[102,108,97,103,123,], |
使用Sage解有限域的矩阵的运算
Sage的使用:
将每轮加密使用的密钥矩阵声明M 在有限域之中(作为右乘元素)
将加密的结果声明成二维数组,其中的每组元素就是一个线性方程的解
第一组解:[72,97,50,51,95]
第二组解:[102 ,48,110, 95 ,57]
第三组解:[110,100 ,95 ,71 ,48]
第四组解:[111,100 ,45 ,49,117]
第五组解:[ 99,107 ,45 ,79 ,72]
整体:
1 | tuple(M.solve_right(Matrix(GF(257),(arrayFlag[i] for i in range(5))).transpose()).transpose()) |
转为字符串
1 | obj = [[72,97,50,51,95],[102 ,48,110, 95 ,57],[110,100 ,95 ,71 ,48],[111,100 ,45 ,49,117],[ 99,107 ,45 ,79 ,72]] |
Ha23_f0n_9nd_G0od-1uck-OH
*ctf{Ha23_f0n_9nd_G0od-1uck-OH}
Favourite Architecture flag0
这道题使用的 qemu-riscv64 框架 RISC-V的反编译在ida之中不能完成,要使用Ghidra这款软件反编译
运行该程序,加载了Input the flag 这个字符串,我们能够通过字符串定位到代码之中的位置
定位关键代码
在Ghidra之中查看字符串窗口,找到关键的字符串(有特别含义的)
通过上面的三个字符串定位到相应的代码位置
定位到相对应的代码位置(这三个字符串都在同一函数之中)
在GHidra之中按Ctrl+E反汇编,但是这里不能直接反汇编(这个地方的反编译失败和gp在最开始的设置的数值有关)
重置gp
看反编译代码,这里的指针调用使用了gp
gp在程序运行的时候就会进行初始化
这里初始化成的是0x6f178,不正确,需要修改gp的值
gp:
- gp寄存器在启动代码中加载为__global_pointer$的地址,并且之后不能被改变。
- 通过gp指针,访问其值±2KB,即4KB范围内的全局变量,可以节约一条指令。4KB区域可以位于寻址内存中任意位置,但是为了使优化更有效率,最好覆盖最频繁使用的RAM区域。
根据gp寄存器的作用,我将gp寄存器的值修改为需要反编译的代码附近
重置gp的步骤:
- Ctrl+A选中所有的反汇编的代码
- Ctrl+R打开寄存器的修改界面
- 选择gp寄存器
- 将值修改为hex的00010400这个地址
反编译关键代码
再回到目标的代码处,Ctr+E 就能够对该段代码进行反汇编
1 | undefined8 UndefinedFunction_00010400(void) |
chacha20加密
第一个加密的过程:将输入进行了异或运算,异或运算的对象上一个函数初始化的结果
加密之后对比的数据:
1 | 88 e7 03 b4 36 cd 97 ab 5a a5 a6 0b df ce 08 3b 9d 90 32 3c 4e 15 14 bd 8d 38 38 b0 ee 2a bc 4b f9 aa 24 26 76 a3 a5 75 5e 00 00 00 00 00 00 00 |
1 | [0x88,0xe7,0x03,0xb4,0x36,0xcd,0x97,0xab,0x5a,0xa5,0xa6,0x0b,0xdf,0xce,0x08,0x3b,0x9d,0x90,0x32,0x3c,0x4e,0x15,0x14,0xbd,0x8d,0x38,0x38,0xb0,0xee,0x2a,0xbc,0x4b,0xf9,0xaa,0x24,0x26,0x76,0xa3,0xa5,0x75,0x5e] |
进入这个加密函数之中,进入里面引用了字符串的函数之中(FUN_000106ce(param_1,param_2,param_3))
这个函数之中是初始化密钥,经过搜索字符串“expand 32-byte k”,这个地方使用的可能是Salsa20的流密码 => chacha20
前半部分的解密是chahca20 密钥是32字节的 tzgkwukglbslrmfjsrwimtwyyrkejqzo 随机数是16字节的 oaeqjfhclrqk
flag{have_you_tried_ghidra9.2_decompiler_
但是这里的流密码加密中间应该是做了修改的,我不知道具体是在哪里,猜测有可能是最后的异或的对象做了修改(下面两个就是chacha20的密钥和随机数)
tea加密
第二个加密(在while循环里面) 这个地方是tea加密
tea加密:
1 | void encrypt (uint32_t v[2], const uint32_t k[4]) { |
变形:进行的是16轮加密 -0x61c88647 所以解密代码之中的 减delta -> 加delta 其中的sum = delta << 4(16轮)进行解密
密钥:
1 | 0x1368A0BB,0x190ACE1E,0x35D8A357,0x26BF2C61 |
加密的数据:
1 | 0xC45087F9,0x703F2B2, |
tea解密脚本:
1 | #include <stdio.h> |
这就是后半部分的flag
所以整个flag是 flag{have_you_tried_ghidra9.2_decompiler_if_you_have_hexriscv_plz_share_it_with_me_thx:P}
chacha20加密的分析过程(未解)-自己的分析过程
下面是我的解密的思路,但是不知道到底是哪里的数据出问题了,并不能解得答案?????
初始化 ,利用下面的特征我推测出来这个地方是进行chacha20加密的方法,密钥是tzgkwukglbslrmfjsrwimtwyyrkejqzo(32字节)
随机数 oaeqjfhclrq(12字节)
初始化之后进入加密函数之中
我猜测下面标注的这个函数是利用初始化的表生成密钥流的过程
生成的密钥流用下面的方法和输入的字符进行异或运算
最后生成的密文是
1 | [0x88,0xe7,0x03,0xb4,0x36,0xcd,0x97,0xab,0x5a,0xa5,0xa6,0x0b,0xdf,0xce,0x08,0x3b,0x9d,0x90,0x32,0x3c,0x4e,0x15,0x14,0xbd,0x8d,0x38,0x38,0xb0,0xee,0x2a,0xbc,0x4b,0xf9,0xaa,0x24,0x26,0x76,0xa3,0xa5,0x75,0x5e] |
但是这里并不能解密,中间的加密步骤相比于常规的chacha20加密有可能被改了,我猜测是不是最后一步异或的过程,常规的流密钥加密
单次运算的结果
Java的Chacha20的解密脚本:
1 | package Coco2; |
用c语言编写的得到密钥流的脚本:
1 |
|
我对比了源码和我的解密脚本,以及密钥流的生成过程,我觉得是一样的,到底是哪里处问题了,望指正。
RCT-Harmony
就是为了练习一下使用Ghidra
这道题是RISC-V题目
使用Ghidra打开该程序
通过查看字符串的表,我们找到特殊的字符串
加密代码:
1 | void UndefinedFunction_8000095c(void) |
通过该字符串定位到对应的位置,Ctrl+E反编译
1 | src1= [0x48,0x41,0x52,0x4d,0x4f,0x4e,0x59,0x44,0x52,0x45,0x41,0x4d,0x49,0x54,0x50,0x4f,0x53,0x53,0x49,0x42,0x4c,0x45] |
ChineseGame
先导入一个签名文件,将函数符号化,这个程序是用c++写的,所以找签名文件的时候找c++的签名文件会更好
数组之中的元素数据
函数逻辑分析
进入到v9的赋值函数之中
这里赋值了10个数,用1表示[101,200]的数 用0表示[0,100)的数
所以初始化的结果是1011111111
主体的逻辑:
通过输入0和1来判断如何给v9中的元素重新赋值
当输入的元素是0时:
将对应v9的值重新赋值为一个小于100的数
能赋值的条件:
- 是v9[9]
- 该值本生就小于100
- 当该值大于100,它后面的那位大于100,它再后面的每一位都小于100
当输入的元素是1时:
将对应v9的值重新赋值为一个大于100的数
能赋值的条件:
- 是v9[9]
- 该值本生就大于100
- 当该值大于100,它后面的那位大于100,它再后面的每一位都小于100
最后判断的条件:
v9中的所有数都是小于100时就成功了
这里将大于100的状态定义为1 小于100的状态定义为0
所以就是将1011111111 =》 0000000000
脚本
我起初以为要自己来走,后来看别人的wp发现,这个给的数组就是正确的走的方式,为了保证每一次走的位置有意义(相应的v9的状态能发生变化),我们要判断这个点当前的状态,然后输出使其达到取反的方式(0or1)
脚本:
1 | src = [0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x09,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x08,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x0a,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x08,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x09,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x08,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01] |
因为复现的时候环境关了,但是通过上面最后status都是0,达到了我们的最后目的可以知道这个路线是正确的
网上最后得到的flag是:*CTF{4ncient_G4me_Fr0m_4ncient_Ch1na!}