Reverse(九)
数学不及格
https://ctf.show/challenges#%E6%95%B0%E5%AD%A6%E4%B8%8D%E5%8F%8A%E6%A0%BC-121
ida64打开找main
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@16
__int64 v4; // rsi@16
signed int v5; // [sp+14h] [bp-4Ch]@4
char *endptr; // [sp+18h] [bp-48h]@4
char *v7; // [sp+20h] [bp-40h]@4
char *v8; // [sp+28h] [bp-38h]@4
char *v9; // [sp+30h] [bp-30h]@4
__int64 v10; // [sp+38h] [bp-28h]@4
__int64 v11; // [sp+40h] [bp-20h]@4
__int64 v12; // [sp+48h] [bp-18h]@4
__int64 v13; // [sp+50h] [bp-10h]@4
__int64 v14; // [sp+58h] [bp-8h]@1
v14 = *MK_FP(__FS__, 40LL);
if ( argc != 5 ) // 输入参数必须为5个,包含运行文件名,所以关键看剩下四个
{
puts("argc nonono");
exit(1);
}
v5 = (unsigned __int64)strtol(argv[4], &endptr, 16) - 25923; // 这四个都是16进制字符串
v10 = f(v5);
v11 = strtol(argv[1], &v7, 16);
v12 = strtol(argv[2], &v8, 16);
v13 = strtol(argv[3], &v9, 16);
if ( v10 - v11 != 151381742876LL )
{
puts("argv1 nonono!");
exit(1);
}
if ( v10 - v12 != 117138004530LL )
{
puts("argv2 nonono!");
exit(1);
}
if ( v10 - v13 != 155894355749LL )
{
puts("argv3 nonono!");
exit(1);
}
if ( v5 + v13 + v12 + v11 != 1349446086540LL )
{
puts("argv sum nonono!");
exit(1);
}
puts("well done!decode your argv!");
result = 0;
v4 = *MK_FP(__FS__, 40LL) ^ v14;
return result;
}
__int64 __fastcall f(signed int a1)
{
__int64 result; // rax@3
signed int i; // [sp+1Ch] [bp-14h]@4
__int64 v3; // [sp+20h] [bp-10h]@4
_QWORD *ptr; // [sp+28h] [bp-8h]@4
if ( a1 > 1 && a1 <= 200 ) // 输入范围必须在2-200
{
ptr = malloc(8LL * a1);
*ptr = 1LL;
ptr[1] = 1LL;
v3 = 0LL;
for ( i = 2; i < a1; ++i )
{
ptr[i] = ptr[i - 1] + ptr[i - 2]; // 斐波那契数列
v3 = ptr[i];
}
free(ptr);
result = v3; // 取最后一个
}
else
{
result = 0LL;
}
return result;
}
已知条件:
- f(argv[4] - 25923) - argv[1] = 151381742876
- f(argv[4] - 25923) - argv[2] = 117138004530
- f(argv[4] - 25923) - argv[3] = 155894355749
- argv[4] - 25923 + argv[1] + argv[2] + argv[3] = 1349446086540
python求解并转为字符串
f = [1, 1]
for i in range(2, 201):
f.append(f[i-1] + f[i-2])
print(f)
for i in range(2, 201):
if f[i] * 3 + i+1 == 151381742876 + 117138004530 + 155894355749 + 1349446086540: # 这里i要注意
print('i is', i)
break
print('Argv[1]: %x' % (f[i] - 151381742876))
print('Argv[2]: %x' % (f[i] - 117138004530))
print('Argv[3]: %x' % (f[i] - 155894355749))
print('Argv[4]: %x' % (i + 1 + 25923))
flag = ''
flag += bytes.fromhex(str(hex(f[i] - 151381742876)[2:])).decode('utf-8')
flag += bytes.fromhex(str(hex(f[i] - 117138004530)[2:])).decode('utf-8')
flag += bytes.fromhex(str(hex(f[i] - 155894355749)[2:])).decode('utf-8')
flag += bytes.fromhex(str(hex(i + 1 + 25923)[2:])).decode('utf-8')
print(flag)
peter的手机
https://ctf.bugku.com/challenges/detail/id/359.html
这题血亏,不仅花金币买了文件,还买了wp,最后才发现ida6.8、ida8打开进不去函数,看不了伪代码,各种问题,但是ida7.0就可以::cry::
首先解压ipa文件,找到同名64位可执行文件(FirstOS),定位特殊字符串1997019247Acf,查看伪代码
void __cdecl -[ViewController btn](ViewController *self, SEL a2)
{
UITextField *v2; // x0
void *v3; // x0
void *v4; // ST10_8
void *v5; // x0
UILabel *v6; // x0
void *v7; // ST08_8
UILabel *v8; // x0
void *v9; // x0
__int64 v10; // ST00_8
void *v11; // [xsp+18h] [xbp-18h]
SEL v12; // [xsp+20h] [xbp-10h]
ViewController *v13; // [xsp+28h] [xbp-8h]
v13 = self;
v12 = a2;
NSLog(CFSTR("btn click do"));
v2 = -[ViewController edtInput](v13, "edtInput");
v3 = (void *)objc_retainAutoreleasedReturnValue(v2);
v4 = v3;
v5 = objc_msgSend(v3, "text");
v11 = (void *)objc_retainAutoreleasedReturnValue(v5);
objc_release(v4);
if ( (unsigned __int64)objc_msgSend(v11, "isEqualToString:", CFSTR("1997019247Acf")) & 1 )
{
v6 = -[ViewController EndValue](v13, "EndValue");
v7 = (void *)objc_retainAutoreleasedReturnValue(v6);
objc_msgSend(v7, "setText:", CFSTR("right"));
objc_release(v7);
}
else
{
v8 = -[ViewController EndValue](v13, "EndValue");
v9 = (void *)objc_retainAutoreleasedReturnValue(v8);
objc_msgSend(v9, "setText:", CFSTR("error"), v9);
objc_release(v10);
}
objc_storeStrong(&v11, 0LL);
}
可以看出来大概就是个比较字符串的功能,提交就过了。。。。
这道题是ios文件,简单不涉及算法,直接找字符串,要是难点的就不好做了
easyeasy-200
https://ctf.bugku.com/challenges/detail/id/127.html
public class MainActivity extends AppCompatActivity {
String this_is_your_flag;
static {
System.loadLibrary("easyeasy");
}
public MainActivity() {
super();
}
protected void onCreate(Bundle arg4) {
ApplicationInfo v1 = this.getApplicationInfo();
int v2 = v1.flags & 2;
v1.flags = v2;
if(v2 != 0) {
Process.killProcess(Process.myPid());
}
super.onCreate(arg4);
this.setContentView(2130968602);
this.findViewById(2131427413).setOnClickListener(new View$OnClickListener() {
public void onClick(View arg8) {
MainActivity.this.this_is_your_flag = MainActivity.this.findViewById(2131427414).getText().toString(); // 定位赋值
if(MainActivity.this.this_is_your_flag.length() < 35) {
Process.killProcess(Process.myPid());
}
else if(MainActivity.this.this_is_your_flag.length() > 39) {
Process.killProcess(Process.myPid());
}
// flag长度35-39
MainActivity.this.this_is_your_flag = new Format().form(MainActivity.this.this_is_your_flag); // 双击查看发现作用是substring(5,38),说明长度38或39
if(MainActivity.this.this_is_your_flag.length() < 32) {
Toast.makeText(MainActivity.this.getApplicationContext(), "No,more.", 1).show();
}
else if(new Check().check(MainActivity.this.this_is_your_flag)) { // 核心判断函数
Toast.makeText(MainActivity.this.getApplicationContext(), "Congratulations!You got it.", 1).show();
}
else {
Toast.makeText(MainActivity.this.getApplicationContext(), "Oh no.Come on!", 1).show();
}
}
});
}
}
判断函数,经过分析发现这个代码会检查是否用了模拟器,以及调用lib文件里进行判断
public class Check {
String emulator;
private static String[] known_pipes;
static {
Check.known_pipes = new String[]{"/dev/socket/qemud", "/dev/qemu_pipe"};
}
public Check() {
super();
this.emulator = Check.checkPipes();
}
boolean check(String arg2) {
boolean v0 = this.checkEmulator(this.emulator) ? false : this.checkPasswd(arg2);
return v0;
}
protected native boolean checkEmulator(String arg1) {
}
private native boolean checkPasswd(String arg1) {
}
public static String checkPipes() {
String v3;
int v0 = 0;
while(true) {
if(v0 >= Check.known_pipes.length) {
return "false";
}
else if(new File(Check.known_pipes[v0]).exists()) {
v3 = "true";
}
else {
++v0;
continue;
}
return v3;
}
return "false";
}
}
ida64打开so文件(看其他wp说得armeabi-v7a下的so,不清楚具体原因,ida对比查看了下感觉应该是这个反编译的结果最简单?),直接搜索check可以找到对应的函数
bool __fastcall Java_com_example_ring_wantashell_Check_checkPasswd(int a1, int a2, int a3)
{
int v5; // r4
const char *v6; // r6
char *v7; // r4
size_t v8; // r0
size_t i; // r1
char v10; // r2
size_t v11; // r0
char *v12; // r4
char *v14; // [sp+0h] [bp-1Ch] BYREF
unsigned int v15; // [sp+8h] [bp-14h] BYREF
v5 = 0;
v6 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
if ( v6 )
{
v7 = (char *)operator new[](0x21u); // 开辟v7空间
strcpy(v7, v6); // v6赋给v7,猜测v6位我们输入的passwd
sub_8F7C((int)&v15, (char *)&unk_18843); // X查看发现被多次调用,猜测是系统调用函数,和加密无关
v8 = strlen(v7) - 1;
if ( v8 )
{
for ( i = 0; i < v8; ++i ) // 可以看出来做了个倒置字符串操作
{
v10 = v7[i];
v7[i] = v7[v8];
v7[v8--] = v10;
}
}
v11 = strlen(v7);
sub_6ED0(&v15, v7, v11);
encrypt((const char *)&v14, v15); // 很明显加密函数,猜测上面的函数将v7赋值给了v15,在这里进行加密,加密后的存到v14
v12 = v14;
sub_69A4(&v14);
sub_69A4(&v15);
(*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v6);
return sub_7834((int)&secret, v12) == 0;
}
return v5;
}
encrypt加密函数如下
// attributes: thunk
int __fastcall encrypt(const char *a1, unsigned int a2)
{
return _Z7encryptPKcj(a1, a2);
}
int __fastcall encrypt(const char *a1, unsigned int a2, int a3)
{
int v5; // r10
int v6; // r9
char v7; // t1
int v8; // r5
int v9; // r0
int v10; // r0
int i; // r6
int v12; // r5
char v14; // [sp+1h] [bp-17h]
char v15; // [sp+2h] [bp-16h]
char v16; // [sp+3h] [bp-15h]
char v17; // [sp+4h] [bp-14h]
unsigned __int8 v18; // [sp+5h] [bp-13h] BYREF
unsigned __int8 v19; // [sp+6h] [bp-12h]
unsigned __int8 v20; // [sp+7h] [bp-11h]
int v21; // [sp+8h] [bp-10h]
v5 = a3;
*(_DWORD *)a1 = &unk_1D0E0;
if ( a3 )
{
v6 = 0;
do
{
v7 = *(_BYTE *)a2++;
*(&v18 + v6++) = v7;
if ( v6 == 3 ) // 看代码可以感觉出来是base64加密
{
v8 = 1;
v9 = v18 >> 2;
v14 = v18 >> 2;
v15 = (16 * v18) & 0x30 | (v19 >> 4);
v16 = (v20 >> 6) & 0xC3 | (4 * (v19 & 0xF));
v17 = v20 & 0x3F;
while ( 1 )
{
sub_6F28(a1, *(unsigned __int8 *)(dword_1D09C + (unsigned __int8)v9));
if ( v8 > 3 )
break;
LOBYTE(v9) = *(&v14 + v8++);
}
v6 = 0;
}
--v5;
}
while ( v5 );
if ( v6 )
{
if ( v6 <= 2 )
memset(&v18 + v6, 0, 3 - v6);
v10 = v18 >> 2;
v14 = v18 >> 2;
v15 = (16 * v18) & 0x30 | (v19 >> 4);
v16 = (v20 >> 6) & 0xC3 | (4 * (v19 & 0xF));
v17 = v20 & 0x3F;
if ( v6 >= 0 )
{
for ( i = 0; ; ++i )
{
sub_6F28(a1, *(unsigned __int8 *)(dword_1D09C + (unsigned __int8)v10)); // 后面可以知道dword_1D09C存储了base64映射表(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/)
if ( i >= v6 )
break;
LOBYTE(v10) = *(&v15 + i);
}
}
v12 = v6 - 1;
while ( ++v12 <= 2 )
sub_6F28(a1, 46);
}
}
return _stack_chk_guard - v21;
}
最后的secret位于bss段,通常存放全局变量(未初始化和初始化的)
可以X查看调用这个secret的地方(从名字看很有可能是最终加密的字符串),发现sub_520c()同样调用了它,
int sub_520C()
{
int v1; // [sp+0h] [bp-18h] BYREF
char v2[4]; // [sp+4h] [bp-14h] BYREF
int v3; // [sp+8h] [bp-10h]
sub_8F7C(&secret, "dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.", (int)&v1); // 基本实锤这是个赋值字符串的函数
_cxa_atexit((void (__fastcall *)(void *))sub_69A4, &secret, &unk_1D000);
sub_8F7C(&dword_1D09C, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", (int)v2); // 这里也是赋值了
_cxa_atexit((void (__fastcall *)(void *))sub_69A4, &dword_1D09C, &unk_1D000);
return _stack_chk_guard - v3;
}
因此加密流程是这样的:
- 输入字符串
- 先检查环境是否为模拟器,如果是直接返回False,不是调用so文件中的checkPasswd函数检查密码
- 密码做一个倒置
- 密码base64加密
- 和secret("dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.")cmp对比,记得点号替换为=
解密:
easycrack-100
https://ctf.bugku.com/challenges/detail/id/128.html
jeb查看mainactivity
public class MainActivity extends AppCompatActivity {
class CheckText implements TextWatcher {
CheckText(MainActivity arg1) {
MainActivity.this = arg1;
super();
}
public void afterTextChanged(Editable arg5) {
MainActivity.this.findViewById(2131427416).setText("Status: " + MainActivity.this.parseText(arg5.toString()));
}
public void beforeTextChanged(CharSequence arg1, int arg2, int arg3, int arg4) {
}
public void onTextChanged(CharSequence arg1, int arg2, int arg3, int arg4) {
}
}
static {
System.loadLibrary("native-lib");
}
public MainActivity() {
super();
}
public String messageMe() {
String v3 = "";
int v4 = 51;
String[] v1 = this.getApplicationContext().getPackageName().split("\\.");
char[] v6 = v1[v1.length - 1].toCharArray();
int v7 = v6.length;
int v5;
for(v5 = 0; v5 < v7; ++v5) {
v4 ^= v6[v5];
v3 = v3 + (((char)v4));
}
return v3;
}
protected void onCreate(Bundle arg4) {
super.onCreate(arg4);
this.setContentView(2130968603);
this.findViewById(2131427416).setText(this.stringFromJNI());
this.findViewById(2131427415).addTextChangedListener(new CheckText(this));
}
public native String parseText(String arg1) {
}
public native String stringFromJNI() {
}
}
去看jni调用的代码,这次看的是armeabi里的so文件
int __fastcall Java_com_njctf_mobile_easycrack_MainActivity_parseText(int a1, int a2, int a3)
{
int v5; // r0
int v6; // r0
int v7; // r0
size_t v8; // r4
size_t v9; // r6
size_t v10; // r2
unsigned int v11; // r3
unsigned int v12; // r6
unsigned int v13; // r0
unsigned __int8 *v14; // r4
const char *v15; // r5
size_t v16; // r2
size_t v18; // r0
const char *v19; // r1
const char *v21; // [sp+Ch] [bp-130h]
unsigned __int8 *v23; // [sp+10h] [bp-12Ch]
char *v24; // [sp+14h] [bp-128h]
size_t v25; // [sp+18h] [bp-124h]
const char *v26; // [sp+18h] [bp-124h]
_WORD v27[8]; // [sp+1Ch] [bp-120h] BYREF
char v28[256]; // [sp+2Ch] [bp-110h] BYREF
v5 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 24))(a1, "com/njctf/mobile/easycrack/MainActivity");
v6 = (*(int (__fastcall **)(int, int, const char *, const char *))(*(_DWORD *)a1 + 132))(
a1,
v5,
"messageMe",
"()Ljava/lang/String;");
v7 = j__JNIEnv::CallObjectMethod(a1, a2, v6);
v25 = 0;
v24 = (char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, v7, 0); // 调用了java里的messageMe(一个加密字符串的函数)
v21 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
v8 = j_strlen(v21); // 输入字符串的长度
v9 = j_strlen(v24); // messageMe返回字符串长度
v23 = (unsigned __int8 *)j_malloc(v8); // 分配和输入字符串大小相同的空间
if ( v8 )
{
do // 循环加密,加密后的字符串存到v23
{
if ( v9 )
{
v10 = 0;
do
{
v23[v25 + v10] = v24[v10] ^ v21[v25 + v10];
v11 = v25 + v10++ + 1;
}
while ( v10 < v9 && v11 < v8 );
}
v25 += v9;
}
while ( v25 < v8 );
}
memset(v28, 0, sizeof(v28));
strcpy((char *)v27, "I_am_the_key");
HIBYTE(v27[6]) = 0; // 这里比较奇怪,0不是相当于提前终止字符串吗
v27[7] = 0;
v12 = v8; // v12存储输入字符串长度
v13 = j_strlen((const char *)v27); // 新的长度为6
j_init((unsigned __int8 *)v28, (unsigned __int8 *)v27, v13); // 做到这里看到v28是256位空的,v27是密钥,可以联想到之前的RC4加密算法
v14 = v23; // v14存储加密后的字符串
j_crypt((unsigned __int8 *)v28, v23, v12); // 传入的分别是复制过的256位,v23前面异或过的字符串,v12是长度
v26 = (const char *)j_malloc((2 * v12) | 1); // v26大小为2*v12+1,由下面可知存的是2位大写十六进制字符串
if ( v12 )
{
v15 = v26;
do
{
j_snprintf(v15, 3, "%02X", *v14);
v15 += 2;
--v12;
++v14;
}
while ( v12 );
}
v16 = j_strlen(v26); // 长度为2*v12
if ( v16 && !j_strncmp(v26, compare, v16) ) // compare='C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4'
{
j___android_log_print(2, "NJCTF-easycrack", "success: %s", v26);
v18 = j_strlen(compare);
if ( !j_strncmp(v26, compare, v18) )
v19 = "YOU GOT IT!";
else
v19 = "Victory is in sight.";
return (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, v19);
}
else
{
j___android_log_print(6, "NJCTF-easycrack", "failed : %s", v26);
return (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, "Try again.");
}
}
分析得到加密流程如下:
- 首先得到messageMe返回的字符串==V7D=^,M.E==
- 接着输入的字符串循环和上面返回的字符串异或
- 接着RC4初始化,密钥为"I_am_the_key",这里放张RC4初始化代码和这里j_init的代码对比,可以看到都是两个256循环
- 接着RC4加密,这里再对比下:
- 得到的结果存到了v23,也就是v14,接着转为2位长度的16进制,并和compare比较
python脚本解密
def decrypt(v23, v24):
v8 = len(v23)
v9 = len(v24)
v25 = 0
v21 = []
v11 = 0
if v8:
while v25 < v8:
if v9:
v10 = 0
while v10 < v9 and v11 <= v8: # 要注意原来C语言判断是执行完一个循环才判断,python执行循环前就要判断,条件不同
v21.append(v24[v10] ^ v23[v25 + v10])
v10 += 1
v11 = v25 + v10 + 1
v25 += v9
for i in v21:
print(chr(i), end='')
print()
def get_key(key):
key_len = len(key)
a1, a2 = [], []
for i in range(256):
a1.append(i)
a2.append(ord(key[i % key_len]))
v5 = 0
for i in range(256):
v5 = (v5 + a1[i] + a2[i]) % 256
a1[i], a1[v5] = a1[v5], a1[i]
return a1
def rc4_decrypt(key, cipher):
v7, v6 = 0, 0
v23 = []
for i in range(len(cipher)):
v7 = (v7 + 1) % 256
v6 = (v6 + key[v7]) % 256
key[v7], key[v6] = key[v6], key[v7]
v23.append(key[(key[v6] + key[v7]) % 256] ^ cipher[i])
return v23
def messageMe():
message = 'easycrack'
new_message = []
v4 = 51
for i in range(len(message)):
v4 ^= ord(message[i])
new_message.append(v4)
return new_message
if __name__ == '__main__':
# RC4初始化
key = get_key('I_am_the_key')
# 求enflag
compare = 'C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4'
cipher = [int(compare[i]+compare[i+1], 16) for i in range(0, len(compare), 2)]
# RC4解密
v23 = rc4_decrypt(key, cipher)
# 求messageMe返回字符串
v24 = messageMe()
decrypt(v23, v24)
结果:It_s_a_easyCrack_for_beginners,太扎心了,怎么觉得不简单。。。
读反编译的代码还是不太顺利,接着练吧
fake-func
https://ctf.bugku.com/challenges/detail/id/142.html
jeb查看
public class MainActivity extends AppCompatActivity {
public MainActivity() {
super();
}
protected void onCreate(Bundle arg2) {
super.onCreate(arg2);
this.setContentView(2131296284);
this.findViewById(2131165218).setOnClickListener(new View$OnClickListener() {
public void onClick(View arg3) {
if(check.checkflag(MainActivity.this.findViewById(2131165238).getText().toString())) { // 很明显这里做了判断
Toast.makeText(MainActivity.this, "you are right~!", 1).show();
}
else {
Toast.makeText(MainActivity.this, "wrong!", 1).show();
}
}
});
}
}
跳转check发现果然又使用了JNI调用
public class check {
static {
System.loadLibrary("checkso");
}
public check() {
super();
}
public static native boolean checkflag(String arg0) {
}
}
轻车熟路apktool解压,ida静态分析lib下的so文件,定位checkflag函数
unsigned int __fastcall Java_com_example_p7xxtmx_1g_fakefunc_check_checkflag(int a1)
{
const char *v1; // r4@1
v1 = (const char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))(); // 应该就是我们的输入
sub_E08();
return __clz(strcmp(v1, off_6004)) >> 5; // off_6004='c2RuaXNjc2RuaXNjYWJjZA=='看着很像base64加密,解码得到sdniscsdniscabcd
}
int sub_E08()
{
char *v0; // r4@1
size_t v1; // r1@1
v0 = off_6004;
v1 = strlen(off_6004);
return sub_16D8(v0, v1); // 结合里面的代码以及传入的参数可知是base64解密函数
}
分别尝试这个字符串,以及解码的字符串均不对,联想到题目提示fake func,猜测函数发生变化
首先查看字符串发现除了上面那个还有另一个字符串,双击进去按X查看调用地方,并F5查看伪代码,以及X查看调用函数
可以看到strcmp被一个j_registerInlineHook做了处理,并调用了sub_E28函数,里面存储了正好是另一个特殊字符串
int __fastcall sub_E28(int a1)
{
int v1; // r4@1
int v2; // r0@1
int v3; // r0@1
v1 = a1;
v2 = sub_E08(); // 上面也出现过,就是base64解密
v3 = sub_1388(v1, v2); // v2是解密后的字符串,v1是我们的输入
return dword_6008(v3, "K4/7/faihmk9/WEMlfuFdpgrP86ckd4oQQ/UeAiZdx8="); // AES加密后和这个字符串比较
}
int sub_EC8()
{
int result; // r0@2
if ( j_registerInlineHook(&strcmp, sub_E28, &dword_6008) )
{
result = -1;
}
else
{
result = j_inlineHook(&strcmp);
if ( result )
result = -1;
}
return result;
}
网上搜了下直接发现类似代码
registerInlineHook传入的参数:第一个是要hook的目标函数地址,第二个是替换函数的指针,第三个是保留函数原来的指针
因此实际要分析的函数是上面这个sub_E28(),这里强烈安利ida插件findcrypt,真不是一般好用,不过配置了半天发现我这个版本ida不好用,果断换成了吾爱的ida7.7(好用滴很,还自带了python3.8)
结合插件的提示可知,这个函数属于AES加密,那上面
在线AES解密如下
reverse_re3
__int64 sub_940()
{
int v0; // eax
int v2; // [rsp+8h] [rbp-218h]
int v3; // [rsp+Ch] [rbp-214h]
char v4[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v5; // [rsp+218h] [rbp-8h]
v5 = __readfsqword(0x28u);
v3 = 0;
memset(v4, 0, 0x200uLL);
_isoc99_scanf(&unk_1278, v4, v4);
while ( 1 ) // 由特征可知大概是个迷宫问题,只能按wasd
{
do
{
v2 = 0;
sub_86C();
v0 = v4[v3];
if ( v0 == 'd' )
{
v2 = sub_E23();
}
else if ( v0 > 'd' )
{
if ( v0 == 's' )
{
v2 = sub_C5A();
}
else if ( v0 == 'w' )
{
v2 = sub_A92();
}
}
else
{
if ( v0 == 27 )
return 0xFFFFFFFFLL;
if ( v0 == 'a' )
v2 = sub_FEC();
}
++v3;
}
while ( v2 != 1 );
if ( dword_202AB0 == 2 )
break;
++dword_202AB0;
}
puts("success! the flag is flag{md5(your input)}");
return 1LL;
}
再去看sub_86C函数,里面大致说明了迷宫的形状为3个(15*15)正方形,i表示行,j表示列,且分别赋值给dword_202AB4和dword_202AB8
unsigned __int64 sub_86C()
{
int i; // [rsp+0h] [rbp-10h]
int j; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
for ( i = 0; i <= 14; ++i )
{
for ( j = 0; j <= 14; ++j )
{
if ( dword_202020[225 * dword_202AB0 + 15 * i + j] == 3 ) // 联系dword_202AB0==2退出,说明走三个方形,每个225大小
{
dword_202AB4 = i; // 等于3表示这是当前位置
dword_202AB8 = j;
break;
}
}
}
return __readfsqword(0x28u) ^ v3;
}
再去看按下方向键的操作
__int64 sub_E23() // 'd'
{
if ( dword_202AB8 != 14 ) // 首先判断dword_202AB8是否等于14,不等则返回0,继续循环按方向键
{
if ( dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] == 1 ) // 先判断右移是否可以走(是否等于1)
{
dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] = 3; // 如果可以这个位置置3,说明这是当前位置
dword_202020[225 * dword_202AB0 + 15 * dword_202AB4 + dword_202AB8] = 1; // 原来位置置1
}
else if ( dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] == 4 ) // 如果等于4就要退出循环,dword_202AB0++,说明当前这个方形迷宫走完了
{
return 1LL;
} // 如果不为1也不为0,说明走的不对,走的无效
}
return 0LL;
}
__int64 sub_C5A() // 's'
{
if ( dword_202AB4 != 14 )
{
if ( dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] == 1 ) // 按's'是直接进入下一行+15
{
dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] = 3;
dword_202020[225 * dword_202AB0 + 15 * dword_202AB4 + dword_202AB8] = 1;
}
else if ( dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] == 4 )
{
return 1LL;
}
}
return 0LL;
}
至此基本分析完成,迷宫为1的能走,为3表示当前位置,为4表示成功走到,关键要看dword_202020里的值。ida在它上面右键convert to python list(dword)
第一个:ddsssddddsssdss
第二个:dddddsssddddsssaassssddds
第三个:ddssddwddssssssdddssssdddss
crypt
ida64查看main代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // eax
void *v5; // rax
void *v7; // rax
int i; // [rsp+24h] [rbp-D4h]
_DWORD *v9; // [rsp+28h] [rbp-D0h]
char v10[32]; // [rsp+30h] [rbp-C8h] BYREF
char Str[128]; // [rsp+50h] [rbp-A8h] BYREF
strcpy(Str, "12345678abcdefghijklmnopqrspxyz");// copy到Str变量
memset(&Str[32], 0, 0x60ui64); // 填充Str
memset(v10, 0, 0x17ui64);
sub_1400054D0("%s", v10); // 输入字符串
v9 = malloc(0x408ui64); // 正好是分配了(256+2)*4=0x408个字节
v3 = strlen(Str); // 获取Str长度,应该是31
sub_140001120(v9, (__int64)Str, v3); // 获取的了一个初始化的v9,长度为258,内容是0、0、0-255
v4 = strlen(v10); // 获取输入字符串长度
sub_140001240(v9, (__int64)v10, v4); // 基本可以判断是rc4加密
for ( i = 0; i < 22; ++i )
{
if ( ((unsigned __int8)v10[i] ^ 0x22) != byte_14013B000[i] ) // 这里还做了个异或
{
v5 = (void *)sub_1400015A0(&off_14013B020, "error");
_CallMemberFunction0(v5, sub_140001F10);
return 0;
}
}
v7 = (void *)sub_1400015A0(&off_14013B020, "nice job");
_CallMemberFunction0(v7, sub_140001F10);
return 0;
}
__int64 __fastcall sub_140001120(_DWORD *a1, __int64 a2, int a3)
{
__int64 result; // rax
int i; // [rsp+0h] [rbp-28h]
int j; // [rsp+0h] [rbp-28h]
int v6; // [rsp+4h] [rbp-24h]
int v7; // [rsp+8h] [rbp-20h]
int v8; // [rsp+Ch] [rbp-1Ch]
_DWORD *v9; // [rsp+10h] [rbp-18h]
*a1 = 0;
a1[1] = 0;
v9 = a1 + 2;
for ( i = 0; i < 256; ++i )
v9[i] = i; // 初始化v9,也就是a1[2]之后的
v6 = 0;
result = 0i64;
LOBYTE(v7) = 0;
for ( j = 0; j < 256; ++j )
{
v8 = v9[j];
v7 = (unsigned __int8)(*(_BYTE *)(a2 + v6) + v8 + v7);// a2是Str字符串的地址
v9[j] = v9[v7];
v9[v7] = v8; // 很明显做了交换,结合256,这些都是RC4初始化的特征
if ( ++v6 >= a3 ) // a3是Str长度
v6 = 0;
result = (unsigned int)(j + 1);
}
return result;
}
直接借用以前的脚本解密即可
def get_key(key):
key_len = len(key)
a1, a2 = [], []
for i in range(256):
a1.append(i)
a2.append(ord(key[i % key_len]))
v5 = 0
for i in range(256):
v5 = (v5 + a1[i] + a2[i]) % 256
a1[i], a1[v5] = a1[v5], a1[i]
return a1
def decrypt(enc_key, enflag):
v7, v6 = 0, 0
flag = ''
for i in range(len(enflag)):
v7 = (v7 + 1) % 256
v6 = (v6 + enc_key[v7]) % 256
enc_key[v7], enc_key[v6] = enc_key[v6], enc_key[v7]
flag += chr(enc_key[(enc_key[v6] + enc_key[v7]) % 256] ^ enflag[i])
return flag
if __name__ == '__main__':
enc_key = get_key('12345678abcdefghijklmnopqrspxyz')
enflag = [i^0x22 for i in [0x9E, 0xE7, 0x30, 0x5F, 0xA7, 0x01, 0xA6, 0x53, 0x59, 0x1B, 0x0A, 0x20, 0xF1, 0x73, 0xD1, 0x0E, 0xAB, 0x09, 0x84, 0x0E, 0x8D, 0x2B]]
flag = decrypt(enc_key, enflag)
print(flag)
bad_python
尝试在线pyc反编译以及uncompyle6均报错编译失败,查询了下可能原因为文件头被篡改,因此尝试手动生成一个pyc文件,替换文件头
https://zhuanlan.zhihu.com/p/617737294
写一个print hi,由于原始pyc是python36编译的,我们也要用python3.6;编译获取pyc命令是python -m py_compile .hi.py,命令会在当前目录下生成__pycache__,里面就是pyc
winhex打开获取前16个字节复制到原来的上面,再使用uncompyle6即可成功反编译,获取的python如下
# uncompyle6 version 3.9.0
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: pyre.py
# Compiled at: 2023-12-18 18:22:59
# Size of source mod 2**32: 11 bytes
from ctypes import *
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
def encrypt(v, k):
v0 = c_uint32(v[0])
v1 = c_uint32(v[1])
sum1 = c_uint32(0)
delta = 195935983
for i in range(32):
v0.value += (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3]
sum1.value += delta
v1.value += (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3]
return (
v0.value, v1.value)
if __name__ == '__main__':
flag = input('please input your flag:')
k = [255, 187, 51, 68]
if len(flag) != 32:
print('wrong!')
exit(-1)
a = []
for i in range(0, 32, 8):
v1 = bytes_to_long(bytes(flag[i:i + 4], 'ascii'))
v2 = bytes_to_long(bytes(flag[i + 4:i + 8], 'ascii'))
a += encrypt([v1, v2], k)
enc = [
'4006073346', '2582197823', '2235293281', '558171287', '2425328816',
'1715140098', '986348143', '1948615354']
for i in range(8):
if enc[i] != a[i]:
print('wrong!')
exit(-1)
print('flag is flag{%s}' % flag)
只在源代码基础上稍加修改即可
# uncompyle6 version 3.9.0
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: pyre.py
# Compiled at: 2023-12-18 18:22:59
# Size of source mod 2**32: 11 bytes
from ctypes import *
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
def decrypt(v, k):
v0 = c_uint32(int(v[0]))
v1 = c_uint32(int(v[1]))
sum1 = c_uint32(0)
delta = 195935983
sum1.value += delta * 32 # 原来到最后加到了delta * 32,因此逆向初始值为此
for i in range(32):
v1.value -= (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3] # 原来加号改为减号
sum1.value -= delta
v0.value -= (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3]
return (
v0.value, v1.value)
if __name__ == '__main__':
flag = ''
k = [255, 187, 51, 68]
a = []
enc = [
'4006073346', '2582197823', '2235293281', '558171287', '2425328816',
'1715140098', '986348143', '1948615354']
for i in range(0, 8, 2):
v1 = enc[i]
v2 = enc[i+1]
a += decrypt([v1, v2], k)
for i in range(8):
flag += long_to_bytes(a[i]).decode('utf-8')
print('flag is flag{%s}' % flag)
结果:flag is flag{Th1s_1s_A_Easy_Pyth0n__R3veRse_0}
easyre-xctf
这道题类似misc思路,将flag隐藏到里面没用的函数里。首先upx脱壳,ida64查找到特殊字符串“d_0n3_4nd_tw0}”,同时前面有个==f_part2==名称,再去看函数可以看到有一个part1,里面传递了前半部分flag(按R转为字符,同时小段存储要反转下)
得到flag{UPX_4nd_0n3_4nd_tw0}
CatFly
这题真的是新手题么,太打击了,代码太多了反编译又看不出点头绪,对着wp才分析出代码
首先尝试运行(linux下,记得chmod +x赋予权限)
可以看到第一行一直再打印字符,最后一行一直在计数,所以关键是找到打印上面这些无序字符的代码
ida64找到main,再去找printf
while ( v13 )
{
if ( dword_E104 )
printf("\x1B[H");
else
printf("\x1B[u");
for ( k = dword_E1EC; k < dword_E1F0; ++k )
{
for ( m = dword_E1F4; m < dword_E1F8; ++m )
{
if ( k <= 23 || k > 42 || m >= 0 )
{
if ( m >= 0 && (unsigned int)k <= 0x3F && m <= 63 )
{
v19 = off_FA20[v24][k][m];
off_FA88 = sub_6314((unsigned int)v24, k, m, (__int64)v12);// 这里是关键的赋值
}
else
{
v19 = 44;
}
}
else
{
v18 = (2 - m) % 16 / 8;
if ( ((v24 >> 1) & 1) != 0 )
v18 = 1 - v18;
s[128] = (__int64)",,>>&&&+++###==;;;,,";
v19 = asc_BFE3[v18 - 23 + k];
if ( !v19 )
v19 = 44;
}
if ( v25 )
{
printf("%s", *((const char **)&unk_FCC0 + v19));
}
else if ( v19 == v22 || !*((_QWORD *)&unk_FCC0 + v19) )
{
printf("%s", off_FA88);// 这里是关键的打印
}
else
{
v22 = v19;
printf("%s%s", *((const char **)&unk_FCC0 + v19), off_FA88);
}
}
sub_65E2(1LL);
}
if ( dword_E100 )
{
time(&time1);
v11 = difftime(time1, timer);
v10 = sub_63FF((unsigned int)(int)v11);
for ( n = (dword_E1FC - 29 - v10) / 2; n > 0; --n )
putchar(32);
dword_E1E8 += printf("\x1B[1;37mYou have nyaned for %d times!\x1B[J\x1B[0m", (unsigned int)++dword_108E0);
}
v22 = 0;
++v23;
if ( dword_104C4 && v23 == dword_104C4 )
sub_6471();
if ( !off_FA20[++v24] )
v24 = 0LL;
usleep(1000 * v27);
}
重点看sub_6314函数
char *__fastcall sub_6314(__int64 a1, int a2, int a3, __int64 a4)
{
if ( a2 != 18 ) // 必须a2=18
return (char *)a4;
if ( a3 <= 4 || a3 > 54 ) // a3范围5~54,共50个
return (char *)a4;
byte_104C9 = 32;
dword_E120[a3 - 5] ^= sub_62B5();
if ( (unsigned __int8)sub_62E3(dword_E120[a3 - 5]) )
byte_104C8 = dword_E120[a3 - 5] & 0x7F;
else
byte_104C8 = 32;
return &byte_104C8;
}
unsigned int dword_E1E8 = 0x00001106;
unsigned int dword_E120[50] = {
0x000027FB, 0x000027A4, 0x0000464E, 0x00000E36, 0x00007B70, 0x00005E7A, 0x00001A4A, 0x000045C1,
0x00002BDF, 0x000023BD, 0x00003A15, 0x00005B83, 0x00001E15, 0x00005367, 0x000050B8, 0x000020CA,
0x000041F5, 0x000057D1, 0x00007750, 0x00002ADF, 0x000011F8, 0x000009BB, 0x00005724, 0x00007374,
0x00003CE6, 0x0000646E, 0x0000010C, 0x00006E10, 0x000064F4, 0x00003263, 0x00003137, 0x000000B8,
0x0000229C, 0x00007BCD, 0x000073BD, 0x0000480C, 0x000014DB, 0x000068B9, 0x00005C8A, 0x00001B61,
0x00006C59, 0x00005707, 0x000009E6, 0x00001FB9, 0x00002AD3, 0x000076D4, 0x00003113, 0x00007C7E,
0x000011E0, 0x00006C70
};
__int64 sub_62B5()
{
dword_E1E8 = 1103515245 * dword_E1E8 + 12345;
return (dword_E1E8 >> 10) & 0x7FFF;
}
_BOOL8 __fastcall sub_62E3(char a1)
{
return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20;
}
解密
#include<iostream>
#include<string.h>
#include <time.h>
using namespace std;
unsigned int dword_E1E8 = 0x00001106;
unsigned int dword_E120[50] = {
0x000027FB, 0x000027A4, 0x0000464E, 0x00000E36, 0x00007B70, 0x00005E7A, 0x00001A4A, 0x000045C1,
0x00002BDF, 0x000023BD, 0x00003A15, 0x00005B83, 0x00001E15, 0x00005367, 0x000050B8, 0x000020CA,
0x000041F5, 0x000057D1, 0x00007750, 0x00002ADF, 0x000011F8, 0x000009BB, 0x00005724, 0x00007374,
0x00003CE6, 0x0000646E, 0x0000010C, 0x00006E10, 0x000064F4, 0x00003263, 0x00003137, 0x000000B8,
0x0000229C, 0x00007BCD, 0x000073BD, 0x0000480C, 0x000014DB, 0x000068B9, 0x00005C8A, 0x00001B61,
0x00006C59, 0x00005707, 0x000009E6, 0x00001FB9, 0x00002AD3, 0x000076D4, 0x00003113, 0x00007C7E,
0x000011E0, 0x00006C70
};
int sub_62B5()
{
dword_E1E8 = 1103515245 * dword_E1E8 + 12345;
return (dword_E1E8 >> 10) & 0x7FFF;
}
bool sub_62E3(char a1)
{
return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20;
}
int calc(int cnt) {
int i=0;
while(cnt){
cnt=cnt/10;
i++;
}
return i;
}
int main () {
int cnt = 0;
clock_t start, end; //定义clock_t变量
start = clock(); //开始时间
while (1) {
char flag[50];
for (int i = 0; i < 50; i++) {
dword_E120[i] ^= sub_62B5();
if (sub_62E3(dword_E120[i]))
flag[i] = dword_E120[i] & 0x7F;
else
flag[i] = ' ';
}
if (!strncmp(flag, "CatCTF", 6)) {
cout << flag << endl;
break;
}
cnt++;
dword_E1E8 += 41;
dword_E1E8 += calc(cnt); // 后面记得+了printf返回值
}
cout << "cnt: " << cnt << endl;
end = clock(); //结束时间
cout << "took " << double(end-start)/CLOCKS_PER_SEC << "s" << endl; //输出时间(单位:s)
}
BABYRE
ida64查看main
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[24]; // [rsp+0h] [rbp-20h] BYREF
int v5; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 181; ++i )
judge[i] ^= 0xCu; // 这里查看是数组形式,实际上异或完是汇编代码
printf("Please input flag:");
__isoc99_scanf("%20s", s);
v5 = strlen(s);
if ( v5 == 14 && (*(unsigned int (__fastcall **)(char *))judge)(s) ) // 但是这里却看起来judge是个函数
puts("Right!");
else
puts("Wrong!");
return 0;
}
这道题实际上judge数组是汇编指令,但ida反编译识别成了数据
因此需要调试,运行到judge异或完查看其具体代码。这里学习到了如何远程连接Linux来进行调试(这里文件只能在linux上跑)
https://blog.csdn.net/m0_46296905/article/details/115794076
在printf上打断点,然后F5查看judge,并按下C转换为代码,如下图,已经有函数代码的逻辑
再按下P生成函数,F5即可查看伪代码
__int64 __fastcall judge(__int64 a1)
{
char v2[5]; // [rsp+8h] [rbp-20h] BYREF
char v3[9]; // [rsp+Dh] [rbp-1Bh] BYREF
int i; // [rsp+24h] [rbp-4h]
qmemcpy(v2, "fmcd", 4);
v2[4] = 127;
qmemcpy(v3, "k7d;V`;np", sizeof(v3));
for ( i = 0; i <= 13; ++i )
*(_BYTE *)(i + a1) ^= i; // 先按位异或
for ( i = 0; i <= 13; ++i )
{
if ( *(_BYTE *)(i + a1) != v2[i] ) // 再和v2比较(实际上v2+v3)
return 0LL;
}
return 1LL;
}
parallel-comparator-200
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#define FLAG_LEN 20
void * checking(void *arg) {
char *result = malloc(sizeof(char));
char *argument = (char *)arg;
*result = (argument[0]+argument[1]) ^ argument[2];
return result;
}
int highly_optimized_parallel_comparsion(char *user_string)
{
int initialization_number;
int i;
char generated_string[FLAG_LEN + 1];
generated_string[FLAG_LEN] = '\0';
while ((initialization_number = random()) >= 64); // 随机数大于等于64,运行代码发现固定41
int first_letter;
first_letter = (initialization_number % 26) + 97;
pthread_t thread[FLAG_LEN];
char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7};
char *arguments[20];
for (i = 0; i < FLAG_LEN; i++) {
arguments[i] = (char *)malloc(3*sizeof(char));
arguments[i][0] = first_letter;
arguments[i][1] = differences[i];
arguments[i][2] = user_string[i];
pthread_create((pthread_t*)(thread+i), NULL, checking, arguments[i]); // 创建线程,调用checking函数,传入参数arguments[i]
}
void *result;
int just_a_string[FLAG_LEN] = {115, 116, 114, 97, 110, 103, 101, 95, 115, 116, 114, 105, 110, 103, 95, 105, 116, 95, 105, 115}; // 没用
for (i = 0; i < FLAG_LEN; i++) {
pthread_join(*(thread+i), &result); // 等待线程结束,result接收该线程返回值
generated_string[i] = *(char *)result + just_a_string[i];
free(result);
free(arguments[i]);
}
int is_ok = 1;
for (i = 0; i < FLAG_LEN; i++) {
if (generated_string[i] != just_a_string[i]) // 要想相等,result为0
return 0;
}
return 1;
}
int main()
{
char *user_string = (char *)calloc(FLAG_LEN+1, sizeof(char));
fgets(user_string, FLAG_LEN+1, stdin);
int is_ok = highly_optimized_parallel_comparsion(user_string);
if (is_ok)
printf("You win!\n");
else
printf("Wrong!\n");
return 0;
}
题目很简单,解题过程中配c语言环境搞了半天,两点要注意的
- 用gcc编译而不是g++
- 再配置中加上参数-lpthread才行,不然会报错
#include <iostream>
using namespace std;
int main() {
int FLAG_LEN = 20;
char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7};
for (int j = 0; j < 26; j++) {
for (int i = 0; i < FLAG_LEN; i++) {
printf("%c", (differences[i]+j+97)^0);
}
printf("\n");
}
}
可以找到一个规律的字符串lucky_hacker_you_are
secret-galaxy-300
这题类似于Misc隐藏信息的思路,题目压缩包三种文件是不同平台的可执行文件
首先ida32查到很多打印的字符串,主要都是星系,ollydbg下个断点,可以找到很多星系名
发现打印只打印前五个,所以第六个隐藏猫腻
int __cdecl print_starbase(int a1)
{
int result; // eax
const char *v2; // edx
int i; // [esp+1Ch] [ebp-Ch]
puts("--------------GALAXY DATABASE-------------");
printf("%10s | %s | %s\n", "Galaxy name", "Existence of life", "Distance from Earth");
result = puts("-------------------------------------------");
for ( i = 0; i <= 4; ++i ) // 前五个
{
if ( *(_DWORD *)(24 * i + a1 + 8) == 1 )
v2 = "INHABITED";
else
v2 = "IS NOT INHABITED"; // 都是这个选项
result = printf("%11s | %17s | %d\n", *(const char **)(24 * i + a1), v2, *(_DWORD *)(24 * i + a1 + 4));
}
return result;
}
因此 ollydbg改汇编代码判断,并没有看到什么flag
因此转变思路去找函数,发现一个没有被主函数调用的函数
int __libc_csu_gala()
{
int result; // eax
sc[0] = off_409014;
sc[3] = &byte_40DAC0;
sc[1] = 31337;
sc[2] = 1; // 可以看到连续的字节被赋值,且off这些变量都是字符串数组
byte_40DAC0 = off_409004[0][8];
byte_40DAC1 = off_409010[0][7];
byte_40DAC2 = off_409008[0][4];
byte_40DAC3 = off_409004[0][6];
byte_40DAC4 = off_409004[0][1];
byte_40DAC5 = off_409008[0][2];
byte_40DAC6 = '_'; // 95正好是下划线
byte_40DAC7 = off_409004[0][8];
byte_40DAC8 = off_409004[0][3];
byte_40DAC9 = off_40900C[0][5];
byte_40DACA = 95;
byte_40DACB = off_409004[0][8];
byte_40DACC = off_409004[0][3];
byte_40DACD = off_409004[0][4];
byte_40DACE = off_409010[0][6];
byte_40DACF = off_409010[0][4];
byte_40DAD0 = off_409004[0][2];
byte_40DAD1 = 95;
byte_40DAD2 = off_409010[0][6];
result = *((unsigned __int8 *)off_409008[0] + 3);
byte_40DAD3 = off_409008[0][3];
byte_40DAD4 = 0;
return result;
}
看到字符串内容的方法就是ida断点到return result前,双击可以查看,发现特殊字符串即为flag