merak-ctf 3/27 18:00 - 3/29 22:00
前言:一个萌新re的wp

PixelShooter

这个题最简单了,GG修改分数死了就直接出flag

flag:MRCTF{Unity_1S_Fun_233}

因为没有接触过安卓逆向,大佬都是直接把APK丢进反编译软件里的,只能用这种费力的方式了

该题官方下载链接


lualu

这个题先翻了一下脚本发现看不懂,所以在线重写一遍

直接丢进IDA,提示用IDA64,那就是64位程序了

丢进IDA64加载完后翻了一下,卧槽居然没有main

不过不急shift+F12,顺便打开程序看一下

ctrl+F搜到了那个字符串

双击点进去,并按x查看引用

看到了这些汇编,不过咱也看不懂还是直接F5

一顿操作以后

这咋办呐,不急我们走动态看看

一样的做法先搜索字符串,定位关键代码

本来想找他的scanf,然后跟自己输入的假码,在他上面说的字符串之后下了断点,结果就发现了这个,分析如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cmps={83,80,73,80,76,125,61,96,107,85,62,63,121,122,101,33,123,82,101,114,54,100,101,97,85,111,39,97}
print("Give Me Your Flag LOL!:")
flag=io.read()
if string.len(flag)~=28 then [[~是异或]]
print("Wrong flag!")
os.exit()
end
for i=1,string.len(flag) do
local x=string.byte(flag,i)
if i%2==0 then [[第偶数个字符a[i]^=i 这里直接再^一次i就可以了]]
x=x~i
else
x=x+6 [[不是偶数个则是奇数个a[i]+=6 逆向就是a[i]-=6]]
end
if x~=cmps[i] then [[处理完之后与上面给的字符串相比较]]
print("Wrong flag!")
os.exit()
end
end
print("Right flag!")
os.exit()

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main()
{
char a[]={0,83,80,73,80,76,125,61,96,107,85,62,63,121,
122,101,33,123,82,101,114,54,100,101,97,85,111,39,97,0};
for(int i=1;a[i];i++)
{
if(i%2==0)
{
a[i]^=i;
}
else
{
a[i]-=6;
}
}
printf("%s",(char*)a+1);
}

flag:MRCTF{7he_83st_1u@_f0r_yOu!}

之后我尝试在内存里找到运行逻辑,失败告终,然后跑去问出题人,出题人说这个题目就是lua lu,内嵌了一个lua解释器(那么我可以改内存来达到盗用他的lua解释器!),大概就是不让那些喜欢静态调试的人好过吧

该题官方下载链接


Transform

好了这题咱不吃上一题的亏 直接丢进x64dbg(x32dbg提示我的)

丢进去以后老规矩搜索字符串,定位关键点,在Give me your code下面发现strlen函数并且cmp 0x21

过了len的判断 再往下跟就到把input一顿xx的地方

怎么xx的先不管 再往下看看

一个一个cmp 不是就exit 很好这就是被xx过后的input 那么我们拿它来反xx就可以得到flag了

好了我摊牌了,听说re爷爷看汇编跟看小说一样,我…我是re孙子,咱们上神器IDA_F5

这次居然有main,要是没有main的话直接取后面的动态调试的地址ctrl+g过去找近的函数F5就OK了,要是被aslr了那就还是shift+f12找字符串过去吧,最后直接上exp,话说前面那个算法我分析了好久,以为我没弄错,是f5的错(f5经常抽风),害,我好菜,我菜死了

