re_signin 【ctfshow_元旦水友赛】Reserve
题目:
IDA反编译
主函数
1 int __cdecl main(int argc, const char **argv, const char **envp) 2 { 3 char s2[144]; // [rsp+0h] [rbp-1A0h] BYREF 4 char s1[128]; // [rsp+90h] [rbp-110h] BYREF 5 char input[136]; // [rsp+110h] [rbp-90h] BYREF 6 int key; // [rsp+198h] [rbp-8h] 7 int input_len; // [rsp+19Ch] [rbp-4h] 8 9 memset(input, 0, 0x80uLL); 10 memset(s1, 0, sizeof(s1)); 11 memset(s2, 0, sizeof(s2)); 12 s2[0] = 0x9C; 13 s2[1] = 0xCC; 14 s2[2] = 0x88; 15 s2[3] = 0x76; 16 s2[4] = 0xD7; 17 s2[5] = 0x89; 18 s2[6] = 0x78; 19 s2[7] = 0xEC; 20 s2[8] = 0x7C; 21 s2[9] = 0xD7; 22 s2[10] = 0x89; 23 s2[11] = 0x71; 24 s2[12] = 0xE3; 25 s2[13] = 0x6D; 26 s2[14] = 0x98; 27 s2[15] = 0x17; 28 s2[16] = 0x94; 29 s2[17] = 0xF; 30 s2[18] = 0xCA; 31 s2[19] = 0x9F; 32 s2[20] = 0x7E; 33 s2[21] = 0xD9; 34 s2[22] = 0xA0; 35 s2[23] = 0x8A; 36 s2[24] = 0x79; 37 s2[25] = 0xD1; 38 s2[26] = 0x80; 39 s2[27] = 0x77; 40 printf(&byte_400A66, s2, 18LL); 41 fgets(input, 128, stdin); 42 input_len = strlen(input); 43 if ( input[input_len - 1] == 10 ) 44 input[input_len - 1] = 0; 45 key = generate_key(877); 46 encrypt((__int64)input, (__int64)s1, 2024, key); 47 if ( !memcmp(s1, s2, 128uLL) ) 48 puts("success"); 49 else 50 puts("fail"); 51 return 0; 52 }
加密函数
1 __int64 __fastcall encrypt(__int64 input, __int64 s1, int num2024, int key) 2 { 3 __int64 result; // rax 4 int i; // [rsp+20h] [rbp-8h] 5 int v6; // [rsp+24h] [rbp-4h] 6 unsigned int v7; // [rsp+24h] [rbp-4h] 7 8 v6 = 0; 9 for ( i = 0; i <= 127; ++i ) 10 { 11 result = *(unsigned __int8 *)(i + input); // input[i]=result 12 if ( (_BYTE)result ) 13 { 14 v7 = key + num2024 + v6; 15 *(_BYTE *)(i + s1) = *(_BYTE *)(i + input) + v7;// s1[i]=input[i]+v7 16 // v7是偏移量 17 result = (v7 >> 4) ^ *(unsigned __int8 *)(i + s1);// 等于没有 18 v6 = (v7 >> 4) ^ *(unsigned __int8 *)(i + s1);// v6储存的是上一个result的值 19 } 20 } 21 return result; 22 }
KEY
1 __int64 __fastcall generate_key(int a1) 2 { 3 return (unsigned int)(1540483477 * a1 + 305419896) >> 16; 4 }
输入值
s2 = [ 0x9C, 0xCC, 0x88, 0x76, 0xD7, 0x89, 0x78, 0xEC, 0x7C, 0xD7, 0x89, 0x71, 0xE3, 0x6D, 0x98, 0x17, 0x94, 0x0F, 0xCA, 0x9F, 0x7E, 0xD9, 0xA0, 0x8A, 0x79, 0xD1, 0x80, 0x77 ]
个人记录
获得key值——C代码
参考大佬的部分wp:2024ctfshow元旦水友赛 RE re_signin-CSDN博客
1 #include<stdio.h> 2 3 unsigned int ge(unsigned int s) { 4 s = 1540483477 * s + 305419896; 5 return (s >> 16); 6 } 7 8 int main() { 9 unsigned int k = 877; // 为变量 k 赋一个初始值 10 unsigned int key = ge(k); // 调用 ge 函数来计算密钥 key 11 printf("%x", key); // 使用 %x 格式化字符串打印无符号整数值(十六进制) 12 return 0; 13 }
最初的代码11行用的是"%u"——打印出来的是十进制数:41041
但是参考了大佬的py代码:ctfshow 元旦水友赛部分wp | M4sh1r0の万事屋 (m4shir0.com)
大佬用的是十六进制,写解题脚本的时候也是多用0xA051这种十六进制数,就改成"%x"
关于大佬解题wp的疑惑
大佬代码
1 key = 0xA051 2 num = 2024 3 table = [] 4 cmp = [0x9C, 0xCC, 0x88, 0x76, 0xD7, 0x89, 0x78, 0xEC, 0x7C, 0xD7, 0x89, 0x71, 0xE3, 0x6D, 0x98, 0x17, 0x94, 0x0F, 0xCA, 0x9F, 0x7E, 0xD9, 0xA0, 0x8A, 0x79, 0xD1, 0x80, 0x77] 5 6 tb = 0 7 flag = [] 8 for i in range(28): 9 ta = key + num + tb 10 flag.append((cmp[i] - (ta&0xff)) & 0xff) 11 tb = (ta >> 4) ^ cmp[i] 12 print(''.join(chr(i) for i in flag))
flag.append((cmp[i] - (ta&0xff)) & 0xff)——这一部分的我没看懂
ta是偏移量,加密代码里是与偏移量相加,解题脚本里面就相减,这我能理解
(ta&0xff)) & 0xff——这个我就不太理解为什么这样写
以下是关于疑问的解答(参考:C语言的整数溢出机制 - 知乎 (zhihu.com))
ta&0xff——提取了ta中的最低8位,为了获取一个有效字节值(8位)
为什么是8位?
首先这是一个无符号位char类型数对于signed char,正整数最大值为127,负整数最小值为128。unsigned char所能表示的最大值为255。
有符号整型溢出又可分为向上溢出和向下溢出。假设用k个字节表示一个整型变量, 那么这个变量可以表示的有符号整数的范围是-2^(8k-1) ~ 2^(8k-1) – 1
那么两个正整数或者两个负整数相加就有可能超过这个整型变量所能表示的范围
向上超出>2^(8k-1) – 1我们称之为向上溢出
向下超出<-2^(8k-1), 我们称之为向下溢出
无论是向上溢出还是向下溢出,绝对值都在相对于无符号整型能表示的范围内————对于signed char,如果结果为400,超出了位数表示范围,取结果的低八位。
& 0xff——这个操作是为了确保结果在一个字节范围内(0到255),因为解密的结果应该是一个字节
那可以去掉吗?
GPT回答:
在这种情况下,去掉 `(ta&0xff)` 后的 `& 0xff` 是可能的,但是需要对解密结果的范围进行一些考虑。
`(ta&0xff)` 的目的是确保偏移量 `ta` 的范围在0到255之间,因为偏移量是一个字节(8位)的值。在实际应用中,这个操作可以确保解密结果在0到255之间,即一个字节的范围内。
如果你确定在加密过程中偏移量的计算不会导致超出一个字节的范围(例如,`key`、`num` 和 `tb` 的取值都在合适的范围内),那么去掉 `(ta&0xff)` 后的 `& 0xff` 是可以的。
但是,如果你不确定加密过程中的偏移量是否会超出一个字节的范围,保留 `& 0xff` 可以确保解密结果始终在合适的范围内,这样更加安全可靠。
flag
ctfshow{happy_2024_jiayou_a}