首页
社区
课程
招聘
aliyunctf2024 逆向 Euler Writeup
2024-3-25 18:56 4043

aliyunctf2024 逆向 Euler Writeup

2024-3-25 18:56
4043

首先拿到题目,可以看到是一个windows的exe可执行文件。


尝试运行,需要输入口令,然后其判断对错。

PS C:\> .\Euler.exe
input:
randomflag
Wrong

我们使用ghidra反编译,得到“源码”。

找到“entry”

void entry(void)

{
  __security_init_cookie();
  FUN_140001428();
  return;
}

然后顺着函数找到主逻辑。

uint FUN_140001428(void)

{
  undefined8 uVar1;
  bool bVar2;
  int iVar3;
  ulonglong uVar4;
  code **ppcVar5;
  longlong *plVar6;
  char *pcVar7;
  undefined8 *puVar8;
  uint *puVar9;
  ulonglong uVar10;
  uint unaff_EBX;
  int *in_R9;
  
  uVar4 = __scrt_initialize_crt(1);
  if (uVar4 == '\0') {
    FUN_140001a9c(7);
  }
  else {
    bVar2 = false;
    uVar4 = __scrt_acquire_startup_lock();
    unaff_EBX = unaff_EBX & 0xffffff00 | uVar4 & 0xff;
    if (DAT_140004700 != 1) {
      if (DAT_140004700 == 0) {
        DAT_140004700 = 1;
        iVar3 = _initterm_e(&DAT_1400031f0,&DAT_140003208);
        if (iVar3 != 0) {
          return 0xff;
        }
        _initterm(&DAT_1400031d8,&DAT_1400031e8);
        DAT_140004700 = 2;
      }
      else {
        bVar2 = true;
      }
      __scrt_release_startup_lock(uVar4 & 0xff);
      ppcVar5 = FUN_140001a84();
      if ((*ppcVar5 != 0x0) &&
         (uVar4 = __scrt_is_nonwritable_in_current_image(ppcVar5), uVar4 != '\0')) {
        (**ppcVar5)(0,2);
      }
      plVar6 = FUN_140001a8c();
      if ((*plVar6 != 0) && (uVar4 = __scrt_is_nonwritable_in_current_image(plVar6), uVar4 != '\0'))
      {
        _register_thread_local_exe_atexit_callback(*plVar6);
      }
      pcVar7 = _get_initial_narrow_environment();
      puVar8 = __p___argv();
      uVar1 = *puVar8;
      puVar9 = __p___argc();
      uVar10 = *puVar9;
      unaff_EBX = FUN_1400010e0(uVar10,uVar1,pcVar7,in_R9);
      uVar4 = __scrt_is_managed_app();
      if (uVar4 != '\0') {
        if (!bVar2) {
          _cexit();
        }
        __scrt_uninitialize_crt(CONCAT71(uVar10 >> 8,1),'\0');
        return unaff_EBX;
      }
      goto LAB_140001594;
    }
  }
  FUN_140001a9c(7);
LAB_140001594:
                    /* WARNING: Subroutine does not return */
  exit(unaff_EBX);
}
/* DISPLAY WARNING: Type casts are NOT being printed */

void FUN_1400010e0(undefined8 param_1,undefined8 param_2,char *param_3,int *param_4)

