[WUSTCTF2020]level1
查壳,64位无壳,ida打开
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *stream; // ST08_8
signed int i; // [rsp+4h] [rbp-2Ch]
char ptr[24]; // [rsp+10h] [rbp-20h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
stream = fopen("flag", "r");
fread(ptr, 1uLL, 0x14uLL, stream);
fclose(stream);
for ( i = 1; i <= 19; ++i )
{
if ( i & 1 )
printf("%ld\n", (unsigned int)(ptr[i] << i));
else
printf("%ld\n", (unsigned int)(i * ptr[i]));
}
return 0;
}
fopen()函数:
fopen() 是一个 C 标准库函数,用于打开一个文件并返回一个指向该文件的指针。它的声明如下:
FILE *fopen(const char *filename, const char *mode);
其中,
filename
是一个字符串,指定要打开的文件名及其路径,mode
是一个字符串,指定打开文件的模式,它可以是以下值之一:
"r"
: 以只读方式打开文件,文件必须存在。"w"
: 以写入方式打开文件,如果文件不存在,则创建文件;如果文件已存在,则清空文件内容。"a"
: 以追加方式打开文件,如果文件不存在,则创建文件。"r+"
: 以读写方式打开文件,文件必须存在。"w+"
: 以读写方式打开文件,如果文件不存在,则创建文件;如果文件已存在,则清空文件内容。"a+"
: 以读写方式打开文件,如果文件不存在,则创建文件。fopen() 返回一个 FILE 类型的指针,该指针可以用于后续的文件读写操作。如果打开文件失败,fopen() 将返回 NULL。
__readfsqword
是汇编指令,用于读取 FS 段寄存器中存储的 64 位整数。__readfsqword(0x28u)
赋值给变量v7
则表示将 FS 段寄存器中偏移为0x28
的地址处存储的 64 位整数值赋值给变量v7。
fread()
是 C 标准库中的一个函数,用于从文件中读取数据到内存缓冲区中。其函数原型如下:size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
该函数的四个参数分别为:
ptr
:指向要读取数据存储位置的指针。size
:要读取每个数据项的字节数。count
:要读取的数据项数量。stream
:指向 FILE 结构的指针,指示要读取数据的文件。函数返回值是成功读取的数据项数量,如果读取失败则会返回一个小于
count
的值。
fread()
函数可以用于读取二进制文件,如图片、视频、音频等。读取二进制文件时,size
参数通常是数据项的大小,count
参数通常是要读取的数据项数量。ptr
参数是指向存储读取数据的缓冲区的指针。
fclose()
是 C 标准库中的一个函数,用于关闭打开的文件。其函数原型如下:int fclose(FILE *stream);
该函数的参数为指向 FILE 结构的指针,指示要关闭的文件。函数返回值为 0 表示关闭文件成功,返回值为 EOF(-1)则表示关闭文件失败。
fclose()
函数将写入任何挂起的缓冲区数据,释放与文件流相关的所有缓冲区,并关闭该文件。当fclose()
函数调用成功后,再次使用该文件句柄会导致未定义的行为。
在 C 语言中,
<<
和>>
是位运算符,用于对二进制数进行左移和右移操作。
<<
左移运算符将一个二进制数向左移动指定的位数,并在右侧用 0 填充。例如,对于二进制数00000001
,左移 3 位得到的结果是00001000
。左移运算符的语法为:x << n
其中,
x
表示要进行左移操作的二进制数,n
表示要向左移动的位数。
>>
右移运算符将一个二进制数向右移动指定的位数,并在左侧用 0 填充。例如,对于二进制数00001000
,右移 3 位得到的结果是00000001
。右移运算符的语法为:x >> n
其中,
x
表示要进行右移操作的二进制数,n
表示要向右移动的位数。在 C 语言中,位运算符通常用于处理二进制数或者掩码,例如:
- 使用
<<
左移运算符将一个整数的二进制表示向左移动若干位,可以实现乘以 2 的 n 次方的运算。- 使用
>>
右移运算符将一个整数的二进制表示向右移动若干位,可以实现除以 2 的 n 次方的运算。- 使用位运算符可以快速计算掩码,即在二进制数的特定位置置位或清零。
了解完这些,来看一下这段代码的流程
- 打开名为 "flag" 的文件,并读取前 19 个字节的内容;
- 关闭文件;
- 对读取到的 19 个字节进行处理,并输出处理结果; a. 对于第奇数个字节,将其左移 i 位,并将结果作为无符号整数输出; b. 对于第偶数个字节,将其乘以 i,并将结果作为无符号整数输出。
- 有一点要注意,if(i&1)是对变量i进行按位与1运算,并将结果作为条件表达式的值进行判断。由于二进制数1的最低位为1,其它位都是0,因此,i&1的结果就是i的二进制表示中最低位的值。如果i的最低位为1,则if(i&1)的条件表达式为真,否则为假。一般情况下,该表达式用来作判断奇数或者偶数。
然后查看题目给出的另一个文件,名为output,那应该就是输出了:
198
232
816
200
1536
300
6144
984
51200
570
92160
1200
565248
756
1474560
800
6291456
1782
65536000
写代码:
key = [198, 232, 816, 200, 1536, 300, 6144, 984, 51200,
570, 92160, 1200, 565248, 756, 1474560, 800, 6291456,
1782, 65536000]
for i in range(1, 20):
if i % 2 == 1:
print(chr(key[i-1] >> i), end='')
else:
print(chr(key[i-1] // i), end='')
结果ctf2020{d9-dE6-20c}
[GUET-CTF2019]re
查壳
对于这种upx壳,直接使用工具就可以。
通过字符串窗口找到可以字符,进入主要函数,
__int64 sub_400E28()
{
__int64 result; // rax
__int64 v1; // [rsp+0h] [rbp-30h]
__int64 v2; // [rsp+8h] [rbp-28h]
__int64 v3; // [rsp+10h] [rbp-20h]
__int64 v4; // [rsp+18h] [rbp-18h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
v1 = 0LL;
v2 = 0LL;
v3 = 0LL;
v4 = 0LL;
sub_40F950((unsigned __int64)"input your flag:");
sub_40FA80((unsigned __int64)"%s");
if ( (unsigned int)sub_4009AE((char *)&v1) )
sub_410350("Correct!");
else
sub_410350("Wrong!");
result = 0LL;
if ( __readfsqword(0x28u) != v5 )
sub_443550();
return result;
}
进入这个可疑函数sub_4009AE
_BOOL8 __fastcall sub_4009AE(char *a1)
{
if ( 1629056 * *a1 != 166163712 )
return 0LL;
if ( 6771600 * a1[1] != 731332800 )
return 0LL;
if ( 3682944 * a1[2] != 357245568 )
return 0LL;
if ( 10431000 * a1[3] != 1074393000 )
return 0LL;
if ( 3977328 * a1[4] != 489211344 )
return 0LL;
if ( 5138336 * a1[5] != 518971936 )
return 0LL;
if ( 7532250 * a1[7] != 406741500 )
return 0LL;
if ( 5551632 * a1[8] != 294236496 )
return 0LL;
if ( 3409728 * a1[9] != 177305856 )
return 0LL;
if ( 13013670 * a1[10] != 650683500 )
return 0LL;
if ( 6088797 * a1[11] != 298351053 )
return 0LL;
if ( 7884663 * a1[12] != 386348487 )
return 0LL;
if ( 8944053 * a1[13] != 438258597 )
return 0LL;
if ( 5198490 * a1[14] != 249527520 )
return 0LL;
if ( 4544518 * a1[15] != 445362764 )
return 0LL;
if ( 3645600 * a1[17] != 174988800 )
return 0LL;
if ( 10115280 * a1[16] != 981182160 )
return 0LL;
if ( 9667504 * a1[18] != 493042704 )
return 0LL;
if ( 5364450 * a1[19] != 257493600 )
return 0LL;
if ( 13464540 * a1[20] != 767478780 )
return 0LL;
if ( 5488432 * a1[21] != 312840624 )
return 0LL;
if ( 14479500 * a1[22] != 1404511500 )
return 0LL;
if ( 6451830 * a1[23] != 316139670 )
return 0LL;
if ( 6252576 * a1[24] != 619005024 )
return 0LL;
if ( 7763364 * a1[25] != 372641472 )
return 0LL;
if ( 7327320 * a1[26] != 373693320 )
return 0LL;
if ( 8741520 * a1[27] != 498266640 )
return 0LL;
if ( 8871876 * a1[28] != 452465676 )
return 0LL;
if ( 4086720 * a1[29] != 208422720 )
return 0LL;
if ( 9374400 * a1[30] == 515592000 )
return 5759124 * a1[31] == 719890500;
return 0LL;
}
大概看一眼,少了第六个元素,16和17是换了一下,写代码的时候要注意一下
大概就是将一个列表,也就是flag的每一位进行加密。
不得不说,头一次觉得复制粘贴这么麻烦。。。
a = [166163712, 731332800, 357245568, 1074393000, 489211344, 518971936, 406741500, 294236496, 177305856, 650683500,
298351053, 386348487, 438258597, 249527520, 445362764, 981182160, 174988800, 493042704, 257493600,
767478780, 312840624, 1404511500, 316139670, 619005024, 372641472, 373693320, 498266640, 452465676,
208422720, 515592000, 719890500]
b = [1629056, 6771600, 3682944, 10431000, 3977328, 5138336, 7532250, 5551632, 3409728, 13013670, 6088797,
7884663, 8944053, 5198490, 4544518, 10115280, 3645600, 9667504, 5364450, 13464540, 5488432, 14479500,
6451830, 6252576, 7763364, 7327320, 8741520, 8871876, 4086720, 9374400, 5759124]
flag = []
temp = ''
for i in range(len(a)):
flag.append(a[i] // b[i])
flag[i] = chr(flag[i])
for i in range(10):
uu = flag[:]
uu.insert(6, str(i))
print(''.join(uu), end='')
print('')
输出结果
挨个试, flag{e165421110ba03099a1c039337}
[MRCTF2020]Transform
查壳64无壳找到main函数
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
__int64 v4; // rdx
char v6[104]; // [rsp+20h] [rbp-70h]
int j; // [rsp+88h] [rbp-8h]
int i; // [rsp+8Ch] [rbp-4h]
sub_402230();
sub_40E640(argc, (__int64)argv, v3, (__int64)"Give me your code:\n");
sub_40E5F0(argc, (__int64)argv, (__int64)v6, (unsigned __int64)"%s");
if ( strlen(*(const char **)&argc) != 33 )
{
sub_40E640(argc, (__int64)argv, v4, (__int64)"Wrong!\n");
system(*(const char **)&argc);
exit(argc);
}
for ( i = 0; i <= 32; ++i )
{
byte_414040[i] = v6[dword_40F040[i]];
v4 = i;
byte_414040[i] ^= LOBYTE(dword_40F040[i]);
}
for ( j = 0; j <= 32; ++j )
{
v4 = j;
if ( byte_40F0E0[j] != byte_414040[j] )
{
sub_40E640(argc, (__int64)argv, j, (__int64)"Wrong!\n");
system(*(const char **)&argc);
exit(argc);
}
}
sub_40E640(argc, (__int64)argv, v4, (__int64)"Right!Good Job!\n");
sub_40E640(argc, (__int64)argv, (__int64)v6, (__int64)"Here is your flag: %s\n");
system(*(const char **)&argc);
return 0;
}
然后又找到这两个:
在计算机编程中,8 dup(0) 表示申请 8 个字节(或者 8 个元素)的连续内存空间,并将该空间的值初始化为 0。这是一种数组或者缓冲区的定义方式,通常用于存储一定数量的数据或者字符,比如:
char buffer[8] = {0}; // 等价于 char buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0};
该语句申请 8 个 char 类型的元素的内存空间,并将它们初始化为 0。在某些编程语言或者汇编语言中,使用 dup 语句可以重复生成一定数量的相同元素,可以用来快速生成一定数量的数组或者缓冲区。
main函数其实只有中间的一个for有用,写代码
sub1 = [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]
sub2 = [103, 121, 123, 127, 117, 43, 60, 82, 83, 121, 87, 94, 93, 66, 123, 45,
42, 102, 66, 126, 76, 87, 121, 65, 107, 126, 101, 60, 92, 69, 111, 98, 77]
temp = []
flag = []
for i in range(33):
temp.append(sub1[i] ^ sub2[i])
flag.append(0)
for i in range(33):
flag[sub1[i]] = temp[i]
for i in flag:
print(chr(i), end='')
要注意flag原本是空的,如果不append就会报错
MRCTF{Tr4nsp0sltiON_Clph3r_1s_3z}