分析如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for ( i = 0; i <= 32; ++i )
{
input[i] = input_[key_1[i]];//将input第key_1[i]位置的放到key_2[i],
//那么要把key_2[i]放到flag第key_1[i]位置
v4 = i;
input[i] ^= LOBYTE(key_1[i]);//再 ^= key_1[i]
} //LOBYTE相当于取变量的最低byte位来赋值
for ( j = 0; j <= 32; ++j )
{
v4 = j;
if ( key_2[j] != input[j] )//转换后与key_2比较
{
sub_40E640(argc, (__int64)argv, j, (__int64)"Wrong!\n");
system(*(const char **)&argc);
exit(argc);
}
}

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main()
{
char key_1[]={9,10,15,23,7,24,12,6,1,16,3,17,32,29,11,30,
27,22,4,13,19,20,21,2,25,5,31,8,18,26,28,14,0};//本来他是int但是没必要用了
char key_2[]="gy{\x7Fu+<RSyW^]B{-*fB~LWyAk~e<\\EobM";
char flag[34]={0};
for(int i=32;i>=0;i--)
{
flag[key_1[i]] = key_2[i];
flag[key_1[i]] ^= key_1[i];
}
printf("%s",flag);
}

flag:MRCTF{Tr4nsp0sltiON_Clph3r_1s_3z}

该题官方下载链接


Junk

拿到题目先丢进ida,shift+f12看到了 “give me your flag!“ ,然后交叉引用,然后f5,然后欸?怎么f5不了?f5报错了居然!不急不急,按空格切换视图,记下左边的地址,401390

然后咱把它丢进od,ctrl+g跳到这个地方,再一顿改名分析,发现了关键call

enter进去看看

好了len出来了,往下继续看

这个call,噫,有点意思,call指令分为两步,先push下一条指令的地址入栈,然后jmp去一个地方,这里过来直接把入栈的地址加了一,后面retn就是到了之前入栈的下一个地址的+1的地方,意思就是说下面那里根本不是一个call,前面那一位E8是根本不会走的,那么我们把它nop掉,这样就能看见了

这里说一下怎么nop他,在下面的数据窗口ctrl+g输入401256即那个call的地址,再双击第一个E8改成90就是nop了

这样逻辑很清晰了,这里才几句用不着看F5也可以知道这里的算法是把每一位都^3

异或完了可以看见他直接跳过了这里,那么我们就可以料想这两个call一点P用都没有,再往下看看

什么?je跳过去的线断了?其实并不是,只是od把这里以为是个call其实他是个屁call,他直接je到了401280,而这个call的地址是40127F,也就是说这里是一个假call,那咱再把他nop掉

因为上面那个xor,zf标志寄存器一定会为1,je一定会跳过,那么我们直接把je也一块nop了,下面也有一个一毛一样的结构,于是我顺手就nop完了,好了我们现在下面就是剩下的程序了,但是菜鸡看不懂汇编呢,现在能f5了吗?试试看?右键->复制到可执行文件->所有修改,文件->右键->保存文件

我们来对比一下

之前是这个样子的,点进去并不能F5

之后是这个样子的,点进去可以F5

分析一波如下

game3分析如下

逆向算法思路:从key反推是table几,保存起来,在反base64算法,再反game2,最后反game1,完事

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>

