[GXYCTF2019]luck_guy
拿到题目后先查一下壳

ok,没有壳放进工具里反编译
先查看所有字符串

发现一个"get flag"
查看引用它的函数
cpp
void get_flag(void)
{
int iVar1;
time_t tVar2;
long in_FS_OFFSET;
int local_44;
int local_40;
char local_38 [40];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
for (local_44 = 0; local_44 < 5; local_44 = local_44 + 1) {
iVar1 = rand();
switch(iVar1 % 200) {
default:
puts("emmm,you can\'t find flag 23333");
break;
case 1:
puts("OK, it\'s flag:");
memset(local_38,0,0x28);
strcat(local_38,f1);
strcat(local_38,f2);
printf("%s",local_38);
break;
case 2:
printf("Solar not like you");
break;
case 3:
printf("Solar want a girlfriend");
break;
case 4:
builtin_strncpy(local_38,"icug`of\x7f",9);
strcat(f2,local_38);
break;
case 5:
for (local_40 = 0; local_40 < 8; local_40 = local_40 + 1) {
if (local_40 % 2 == 1) {
f2[local_40] = f2[local_40] + -2;
}
else {
f2[local_40] = f2[local_40] + -1;
}
}
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
这段代码是一个典型的概率型逆向题目 。它的核心逻辑在于利用随机数 rand() 来决定程序的执行路径,只有触发了特定的"幸运"路径组合,才能还原出正确的 Flag。
程序循环 5 次,每次根据 rand() % 200 的结果进入不同的分支。我们需要找到对 Flag 有贡献的分支:
分支1:
cpp
case 1:
puts("OK, it\'s flag:");
memset(local_38,0,0x28);
strcat(local_38,f1);
strcat(local_38,f2);
printf("%s",local_38);
- 作用 :这是获取 Flag 的唯一出口。它将全局变量
f1和f2拼接后打印。 - 结论 :Flag =
f1+f2
先查看f1与f2看看有没有预设值
f1:

f2:

f1的值为:47 58 59 7b 64 6f 5f 6e 6f 74 5f 转为ASCII值为:GXY{do_not_
f2的值为空,也就是我们要计算的内容
回顾 get_flag 函数中的逻辑:
-
Case 4:
cppcase 4: builtin_strncpy(local_38,"icug`of\x7f",9); strcat(f2,local_38); break;- 这里将字符串 "
icug`of\x7f"(注意大小端区别)赋值给了f2
- 这里将字符串 "
-
Case 5:
cppcase 5: for (local_40 = 0; local_40 < 8; local_40 = local_40 + 1) { if (local_40 % 2 == 1) { f2[local_40] = f2[local_40] + -2; } else { f2[local_40] = f2[local_40] + -1; }对
f2的前 8 位进行减法混淆。- 偶数位减 1,奇数位减 2。
- 我们需要逆向这个操作来得到真正的
f2
解密代码:
python
f1 = 'GXY{do_not_'
f2 = [0x7F, 0x66, 0x6F, 0x60, 0x67, 0x75, 0x63, 0x69][::-1]
s = ''
for i in range(8):
if i % 2 == 1:
s = chr(int(f2[i]) - 2)
else:
s = chr(int(f2[i]) - 1)
f1 += s
print(f1)
解出flag为GXY{do_not_hate_me}(记得把头改成flag)
Java逆向解密
打开附件发现是一个.class文件
这就不需要查壳了("壳" 是Windows 可执行文件(EXE/DLL)或Linux ELF 文件 特有的概念,指为了保护、加密或混淆程序代码而添加的一层额外保护机制(常见于恶意软件、付费软件)Java 字节码文件(.class) ,由 Java 编译器(javac)生成,运行在 Java 虚拟机(JVM) 中。Java 类文件的结构是固定的 (包含魔数、版本号、常量池、字段、方法、属性等),不存在 "加壳" 或 "脱壳" 的说法。)
直接放进jd-gui反编译
java
import java.util.ArrayList;
import java.util.Scanner;
public class Reverse {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("Please input the flag :");
String str = s.next();
System.out.println("Your input is :");
System.out.println(str);
char[] stringArr = str.toCharArray();
Encrypt(stringArr);
}
public static void Encrypt(char[] arr) {
ArrayList<Integer> Resultlist = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
int result = arr[i] + 64 ^ 0x20;
Resultlist.add(Integer.valueOf(result));
}
int[] KEY = {
180, 136, 137, 147, 191, 137, 147, 191, 148, 136,
133, 191, 134, 140, 129, 135, 191, 65 };
ArrayList<Integer> KEYList = new ArrayList<>();
for (int j = 0; j < KEY.length; j++)
KEYList.add(Integer.valueOf(KEY[j]));
System.out.println("Result:");
if (Resultlist.equals(KEYList)) {
System.out.println("Congratulations!");
} else {
System.err.println("Error!");
}
}
}
通过阅读 Java 源代码,我们可以清晰地看到程序的执行流程:
- 输入获取 :程序接收用户输入的字符串,并将其转换为字符数组
char[] arr。 - 加密/混淆逻辑 :程序遍历字符数组,对每个字符执行以下运算并存入列表:int result = arr[i] + 64 ^ 0x20;
- 比对验证 :将计算得到的
Resultlist与硬编码的KEYList进行全等比较。目标数组KEY为:
java
{ 180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65 }
还原 Flag,我们需要逆向上述加密公式
解密代码:
python
key = [
180, 136, 137, 147, 191, 137, 147, 191, 148, 136,
133, 191, 134, 140, 129, 135, 191, 65
]
flag = ""
for val in key:
# 逆向公式:Char = (Val ^ 32) - 64
char_code = (val ^ 32) - 64
flag += chr(char_code)
print(flag)
#This_is_the_flag_!