近期三场ctf,题目都比较简单。
Securinets Prequals 2K19
AutomateMe
获取输入后比对长度3296,然后是3296个if一个一个的比对,一共有下面两种比对方式:
.text:0000000000000774 ; ----------------------------------------------------------
.text:0000000000000774
.text:0000000000000774 loc_774: ; CODE XREF: main+82↑j
.text:0000000000000774 mov rax, [rbp+var_20]
.text:0000000000000778 add rax, 8
.text:000000000000077C mov rax, [rax]
.text:000000000000077F add rax, 1
.text:0000000000000783 movzx eax, byte ptr [rax]
.text:0000000000000786 cmp al, 68h ; 'h'
.text:0000000000000788 jz short loc_7A0
.text:000000000000078A lea rdi, aNope ; "nope :( "
.text:0000000000000791 mov eax, 0
.text:0000000000000796 call _printf
.text:000000000000079B jmp locret_283A0
.text:00000000000007A0 ; ----------------------------------------------------------
.text:00000000000007A0
.text:00000000000007A0 loc_7A0: ; CODE XREF: main+AE↑j
.text:00000000000007A0 mov rax, [rbp+var_20]
.text:00000000000007A4 add rax, 8
.text:00000000000007A8 mov rax, [rax]
.text:00000000000007AB movzx eax, byte ptr [rax+2]
.text:00000000000007AF mov [rbp+var_1], al
.text:00000000000007B2 xor [rbp+var_1], 0EBh
.text:00000000000007B6 cmp [rbp+var_1], 8Eh
.text:00000000000007BA jz short loc_7D2
.text:00000000000007BC lea rdi, aNope ; "nope :( "
.text:00000000000007C3 mov eax, 0
.text:00000000000007C8 call _printf
.text:00000000000007CD jmp locret_283A0
.text:00000000000007D2 ; ----------------------------------------------------------
由于代码都是相似的,比对的过程也是从头到尾,所以直接找到特定的代码提取出数据就行了
f = open('bin','rb')
f.read(0x74C)
g = open('flag.txt','ab')
with open('bin','rb') as f:
f.read(0x74C)
while(True):
temp = f.read(1)
if temp =='':
break
temp = ord(temp)
if temp == 0x3C:
t = f.read(1)
k = f.read(1)
if k=='\x74':
g.write(t)
elif temp ==0x80:
if ord(f.read(1))==0x75:
f.read(1)
t = ord(f.read(1))
f.read(1)
f.read(1)
f.read(1)
k = ord(f.read(1))
g.write(chr(k^t))
flag就在输出的中间一部分。
warmup
先将输入base64编码,然后在25个check里比对某一位的字符或某两位的相对位置。细心一点把25个check求一遍就行了。
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
k = [0 for i in range(36)]
k[0] = table[28]
k[1] = table[54]
k[2] = k[10] = table[((28+54)>>2) + 1]
k[3] = 'j'
k[4] = chr(ord(k[0])+1)
k[12] = k[22] = k[24] = chr(ord(k[4])-1)
k[23] = k[11] = chr(48)
k[35] = chr(ord(k[11])+9)
k[6] = chr(ord(k[3]) - 32)
k[8] = chr(ord(k[0]) - 1)
k[27] = k[31] = chr(ord(k[4]) + 2)
k[25] = k[9] = chr(ord(k[27]) + 7)
k[13] = k[17] = k[21] = chr(ord(k[1]) + 1)
k[7] = 'p'
k[15] = chr(ord(k[7]) + 3)
k[14] = chr(ord(k[15]) + 1)
k[19] = 'z'
k[34] = chr(ord(k[0]) - 33)
k[5] = k[20] = k[29] = k[33] = 'X'
k[26] = chr(49)
k[16] = k[28] = chr(ord(k[9]) - 32)
k[1] = chr(50)
k[18] = k[30] = chr(ord(k[7]) - 30)
k[32] = k[4]
t = ''
for i in range(36):
t+=k[i]
print(t.decode('base64'))
25个check比较繁琐,需要细心一点。话说应该可以用angr,但是翻了好多文档试了好多脚本都求不出,有时间再仔细学学angr
Matrix of Hell
加密逻辑:
gets(s);
if ( strlen(s) != 14 || (sub_5593FC1E383A(), !v3) )
{
printf("ACCESS DENIED");
exit(0);
}
v16 = 0;
for ( k = 0; k < strlen(s); ++k )
{
for ( l = 0; l <= 4; ++l )
{
for ( m = 0; m <= 4; ++m )
{
if ( matrix[m + 6LL * l] == s[k] )
{
s1[v16] = l + 65;
v4 = v16 + 1;
s1[v4] = m + 49;
v16 = v4 + 1;
}
}
}
}
for ( n = 0; n < strlen(s1); ++n )
s2[n] = n % 4 ^ s1[n];
if ( strcmp(s3, s2) )
{
printf("ACCESS DENIED", s2);
exit(0);
}
先生成一个5*6的固定的矩阵,然后获取输入,输入长度需为14
从输入的第一位开始,在矩阵中找到对应的元素,行数 l+65 放到 字符串s1的2*i位置,列数 m+49放到字符串s2的2*i+1位置,最后将字符串的每一位异或位数(n%4)得到s2,再和常量字符串s3对比。
解密脚本:
t1 = [0x41, 0x42, 0x43, 0x44, 0x45, 0x0, 0x46, 0x47, 0x48, 0x49, 0x4B, 0x0, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x0, 0x51, 0x52, 0x53, 0x54, 0x55, 0x0, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x0]
s = 'B0C2A2C6A3A7C5@6B5F0A4G2B5A2'
s1 = []
for i in range(len(s)):
s1.append(ord(s[i])^(i%4))
pswd = ''
for i in range(14):
l = s1[2*i] - 65
m = s1[2*i+1] - 49
pswd += chr(t1[m + 6*l])
print(pswd)
Vectors
输入一个64位的十六进制数
接下来对数据的操作用到了vector,静态分析比较蛋疼,直接动调。先将输入的数据分成8个字节放入一个vector,在sub_F37
可以看到它将另7个不同的8位16进制数放入另一个vector。直接调试到sub_F37
可以看到它将两个vector排好序了
[heap]:0000564A82C794B0 dword_564A82C794B0 dd 0Ch
[heap]:0000564A82C794B0 dd 0Dh
[heap]:0000564A82C794B0 dd 0ADh
[heap]:0000564A82C794B0 dd 0BEh
[heap]:0000564A82C794B0 dd 0DEh
[heap]:0000564A82C794B0 dd 0EDh
[heap]:0000564A82C794B0 dd 0EFh
[heap]:0000564A82C794E0 dword_564A82C794E0 dd 11h
[heap]:0000564A82C794E0 dd 22h
[heap]:0000564A82C794E0 dd 33h
[heap]:0000564A82C794E0 dd 44h
[heap]:0000564A82C794E0 dd 55h
[heap]:0000564A82C794E0 dd 66h
[heap]:0000564A82C794E0 dd 77h
[heap]:0000564A82C794E0 dd 88h
之后就直接比对结果了。直接输入0C0DADBEDEEDEF并不能的到flag,虽然显示通过了check但是输出的flag并不对:
Welcome resreveR!...
PASSCODE:0C0DADBEDEEDEF
GOOD JOB U GOT THIS, HERE IS UR FLAG:s62ine'
因为一共有7!种排列方式。用itertools爆破求出flag即可
from pwn import *
from itertools import *
context.log_level = 'error'
a = [0xad,0xef,0xbe,0xde,0xc,0xed,0xd]
b = permutations(a,7)
for i in b:
p = process('./bin')
payload = ''
for j in range(7):
k = hex(i[j])[2:]
if(len(k)==1):
k = '0'+k
payload+=k
p.recv()
p.sendline(payload)
p.recvuntil('FLAG:')
a = p.recv()
if a.startswith('securinets'):
print(a)
exit()
p.close()
RBOOM!
上来有个ptrace的反调试,直接jmp掉
读取输入后,写入文件"lla"。主要的加密逻辑在sub_93A
里。从文件"lla"和文件“la”读取数据后,在sub_CCF
加密。
v11 = 0;
v13 = 0;
for ( i = 0; i <= 255; ++i )
{
v18[i] = i;
v17[i] = la[i % a4];
}
for ( j = 0; j <= 255; ++j )
{
v5 = (unsigned int)(((unsigned __int8)v18[j] + v11 + (unsigned __int8)v17[j]) >> 31) >> 24;
v11 = (unsigned __int8)(v5 + v18[j] + v11 + v17[j]) - v5;
v6 = v18[v11];
v18[v11] = v18[j];
v18[j] = v6;
}
v12 = 0;
for ( k = 0; k < a2; ++k )
{
v13 = (unsigned __int8)((char *)&off_2F98
+ 0xFFFFD069
+ v13
+ ((unsigned int)((signed int)((signed int)&off_2F98 + 0xFFFFD069 + v13) >> 31) >> 24))
- ((unsigned int)((signed int)((signed int)&off_2F98 + 0xFFFFD069 + v13) >> 31) >> 24);
v7 = (unsigned int)((v12 + (unsigned __int8)v18[v13]) >> 31) >> 24;
v12 = (unsigned __int8)(v7 + v12 + v18[v13]) - v7;
v8 = v18[v12];
v18[v12] = v18[v13];
v18[v13] = v8;
a5[k] = v18[(unsigned __int8)(v18[v13] + v18[v12])] ^ input[k];
}
看到有两个长度为256的循环,猜测一下是RC4。不太想分析太多,直接在input异或的地方00000F5F
下断点,编辑断点添加condition:print(GetRegValue('ecx')),这样直接能在ida的输出窗口将它异或的东西输出出来,再之后就是和常量比较了。
当然也可以直接用RC4解密,看个人喜好
a = [219, 87, 247, 80, 74, 188, 141, 29, 127, 165, 123, 43, 219, 11, 64, 236, 244, 233, 240, 132, 136, 239, 180, 2, 232, 137, 128, 129, 139, 1, 251, 46, 19, 18, 176, 44, 71, 111, 163, 36, 109, 38, 229, 248, 92, 183, 230, 30, 75, 97, 236, 159, 242]
with open('ll','rb') as f:
s = ''
for i in range(33):
s+=chr(a[i]^ord(f.read(1)))
print(s)
monster
这题算法十分简单,但如果不熟悉大整数运算算法的话看起来会比较吃力
首先还是接受16进制的输入,主要加密逻辑在00000B52
里面
void __cdecl encode(char *s, __int64 a3)//s:"tsebehtsignisrever" a3:input
{
unsigned __int64 a1; // [esp+8h] [ebp-20h]
size_t i; // [esp+18h] [ebp-10h]
a1 = __PAIR__(HIDWORD(a3), (unsigned int)a3);
for ( i = 0; strlen(s) > i; ++i )
{
temp[i] = a1 ^ s[i];
a1 = mod(1337 * a1, 133713371337LL);
}
}
当然原本是没有符号表的,这里是我改的。
这里的mod函数就是取模运算,不过是对64位大整数的取模运算,这是一个32位程序,所以要对64位数取模要用两个寄存器/Dword表示,会麻烦一些。一开始对着这个函数日了好久,最终发现是手动在32程序里写了一个64位整数的取模运算,也是浪费了一些时间= =
直接用z3约束求解器解吧
from z3 import *
from pwn import *
context.log_level = 'debug'
p = process('./rev',)
# p = remote('54.87.182.197','1337')
a= BitVec('a',64)
s = Solver()
const = [0xCA, 0x3D, 0x3B, 0x5B, 0x4C, 0x9D, 0xD2, 0xCB, 0xDD, 0x17, 0x8D, 0xDC, 0xB9, 0x49, 0x3B, 0xEA, 0x12, 0x25]
key = [ 0x74, 0x73, 0x65, 0x62, 0x65, 0x68, 0x74, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x73, 0x72, 0x65, 0x76, 0x65, 0x72]
for i in range(len(key)):
const[i]^=key[i]
s.add(a &0xff == const[i])
a = (a*1337)%133713371337
if s.check() == sat:
m = s.model()
payload = hex(m[m[0]].as_long())
print(payload)
# payload = '564e9367e6e30be'
p.sendline(payload)
print(p.recv())
比赛的服务器已经关闭了,本地肯定能打
Sunshine CTF 2019
Patches' Punches
打开程序后直接结束了,看一下汇编有一个永真的语句,把00000540
patch成与1比较,直接得到flag
.text:0000052B push ecx
.text:0000052C sub esp, 10h
.text:0000052F call __x86_get_pc_thunk_ax
.text:00000534 add eax, 1AA4h
.text:00000539 mov [ebp+var_10], 1
.text:00000540 cmp byte ptr [ebp+var_10], 1
.text:00000544 jnz short lose
.text:00000546 mov [ebp+var_C], 0
.text:0000054D jmp short win
Smash
加密逻辑在checkAccessCode
函数里,把输入右移一定大小,然后跟常量对比。
a = [0x0E60, 0x3A8, 0x1B80, 0x0F60, 0x120, 0x0EA0, 0x188, 0x358, 0x1A0, 0x9A0, 0x184, 0x4E0, 0x0C40, 0x0C20, 0x5A0, 0x1C8, 0x1D4, 0x9C0, 0x1CC, 0x0B40, 0x0AE0, 0x62, 0x360, 0x340, 0x5A0, 0x180, 0x6E0, 0x0B40, 0x1540, 0x0FA0]
b = [5, 3, 6, 5, 2, 5, 3, 3, 3, 5, 2, 4, 6, 5, 5, 2, 2, 5, 2, 6, 5, 1, 3, 4, 5, 3, 4, 6, 6, 5]
s = ''
for i in range(len(a)):
a[i] >>= b[i]
s+=chr(a[i])
print(s)
The Whole Pkg
这题就有点意思了,直接打开是个文件读取系统
输入1看文件目录,输入2打印文件
输入3提示没有权限
其他的文件都能正常读
思考一下,可能输3之后会有个函数check,想办法定位到那个check,如果它是采用返回0/1的方式check,可以尝试修改返回值(eax)
用ida打开直接凉凉,里面一大堆库函数乱起八糟挺难分析的
用x64dbg动调,直接运行,先输入一个2,接下来先暂停再输入3回车,这样会暂停到输完3那一步,可以看到我们的输入“3\r\n”在栈里面。
在字符'3'下硬件断点,继续运行,会停到一个函数内,看起来像是strlen,返回了函数长度3。
继续运行,断到另一个函数,看起来像是strcpy,在copy的新地址下硬件断点。
继续运行,来到一个类似check的地方,直接运行到返回,返回值是1,尝试改成0(返回后也能看到它和1,-1,0比较),把之前的断点删除继续与运行,这时候又来到一个要求输入的地方,尝试输入一个3和回车,flag就直接被打印出来了。。。
做法很玄学,希望大佬们能讲解下具体细节= =