int main()
{
char table[]={0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,
0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,
0x56,0x57,0x58,0x59,0x5A,0x61,0x62,0x63,0x64,0x65,0x66,
0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,
0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x29,0x21,
0x40,0x23,0x24,0x25,0x5E,0x26,0x2A,0x28,0x2B,0x2F};
char key[]={0x25,0x42,0x55,0x45,0x64,0x56,0x53,0x48,0x6C,0x6D,
0x66,0x57,0x68,0x70,0x5A,0x6E,0x21,0x6F,0x61,0x57,0x5A,
0x28,0x61,0x47,0x42,0x73,0x5A,0x40,0x5A,0x70,0x5A,0x6E,
0x21,0x6F,0x61,0x57,0x5A,0x28,0x61,0x47,0x42,0x73,0x5A,
0x40,0x5A,0x70,0x5A,0x6E,0x21,0x6F,0x59,0x47,0x78,0x6E,
0x5A,0x6D,0x25,0x77,0x2E,0x2E};
unsigned char flag[60]={0};
int temp[60]={0};
for(int i=0;key[i];i++)//这一步是从key反推table里的经过仿base64的input
{
for(int j=0;table[j];j++)
if(key[i]==table[j])
{
temp[i]=j;
break;
}
}
for(int i=0,j=0;i<43;i+=3,j+=4)//这里是反仿base64,在纸上写两下再用计算器转换二进制就能看明白
{
flag[i] = ((temp[j] & 0x3F) << 2) + ((temp[j+1] & 0x30) >> 4);
flag[i+1] = ((temp[j+1] & 0xF) << 4) + ((temp[j+2] & 0x3C) >> 2);
flag[i+2] = ((temp[j+2] & 3) << 6) + (temp[j+3] & 0x3F);
}//不过当时这里卡了我挺久菜鸡还是菜啊
/* for(int i=0;i<45;i++)//我们甚至可以看到补上的那两个0
{
printf("%+02X ",flag[i]);
if((i+1)%9==0)
printf("\n");
}*/
for(int i=0;i<43;i++)
{
flag[i] = flag[i] % 16 * 16 + flag[i] / 16;//反game2,单字节十六进制换位置
flag[i] ^= 3;//反game1
}
printf("%s",flag);
}

flag:MRCTF{junkjunkjunkcodejunkjunkcodejunkcode}

该题官方下载链接


shit

丢进IDA里容易发现这个,不过呢这里点过去不能F5,因为他做了混淆,我们动态跑进去把他混淆的地方nop掉

image-20200401114455599

然后右键补丁,效果如下

反汇编分析如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
char __cdecl sub_4012F0(const char *input)
{
unsigned int v1; // ST20_4
int v2; // ST20_4
int v4; // [esp+14h] [ebp-14h]
signed int i; // [esp+1Ch] [ebp-Ch]
int v6; // [esp+20h] [ebp-8h]

v4 = 0;
for ( i = 0; i < strlen(input); i += 4 )
{
v1 = input[i + 3] | (input[i + 2] << 8) | (input[i + 1] << 16) | (input[i] << 24);
// 4个char的input放到一个unsigned int里
v2 = (v1 << (32 - *(&key_1 + 4 * (i / 4)))) | (v1 >> *(&key_1 + i / 4));
// 循环右移key_1[i/4]位 这里key_1有问题,运行之后会被改变,动调把他取下来
sub_4013CA();// p用没有
v6 = ((v2 << 16) | ~HIWORD(v2)) ^ (1 << *(&key_1 + i / 4));
// 前四位后四位换位置并把前四位取反之后^= 1<<key_1[i/4]
sub_40140F();// 把那里面两个call nop掉,这里就看不见了,不信你试试
if ( i > 0 )
v6 ^= v4;// 再^=上一个v6 如果有的话
v4 = v6;
if ( v6 != key_2[i / 4] )// 此时v6=key_2[i/4]
return 0;
}
return 1;
}

逆向思路:

  1. key_2 ^=上一个key_2(如果有的话),
  2. 再拿结果 ^=1<<key_1[i/4]后四位取反,再把前后4位换位置,
  3. 循环左移key_1[i/4]位,这个时候的结果转换成char输出就是flag了

完整exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#define rol( a , o ) \
((a<<(o%0x20)) | (a>>(0x20 - (o%0x20))))
#define ror( a , o ) \
((a>>(o%0x20)) | (a<<(0x20 - (o%0x20))))

