[CNSS Recruit 2021] Reverse部分赛题复现
成电新生赛,这个我应该复现得动吧。。
0x07 meta_game
首先exeinfo查一下,发现是32位程序
运行程序, 提示you can see the password by debuger.
,并且第一步得猜一个数字
ida打开,动态调试,在第十三行设置断点,可以在调试的时候看到十二行显示了v2的值,直接输入即可
然后来到下一个函数,发现有语句 if (i == 2000) break
,一直需要循环2000次才可进入下一步
打开汇编代码,发现
此处的7D0h
即十进制的2000,则此句即为判断i是否为2000,猜测下一句代码的jz
为跳到下一个函数,那么直接将jz
修改为jnz
,运行后发现该函数通过
来到下一步,发现勇士直接GG,查看伪代码
考虑第一个语句rand() % 100 == 1
为真才可进入后续,直接查看汇编代码
直接把jnz
改为jz
即可
再查看后续代码,发现必须让if(IsDebuggerPresent())
不能为真
继续查看汇编,再把最后一个jz
改成jnz
最后动态调试中运行
得到flag:
cnss{0f3b82c6c7f1808e3e464ebe338c71e0}
0x08 encoder
扔进ida,点开前两个加密函数,看到有数组叫aAbcdefghijklmn
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
显然是base64的索引表,但是这里对这个数组进行了操作,那就是base64换表了
首先要找到他把索引表换成啥了
char v2[65];
// unsigned char v2[65]
char aAbcdefghijklmn[65] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};
#define HIDWORD(x) (*((unsigned int*)&(x)+1))
#define _BYTE unsigned char
char aJlg5q7iz0ni9it[65] = {"jLG/5Q7iZ0NI9ituYKtwZLN0Z8UNxwiIWLit"};
inline void sub_401690(char *a1, char *a2)
{
char *result; // eax
char v3; // [esp+Fh] [ebp-1h]
v3 = *a1;
*a1 = *a2;
result = a2;
*a2 = v3;
}
inline void sub_4016B6()
{
char *result; // eax
LL v1_1; // rax
int v4; // [esp+5Ch] [ebp-Ch]
result = (char *)memset(v2, 0, sizeof(v2));
for (rg int i = 0; i <= 63; ++i)
{
v1_1 = dword_406420[i + 1];
v4 = (((HIDWORD(v1_1) >> 26) + (_BYTE)v1_1) & 0x3F) - (HIDWORD(v1_1) >> 26);
if ( v2[v4] )
{
while ( v2[v4] )
v4 = (v4 + 1) % 64;
}
sub_401690(&aAbcdefghijklmn[i], &aAbcdefghijklmn[v4]);
result = &v2[v4];
v2[v4] = 1;
}
}
inline _BYTE sub_401898(char *flag) // sub_401898(flag) = aJlg5q7iz0ni9it
{
signed int v3; // [esp+20h] [ebp-18h]
int v4; // [esp+24h] [ebp-14h]
int v5; // [esp+28h] [ebp-10h]
int v6; // [esp+2Ch] [ebp-Ch]
// v3 = strlen(flag);
// if (v3 % 3) v6 = 4 * (v3 / 3 + 1);
// else v6 = 4 * (v3 / 3);
v3 = 27, v6 = 36;
v2[v6] = 0;
v5 = 0;
v4 = 0;
while ( v5 < v6 - 2 )
{
v2[v5] = aAbcdefghijklmn[(unsigned __int8)flag[v4] >> 2];
v2[v5 + 1] = aAbcdefghijklmn[(16 * flag[v4]) & 0x30 | ((unsigned __int8)flag[v4 + 1] >> 4)];
v2[v5 + 2] = aAbcdefghijklmn[(4 * flag[v4 + 1]) & 0x3C | ((unsigned __int8)flag[v4 + 2] >> 6)];
v2[v5 + 3] = aAbcdefghijklmn[flag[v4 + 2] & 0x3F];
v4 += 3;
v5 += 4;
}
if ( v3 % 3 == 1 )
{
v2[v5 - 2] = 61;
v2[v5 - 1] = 61;
}
else if ( v3 % 3 == 2 )
{
v2[v5 - 1] = 61;
}
}
char flag[233];
inline void decod()
{
#define dec aJlg5q7iz0ni9it
}
int main()
{
sub_4015C0();
sub_4016B6(); // adjust aAbcdefghijklmn
decod();
__endl, cout << aAbcdefghijklmn;
// qAFHSNKTBUglYrbVc9MXWiaZjm345xkROp6Ewhouz2JDv7dfyCI/08LQPG+nste1
return 0;
}
(又臭又长,懒得删注释什么的了)
关键在于换表的base64不能简单的用原始的解密脚本替换索引表实现解密,这里贴一份好用的base64换表解密脚本
"""
Customized base64 algorithm
You can set you own indexing string using the config() method.
Usage:
b = CusBase64()
b.encode('binary\x00string') # Output: YmluYXJ5AHN0cmluZw==
b.decode('YmluYXJ5AHN0cmluZw==') # Output: binary\x00string
b.config('aABCDEFGHIJKLMNOPQRSTUVWXYZbcdefghijklmnopqrstuvwxyz0123456789+/')
b.decode('c2UsYi1kYWM0cnUjdFlvbiAjb21wbFU0YP==') # Output: self-destruction complete
"""
class CusBase64(object):
DEFAULT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def __init__(self):
self.idx_str = CusBase64.DEFAULT
def encode(self, str):
"""
Encode string using the customized indexing string.
- args:
str: String to be encoded
"""
# Get the binary string
binary = ''.join([format(ord(c),'0>8b') for c in str])
# Add additional zero
binary = self.padding(binary)
# Get the index in indexing string
idxs = [int(binary[6*i:6*i+6], 2) for i in range(len(binary)//6)]
result = ''.join([self.idx_str[i] for i in idxs])
# add '='
if len(str)%3 != 0:
result = result + (3-len(str)%3)*'='
print("%r" % result)
def decode(self, str):
"""
Decode string using the customized indexing string.
- args:
str: String to be decoded
"""
if len(str) == 0:
return
# remove '='
while str[-1]=='=':
str = str[:-1]
try:
# Get the binary string
binary = ''.join([format(self.idx_str.index(c), '0>6b') for c in str])
# Remove additional zero
binary = self.remove(binary)
result = ''.join([chr(int(binary[8*i:8*i+8], 2)) for i in range(len(binary)//8)])
except ValueError:
result = "Please check again!"
print("%r" % result)
def remove(self, binary):
"""
Remove additional zero while decoding string.
- args:
binary: Binary format of the index.
- returns:
Binary string without additional zero.
"""
if len(binary)%8 == 0:
return binary
else:
return binary[:-(len(binary)%8)]
def padding(self, binary):
"""
Add additional zero while encoding string.
- args:
binary: Binary format of the string.
- returns:
Binary string with additional zero.
"""
if len(binary)%6 == 0:
return binary
n = 6 - len(binary)%6
binary = binary + n * '0'
return binary
def config(self, str):
"""
Set customized indexing string.
"""
self.idx_str = str
print("New indexing string is %r" % self.idx_str)
得到flag:
cnss{U_ArE_g0od_at_REvErSe}
0x09 CSS大师第一步
首先观察主体代码,发现用flag更改了coord内元素对应的值,最后让x对应的值为1就算胜利
然后看root里面的代码,发现他有一些元素初值为1,有一些初值为0,还有一些是由其他的元素计算而出。
这里计算近包括全为乘法或者全为加法,那么假设这个数是1,如果他由加法而来,那么加法中有且仅有一个元素为1;如果他由乘法而来,那么乘法中所有元素均应为1。
找到关于x的计算,
考虑从这里倒着走,输出“一些数得改成有且仅有一个为1”以及“一些数中得改成至少有一个为1”,然后手算拿到flag
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
#define rg register
#define LL long long
#define __space putchar(' ')
#define __endl putchar ('\n')
template <typename qwq> inline void read(qwq &x)
{
x = 0;
rg int f = 1;
rg char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
x *= f;
}
template <typename qaq> inline void print(qaq x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
string list[171] = {"--2eead", "--68b16", "--bd92b", "--8eb64", "--fec8a", "--f6579", "--12447", "--65353", "--1bbf5", "--42d17", "--86526", "--00c17", "--d961b", "--c7e32", "--bcdac", "--3bf8d", "--7ce1b", "--4d92b", "--eef0f", "--b488f", "--33fa7", "--2bbe3", "--5bb71", "--3384e", "--5e2ce", "--bdbac", "--d75a1", "--0624d", "--1c582", "--2b916", "--28633", "--d5cb1", "--81e26", "--d7225", "--1d2b3", "--4f67a", "--084e9", "--ae4af", "--e9d11", "--05d82", "--4daa1", "--54887", "--fe35a", "--12e13", "--a8471", "--ec4c9", "--c9228", "--d4655", "--03f15", "--22cba", "--9c47e", "--68930", "--191cc", "--6d330", "--6b658", "--0e125", "--8bfeb", "--62771", "--c4575", "--beba0", "--bb2bb", "--c0c00", "--48322", "--8cbdf", "--71216", "--95a2a", "--6748b", "--5ed01", "--db963", "--8ed74", "--e0019", "--7f4b5", "--6b4a8", "--3bf7a", "--ad937", "--96afc", "--4c8b7", "--d660c", "--f5e87", "--63e08", "--afd50", "--b9e3e", "--e1d99", "--161ea", "--bc078", "--dfc3b", "--45812", "--75c62", "--0cd13", "--60d47", "--6b99d", "--acbb8", "--9f815", "--46fd1", "--ff63e", "--e8a89", "--98720", "--0fe50", "--4ad76", "--81a61", "--f3ea9", "--49059", "--14e69", "--f0ffe", "--7bb49", "--29580", "--99e12", "--ec297", "--00241", "--6c431", "--78082", "--3b5b5", "--6b426", "--2d350", "--90d05", "--70006", "--2fd1b", "--9823e", "--e68b7", "--d0d96", "--14b57", "--ebd5f", "--a4f2b", "--097e7", "--d4401", "--e9c7d", "--1e967", "--286e7", "--1290c", "--7f952", "--d6061", "--2607e", "--25631", "--df315", "--111ff", "--dd697", "--f6822", "--9b112", "--f1471", "--ff9fc", "--714de", "--b28be", "--33348", "--9ee6e", "--f81ec", "--a8c08", "--0eb94", "--7cf78", "--3366b", "--a43bf", "--7a377", "--28b62", "--69fe1", "--ee762", "--cb980", "--091c0", "--bad61", "--428f4", "--ddee7", "--642a2", "--b379c", "--b2a13", "--62a0f", "--b32a6", "--5bb37", "--04466", "--4d61b", "--91408", "--40a02", "--03300", "--8ada7"};
int cnt, idx;
map<string, int> mp_1;
struct dat
{
int type, tot;
string element[23];
}data[23333];
inline bool find(string now)
{
if (data[mp_1[now]].type == 0)
{
cout << now << " ";
return false;
}
if (data[mp_1[now]].type == 2); // +
{
rg bool flag = false;
for (rg int i = 1; i <= data[mp_1[now]].tot; ++i)
{
if (data[mp_1[data[mp_1[now]].element[i]]].type == 0) continue;
if (data[mp_1[data[mp_1[now]].element[i]]].type == 1 || find(data[mp_1[now]].element[i]))
{
flag = true;
break;
}
}
}
}
int main()
{
freopen("read.txt", "r", stdin);
while (1)
{
rg string now, nowls;
if (cnt == 1002) break;
getline(cin, now);
nowls = "";
for (rg int i = 0; i < 7; ++i) nowls += now[i];
mp_1[nowls] = ++idx; // hash: list -> index
if (now[9] == '0') data[idx].type = 0;
if (now[9] == '1') data[idx].type = 1;
if (now[9] == 'c') // calc
{
rg int len = now.size();
for (rg int i = 10; i < len; ++i)
{
if (now[i] == '-' && now[i + 1] == '-') // find a element
{
rg string tmp = "";
for (rg int j = i; j <= i + 6; ++j) tmp += now[j];
data[idx].element[++data[idx].tot] = tmp;
}
if (now[i] == '+') data[idx].type = 2;
if (now[i] == '*') data[idx].type = 3;
}
}
++cnt;
}
// calc(var(--dd1bd)*var(--28e99)*var(--5588f));
find("--dd1bd"), __endl;
find("--28e99"), __endl;
find("--5588f"), __endl;
return 0;
}
CNSS{8718346957215865968}
0x0? 生瓜蛋子
扔进ida
支持正义花指令,刚好不是很会,就写详细点吧
阅读汇编,一个一个call调用函数跳过去看,发现loc_401619有点东西
把数据块按c转化为代码
这里大概是比较和最后判断正误的区域,发现loc_401725还没看过,跳转一下
经过尝试后(汇编水平不是很够..)发现loc_401725中jz/jnz和loc_401734的jz是花指令,应该是这些语句尝试跳转到一个无法识别的地址,导致反汇编无法继续进行
把他们nop掉,然后再按几下c把剩下的机器码搞成
按P然后f5终于搞定
sub都是很简单的运算函数,Str2可以猜测是我们输入的数据
v3 v4 v5由i得出,但v8是由Str的数据操作而得到的
但由模运算发现v8是0~255的数据,那么考虑直接枚举每个v8对应的Str2
int __cdecl sub_4018E6(int a1)
{
if ( a1 )
return a1 * sub_4018E6(a1 - 1);
else
return 2;
}
int __cdecl sub_40190D(int a1);
int __cdecl sub_40193A(int a1)
{
if ( a1 )
return sub_40190D(a1 - 1) + 3;
else
return 3;
}
int __cdecl sub_40190D(int a1)
{
if ( a1 > 0 )
return sub_40193A(a1 - 1) + a1 + 1;
else
return 1;
}
char str2[233];
int main()
{
freopen("flag.out", "w", stdout);
int v1; // ebx
int v2; // ebx
int v3; // ebx
int v4; // ebx
int v5; // eax
char Str1[33]; // [esp+17h] [ebp-31h] BYREF
int v8; // [esp+38h] [ebp-10h]
int i; // [esp+3Ch] [ebp-Ch]
Str1[0] = -40;
Str1[1] = -34;
Str1[2] = -23;
Str1[3] = 87;
Str1[4] = 26;
Str1[5] = 43;
Str1[6] = 99;
Str1[7] = -81;
Str1[8] = -83;
Str1[9] = -53;
Str1[10] = -20;
Str1[11] = 108;
Str1[12] = -22;
Str1[13] = 58;
Str1[14] = 72;
Str1[15] = 59;
Str1[16] = 42;
Str1[17] = -104;
Str1[18] = 122;
Str1[19] = -95;
Str1[20] = 43;
Str1[21] = -122;
Str1[22] = -125;
Str1[23] = 85;
Str1[24] = -118;
Str1[25] = -34;
Str1[26] = 44;
Str1[27] = 60;
Str1[28] = 37;
Str1[29] = -111;
Str1[30] = -65;
Str1[31] = -13;
Str1[32] = 0;
// v1 = sub_4018E6(Str2[6]);
// v2 = sub_40190D(Str2[18]) + v1;
// v8 = (v2 + sub_40193A(Str2[26])) % 255;
for ( i = 0; i <= 31; ++i )
{
v3 = sub_4018E6(i);
v4 = sub_40190D(i) * v3;
v5 = sub_40193A(i);
Str1[i] ^= ((v4 + v5) % 0xFFFF);
}
for (v8 = 0; v8 <= 255; ++v8)
{
for (i = 0; i <= 31; ++i) putchar(Str1[i] ^ v8);
__endl;
}
}
直接搜索cnss可以得到答案:
cnss{We1Come_To_ReVErze_w0rld!!}
0x0A Baby Exception
摸进加密函数,一眼AES
现在就考虑一下调试拿到data和key就成
ida里面有一堆int 3 和mov eax 0CCCCCh,全nop掉就搞定int 3了
(后面发现Ollydbg好像可以一键绕过所有int 3...)
动调后拿到data = \x96\x7f\x37\x7c\x26\x30\x03\xeb\x61\x6d\xa3\xda\x0c\x77\x3e\x7c\xdf\x18\x5d\x4e\xd9\xbe\x0a\x5c\x02\x36\x87\x37\xb4\x2f\xb1\x9f
key = \x4D\x4C\x57\x4E\x46\x7C\x19\x0A\x4D\x4C\x57\x4E\x46\x7C\x7B\x67
网上扒一个exp跑一跑
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('\0' * add)
return text.encode('utf-8')
def encrypt(text):
key = '\x4D\x4C\x57\x4E\x46\x7C\x19\x0A\x4D\x4C\x57\x4E\x46\x7C\x7B\x67'.encode('utf-8')
mode = AES.MODE_ECB
text = add_to_16(text)
cryptos = AES.new(key, mode)
cipher_text = cryptos.encrypt(text)
return cipher_text
def decrypt(text):
key = '\x4D\x4C\x57\x4E\x46\x7C\x19\x0A\x4D\x4C\x57\x4E\x46\x7C\x7B\x67'.encode('utf-8')
mode = AES.MODE_ECB
cryptor = AES.new(key, mode)
plain_text = cryptor.decrypt(a2b_hex(text))
return bytes.decode(plain_text).rstrip('\0')
data = b'\x96\x7f\x37\x7c\x26\x30\x03\xeb\x61\x6d\xa3\xda\x0c\x77\x3e\x7c\xdf\x18\x5d\x4e\xd9\xbe\x0a\x5c\x02\x36\x87\x37\xb4\x2f\xb1\x9f'
data = b2a_hex(data)
ans = decrypt(data)
print(ans)
(如果你安Crypto库后还是没法跑这个脚本,建议你在site-packages里面把crypto文件夹改成Crypto)
得到flag:
cnss{s3h_v3h_b0th_ez_r1ght?__:)}
[Boss] Trivual Game
tbd..