{
  longlong lVar1;
  int iVar2;
  longlong lVar3;
  char *pcVar4;
  int *piVar5;
  uint uVar6;
  char *pcVar7;
  int *piVar8;
  int *piVar9;
  undefined auStack_68 [32];
  undefined local_48 [16];
  undefined local_38 [16];
  undefined local_28 [16];
  undefined2 local_18;
  ulonglong local_10;
  
  local_10 = DAT_140004008 ^ auStack_68;
  local_48 = ZEXT816(0);
  local_18 = 0;
  local_38 = ZEXT816(0);
  local_28 = ZEXT816(0);
  FUN_140001020("input:\n",param_2,param_3,param_4);
  pcVar7 = local_48;
  FUN_140001080(&DAT_140003258,pcVar7,param_3,param_4);
  lVar1 = -1;
  do {
    lVar3 = lVar1;
    lVar1 = lVar3 + 1;
  } while (local_48[lVar3 + 1] != '\0');
  if (lVar3 + 1 != 0x1d) {
LAB_1400012fb:
    FUN_140001020("Wrong\n",pcVar7,param_3,param_4);
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  param_3 = lVar3 - 0x12;
  pcVar7 = "aliyunctf{";
  iVar2 = strncmp(local_48,"aliyunctf{",param_3);
  if ((iVar2 != 0) || (local_38[12] != '}')) goto LAB_1400012fb;
  piVar9 = 0x0;
  pcVar7 = 0xffffffffffffffff;
  do {
    param_3 = pcVar7;
    pcVar7 = param_3 + 1;
  } while ((local_48 + 0xb)[param_3] != '\0');
  piVar8 = piVar9;
  if (param_3 != 0x0) {
    pcVar4 = local_48 + 10;
    do {
      pcVar7 = piVar8;
      if (8 < *pcVar4 - 0x30U) goto LAB_1400012fb;
      uVar6 = piVar8 + 1;
      piVar8 = uVar6;
      pcVar4 = pcVar4 + 1;
    } while (uVar6 < param_3);
  }
  if (local_48[12] < local_48[11]) {
    piVar8 = local_48[13];
    if (local_48[13] < local_48[14]) {
      if (local_48[10] == local_38[2]) {
        if (local_38[5] == local_38[9]) {
          if ((((local_48[15] < local_38[4]) && (local_48[13] < local_38[7])) &&
              (local_38[1] < local_48[14])) && ((local_38[8] == '7' && (local_38[11] == '4')))) {
            pcVar4 = param_3 + -1;
            piVar8 = &DAT_140004040;
            param_4 = piVar9;
            pcVar7 = piVar8;
            if (param_3 != 0x1) {
              param_3 = local_48 + 0xb;
              do {
                lVar1 = *param_3 + -0x30 + (param_3[-1] + -0x30) * 9;
                if (((&DAT_140004040)[lVar1] != 1) ||
                   (lVar3 = param_3[-1] + -0x30 + (*param_3 + -0x30) * 9,
                   (&DAT_140004040)[lVar3] != 1)) goto LAB_1400012fb;
                uVar6 = param_4 + 1;
                param_4 = uVar6;
                (&DAT_140004040)[lVar1] = 0;
                param_3 = param_3 + 1;
                (&DAT_140004040)[lVar3] = 0;
              } while (uVar6 < pcVar4);
            }
            param_3 = &DAT_140004184;
            piVar5 = piVar9;
            do {
              if (*piVar8 != 0) goto LAB_1400012fb;
              piVar5 = piVar5 + 1;
              piVar8 = piVar8 + 1;
            } while ((piVar5 < 9) ||
                    (piVar8 = pcVar7 + 0x24, piVar5 = piVar9, pcVar7 = piVar8, piVar8 < 0x140004184)
                    );
            pcVar7 = "Right\n";
            goto LAB_1400012c7;
          }
        }
      }
    }
  }
  pcVar7 = "Wrong\n";
LAB_1400012c7:
  FUN_140001020(pcVar7,piVar8,param_3,param_4);
  FUN_140001320(local_10 ^ auStack_68);
  return;
}

可以看到“FUN_1400010e0”是主逻辑,在flag正确的情况下会返回“Right”,否则返回“Wrong”。


大致对程序进行走读,发现其逻辑为对flag的一些检查,如果同时满足便可接受。检查包括:长度检查(等于29)、前缀后缀检查(aliyunctf{XXXXX})、所有的字符的ascii值不大于56、一些flag不同位置的管理(如第14位小于第8位等)、以及一个根据mask的校验。这个检查并不难理解,但是如何找到符合这些条件的字符串呢?


首先为了方便理解,我们可以用Python把同样的逻辑写一遍。在确定无误的情况下,整理代码使其意图更清晰。最终得到以下代码。

DAT_140004040 = [  # the mask
    0, 0, 1, 0, 0, 1, 0, 0, 1, 
    0, 0, 0, 1, 1, 1, 0, 0, 1, 
    1, 0, 0, 1, 0, 0, 1, 1, 0, 
    0, 1, 1, 0, 1, 0, 0, 0, 1, 
    0, 1, 0, 1, 0, 0, 1, 0, 0, 
    1, 1, 0, 0, 0, 0, 1, 0, 1, 
    0, 0, 1, 0, 1, 1, 0, 0, 1, 
    0, 0, 1, 0, 0, 0, 0, 0, 1, 
    1, 1, 0, 1, 0, 1, 1, 1, 0
]


def fail():
    print('Wrong')
    raise


def check_len(string):
    if len(string) == 29:
        return
    
    fail()


def check_prefix_postfix(string):
    if string.startswith('aliyunctf{') and string[28] == '}':
        return
    
    fail()


def check_char_range(string):
    for c in string[10:-1]:
        if 56 < ord(c):
            fail()


def check_some_pos(string):
    if (string[12] < string[11]):
        if (string[13] < string[14]):
            if (string[10] == string[18]):
                if (string[21] == string[25]):
                    if ((((string[15] < string[20]) 
                          and (string[13] < string[23])) 
                          and (string[17] < string[14])) 
                          and ((string[24] == '7' and (string[27] == '4')))):
                        return
    
    fail()
                    
     
def check_final(string):
    local_48 = [ord(i) for i in string]
    lVar4 = len(local_48) - 1  # 28

    for param_4 in range(len(local_48) - 12):  # 17
        param_3 = 11 + param_4
        lVar1 = local_48[param_3] - 48 + (local_48[param_3 - 1] - 48) * 9
        lVar4 = local_48[param_3 - 1] - 48 + (local_48[param_3] - 48) * 9
        if DAT_140004040[lVar1] != 1 or DAT_140004040[lVar4] != 1:
            fail()
        
        DAT_140004040[lVar1] = 0
        DAT_140004040[lVar4] = 0
    
    if any(DAT_140004040):
        fail()

    return


def mimic(ans=None):
    if ans is None:
        ans = input("input:")
    check_len(ans)  
    check_prefix_postfix(ans)
    check_char_range(ans)  
    check_some_pos(ans)
    check_final(ans)
    print('Right')

通过"check_final"的逻辑可分析,对于flag内部的内容,也就是aliyunctf{XXXXX}中的XXXXX的校验。首先我们不考虑mask的索引出现负数的情况,所以锁定可能的字符在48到56之间。其次看出每次校验的对象是挨着的前后两个字符,其ascii值会用来和mask的两处作比较。如果成功,会将mask相应的位置制成0。


查看mask,有34个1,而XXXXX中连续的两个字符一共有17组,刚好2个mask对应一组。


我们可以先找到所有可能的连续字符的组合,以符合mask。注意,我们不在意顺序,因为“ab”和“ba”的mask完全一样。可以注意到,“ab”和“ba”不能同时出现于flag内。

good_2_grams = {}
for a in range(48, 56 + 1):  # no less than 48 since negative index is not considered
    for b in range(48, 56 + 1):
        pos0 = a - 48 + (b - 48) * 9
        pos1 = (a - 48) * 9 + b - 48
        if pos0 >= 0 and pos1 >= 0 and DAT_140004040[pos0] and DAT_140004040[pos1]:
            key = tuple(sorted([pos0, pos1]))
            if not key in good_2_grams:
                good_2_grams[key] = set()
            good_2_grams[key].add(tuple(sorted([a, b])))

可以看到“good_2_grams”中刚好有17组字符串(不分前后顺序)。那么现在我们可以遍历所有可能的flag。首先字符串必须遵循检查的规则,对于flag中每一位字符,其可能的下一位字符一定在“good_2_grams”中能找到,且每个只能用一次。通过“check_some_pos”中的检查,我们可以知道flag中有几个位置的字符。

我们写一个DFS算法,根据条件遍历和筛选,找到可能的flag。

def solve():
    good_2_grams = {}
    for a in range(48, 56 + 1):  # no less than 48 since negative index is not considered
        for b in range(48, 56 + 1):
            pos0 = a - 48 + (b - 48) * 9
            pos1 = (a - 48) * 9 + b - 48
            if pos0 >= 0 and pos1 >= 0 and DAT_140004040[pos0] and DAT_140004040[pos1]:
                key = tuple(sorted([pos0, pos1]))
                if not key in good_2_grams:
                    good_2_grams[key] = set()
                good_2_grams[key].add(tuple(sorted([a, b])))

    # we notice that good_2_grams has 34 keys, and each has only one item

    edge = {}  # edge[a][b] means whether "ab" or "ba" is used in flag enumerating
    for val in good_2_grams.values():
        a, b = list(val)[0]
        if a not in edge:
            edge[a] = {}
        if b not in edge:
            edge[b] = {}
        edge[a][b] = 0
        edge[b][a] = 0

    # possible_flags = []


    def rules_checked(flag):
        nums = [ord(i) for i in flag]
        if not (nums[11] > nums[12]):
            return False
        if not (nums[14] > nums[13]):
            return False
        if not (nums[25] == nums[21]):
            return False
        if not (nums[20] > nums[15]):
            return False
        if not (nums[23] > nums[13]):
            return False
        if not (nums[14] > nums[17]):
            return False
        return True

    total_tries = 0
    def dfs(flag='0'):
        nonlocal total_tries
        # return false if flag is invalid
        i = len(flag) - 1

        # check against known, from position rules
        known = '0???????0?????7??4'
        if known[i] != '?' and flag[i] != known[i]:
            total_tries += 1
            return False
        
        char = ord(flag[i])
        if len(flag) >= 18:
            final = 'aliyunctf{' + flag + '}'
            if rules_checked(final):
                print('Correct flag is:', final)
            total_tries += 1
            return True
        
        for v in edge[char]:
            if edge[char][v]:
                continue

            edge[char][v] = 1
            edge[v][char] = 1
            dfs(flag + chr(v))
            edge[char][v] = 0
            edge[v][char] = 0

    dfs()
    print(f'Enumerated {total_tries} times')

最终得到flag

python3 solve.py                                  
Correct flag is: aliyunctf{085134620568327814}
Enumerated 3367 times



[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞3
打赏
分享
最新回复 (3)
雪    币: 19085
活跃值: (28674)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-3-26 09:21
2
1
感谢分享
雪    币: 857
活跃值: (294)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
Vsbat 4 2024-3-26 13:06
3
0
那,这就是人才
雪    币: 715
活跃值: (29)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_opmktjff 2024-3-27 08:18
4
0
Vsbat 那,这就是人才
谢谢大佬
游客
登录 | 注册 方可回帖
返回