【虚空】【ISCTF2024】ISCTF2024 Reverse 全WP

去年写了misc和web,今年确实没时间了,就只把逆向和密码写了写。
简单聊一下逆向吧,今年的逆向确实考点种类比去年要繁杂一点。有个魔改的SM4,我也有个自建的密码学库的题。还有很多很好玩的比如rust和cpython逆向,并且包括smc和反调试这些基础题型都有。总体来说还是比较全面的。

不过有一说一啊(我是没想到我rust解出数居然还没cpython多,可能是大家对动态调试不太熟悉的原因?我rust我是真连rust风格都没敢写,全是c的语句用rust写了,本来想用trait和impl去给enum,struct之类的做成类c++那种,想了想算了,就来了点基础的RC4。)

ezre

在这里插入图片描述

这道题宛如暴风雨来临前的平静。。。

玩个花点的,本质上还是维吉尼亚,但是key有点小问题。
解密也行,我这儿直接无脑爆破了(乐

data = "QKEMK{7JB5_i5_W3SllD_3z_W3}"
key = "ISCTF"


def enc(word, key):
	if 25 >= ord(word) - 65 >= 0:
		return chr(65 + (ord(word) - 65 + ord(key) - 65) % 26)
	else:
		return word


for pos in range(len(data)):
	for i in range(0x20, 0xff):
		if enc(chr(i), key[pos % 5]) == data[pos]:
			print(chr(i),end = "")

桀桀桀

在这里插入图片描述

进来之后通过string定位到主函数
在这里插入图片描述

交叉引用到主函数
在这里插入图片描述

将函数变得稍微好看点

在这里插入图片描述

因为每次put完之后都有个相同的函数,很容易想到等价于std::endl。
然后scanf进来了一个字符串,%5s表示的是个字符串,长度为5.也就是无论输入进来的是多长,都会补全为长度为5。

其实看到长度为5也能猜出来说是,毕竟就是ISCTF嘛,典中典。不过也可以z3求解一下。

from z3 import *

sol = Solver()
key = [Int(f"v{
     i}") for i in range(5)]

sol.add((key[0] * key[1]) * 2**10 == 0x5eac00)
sol.add((key[1] + 1) * (key[1] - 1) == 0x1ae8)
sol.add(key[2] + key[3] == 151)
sol.add((key[2] * 2**11) - key[3] == 137132)
sol.add(sum(key) == 377)
  

for i in key:
    sol.add(i < 0xff)
    sol.add(i > 0)
  
sol.check()
result = sol.model()
print("".join([chr(result[i].as_long()) for i in key]))
# key = ISCTF
Seed = sum([ord(i) ^ 0xa1 for i in "ISCTF"])
print(Seed)

拿到key和Seed

然后是一个加密函数

在这里插入图片描述

在sub_4111B3,也就是图中的RC4(我也不知道为啥脑子懵逼了写的RC4,实际上这儿是个TEA加密)这个函数中,有点花指令,我们nop掉就行了

为了方便演示,我们重新打开一遍这个程序,并把数据库取消掉

在这里插入图片描述

双击进去

在这里插入图片描述

显然是花指令

按tab键进到汇编页面

一眼看到这儿有个花指令

nop掉就行了,或者快捷键 Ctrl + N

在这里插入图片描述

然后我们重建函数

从push ebp(就花指令上边能找到这个,上图也有,在loc_411990下边)
到 retn这个指令(往下找,遇见的第一个就是)

在这里插入图片描述

选中他们,然后按P键构造函数

构造完成后,就可以按F5查看汇编了

在这里插入图片描述

一眼TEA加密,然后稍微有点变种
这个无所谓,写个脚本解密

#include <iostream>
#include <stdint.h>

using namespace std;

void TEA_decode(uint32_t* v){
   
	unsigned int sum = 8*0x114514 + 0x9E3779B9 * 24;
	uint32_t  key[4] = {
   0x6fc6, 0x69d3, 0x68d5, 0x73cc};
	uint32_t  key1,key2,key3,key4;
	key1 = key[2];
	key2 = key[3];
	key3 = key[0];
	key4 = key[1];
	uint32_t delta = 0x114514;

	for(int i = 31; i >= 0; i--){
   
		if(i == 23){
   
        	delta = 0x9e3779b9;
		}
		if(i == 15){
   
			key1 = key[0];
			key2 = key[1];
			key3 = key[2];
			key4 = key[3];
		}
        v[1] -= ((v[0] << 4) + key3) ^ (v[0] + sum) ^ ((v[0] >> 5) + key4);
        v[0] -= ((v[1] << 4) + key1) ^ (v[1] + sum) ^ ((v[1] >> 5) + key2);
        sum -= delta;
	}
}

main函数还有别的内容,所以我们先继续往下看

TEA加密完了用前边的seed置随机数种子了,然后rand产生了个随机数,实际上给到v12了。

在这里插入图片描述

然后将加密之后的flag和随机数传到了这个函数
sub_7D128A

在这里插入图片描述

这个函数说白了也是个陷阱函数

我们进去,ida只能识别到有个strlen,而且这个strlen还有个坑人的地方,我们后边说

在这里插入图片描述

但是当我们进到汇编,就可以发现,后边还藏着一部分
我们来看这三行汇编
首先是将esp + 4,因为又要call函数了,所以这里esp+4

然后这里将eax对应的值给了 [ebp + var_20] 这个地址
其中var_20就是-0x20,我们一会儿还会见到它。
那这个eax的值是什么呢?
实际上就是我们strlen的值,也就是将strlen的值给到了ebp - 0x20这个地址

在这里插入图片描述

那接下来,我们就要进到动调的世界,来看看这个call的到底是何方神圣

在这里插入图片描述

给这儿下个断点

在这里插入图片描述

随便输入个flag值

然后按F7步入

在这里插入图片描述

先到这儿

在这里插入图片描述

跳到了这里,我们按C键解释为代码

在这里插入图片描述

又是call一个函数
我们继续F7

在这里插入图片描述

套到了这里

再按一下F7
就进到了实际的函数体了

在这里插入图片描述

但是这里ida没有识别出来是个函数

我们需要自己构造一下函数,依旧是跟上边构造函数一样

从loc_6F1BF7下边的mov开始,到retn结束,选中然后按P键

在这里插入图片描述

然后这个时候再按F5,就可以看到伪代码了

在这里插入图片描述

动调或者分析可知,这个所谓的 (a1 - 44),实际上就是个循环的值 i。
这个a1 - 32,实际上就是上边看到的ebp - 0x20,也就是strlen的值。
而这个a1 + 8,实际上就是经过上边TEA加密后的flag值了。

然后就是a1 - 8和a1 - 20,我们可以在内存里找到他们的值
先确定a1的地址是0xCFFBD4

在这里插入图片描述

那么a1 - 8就是0xCFFBCC

在这里插入图片描述

找到他的值,是0x3C

同理,a1 - 20是0xCFFBC0

在这里插入图片描述

是0x26

这样我们就把值找全了。

让我们把上边的伪代码重新写一下

int i = 0;
int v1 = 0;
while(i < strlen(flag)){
   
	if(i % 2){
   
		v1 = 0x3C ^ flag[i];
	}
	else{
   
		v1 = 0x26 ^ flag[i];
	}
	flag[i] = v1;
	i++;
}

OK,看似万事俱备只欠东风,直接解密就行了。
但是对于我们这个例子(测试的flag为 ISCTF{aaaaaaaaaaaaaaaaaaaaaaaaa} )
如果你去细心看strlen的值

在这里插入图片描述

会发现这里不是0x20,而是0x1A

如果你再换个例子,你会发现这里的值发生改变了。
但我们输入的flag长度就是32啊,为什么会出现问题呢?
实际上这是因为strlen被\x00 给截断了

在这里插入图片描述

箭头指的地方就是0x1A,也就是第26个字符。

所以后边的就没有发生加密

而如果我们要判断原flag有没有被截断,只需要看后边一直的那个加密后的数据,中间有没有产生 \x00 就行了。

在这里插入图片描述

我们返回出来提取这个v20,先给for打个断点,然后执行到这个断点处(F9)

在这里插入图片描述

然后提取v20的值即可

在这里插入图片描述

提取32字节,并且发现没有截断,所以循环长度是32


data = [0xB1, 0xD2, 0xF9, 0x7A, 0x83, 0x4C, 0x51, 0x23, 0xB7, 0xAD, 0xA9, 0xBE, 0xE8, 0xFA, 0x24, 0x16, 0x93, 0xFE, 0x42, 0xD7, 0xB0, 0x1F, 0x52, 0xF7, 0x5A, 0x7D, 0x80, 0xE8, 0x28, 0xFC, 0x41, 0x6F]

for i in range(32):
    if i % 2 == 1:
        data[i] ^= 0x3c
    else:
        data[i] ^= 0x26

#这里是在处理成标准格式,方便交到上边的TEA的c++程序去解密
result = ", ".join(["0x" + "".join([hex(j)[2:].rjust(2, "0") for j in data[i:i+4][::-1]]) for i in range(0, len(data), 4)])

print(result)

得到结果

0x46dfee97, 0x1f7770a5, 0x828f9191, 0x2a02c6ce, 0xeb64c2b5, 0xcb742396, 0xd4a6417c, 0x5367c00e

然后让我们补齐c++程序的main函数,将上边的值填到data里边

int main(void){
   
	unsigned int data[] = {
   
    	0x46dfee97, 0x1f7770a5, 0x828f9191, 0x2a02c6ce, 0xeb64c2b5, 0xcb742396, 0xd4a6417c, 0x5367c00e
	};
	for(int i = 0; i < 8; i += 2){
   
		TEA_decode(data+i);
	}
	for(int k = 0;k < 8; k++){
   
		cout<<"0x"<<hex<<data[k]<<", ";
	}
	return 0;
}

合并起来的程序就是


#include <iostream>
#include <stdint.h>

using namespace std;

void TEA_decode(uint32_t* v){
   
	unsigned int sum = 8*0x114514 + 0x9E3779B9 * 24;
	uint32_t  key[4] = {
   0x6fc6, 0x69d3, 0x68d5, 0x73cc};
	uint32_t  key1,key2,key3,key4;
	key1 = key[2];
	key2 = key[3];
	key3 = key[0];
	key4 = key[1];
	uint32_t delta = 0x114514;

	for(int i = 31; i >= 0; i--){
   
		if(i == 23){
   
        	delta = 0x9e3779b9;
		}
		if(i == 15){
   
			key1 = key[0];
			key2 = key[1];
			key3 = key[2];
			key4 = key[3];
		}
        v[1] -= ((v[0] << 4) + key3) ^ (v[0] + sum) ^ ((v[0] >> 5) + key4);
        v[0] -= ((v[1] << 4) + key1) ^ (v[1] + sum) ^ ((v[1] >> 5) + key2);
        sum -= delta;
	}
}


int main(void){
   
	unsigned int data[] = {
   
    	0x46dfee97, 0x1f7770a5, 0x828f9191, 0x2a02c6ce, 0xeb64c2b5, 0xcb742396, 0xd4a6417c, 0x5367c00e
	};
	for(int i = 0; i < 8; i += 2){
   
		TEA_decode(data+i);
	}
	for(int k = 0;k < 8; k++){
   
		cout<<"0x"<<hex<<data[k]<<", ";
	}
	return 0;
}

运行,得到输出结果

0x54435349, 0x30797b46, 0x4e6b5f55, 0x525f7730, 0x30446e41, 0x4e415f6d, 0x33375f64, 0x7d212161

同理,将值扔到python脚本中

from libnum import n2s

msg = [0x54435349, 0x30797b46, 0x4e6b5f55, 0x525f7730, 0x30446e41, 0x4e415f6d, 0x33375f64, 0x7d212161]
flag = "".join([n2s(i)[::-1].decode('utf-8') for i in msg])

print(flag)

得到flag:
ISCTF{y0U_kN0w_RAnD0m_ANd_73a!!}

第二章-当记忆被割裂

在这里插入图片描述

这题有点像ollvm,但不完全像

我是没恢复出来他的反汇编
但是跟着函数一个一个走就能看到具体的东西了
在这里插入图片描述

主要的加密逻辑在enc这个函数里边

在这里插入图片描述

从上到下分别是调用了这5个函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

其中分析一下就能看到是有个for循环一样的i值,然后不断在对data进行计算。
写出来python代码

data ^= (i + 102) ^ 82
data += 6
data ^= i + key[i]
return data

然后将key和数据进行解密就得到flag了

key = [ord(i) for i in "i_can_reverse_but_i_can_not_have_you"]
ans = [0xEA, 0x0C, 0x1A, 0x11, 0xF6, 0x2C, 0x1D, 0x3E, 0x17, 0x35, 0x31, 0x29, 0xF4, 0x39, 0x39, 0xD3, 0xC3, 0x2D, 0x00, 0x10, 0x30, 0x3D, 0xCC, 0x00, 0xD3, 0xC0, 0x4B, 0xC6, 0x11, 0xC7, 0x29, 0x3E, 0xBA, 0x60, 0x90, 0x34]


for pos, elem in enumerate(ans):
	temp = elem ^ (key[pos % len(key)] + pos)
	temp -= 6
	temp ^= (102 + pos) ^ 82
	print(chr(temp & 0xff),end="")

ISCTF{as_her_never_will_come_back!!}

第三章-逃不出的黑墙

在这里插入图片描述

这道题有一说一感觉是这三道题里边最简单的了)
就是一个非常简单的迷宫,只不过迷宫对应的字符变了
我觉得甚至不逆向程序,直接找对应的字符解迷宫就行。乐

就只贴一张我修复过的ida的图了
我取消了很多中间变量(快捷键是 = 号)

在这里插入图片描述

目的就是跑到e那边
把maze提取出来

然后用cyberchef处理一下,变成程序可以识别的墙壁(墙 = # = 1)(路 = . = 0)

在这里插入图片描述

我们会发现,右边和下边少了两面墙
隐藏通过程序给他补上

maze = 

maze = ["[" + i[:-1] + ',1],\\' for i in maze] # 格式化并补上右侧的墙
maze.append(maze[0]) # 补上下边的墙
print("\n".join(maze))

然后写个解迷宫的程序就行

dirs=[(0,1),(1,0),(0,-1),(-1,0)] #当前位置四个方向的偏移量
path=[]              #存找到的路径



def mark(maze,pos):  #给迷宫maze的位置pos标"2"表示“倒过了”
	# print()
	maze[pos[0]][pos[1]]=2
 
def passable(maze,pos): #检查迷宫maze的位置pos是否可通行
	return maze[pos[0]][pos[1]]==0
 
def find_path(maze,pos,end):
	mark(maze,pos)
	if pos==end:
		print(pos,end=" ")  #已到达出口,输出这个位置。成功结束
		path.append(pos)
		return True
	for i in range(4):      #否则按四个方向顺序检查
		nextp=pos[0]+dirs[i][0],pos[1]+dirs[i][1]
		#考虑下一个可能方向
		if passable(maze,nextp):        #不可行的相邻位置不管
			if find_path(maze,nextp,end):#如果从nextp可达出口,输出这个位置,成功结束
				print(pos,end=" ")
				path.append(pos)
				return True
	return False
 
def see_path(maze,path):     #使寻找到的路径可视化
	for i,p in enumerate(path):
		if i==0:
			maze[p[0]][p[1]] ="E"
		elif i==len(path)-1:
			maze[p[0]][p[1]]="S"
		else:
			maze[p[0]][p[1]] =3
	print("\n")
	for r in maze:
		for c in r:
			if c==3:
				print('\033[0;31m'+"*"+" "+'\033[0m',end="")
			elif c=="S" or c=="E":
				print('\033[0;34m'+c+" " + '\033[0m', end="")
			elif c==2:
				print('\033[0;32m'+"#"+" "+'\033[0m',end="")
			elif c==1:
				print('\033[0;;40m'+" "*2+
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值