说实话,其实谈不上是思考,只能说是一个题目在仔细研究的时候终于理清了一些奇奇怪怪的东西,所以整理一下。不说啥了,RX,yyds
题目是Re学习之RX引路-week1的第三题GWCTF 2019 xxor
前置知识:
C
语言数据类型大小与范围
类型名称 | arch | 字节数 | 位数 |
---|---|---|---|
char |
* | 1 | 8 |
short int |
2 | 16 | |
int |
4 | 32 | |
long |
win |
||
linux32 |
|||
linux64 |
8 | 64 | |
long long |
* |
-
IDA
中涉及的一些数据类型:我们写个程序在
IDA
里面看看,这是第一个版本:#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)
……并没有在堆栈里面显示我声明的三个变量,我瞬间就不爽了。所以这是第二版:
#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; }
我们看看堆栈内容:
我们看到那里有一个dd
,dq
,这里是用来表述数据大小,相似的还有db
、dw
标记 | 全称 | 大小/字节 |
---|---|---|
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
查一下,没什么……拖进IDA64
,woc
惊喜!没扣符号表!!!
找到main
函数,美化一下:
__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
函数,美化:
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
解方程:
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
函数:
__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
的时候数据就用什么数据类型,否则可能会出锅。
#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
稍微跑一下:
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
函数:
输入这个地方我嗯仔细分析一下,占位符%d
对应整形int
,也就是4个字节;char
对应一个字节,也和a2
那块的4*i
相呼应了。综述这里的v6
和v7
应该都是整形数组,也就是32位,但IDA
把他识别成了64位的。我们手动改一下:
__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;
}
这样的话原本有点难理解的LODWORD
和HIDWORD
两个函数也就处理很好了。
这里还有一个问题:
再分析change
函数的时候,他的第二个参数是unk_601060
,如果你直接扣unk_601060
的话他是:2,0,0,2,0,0,0,3,0,0,0,4,0,0,0
但是明显他应该是2,2,3,4
。这里我们要进一下change
函数:
change
函数里面的第二个参数声明是_DWORD
,也就是4字节一单位,而unk_601060
的单位是byte
,所以我们要在unk_601060
里面按一下D
:
这样就很舒服了~