int main()
{
int key1[]={0x3,0x10,0xD,0x4,0x13,0xB};
int key2[]={0x8C2C133A,0xF74CB3F6,0xFEDFA6F2,0xAB293E3B,0x26CF8A2A,0x88A1F279};
unsigned int v1,v2,v3,v4;//unsigned特别重要
char input[]={0x66,0x6C,0x61,0x67,0x7B,0x61,0x5F,0x33,0x61,0x32,0x79,
0x5F,0x72,0x65,0x5F,0x66,0x6F,0x72,0x5F,0x74,0x65,0x73,0x74,0x7D,0};
for(int i=0;i<6;i++)
{
v3 = key2[i];
if(i)
v3 ^= key2[i-1];
v3 ^= (1 << key1[i]);
v2 = ((v3& 0xFFFF0000) >> 16) + (((~v3) & 0xFFFF) << 16);//先取反后取位,不然错
//v1 = rol(v2,key1[i]);
v1 = (v2 >> (32 - key1[i])) | (v2 << key1[i]);
printf("%c%c%c%c",*((char*)&v1+3),*((char*)&v1+2),*((char*)&v1+1),*(char*)&v1);
}
}

flag:flag{a_3a2y_re_for_test}

官方下载链接


撒花完结~这次就写了这么几个题,主要是因为IDA里的反汇编太不熟悉了,以及对基本算法逆向的不熟悉,需要多多做题,多多写wp


virtual_tree

(再补一道题)

这个题动态静态双管齐下

大体结构是这样的

第一个函数进去乍一看以为是个算法,仔细一看发现根本不会走,判断直接跳过了,然后len=16

这个树着实麻烦,不过关键代码只有一句,在这里下断点跑一次就可以拿到所有的数据

这个game其实挺简单的,不过这是经过处理之后的如果是直接静态来看,会直接懵B

作者坏坏的把名字改了,而且还有一点混淆(作者说这是编译器的问题)

具体处理方法是这样的,不过其实没有必要,动态走一遍记录一下跑的地方和参数也是一样的,主要是迷惑性很强

exp及部分解析唠叨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>

char key1[]={0x17,0x63,0x77,0x3,0x52,0x2E,0x4A,0x28,0x52,0x1B,0x17,0x12,0x3A,0x0A,0x6C,0x62};

void uadd(char a,char b)
{
key1[b]-=a;
}

void uxor(char a,char b)
{
key1[b]^=key1[a];
}

void usub(char a,char b)
{
/*//没绝对值处理过优先级更高
if(key1[b]+key1[a]<0x20&&key1[b]+key1[a]>127)
key1[b]=-key1[b]+key1[a];
else key1[b]=key1[b]+key1[a];
*/
/*//被绝对值处理过优先级更高
if(-key1[b]+key1[a]<0x20&&-key1[b]+key1[a]>127)
key1[b]=key1[b]+key1[a];
else key1[b]=-key1[b]+key1[a];
*/
/*
key1[b]=key1[b]+key1[a];//一律当作没做过绝对值处理
*/
key1[b]=-key1[b]+key1[a];//一律当作做过绝对值处理
}//这个反绝对值恶心死我了,多解
//A 0 2 1 7 2 7 3 5 4 1 6 3 7 7 8 8 9 7 A C B 2 C F E 2 F
//ADD XOR ADD SUB XOR SUB ADD XOR SUB SUB XOR SUB XOR ADD
//A[0]+A
//A[1]^A[2]
//A[3]=abs(A[3]-A[7])

int main()
{
char input[17]={0};
uadd(2,0xf);
uxor(0xf,0xe);
usub(2,0xc);
uxor(0xc,0xb);
usub(7,0xa);
usub(8,9);
uxor(7,8);
uadd(3,7);
usub(1,6);
uxor(5,4);
usub(7,3);
uadd(7,2);
uxor(2,1);
uadd(0xa,0);
char key2[]={0x4D,0x4C,0x47,0x50,0x4F,0x4B,0x46,0x43,0x4A,0x45,0x4E,0x49,0x48,0x44,0x42,0x41}; //这是跑一遍出来的异或的数据
for(int i=0;i<16;i++)
input[i]=key1[i]^key2[i];
printf("%s",input);
}

flag:flag{@_7r3e_f0r_fuNN!}

文章作者: Usher
文章链接: https://usher2008.github.io/2020/04/01/merak-ctf/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Usher