近期题目复现

一些比赛的复现

0x00-Zer0pts2022-service

分析过程

先定位到正确的判断,进入加密函数

可以看到有几个api函数,并且只有所有的值都相等,才能实现return的值为1

但是动调的时候很多函数看不到,而且这里面有很多东西未被正确识别,所以使用X64DBG打开并定位到加密函数

加密函数

首先我们要知道x64的函数调用机制

所以在call前的那几个mov就是函数的参数

先看循环的次数

rbp-0x44

很明显这段就是循环的判断

循环判断

循环内部,可以看到有三个api函数,先不管

因为刚才在ida看到,最后是有一个比较的,我们先确定存储加密后字符串的位置,根据这个跳转和0x1F也就是31,确定这就是判断

可以看到在这之前先把两个地址存放的一个byte放入edx和eax中,我们就可以定位过去,先运行到这

关键判断

可以看到,我们通过64FD80-0x40得到了存放加密字符串的地址,那么这一段就是循环判断

接下来我们重新运行一下,然后这次我们先右键锁定堆栈,观察他的变化

先来看一下第一个API函数

CryptCreateHash

1
2
3
4
5
6
7
BOOL CryptCreateHash(
[in] HCRYPTPROV hProv,
[in] ALG_ID Algid,
[in] HCRYPTKEY hKey,
[in] DWORD dwFlags,
[out] HCRYPTHASH *phHash
);

而前面的一些是函数的参数,关键的是下面这个,我们点进这个链接

关键参数

0x800C,和我们的参数对应上了,那么这个应该就是加密的方式

再看第二个函数

CryptHashData

1
2
3
4
5
6
BOOL CryptHashData(
[in] HCRYPTHASH hHash,
[in] const BYTE *pbData,
[in] DWORD dwDataLen,
[in] DWORD dwFlags
);

关键是是我们加密的数据和数据的长度,也就是说每次取出2长度的字符串生成hashdata

再看第三个

CryptGetHashParam

1
2
3
4
5
6
7
BOOL CryptGetHashParam(
[in] HCRYPTHASH hHash,
[in] DWORD dwParam,
[out] BYTE *pbData,
[in, out] DWORD *pdwDataLen,
[in] DWORD dwFlags
);

具体的可以不用细看,只需知道这一段是真正的加密,因为在执行前,目标地址没有数据

执行后

所以整个过程就是将我们输入的每两位进行sha256加密,然后和他给我们的hash表进行表,那我们就可以先生成两字符的sha256彩虹表,然后反查

验证一下

sha256表

flag格式是zer0pts,取前两个字符进行sha256加密,正好对应

