目录
Base
IKUN检查器
dnSpy
junk code
Cheat Engine工具使用:
奇怪的ELF
mov混淆问题:
Xor
打开附件,可以看到主函数
先是给出一个物理题,要求输入答案,这个无关紧要,接着要求输入一串字符,经过encode_1和encode_2的加密过程后,要求等于enflag。直接进入encode_1和encode_2查看
encode_1:
char __cdecl encode_1(char *flag, int num)
{
size_t v2; // rax
int i; // [rsp+2Ch] [rbp-54h]
for ( i = 0; ; ++i )
{
v2 = strlen(flag);
if ( i >= v2 )
break;
if ( flag[i] <= 64 || flag[i] > 90 )
{
if ( flag[i] > 96 && flag[i] <= 122 )
flag[i] = (flag[i] - 97 + num) % 26 + 97;
}
else
{
flag[i] = (flag[i] - 65 + num) % 26 + 65;
}
}
return v2;
}
很明显的凯撒加密,偏移量是num,传进去的参数可以知道num的值为3.
再进入encode_2查看
char __cdecl encode_2(char *str, int len, char *str1)
{
int v3; // eax
int v4; // eax
int v5; // eax
int v6; // r8d
int v7; // eax
int v8; // r8d
int v9; // eax
int v10; // eax
int v11; // eax
int v12; // r8d
int v13; // eax
int v14; // eax
int v15; // eax
int v16; // eax
char base64[65]; // [rsp+20h] [rbp-60h] BYREF
char *flag; // [rsp+68h] [rbp-18h]
int encodeStrLen; // [rsp+74h] [rbp-Ch]
int k; // [rsp+78h] [rbp-8h]
int i; // [rsp+7Ch] [rbp-4h]
strcpy(base64, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
encodeStrLen = 4 * (len / 3) + 1;
k = 0;
if ( len % 3 )
v3 = 4;
else
v3 = 0;
encodeStrLen += v3;
flag = malloc(encodeStrLen);
for ( i = 0; i < len; ++i )
{
if ( len - i <= 2 )
{
v10 = k++;
if ( len - i == 2 )
{
flag[v10] = base64[str[i] >> 2];
v11 = k++;
v12 = 16 * (str[i++] & 3);
flag[v11] = base64[v12 | (str[i] >> 4)];
v13 = k++;
flag[v13] = base64[4 * (str[i] & 0xF)];
}
else
{
flag[v10] = base64[str[i] >> 2];
v15 = k++;
flag[v15] = base64[16 * (str[i] & 3)];
v16 = k++;
flag[v16] = 61;
}
v14 = k++;
flag[v14] = 61;
}
else
{
v4 = k++;
flag[v4] = base64[str[i] >> 2];
v5 = k++;
v6 = 16 * (str[i++] & 3);
flag[v5] = base64[v6 | (str[i] >> 4)];
v7 = k++;
v8 = 4 * (str[i++] & 0xF);
flag[v7] = base64[v8 | (str[i] >> 6)];
v9 = k++;
flag[v9] = base64[str[i] & 0x3F];
}
}
flag[k] = 0;
return strcpy(str1, flag);
}
一个base64加密
最后将加密后的数据与enflag进行比较,解密过程就是先将enflag进行base64解码,然后再将得到的值进行凯撒解密,偏移量为3最后得到flag。
打开附件,可以发现是c#编写的程序,c#程序需要使用dnSpy打开
可以直接从官网上下载
打开可以看到主函数
// WindowsFormsApp1.From1
// Token: 0x06000006 RID: 6 RVA: 0x00002074 File Offset: 0x00000274
private void button1_Click(object sender, EventArgs e)
{
string text = this.textBox1.Text;
bool flag = text.Length != 34;
if (flag)
{
this.label1.Text = "你不是真ikun";
}
else
{
string a = text.Substring(0, 8);
string a2 = text.Substring(8, 1);
string a3 = text.Substring(9, 10);
string b = text.Substring(19, 1);
string a4 = text.Substring(20, 14);
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
num = this.check1(a, num);
num2 = this.check2(a3, num2);
num3 = this.check3(a2, b, num3);
num4 = this.check4(a4, num4);
bool flag2 = num + num2 + num3 + num4 == 4;
if (flag2)
{
this.check5(text);
}
}
}
先判断flag长度是否等于34,然后会对输入的字符串分段分别经过check1,2,3,4函数进行检测,都通过时使用check5输出
check1:
public int check1(string a, int checkbox)
{
string value = "3eabbb3900d553d7e98a154bffa4d24a";
char[] array = a.ToCharArray();
MD5 md = new MD5CryptoServiceProvider();
byte[] bytes = Encoding.Default.GetBytes(a);
byte[] array2 = md.ComputeHash(bytes);
md.Clear();
string text = "";
for (int i = 0; i < array2.Length; i++)
{
text += array2[i].ToString("x").PadLeft(2, '0');
}
bool flag = text.Equals(value);
if (flag)
{
this.label1.Text = "哇,珍德食你鸭!";
checkbox = 1;
}
else
{
this.label1.Text = "你干嘛,嗨嗨呦!";
checkbox = 0;
}
return checkbox;
}
是一个md5加密,直接爆破解密,得到 1998-8-2
check2:
// Token: 0x0600000C RID: 12 RVA: 0x00002590 File Offset: 0x00000790
public int check2(string a, int checkbox)
{
string value = "ce143362813587af5af8592984c91b5a8c11b779";
string text = "";
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] bytes = Encoding.Default.GetBytes(a);
byte[] array = sha.ComputeHash(bytes);
sha.Clear();
for (int i = 0; i < array.Length; i++)
{
text += array[i].ToString("x").PadLeft(2, '0');
}
bool flag = text.Equals(value);
if (flag)
{
this.label1.Text = "哇,珍德食你鸭!";
checkbox = 1;
}
else
{
this.label1.Text = "你干嘛,嗨嗨呦!";
checkbox = 0;
}
return checkbox;
是一个sha1加密,解密得到 jinitaimei
check3:
// Token: 0x0600000D RID: 13 RVA: 0x0000264C File Offset: 0x0000084C
public int check3(string a, string b, int checkbox)
{
string value = "-";
bool flag = a.Equals(b) && a.Equals(value);
if (flag)
{
this.label1.Text = "哇,珍德食你鸭!";
checkbox = 1;
}
else
{
this.label1.Text = "你干嘛,嗨嗨呦!";
checkbox = 0;
}
return checkbox;
}
直接判断是一个 '-'
check4:
// Token: 0x0600000E RID: 14 RVA: 0x000026AC File Offset: 0x000008AC
public int check4(string a, int checkbox)
{
string text = "7vKrvRh4Gu7wBQn7o9VfKQ==";
byte[] bytes = Encoding.UTF8.GetBytes("pijinchengwangbankunyuanhang!!!!");
byte[] bytes2 = Encoding.UTF8.GetBytes(a);
ICryptoTransform cryptoTransform = new RijndaelManaged
{
Key = bytes,
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
}.CreateEncryptor();
byte[] array = cryptoTransform.TransformFinalBlock(bytes2, 0, bytes2.Length);
string value = Convert.ToBase64String(array, 0, array.Length);
bool flag = text.Equals(value);
if (flag)
{
this.label1.Text = "哇,珍德食你鸭!";
checkbox = 1;
}
else
{
this.label1.Text = "你干嘛,嗨嗨呦!";
checkbox = 0;
}
return checkbox;
}
是一个AES加密.ECB,PKCS7,直接解密,得到:xiaoheizishiba
直接输入,得到flag(没想到直接输入....)
看wp知道是使用cheat engine工具
方法二:直接使用ida动态调试
在ida中打开文件,动态调试
注意:一定要在此处,因为此处是字符串经过异或运算后的flag值
得到flag
看了wp才知道是mov 混淆了
MOV这种混淆是怎样产生的呢?剑桥大学的Stephen Dolan证明了x86的mov指令可以完成几乎所有功能了(可能还需要jmp),其他指令都是“多余的”。受此启发,有个大牛做了一个虚拟机加密编译器。它是一个修改版的LCC编译器,输入是C语言代码,输出的obj里面直接包含了虚拟机加密后的代码。如它的名字,函数的所有代码只有mov指令,没有其他任何指令。
这种题目的特征就是:汇编代码的汇编指令几乎全部就是MOV
1、 字符串的搜索是给我们最好的提示。
2、 MOV混淆是不会混淆函数的逻辑的。因此函数的逻辑还是不变的。
3、 大多数汇编代码的意思是可以猜测的。可以大概推测出具体操作了什么
具体mov混淆问题可以看:movfuscator混淆_Cherest_San的博客-CSDN博客
和这篇文章:浅析CTF中的反静态调试(二) - 先知社区
观察代码可以发现,flag的指令存储在R2中,直接手动搜索得到flag,search->text->R2。
直接搜索字符串,找到可疑字符串
设置断点,动态调试
一路F8,可以看到Please input flag,下面的异或部分
在此处创建函数
可以看到函数的主逻辑
__int64 v8; // rdi
__int64 v9; // rax
__int64 i; // rcx
_QWORD *v11; // rax
__int64 v13; // [rsp-1C8h] [rbp-1D8h]
__int64 v14; // [rsp-1C0h] [rbp-1D0h]
unsigned __int64 v15; // [rsp-1A0h] [rbp-1B0h]
__int64 v16; // [rsp-198h] [rbp-1A8h]
__int64 v17; // [rsp-190h] [rbp-1A0h]
__int64 v18; // [rsp-188h] [rbp-198h]
__int64 v19; // [rsp-180h] [rbp-190h]
_QWORD v20[34]; // [rsp-158h] [rbp-168h]
__int64 v21; // [rsp-48h] [rbp-58h]
const char *v22; // [rsp-38h] [rbp-48h]
_QWORD *v23; // [rsp-30h] [rbp-40h]
void *v24; // [rsp-28h] [rbp-38h]
char **v25; // [rsp-20h] [rbp-30h]
while ( 1 )
{
v19 = a6;
v15 = a4;
v20[32] = v6;
sub_81A060();
v8 = sub_819800();
a4 = v6;
v6 = v8;
if ( v19 + 1 >= v16 )
break;
a6 = v19 + 1;
if ( v19 + 1 - ((v19 + 1) & 0xFFFFFFFFFFFFFFFCLL) >= 4 )
sub_82EB60(v13, v14);
}
v21 = sub_819DA0();
v17 = v8;
*(&v13 - 2) = v7;
v9 = sub_82F0E0();
for ( i = 0LL; i < 32; ++i )
{
if ( v6 <= i )
sub_82EB60(v13, v14);
if ( v20[i] != *(unsigned __int8 *)(v9 + i) )
{
v18 = i;
v14 = sub_85F300(v13);
v9 = v21;
i = v18;
v6 = v17;
}
}
v24 = &unk_875580;
v25 = &off_8ABBB0;
sub_861F60();
v11 = (_QWORD *)sub_7DC740();
*v11 = 0LL;
v22 = "\b";
v23 = v11;
return sub_867420();
}
函数有两个循环,第一个循环时加密过程,第二个循环是对加密的明文进行判断,所以我们需要动态调试,过第一个循环,在第二个循环中
在此处先断点,随便输入什么跳过第一个循环,F8到第二个循环,拿到密文
写脚本解密
#include
#include
#include
int main(){
int i;
char arr[]={0x3f,0x12,0x0,0x2,0x4,0xc,0x3d,0x42,0x3,0x28,0xc,0x1,0x2e,0x12,0x4,0x1,0x8,0x28,0x54,0x40,0x42,0x43,0x54,0x40,0x42,0x43,0x54,0x40,0x42,0x43,0x50,0xf};
char key[]="qwer";
for(i=0;i<40;i++){
arr[i]=arr[i]^key[i%4];
printf("%c",arr[i]);
}
return 0;
}
//Neepu{X0r_is_easy_1234123412345}