re2
首先用ida64打开re2文件,定位到main函数
f5反汇编得到如下代码
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 60 61 62
| __int64 __fastcall main(int a1, char **a2, char **a3) { unsigned int v3; // eax __int64 result; // rax unsigned int v5; // [rsp+4h] [rbp-1Ch] unsigned int v6; // [rsp+8h] [rbp-18h] unsigned int v7; // [rsp+Ch] [rbp-14h] unsigned int v8; // [rsp+10h] [rbp-10h] unsigned int v9; // [rsp+14h] [rbp-Ch] int v10; // [rsp+18h] [rbp-8h]
puts("hi all ----------------------"); puts("Welcome to dice game!"); puts("You have to roll 5 dices and get 6, 6, 6, 6, 6 in order."); puts("Press enter to roll."); getchar(); v3 = time(0LL); srand(v3); v10 = time(0LL); v9 = rand() % 6 + 1; v8 = rand() % 6 + 1; v7 = rand() % 6 + 1; v6 = rand() % 6 + 1; v5 = rand() % 6 + 1; printf("You rolled %d, %d, %d, %d, %d.\n", v9, v8, v7, v6, v5); if ( v9 != 6 ) goto LABEL_15; if ( time(0LL) - v10 > 2 ) goto LABEL_13; if ( v8 != 6 ) goto LABEL_15; if ( time(0LL) - v10 > 2 ) goto LABEL_13; if ( v7 != 6 ) goto LABEL_15; if ( time(0LL) - v10 > 2 ) goto LABEL_13; if ( v6 != 6 ) goto LABEL_15; if ( time(0LL) - v10 > 2 ) goto LABEL_13; if ( v5 == 6 ) { if ( time(0LL) - v10 > 2 ) { LABEL_13: puts("No cheat!"); return 0xFFFFFFFFLL; } puts("You rolled as I said! I'll give you the flag."); sub_4006B6(); result = 0LL; } else { LABEL_15: puts("You DID NOT roll as I said!"); puts("Bye bye~"); result = 0xFFFFFFFFLL; } return result; }
|
分析得知,这是一个摇骰子的游戏,v5到v9是1到6的时间种子随机数,要求v5到v9都是6,且time(0LL) - v10 > 2两个条件都满足才能执行 sub_4006B6()函数,输出正确的flag,而v10 = time(0LL),所以第二个条件无法满足,想到用patch的方法修改判断条件
查看对应汇编代码我们能找到如下指令
类似的条件共有5组,第一个条件对应摇出数字6,第二个条件对应time(0LL) - v10 > 2,现更改汇编,使第一类跳转不执行,第二类跳转均执行,使用keypatcher插件,将第一个条件中的6改为0,由于随机数的范围是1到6所以一定与0不等,再将jnz改为jz,则跳转一定不执行,将第二个条件中的jle改为jmp,则跳转一定执行,如下图所示
将5组条件均进行相同的修改,然后保存
将保存后的re2文件放在linux下执行,运行得到flag
1
| flag{4b34098e229eab72739923a0b40b3be6}
|
re3
首先用ida64打开re3文件,定位到main函数,f5得到如下伪代码
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| ![re3_address](%E5%A4%8D%E6%97%A6%E7%99%BD%E6%B3%BD%E6%9A%91%E6%9C%9F%E8%AF%BE%E7%A8%8B%E9%80%86%E5%90%91%E5%85%A5%E9%97%A8%E8%AF%BEwp/re3_address.png)__int64 __fastcall main(int a1, char **a2, char **a3) { __int64 v3; int v4; char v5; char v6; const char *v7; unsigned int v9; int v10[9];
v10[0] = 0; v9 = 0; puts("Give flag:"); scanf("%s", &s1); if ( strlen(&s1) != 26 || strncmp(&s1, "flag{", 5uLL) || *(&byte_6010BF + 26) != 125 ) { LABEL_22: puts("Wrong flag!"); exit(-1); } v3 = 5LL; if ( strlen(&s1) - 1 > 5 ) { while ( 1 ) { v4 = *(&s1 + v3); v5 = 0; if ( v4 > 67 ) { if ( (unsigned __int8)v4 == 68 ) { v6 = sub_400650(v10); goto LABEL_14; } if ( (unsigned __int8)v4 == 69 ) { v6 = sub_400660(v10); goto LABEL_14; } } else { if ( (unsigned __int8)v4 == 65 ) { v6 = sub_400670(&v9); goto LABEL_14; } if ( (unsigned __int8)v4 == 66 ) { v6 = sub_400680(&v9); LABEL_14: v5 = v6; goto LABEL_15; } } LABEL_15: if ( !(unsigned __int8)sub_400690(asc_601060, (unsigned int)v10[0], v9) ) goto LABEL_22; if ( ++v3 >= strlen(&s1) - 1 ) { if ( v5 ) break; LABEL_20: v7 = "Wrong flag!"; goto LABEL_21; } } } if ( asc_601060[8 * v9 + v10[0]] != 42 ) goto LABEL_20; v7 = "Congratulations!"; LABEL_21: puts(v7); return 0LL; }
|
程序大体逻辑是输出一个Give flag:,然后将我们输入的字符串长度为26,存入s1数组,根据判断条件知s1数组的前五位为flag{先随意试一下
双击byte_6010BF,发现它是s1的前一位,则*(&byte_6010BF + 26)对应s1的最后一位,利用ida可将125转换为对应的字符”}”
那我们的任务就是判断剩下的20个字符,它们存储在v4数组中,利用转换字符的功能,可以将68、69、65、66依次转换为D、E、A、B,继续分析,我们注意到判断flag正确与否的关键在这个地方
双击asc_601060发现它是这样的字符串
典型的迷宫问题,42对应”*”,这个字符即为出口,根据asc_601060[8 * v9 + v10[0]],判断出迷宫的行号对应v9,列号对应v10[0],且每行有8列,先把迷宫整理一下,为便于观察用”#”代替” “
将v9、v10[0]改名为row、column,根据函数判断出
再根据第57行的sub_400690函数判断出空格和*可走
则得出flag为
1
| flag{EBEEBBDBBBEEEEAADAAE}
|
验证
re1思考题
由于scanf %s有可能造成溢出,且程序中没有对输入字符串的长度进行检验,所以在正确的flag后,加上任意的字符也会puts(“you are right!”)