脚本

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
hash = [
"33129567e0bd787efb15a26307e5311e06ba66e3b8dbc2206ad59f99780a4d78",
"dd191696e15e2ee293410d02454c5f9461a2249dee6d57c75f264eaeb83a3782",
"e75b11da693d7bb5273985dcf9f02729455da7e7c80e54a0615e00ec2ae76d8e",
"04249e0c258e1a4e43cfdae291a835cd15735f650bbbba0465ada1cd9846622a",
"e4223ed20d7ea5740a326e2b268ca6db91d041cf5194f577e393a8ba3b85d8e9",
"8b53639f152c8fc6ef30802fde462ba0be9cf085f7580dc69efd72e002abbb35",
"0117834bf60dcf977229bf1e982cf9bc63b60ef42052f7ce7e800ce1216a9af6",
"741d14df730e53a5a019a710116f696db4ec23a132b74cf6fbb3cf7617e68313",
"e30e580a4c2916bcff30ca047f2d6a494168ceaf8fb9171037a773a9f8e7268e",
"294763754a8efd4c739d9f679bfca3ab510106f42ddb5dc0216ba8bc98ba3158",
"46c9e22099ee4bfe54a99a3cdbaf69f17f7c6e2581b92f7bab25128fd2100b7a",
"68dbf73d03d3a5107edad3b05676eee240e68c280296e52b6986873c54cef3cb",
"c1818d580d8c8bc111302f4a5e6903ef2d32b11a5613efba507693de8060fb8c",
"44ad63f60af0f6db6fdde6d5186ef78176367df261fa06be3079b6c80c8adba4",
"46c9e22099ee4bfe54a99a3cdbaf69f17f7c6e2581b92f7bab25128fd2100b7a",
"5e07d6fdc602b0f9b99f6ea24c39e65835992faac400264c52449bc409cf4efa",
"e4dcd6d313af71559596d3009c12d025301842d8c7f888c2850333e91a9bda68",
"fffdff4b07a9d973fd1c3a6be443851bc13e82c4af94c88325244694e352aa31",
"3fffd018d2223020be85670d93f565b63df54a9ce3ed2cdf6347a61df016938c",
"b2941852282562cc3d813e8bf1705d0480a7a008ffa2475501d7c5161165a7fb",
"635ca73d00d4f28b5f573b16eea56e9e4579d77e561c32aa68189d9769fa1753",
"a4d0ef23161b5b7c6a8d5b287543fd74e16b3bf313d71aa187c24cdd728a7b1e",
"e0b9a8799f32453a478c9122f8b83cee68e16db18f493ac81bc1d474594b5df4",
"564999cbbfea80170ba068dcf961d9914625f3be951b2c1fe163bae0f8156c24",
"4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5",
"e91787068a3c60e9712a7abeb6a67f518a40723c1b89c11d6070fe5f9389ebf9",
"7eb70257593da06f682a3ddda54a9d260d4fc514f645237f5ca74b08f8da61a6",
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
]

import hashlib
import itertools

def dec(h):
for l in range(2,3):
for p in itertools.product(range(128), repeat=l):
if hashlib.sha256(bytes(p)).hexdigest()==h:
return bytes(p)

flag = b""
for h in hash:
flag += dec(h)
print(flag.decode())

zer0pts{m0d1fy1ng_PE_1mp0rts_1s_4n_34sy_0bfusc4t10n}

https://www.cnblogs.com/xxxxxxxxx/p/11544432.html

这里介绍了itertools.product,其实目的就是生成2位字符的彩虹表,然后和密文对比

SpaceHeroesCtf2022-Shai-Hulud

0x00-前言

比较有趣的一道题,当时专注于改源码搞定游戏,然后一直失败,主要是自己写的C语言生成的随机数和调试得到的不同(其实是没注意linux和windows生成随机数的不同),所以就想玩游戏得到flag

另外的解法

https://breaking-bits.gitbook.io/breaking-bits/spaceheros-ctf-2022/re-shai-hulud

0x01-分析过程

运行程序知道这是个贪吃蛇小游戏,通过不等于0x295判断这就是贪吃蛇长度,然后SHA256_Init可以看出这是sha256加密

前面的一些函数就是生成地图,初始化游戏等操作

主要看下面这个函数,是随机生成需要吃掉的点的

vibration

这两个函数是一些规则,告诉你wasd是移动

主要看frame函数

下面这个函数是说不能碰到尾巴,继续往下看

判定退出

可以看到等于-2的时候,而-2刚好对应随机生产点函数的-2,然后对该值进行sha256加密,并且重新生成-2的点,然后长度+1

所以我们接下来只需要跑到最后的长度即可得到最后的sha256加密值,然后在print_flag函数中,最后进行了一次异或

print_flag

本来修改好规则打算玩到0x294的,但是在长度为195的时候随机点找不到。所以只能老实做

在导入表可以看到SHA256_Init,SHA256_Update,SHA256_Final函数,可以知道调用了OPENSSL,版本为1.1.0

导入表

安装好之后,模仿该过程生成SHA256加密,注意因为每次都会Update,所以不能将最后一次生成的值直接SHA256加密,这样得到的结果不一样

函数说明

