抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

BlackBird的博客

这世界上所有的不利状况,都是当事者能力不足导致的

说实话,其实谈不上是思考,只能说是一个题目在仔细研究的时候终于理清了一些奇奇怪怪的东西,所以整理一下。不说啥了,RX,yyds

题目是Re学习之RX引路-week1的第三题GWCTF 2019 xxor

前置知识:

  • C语言数据类型大小与范围
类型名称 arch 字节数 位数
char * 1 8
short int * 2 16
int * 4 32
long win 4 32
linux32 4 32
linux64 8 64
long long * 8 64
  • IDA中涉及的一些数据类型:

    我们写个程序在IDA里面看看,这是第一个版本:

    1
    2
    3
    4
    5
    6
    7
    8
    #include<stdio.h>
    int main(){
    int a;
    long b;
    long long c;
    printf("%d\n%ld\n%lld",sizeof(a),sizeof(b),sizeof(c));
    return 0;
    }

    但是IDA的代码直接给我printf(4)……并没有在堆栈里面显示我声明的三个变量,我瞬间就不爽了。

    所以这是第二版:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include<stdio.h>
    int main(){
    int a;
    long b;
    long long c;
    scanf("%d",&a);
    scanf("%ld",&b);
    scanf("%lld",&c);
    printf("%lld\n",a+b+c);
    printf("int %d\n",sizeof(a));
    printf("long %d\n",sizeof(b));
    printf("long long %d\n",sizeof(c));
    return 0;
    }

    我们看看堆栈内容:

    image-20201231125420850

我们看到那里有一个dddq,这里是用来表述数据大小,相似的还有dbdw

标记 全称 大小/字节
db define byte 1
dw define word 2
dd define double word 4
dq define quad word 8

这里的word和计算机的字长不一样,我们所说的计算机字长是指他的总线宽度。所以不同的arch对应的字长是不一样的,而word做为一个单位由于它是来源于16位机,所以当word作为一个空间大小单位的时候,固定为2bytes

Archieve 总线宽度 字长
8086 16位 2
x86 32位 4
x64 64位 8

下面我们可以来看看这个题目了。

题目复现

总述WP

DIE查一下,没什么……拖进IDA64woc惊喜!没扣符号表!!!

找到main函数,美化一下:

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-68h]
int j; // [rsp+Ch] [rbp-64h]
__int64 v6[6]; // [rsp+10h] [rbp-60h] BYREF
__int64 v7[6]; // [rsp+40h] [rbp-30h] BYREF

v7[5] = __readfsqword(0x28u);
puts("Let us play a game?");
puts("you have six chances to input");
puts("Come on!");
v6[0] = 0LL;
v6[1] = 0LL;
v6[2] = 0LL;
v6[3] = 0LL;
v6[4] = 0LL;
for ( i = 0; i <= 5; ++i )
{
printf("%s", "input: ");
a2 = (char **)((char *)v6 + 4 * i);
__isoc99_scanf("%d", a2);
}
v7[0] = 0LL;
v7[1] = 0LL;
v7[2] = 0LL;
v7[3] = 0LL;
v7[4] = 0LL;
for ( j = 0; j <= 2; ++j )
{
dword_601078 = v6[j];
dword_60107C = HIDWORD(v6[j]);
a2 = (char **)&unk_601060;
change(&dword_601078, &unk_601060);
LODWORD(v7[j]) = dword_601078;
HIDWORD(v7[j]) = dword_60107C;
}
if ( (unsigned int)check(v7, a2) != 1 )
{
puts("NO NO NO~ ");
exit(0);
}
puts("Congratulation!\n");
puts("You seccess half\n");
puts("Do not forget to change input to hex and combine~\n");
puts("ByeBye");
return 0LL;
}

思路比较清晰,输入以后change函数处理,check函数比较,出结果。

我们先来看看check函数,美化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
signed __int64 __fastcall check(_DWORD *a1)
{
signed __int64 result; // rax

if ( a1[2] - a1[3] != 0x84A236FFLL || a1[3] + a1[4] != 0xFA6CB703LL || a1[2] - a1[4] != 0x42D731A8LL )
{
puts("Wrong!");
result = 0LL;
}
else if ( *a1 != 0xDF48EF7E || a1[5] != 0x84F30420 || a1[1] != 0x20CAACF4 )
{
puts("Wrong!");
result = 0LL;
}
else
{
puts("good!");
result = 1LL;
}
return result;
}

根据提示,z3解方程:

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
from z3 import *
s=Solver()
x0 = Int('x0')
x1 = Int('x1')
x2 = Int('x2')
x3 = Int('x3')
x4 = Int('x4')
x5 = Int('x5')
s.add(x0==0xDF48EF7E)
s.add(x5==0x84F30420)
s.add(x1==0x20CAACF4)
s.add(x2-x3==0x84A236FF)
s.add(x3+x4==0xFA6CB703)
s.add(x2-x4==0x42D731A8)
if s.check() == sat:
m = s.model()
print(m)
'''
[x2 = 3774025685,
x3 = 1548802262,
x4 = 2652626477,
x1 = 550153460,
x5 = 2230518816,
x0 = 3746099070]
'''

