看雪CTF-Writeup
2019/03/11

第一题 流浪者

下载到程序 简单执行下 就是简单的creakerme ,需要输入正确验证码。

载入 ghidra 分析

void __fastcall FUN_00401890(CWnd *param_1)

{

  int local_78 [26];

  pCVar1 = param_1 + 100;
  local_8 = param_1;
  this = GetDlgItem(param_1,0x3ea);
  GetWindowTextA(this,(CString *)pCVar1);
  iVar2 = FUN_00401a30((int *)(local_8 + 100));
  local_c = GetBuffer((CString *)(local_8 + 100),iVar2);
  sVar3 = strlen(local_c);
  if (sVar3 == 0) {
    MessageBoxA(local_8,&DAT_004035dc,(char *)0x0,0);
  }
  else {
    local_10 = 0;
    while (local_c[local_10] != 0) {
      if ((local_c[local_10] < 58) && (47 < local_c[local_10])) {
        local_78[local_10] = (int)local_c[local_10] + -0x30;
      }
      else {
        if ((local_c[local_10] < 123) && (96 < local_c[local_10])) {
          local_78[local_10] = (int)local_c[local_10] + -0x57;
        }
        else {
          if ((local_c[local_10] < 91) && (64 < local_c[local_10])) {
            local_78[local_10] = (int)local_c[local_10] + -0x1d;
          }
          else {
            FUN_004017b0();//返回错误
          }
        }
      }
      local_10 = local_10 + 1;
    }
    FUN_004017f0((int)local_78); 得到的local_78数组传入到FUN_004017f0函数
  }
  return;
}

程序从文本框获取到字符串、然后判断是否为空 如果为空 弹窗 程序结束

可以看到接收长度为26 // int local_78 [26];

然后是对接收到的字符串进行判断并进行运算操作

进入FUN_004017f0

void __cdecl FUN_004017f0(int param_1)

{
  int iVar1; 
  char local_28 [28];
  undefined4 local_c;
  int local_8;
  // param_1为上函数运算得到的数组
  local_8 = 0;
  local_c = 0;
  while ((*(int *)(param_1 + local_8 * 4) < 0x3e && (-1 < *(int *)(param_1 + local_8 * 4)))) {
    local_28[local_8] =
         "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
         [*(int *)(param_1 + local_8 * 4)];
    local_8 = local_8 + 1;
  }
  local_28[local_8] = 0;
  iVar1 = strcmp(local_28,"KanXueCTF2019JustForhappy");
  if (iVar1 == 0) {
    FUN_00401770();//返回pass
  }
  else {
    FUN_004017b0();//返回错误
  }
  return;
}

iVar1 = strcmp(local_28,”KanXueCTF2019JustForhappy”);

可以得到 local_28的值为 KanXueCTF2019JustForhappy

又可以看到

local_28[local_8] =
“abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ”
[(int )(param_1 + local_8 * 4)];

local_28[local_8] 数组的每一位是从这段字符串中取出 取出位置由param_1决定

所以在当前函数 已知 local_28 已知字符串 求param_1

re.c

char a[28]= "KanXueCTF2019JustForhappy";
   char b[62]="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
  int c[26];
   int d=0;
   while (d<25) {
       for (int i=0; b[i]; i++) {
           if (b[i]==a[d]) {
               c[d]=i;
              printf("%d,",c[d]);
               d=d+1;
               //break ;
           }

       }
   }

得到param_1

param_1[25]={19,0,27,59,44,4,11,55,14,30,28,29,37,18,44,42,43,14,38,41,7,0,39,39,48}

接着回带到上一函数

关键位置

local_10 = 0;

      if ((local_c[local_10] < 58) && (47 < local_c[local_10])) {
        local_78[local_10] = (int)local_c[local_10] + -0x30;
      }
      else {
        if ((local_c[local_10] < 123) && (96 < local_c[local_10])) {
          local_78[local_10] = (int)local_c[local_10] + -0x57;
        }
        else {
          if ((local_c[local_10] < 91) && (64 < local_c[local_10])) {
            local_78[local_10] = (int)local_c[local_10] + -0x1d;
          }
          else {
            FUN_004017b0();//返回错误
          }
        }
      }

已知最后获得的字符串 local_78 已知判断条件 可以求出原字符串

因为获得的字符串最后进行减数操作 所以求原函数只需要将判断条件的值减取相应判断条件里的减去的值并将判断条件里的值由减改为加即可。

re1.c

int v5[25]={19,0,27,59,44,4,11,55,14,30,28,29,37,18,44,42,43,14,38,41,7,0,39,39,48}; //得到的local_78
   int local_10 = 0;
    int v5[26];
    local_10 = 0;
    while (local_10 != 25) {

        if ((c[local_10] < 62) && (35 < c[local_10])) {
            v5[local_10] = (int)c[local_10] + 29;
        }
        else {
            if ((c[local_10] < 36) && (9 < c[local_10])) {
                v5[local_10] = (int)c[local_10] + 87;
            }
            else {
                if ((c[local_10] < 10) || (-1 < c[local_10])) {
                    v5[local_10] = (int)c[local_10] + 48;
                }

            }
        }
        printf("%c",v5[local_10]);
        local_10 = local_10 + 1;

    }
    printf("\n");

所以可以逆向求出应该输入的字符串

exp.c

//
//  main.c
//  re1
//
//  Created by ios on 2019/3/10.
//  Copyright © 2019 ios. All rights reserved.
//
#include<stdio.h>

int main()
{
    char a[28]= "KanXueCTF2019JustForhappy";
    char b[62]="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
   int c[26];
    int d=0;
    while (d<25) {
        for (int i=0; b[i]; i++) {
            if (b[i]==a[d]) {
                c[d]=i;
               //printf("%d,",c[d]);
                d=d+1;
                //break ;
            }

        }
    }



//int Str[25]={19,0,27,59,44,4,11,55,14,30,28,29,37,18,44,42,43,14,38,41,7,0,39,39,48};
   int local_10 = 0;
    int v5[26];
    local_10 = 0;
    while (local_10 != 25) {

        if ((c[local_10] < 62) && (35 < c[local_10])) {
            v5[local_10] = (int)c[local_10] + 29;
        }
        else {
            if ((c[local_10] < 36) && (9 < c[local_10])) {
                v5[local_10] = (int)c[local_10] + 87;
            }
            else {
                if ((c[local_10] < 10) || (-1 < c[local_10])) {
                    v5[local_10] = (int)c[local_10] + 48;
                }

            }
        }
        printf("%c",v5[local_10]);
        local_10 = local_10 + 1;

    }
    printf("\n");


}

成功拿到key

j0rXI4bTeustBiIGHeCF70DDM

tips

这里如果不好理解的可以对照IDA理解源码,并且使用动态调试 在strcmp处下断

图一

任意输入字符例如 A 达到断点处会得到

图二

通过两次处理后的结果为8 ,因为第二次做的处理为从数组出取元素 所以通过结果得到 param_1 的值

逆推到第一次处理 运算得到第一位应输入字符

这样可以帮助理解(动态调试大法好~)

请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!