这也就是前面说的,每加密完一次,hash初始值都会被改变

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
#include <openssl/sha.h>
#include <stdio.h>
#include<stdlib.h>
int main(int argc, char const* argv[])
{
SHA256_CTX ctx;
srand(0x2454);
char output[256];
__int64_t buffer;
int x, y;
SHA256_Init(&ctx);
for (int i = 0; i < 0x294; ++i)
{
x = rand() % 33;
y = rand() % 20;
buffer = 16 * x + y;
SHA256_Update(&ctx, &buffer, 8);
SHA256_Final(output, &ctx);
}
for(int i = 0; i<32; i++) //将SHA256以16进制输出
{
printf("%02x", (int)output[i]&0xff);
}
puts("\n");
return 0;

编译

g++ -o openssl-sha256 openssl-sha256.c -std=c++11 -lssl -lcrypto

也可以动调得到最后的sha256值

最后异或一下即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<string.h>
#include <stdio.h>

int main(int argc, char const* argv[])
{
char data[33] = {
0xC2, 0x98, 0xD1, 0x8E, 0xC8, 0xBF, 0x99, 0x75, 0x50, 0x41, 0x54, 0x5D, 0x3C, 0x39, 0xA8, 0x05,
0x73, 0x7B, 0xDE, 0xEA, 0xA3, 0xBE, 0x4C, 0x40, 0x2B, 0xE2, 0x48, 0x90, 0x80, 0x7F, 0x7B, 0x8D };
unsigned char magic_bytes[32] = {
0xB2, 0xEA, 0xE5, 0xBF, 0xBB, 0x8C, 0xC6, 0x01, 0x38, 0x72, 0x0B, 0x2F, 0x0F, 0x54, 0x9C, 0x6E,
0x40, 0x24, 0xEA, 0x84, 0xC7, 0xE1, 0x7D, 0x34, 0x58, 0xBD, 0x2E, 0xE2, 0xB4, 0x12, 0x48, 0xFE
};
for (int i = 0; i < strlen(data); ++i)
{
data[i] ^= magic_bytes[i];
}
printf("%s", data);
return 0;
}

https://blog.csdn.net/zyhse/article/details/108026800?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_default&utm_relevant_index=2

上面介绍了openssl的一些加密算法使用,下面这个是函数说明

https://www.jianshu.com/p/3c59291f8f98

SpaceHeroesCtf2022-Timesup

进去就是输入三个数满足一个方程式

计算方法就不多说了,可以看这篇wp

https://ctftime.org/writeup/32973

主要看第二个限制,可以看到这里有个限制应该是在16到17秒之间,但是wp里说的是17分,我自己写了代码发现第一个参数是秒,这里也有说明

https://www.runoob.com/cprogramming/c-function-localtime.html

那么我们就需要借助工具来进行传送参数,需要pwntools

1
2
3
4
process是连接本地连接
remote是连接远端的,格式t=remote('网址',端口)

我们可以发送数据过去,也可以接受数据
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
val = 0xa4c570

a = 1
b = (val * 7 - 4) // 2
c = 1
def testfunc(a, b, c):
return (a + b + c << ((a % b) & 0x1f)) // ((2 << (a & 0x1f) ^ 3) * c)

result = testfunc(a, b, c)

print(val)
print(result, hex(result))

print(hex(a), hex(b), hex(c))

from pwn import *

#s = process('./timesup')
s = remote('0.cloud.chals.io', 26020)

print(s.readuntil(b'>>> ').decode())

s.writeline('{} {} {}'.format(hex(a), hex(b), hex(c)).encode())

s.interactive()

直接贴代码吧,感觉需要系统学pwntools

Hgame2022-week4-hardasm

一打开有几千行的汇编代码,先定位到关键判断

可以看到有很多的比较+跳转,而跳转的位置都是error

那么只需要保证不符合等于0即可

构造flag:hgame{12345678901234567890}动调

比较内容

可以看到只要输入正确,最后得到的值是0xFF,不正确则为0,可以采取爆破的方式爆破出flag

可以看到printf的参数是通过rcx传入的,所以我们可以patch程序,让他打印[rsp+70h+var_50]的值,而不是success或error

asm-print

edit->patch Program->Assemble,不知道为什么KeyPatch改不了

subprocess模块

subprocess模块可以生成新的进程,连接到它们的input/output/error管道,同时获取它们的返回值

我们可以使用该模块进行爆破

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
import subprocess
real_flag="hgame{"#绝对正确的前6个字符
cur_index=6#当前爆破的位置
while cur_index<32:
for i in range(32,128):#当前爆破的位置上的字符
real_flag_arr = [0] * 32
for j in range(len(real_flag)):#正确的先复制一下
real_flag_arr[j]=ord(real_flag[j])

real_flag_arr[cur_index]=i#设置当前爆破的位置上的字符
real_flag_arr_s="".join(chr(k) for k in real_flag_arr)#输入到程序中的字符串
#上面都是一些初始化
p = subprocess.Popen(["D:\\new\\AD\\game\\hgame2022\\week4\\hardasm.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(real_flag_arr_s.encode())
p.stdin.close()#停止输入
out = p.stdout.read()
if len(out)>cur_index:#判断程序打印出的0xFF的个数是否增加,增加则说明当前爆破的位置上的字符设置的是正确的,因为存储的内容为0时,printf不打印
real_flag+=chr(i)
cur_index+=1
print(real_flag)
break
# hgame{r
# hgame{ri
# hgame{rig
# hgame{righ
# hgame{right
# hgame{right_
# hgame{right_y
# hgame{right_yo
# hgame{right_you
# hgame{right_your
# hgame{right_your_
# hgame{right_your_a
# hgame{right_your_as
# hgame{right_your_asm
# hgame{right_your_asm_
# hgame{right_your_asm_i
# hgame{right_your_asm_is
# hgame{right_your_asm_is_
# hgame{right_your_asm_is_g
# hgame{right_your_asm_is_go
# hgame{right_your_asm_is_goo
# hgame{right_your_asm_is_good
# hgame{right_your_asm_is_good!
# hgame{right_your_asm_is_good!!
# hgame{right_your_asm_is_good!!}
# hgame{right_your_asm_is_good!!}

代码来源-https://blog.csdn.net/weixin_45582916/article/details/122909419

POpen参数说明

1
2
3
stdin stdout和stderr:

stdin stdout和stderr,分别表示子程序的标准输入、标准输出和标准错误。可选的值有PIPE或者一个有效的文件描述符(其实是个正整数)或者一个文件对象,还有None。如果是PIPE,则表示需要创建一个新的管道,如果是None,不会做任何重定向工作,子进程的文件描述符会继承父进程的。另外,stderr的值还可以是STDOUT,表示子进程的标准错误也输出到标准输出。
1
2
3
stdin.write()#输入
stdin.close()#关闭输入
stout.read()#获取输出

Foobarctf2022-Matrix

一道vm题+angr求解

vm分析部分

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
unsigned char opcode[124] = {
0x04, 0x0B, 0x1D, 0x1C, 0x1B, 0x1A,
0x04, 0x0A, 0x5A, 0x50, 0x4E, 0x5D, //先把mov后面四个进行异或,然后将异或后的值与我们的输入异或
0x00, 0x0A, 0x0B,
0x00, 0x00, 0x0A, //0x00存的是我们的输入
0x04, 0x0B, 0x2D, 0x2C, 0x2B, 0x2A,
0x04, 0x0A, 0x56, 0x69, 0x58, 0x49,
0x00, 0x0A, 0x0B,
0x00, 0x01, 0x0A,
0x04, 0x0B, 0x3D, 0x3C, 0x3B, 0x3A,
0x04, 0x0A, 0x5C, 0x6C, 0x08, 0x65,
0x00, 0x0A, 0x0B,
0x00, 0x02, 0x0A,
0x04, 0x0B, 0x4D, 0x4C, 0x4B, 0x4A,
0x04, 0x0A, 0x7A, 0x04, 0x2E, 0x15,
0x00, 0x0A, 0x0B,
0x00, 0x03, 0x0A,
0x04, 0x0B, 0x5D, 0x5C, 0x5B, 0x5A,
0x04, 0x0A, 0x30, 0x1C, 0x0F, 0x08,
0x00, 0x0A, 0x0B,
0x00, 0x04, 0x0A,
0x04, 0x0B, 0x6D, 0x6C, 0x6B, 0x6A,
0x04, 0x0A, 0x24, 0x14, 0x16, 0x6A,
0x00, 0x0A, 0x0B,
0x00, 0x05, 0x0A,

0x02, 0x00, 0x01, //将我们异或后的值|=其后面一个值
0x02, 0x00, 0x02,
0x02, 0x00, 0x03,
0x02, 0x00, 0x04,
0x02, 0x00, 0x05,
0xFF

angr求解,因为最后的结果字符串存在GLUG,所以直接angr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import angr
import claripy
import sys



proj = angr.Project("./matrix")#创建文件
state = proj.factory.entry_state()

simgr = proj.factory.simgr()
simgr.run()


if simgr.deadended:
for s in simgr.deadended:
tmp = s.posix.dumps(0)#获取最终结果
if b"glug" in tmp.lower():
print(tmp)

不知道为啥只能在linux跑,在windows跑会报错

*CTF2022-Simplefs

根据附件里的描述,可以知道这是关键函数

这三段都很类似,不同的在于这里传入的参数

进去看看,根据不同的参数产生不同的结果,然后写入

当传入2时,生成的是随机数,而且以时间为种子,所以我们不可逆

当传入1时,移位和异或操作,通过动调可以得到异或的值

解题思路

遍历文件所有字节流,进行解密,然后和*CTF比较,比对成功就打印其后面的32位

也可以先将*CTF进行加密,然后将得到的十六进制去加密的文件搜索,取出之后爆破即可(NU1L的题解就是爆破的)

IDA命令行参数动调

本体涉及命令行参数,红色框输入的是命令行的参数

脚本

下面贴一下我的脚本,调试了挺久emmm,太菜了

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
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#include<stdlib.h>

#define FilePath "D:\\new\\AD\\game\\星ctf2022\\dcacb9c0054e4a4695b2f373f255ac8e\\image.flag"
int main()
{
char xor_table[4] = { 0xEF,0xBE,0xED,0xDE };
FILE* pfile = fopen(FilePath, "r");
fseek(pfile, 0 , SEEK_END);
int filesize = ftell(pfile);
fseek(pfile, 0, SEEK_SET);
unsigned char *Buffer = { 0 };

Buffer = (unsigned char*)malloc(filesize);
memset(Buffer, 0, filesize);
fread(Buffer, filesize, 1, pfile);
int j = 0;
int i = 0;
int count = 0;
while (1)
{

for (i=j;count<32; ++i,count++)
{
Buffer[i] = ((Buffer[i] << 5) | (Buffer[i] >> 3)) & 0xff;
Buffer[i] ^= xor_table[3];
Buffer[i] = ((Buffer[i] << 4) | (Buffer[i] >> 4)) & 0xff;
Buffer[i] ^= xor_table[2];
Buffer[i] = ((Buffer[i] << 3) | (Buffer[i] >> 5)) & 0xff;
Buffer[i] ^= xor_table[1];
Buffer[i] = ((Buffer[i] << 2) | (Buffer[i] >> 6)) & 0xff;
Buffer[i] ^= xor_table[0];
Buffer[i] = ((Buffer[i] << 1) | (Buffer[i] >> 7)) & 0xff;
}
if ((Buffer[j] == '*') && (Buffer[j+1] == 'C') && (Buffer[j+2] == 'T') && (Buffer[j+3] == 'F'))
{
for (int k = j; k < 32+j; ++k)
{
printf("%c", Buffer[k]);
}
break;
}
j += 32;
count = 0;

}

fclose(pfile);
return 0;
}

要注意的是

BYTE是unsigned char型,所以前面的代码不需要加上&0xff,因为给Buffer定义为unsigned char,所以赋值时会截断