那么相当于知道了flag_enc,再看change函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall change(unsigned int *ini, _DWORD *tables)
{
__int64 result; // rax
unsigned int var; // [rsp+1Ch] [rbp-24h]
unsigned int var_1; // [rsp+20h] [rbp-20h]
int tmp; // [rsp+24h] [rbp-1Ch]
unsigned int i; // [rsp+28h] [rbp-18h]

var = *ini;
var_1 = ini[1];
tmp = 0;
for ( i = 0; i <= 63; ++i )
{
tmp += 0x458BCD42;
var += (var_1 + tmp + 11) ^ ((var_1 << 6) + *tables) ^ ((var_1 >> 9) + tables[1]) ^ 0x20;
var_1 += (var + tmp + 20) ^ ((var << 6) + tables[2]) ^ ((var >> 9) + tables[3]) ^ 0x10;
}
*ini = var;
result = var_1;
ini[1] = var_1;
return result;
}

change函数的三行关键代码进行分析,发现var_1的处理只与此时var的值有关,其余都是常量;var的处理只与此时var_1值有关,其余都是常量。所以直接把加号变成减号就ok了。

然后还有一点需要注意的就是在IDA里面你的数据数据类型是什么,(你把鼠标移动到变量上面就会显示该变量的数据类型)你在写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
#include<bits/stdc++.h>
using namespace std;
int main(){
unsigned int flag_enc[5],flag[5];
flag_enc[0]=3746099070;flag_enc[1]=550153460;flag_enc[2]=3774025685;flag_enc[3]=1548802262;flag_enc[4]=2652626477;flag_enc[5]=2230518816;
unsigned int var[3];
unsigned int tables[4]={2,2,3,4};
for(int i=0;i<5;i+=2){
int tmp = 0x458BCD42*64;
var[0]=flag_enc[i];
var[1]=flag_enc[i+1];
for(int j=0;j<=0x3F;j++){
var[1] -= (var[0] + tmp + 20) ^ ((var[0] << 6) + tables[2]) ^ ((var[0] >> 9) + tables[3]) ^ 0x10;
var[0] -= (var[1] + tmp + 11) ^ ((var[1] << 6) + tables[0]) ^ ((var[1] >> 9) + tables[1]) ^ 0x20;
tmp-=0x458BCD42;
}
flag[i]=var[0];
flag[i+1]=var[1];
}
for(int i=0;i<6;i++)
printf("0x%x\n",flag[i]);
}
/*
0x666c61
0x677b72
0x655f69
0x735f67
0x726561
0x74217d
*/

再用sublime处理一下,用python稍微跑一下:

1
2
3
4
flag=[0x66,0x6c,0x61,0x67,0x7b,0x72,0x65,0x5f,0x69,0x73,0x5f,0x67,0x72,0x65,0x61,0x74,0x21,0x7d]
for i in flag:
print(chr(i),end='')
# flag{re_is_great!}

一些奇奇怪怪的地方

main函数:

image-20201231155258537

输入这个地方我嗯仔细分析一下,占位符%d对应整形int,也就是4个字节;char对应一个字节,也和a2那块的4*i相呼应了。综述这里的v6v7应该都是整形数组,也就是32位,但IDA把他识别成了64位的。我们手动改一下:

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-68h]
int j; // [rsp+Ch] [rbp-64h]
__int32 v6[12]; // [rsp+10h] [rbp-60h] BYREF
__int32 v7[12]; // [rsp+40h] [rbp-30h] BYREF

*(_QWORD *)&v7[10] = __readfsqword(0x28u);
puts("Let us play a game?");
puts("you have six chances to input");
puts("Come on!");
*(_QWORD *)v6 = 0LL;
*(_QWORD *)&v6[2] = 0LL;
*(_QWORD *)&v6[4] = 0LL;
*(_QWORD *)&v6[6] = 0LL;
*(_QWORD *)&v6[8] = 0LL;
for ( i = 0; i <= 5; ++i )
{
printf("%s", "input: ");
a2 = (char **)&v6[i];
__isoc99_scanf("%d", a2);
}
*(_QWORD *)v7 = 0LL;
*(_QWORD *)&v7[2] = 0LL;
*(_QWORD *)&v7[4] = 0LL;
*(_QWORD *)&v7[6] = 0LL;
*(_QWORD *)&v7[8] = 0LL;
for ( j = 0; j <= 4; j += 2 )
{
dword_601078 = v6[j];
dword_60107C = v6[j + 1];
a2 = (char **)&unk_601060;
change(&dword_601078, &unk_601060);
v7[j] = dword_601078;
v7[j + 1] = dword_60107C;
}
if ( (unsigned int)check(v7, a2) != 1 )
{
puts("NO NO NO~ ");
exit(0);
}
puts("Congratulation!\n");
puts("You seccess half\n");
puts("Do not forget to change input to hex and combine~\n");
puts("ByeBye");
return 0LL;
}

这样的话原本有点难理解的LODWORDHIDWORD两个函数也就处理很好了。

这里还有一个问题:image-20201231160034433

再分析change函数的时候,他的第二个参数是unk_601060,如果你直接扣unk_601060的话他是:2,0,0,2,0,0,0,3,0,0,0,4,0,0,0

image-20201231160436992

但是明显他应该是2,2,3,4。这里我们要进一下change函数:

image-20201231161751413

change函数里面的第二个参数声明是_DWORD,也就是4字节一单位,而unk_601060的单位是byte,所以我们要在unk_601060里面按一下D

image-20201231161951206

这样就很舒服了~

评论