【攻防世界】reverse | re1-100 详细题解 WP

【攻防世界】reverse | re1-100 详细题解 WP

下载附件

main函数伪代码:

c 复制代码
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  __pid_t v3; // eax
  size_t v4; // rax
  ssize_t v5; // rbx
  bool v6; // al
  bool bCheckPtrace; // [rsp+13h] [rbp-1BDh]
  ssize_t numRead; // [rsp+18h] [rbp-1B8h]
  ssize_t numReada; // [rsp+18h] [rbp-1B8h]
  char bufWrite[200]; // [rsp+20h] [rbp-1B0h] BYREF
  char bufParentRead[200]; // [rsp+F0h] [rbp-E0h] BYREF
  unsigned __int64 v12; // [rsp+1B8h] [rbp-18h]

  v12 = __readfsqword(0x28u);
  bCheckPtrace = detectDebugging();
  if ( pipe(pParentWrite) == -1 )
    exit(1);
  if ( pipe(pParentRead) == -1 )
    exit(1);
  v3 = fork();
  if ( v3 != -1 )
  {
    if ( v3 )
    {
      close(pParentWrite[0]);
      close(pParentRead[1]);
      while ( 1 )
      {
        printf("Input key : ");
        memset(bufWrite, 0, sizeof(bufWrite));
        gets(bufWrite);
        v4 = strlen(bufWrite);
        v5 = write(pParentWrite[1], bufWrite, v4);
        if ( v5 != strlen(bufWrite) )
          printf("parent - partial/failed write");
        do
        {
          memset(bufParentRead, 0, sizeof(bufParentRead));
          numReada = read(pParentRead[0], bufParentRead, 0xC8uLL);
          v6 = bCheckPtrace || checkDebuggerProcessRunning();
          if ( !v6 && checkStringIsNumber(bufParentRead) && atoi(bufParentRead) )
          {
            puts("True");
            if ( close(pParentWrite[1]) == -1 )
              exit(1);
            exit(0);
          }
          puts("Wrong !!!\n");
        }
        while ( numReada == -1 );
      }
    }
    close(pParentWrite[1]);
    close(pParentRead[0]);
    while ( 1 )
    {
      memset(bufParentRead, 0, sizeof(bufParentRead));
      numRead = read(pParentWrite[0], bufParentRead, 0xC8uLL);
      if ( numRead == -1 )
        break;
      if ( numRead )
      {
        if ( !childCheckDebugResult()
          && bufParentRead[0] == 123
          && strlen(bufParentRead) == 42
          && !strncmp(&bufParentRead[1], "53fc275d81", 0xAuLL)
          && bufParentRead[strlen(bufParentRead) - 1] == 125
          && !strncmp(&bufParentRead[31], "4938ae4efd", 0xAuLL)
          && confuseKey(bufParentRead, 42)
          && !strncmp(bufParentRead, "{daf29f59034938ae4efd53fc275d81053ed5be8c}", 0x2AuLL) )
        {
          responseTrue();
        }
        else
        {
          responseFalse();
        }
      }
    }
    exit(1);
  }
  exit(1);
}

checkDebuggerProcessRunning函数伪代码:

c 复制代码
bool __cdecl checkDebuggerProcessRunning()
{
  char szProcessName[200]; // [rsp+10h] [rbp-D0h] BYREF
  unsigned __int64 v2; // [rsp+D8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(szProcessName, 0, sizeof(szProcessName));
  return checkParentProcessRunning() || checkDebuggerRunningInTask();
}

checkStringIsNumber函数伪代码:

c 复制代码
bool __cdecl checkStringIsNumber(char *szBuffer)
{
  int i; // [rsp+18h] [rbp-8h]
  int iLen; // [rsp+1Ch] [rbp-4h]

  iLen = strlen(szBuffer);
  if ( !szBuffer )
    return 0;
  for ( i = 0; i < iLen; ++i )
  {
    if ( szBuffer[i] <= 47 || szBuffer[i] > 57 )
      return 0;
  }
  return 1;
}

confuseKey函数伪代码:

c 复制代码
bool __cdecl confuseKey(char *szKey, int iKeyLength)
{
  char szPart1[15]; // [rsp+10h] [rbp-50h] BYREF
  char szPart2[15]; // [rsp+20h] [rbp-40h] BYREF
  char szPart3[15]; // [rsp+30h] [rbp-30h] BYREF
  char szPart4[15]; // [rsp+40h] [rbp-20h] BYREF
  unsigned __int64 v7; // [rsp+58h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  memset(szPart1, 0, sizeof(szPart1));
  memset(szPart2, 0, sizeof(szPart2));
  memset(szPart3, 0, sizeof(szPart3));
  memset(szPart4, 0, sizeof(szPart4));
  if ( iKeyLength != 42 )
    return 0;
  if ( !szKey )
    return 0;
  if ( strlen(szKey) != 42 )
    return 0;
  if ( *szKey != 123 )
    return 0;
  strncpy(szPart1, szKey + 1, 0xAuLL);
  strncpy(szPart2, szKey + 11, 0xAuLL);
  strncpy(szPart3, szKey + 21, 0xAuLL);
  strncpy(szPart4, szKey + 31, 0xAuLL);
  memset(szKey, 0, 0x2AuLL);
  *szKey = 123;
  strcat(szKey, szPart3);
  strcat(szKey, szPart4);
  strcat(szKey, szPart1);
  strcat(szKey, szPart2);
  szKey[41] = 125;
  return 1;
}

exp:

python 复制代码
def confuse_key(sz_key: str) -> str:
    """
    复现confuseKey函数的密钥混淆逻辑
    :param sz_key: 原始密钥(长度42,格式为{...})
    :return: 混淆后的密钥,失败则返回空字符串
    """
    # 合法性检查
    if len(sz_key) != 42:
        return ""
    if sz_key[0] != '{':
        return ""
    if sz_key[-1] != '}':  # 虽然原函数最后强制设为'}',但输入最好符合格式
        return ""
    
    # 分割为4个10字符片段(去掉首尾的{})
    content = sz_key[1:-1]  # 提取中间40字符
    if len(content) != 40:
        return ""
    
    p1 = content[0:10]    # 片段1:0-9
    p2 = content[10:20]   # 片段2:10-19
    p3 = content[20:30]   # 片段3:20-29
    p4 = content[30:40]   # 片段4:30-39
    
    # 重组:{ P3 P4 P1 P2 }
    confused_content = p3 + p4 + p1 + p2
    confused_key = '{' + confused_content + '}'
    
    # 确保长度正确(42)
    return confused_key if len(confused_key) == 42 else ""

def reverse_confuse(confused_key: str) -> str:
    """
    从混淆后的密钥反推原始密钥(confuse_key的逆操作)
    :param confused_key: 混淆后的密钥(长度42,格式为{...})
    :return: 原始密钥
    """
    if len(confused_key) != 42 or confused_key[0] != '{' or confused_key[-1] != '}':
        return ""
    
    content = confused_key[1:-1]  # 中间40字符
    if len(content) != 40:
        return ""
    
    # 混淆后的结构是 P3 P4 P1 P2(各10字符)
    p3 = content[0:10]
    p4 = content[10:20]
    p1 = content[20:30]
    p2 = content[30:40]
    
    # 原始结构是 P1 P2 P3 P4
    original_content = p1 + p2 + p3 + p4
    original_key = '{' + original_content + '}'
    
    return original_key if len(original_key) == 42 else ""

def check_string_is_number(sz_buffer: str) -> bool:
    """验证字符串是否全为数字字符"""
    if not sz_buffer:
        return False
    for c in sz_buffer:
        # 检查是否在'0'-'9'范围内(ASCII 48-57)
        if not (48 <= ord(c) <= 57):
            return False
    return True

# 混淆后的密钥(来自.rodata)
confused_flag = "{daf29f59034938ae4efd53fc275d81053ed5be8c}"
# 反推原始密钥
original_flag = reverse_confuse(confused_flag)
print("原始密钥(可能的Flag):", original_flag)

def remove_braces(s):
    """移除字符串首尾的{和}符号"""
    # 检查字符串是否以{开头且以}结尾
    if len(s) >= 2 and s[0] == '{' and s[-1] == '}':
        return s[1:-1]  # 切片取中间部分(从索引1到倒数第2个字符)
    else:
        return s  # 若没有花括号则直接返回原字符串

original_with_braces = original_flag
result = remove_braces(original_with_braces)

# 验证:原始密钥混淆后应等于.rodata中的字符串
verify = confuse_key(original_flag)
print("验证结果(混淆后):", verify)
print("是否匹配:", verify == confused_flag)
print("正确的flag:", result)

运行 exp 脚本:

复制代码
正确的flag: 53fc275d81053ed5be8cdaf29f59034938ae4efd

【攻防世界】reverse | re1-100 详细题解 WP 原理深度解析:

CTF 逆向实战:攻防世界 re1-100 深度解析与解题方法论

一、题目概述

在 CTF 逆向领域,"re1-100" 是一道典型的入门级字符串处理类题目,核心考察对程序逻辑的静态分析能力,尤其是字符串分割与重组逻辑的逆向推导。题目通过父子进程通信验证输入的密钥(flag),其中子进程对密钥的验证逻辑是解题关键。

二、程序核心逻辑静态分析

2.1 整体流程梳理

从 main 函数伪代码可知,程序通过pipe创建了两个管道(pParentWritepParentRead)实现父子进程通信,流程如下:

  1. 父进程接收用户输入的密钥,通过管道发送给子进程;
  2. 子进程对接收的密钥进行一系列验证,若验证通过则返回 "True",否则返回 "Wrong";
  3. 验证过程中包含反调试检测(checkDebuggerProcessRunning)和密钥格式校验(confuseKey)。

2.2 关键函数深度解析

(1)反调试函数:checkDebuggerProcessRunning
c 复制代码
bool __cdecl checkDebuggerProcessRunning()
{
  char szProcessName[200]; // 存储进程名
  unsigned __int64 v2;     // 栈保护变量
  v2 = __readfsqword(0x28u); // 栈溢出保护
  memset(szProcessName, 0, sizeof(szProcessName));
  return checkParentProcessRunning() || checkDebuggerRunningInTask();
}

功能:通过检查父进程状态或任务列表中的调试器进程,判断程序是否被调试。

CTF 意义:这是典型的反调试手段,目的是阻止分析者动态调试程序。解题时可通过静态分析直接跳过(无需实际绕过,因关键逻辑在字符串处理)。

(2)数字验证函数:checkStringIsNumber
c 复制代码
bool __cdecl checkStringIsNumber(char *szBuffer)
{
  int i; // 循环索引
  int iLen; // 字符串长度
  iLen = strlen(szBuffer);
  if (!szBuffer) return 0;
  for (i = 0; i < iLen; ++i) {
    if (szBuffer[i] <= 47 || szBuffer[i] > 57) // 检查是否为'0'-'9'(ASCII 48-57)
      return 0;
  }
  return 1;
}

功能:验证字符串是否仅包含数字字符。

CTF 意义:本题中用于验证子进程返回的结果是否为数字,但对密钥本身验证无直接影响,可暂不聚焦。

(3)核心函数:confuseKey(密钥混淆逻辑)
c 复制代码
bool __cdecl confuseKey(char *szKey, int iKeyLength)
{
  char szPart1[15], szPart2[15], szPart3[15], szPart4[15]; // 4个片段缓冲区
  // 合法性检查
  if (iKeyLength != 42 || !szKey || strlen(szKey) != 42 || *szKey != 123)
    return 0; // 123是'{'的ASCII码
  // 分割为4个10字符片段
  strncpy(szPart1, szKey + 1, 10);   // 片段1:索引1-10
  strncpy(szPart2, szKey + 11, 10);  // 片段2:索引11-20
  strncpy(szPart3, szKey + 21, 10);  // 片段3:索引21-30
  strncpy(szPart4, szKey + 31, 10);  // 片段4:索引31-40
  // 重组逻辑:{P3 P4 P1 P2}
  memset(szKey, 0, 42);
  *szKey = 123; // 重置'{'
  strcat(szKey, szPart3);
  strcat(szKey, szPart4);
  strcat(szKey, szPart1);
  strcat(szKey, szPart2);
  szKey[41] = 125; // 125是'}'的ASCII码
  return 1;
}

核心逻辑拆解

  • 输入要求:密钥必须为 42 字符,格式为{P1P2P3P4}(其中 P1-P4 各 10 字符,首尾为{})。
  • 混淆规则:将原始密钥按{P1P2P3P4}分割后,重组为{P3P4P1P2}

2.3 关键数据:.rodata 段字符串

程序的只读数据段(.rodata)存储了一个关键字符串:

复制代码
"{daf29f59034938ae4efd53fc275d81053ed5be8c}"
  • 长度为 42(含{}),符合confuseKey的输出格式,推测为混淆后的 flag

三、flag 推导过程

3.1 逆向混淆逻辑

已知混淆后的密钥格式为{P3P4P1P2},需反推原始格式{P1P2P3P4}。步骤如下:

  1. 提取混淆后密钥的中间 40 字符(去掉{});
  2. 按 10 字符分割为P3(0-9)、P4(10-19)、P1(20-29)、P2(30-39);
  3. 重组原始内容为P1P2P3P4,加上首尾{}即得原始密钥。

3.2 Python 代码实现与验证

(1)逆向混淆函数
python 复制代码
def reverse_confuse(confused_key: str) -> str:
    if len(confused_key) != 42 or confused_key[0] != '{' or confused_key[-1] != '}':
        return ""
    content = confused_key[1:-1]  # 提取中间40字符
    p3 = content[0:10]    # 混淆后的P3
    p4 = content[10:20]   # 混淆后的P4
    p1 = content[20:30]   # 混淆后的P1(原始P1)
    p2 = content[30:40]   # 混淆后的P2(原始P2)
    original_content = p1 + p2 + p3 + p4  # 原始内容:P1P2P3P4
    return '{' + original_content + '}'
(2)验证推导结果
python 复制代码
# 混淆后的密钥(来自.rodata)
confused_flag = "{daf29f59034938ae4efd53fc275d81053ed5be8c}"
original_flag = reverse_confuse(confused_flag)
print("原始密钥:", original_flag)  # 输出:{53fc275d81053ed5be8cdaf29f59034938ae4efd}

# 验证:用正向混淆函数检查是否匹配
def confuse_key(sz_key: str) -> str:
    if len(sz_key) != 42 or sz_key[0] != '{' or sz_key[-1] != '}':
        return ""
    content = sz_key[1:-1]
    p1, p2, p3, p4 = content[0:10], content[10:20], content[20:30], content[30:40]
    return '{' + p3 + p4 + p1 + p2 + '}'

print("验证匹配:", confuse_key(original_flag) == confused_flag)  # 输出:True
(3)去除首尾符号

最终 flag 通常不含{},需提取中间内容:

python 复制代码
flag = original_flag[1:-1]
print("flag:", flag)  # 输出:53fc275d81053ed5be8cdaf29f59034938ae4efd

四、同类型题目解题方法论

4.1 核心分析步骤

  1. 定位关键函数 :通过字符串(如 "Input key"、"True")或交叉引用,找到密钥验证逻辑(如本题的confuseKey)。
  2. 解析字符串操作 :重点关注strncpystrcat等函数,梳理字符串分割、重组、加密规则(可画图记录片段位置)。
  3. 利用.rodata 线索 :只读数据段的常量字符串(尤其是{...}格式)往往是混淆后的 flag 或验证依据。
  4. 逆向推导与验证:根据正向逻辑编写逆向函数,用已知结果(如.rodata 字符串)反推原始 flag,再用正向函数验证正确性。

4.2 举一反三技巧

  • 反调试处理 :遇到ptracecheckDebugger等函数时,优先静态分析(无需动态调试),因关键逻辑通常在字符串处理。
  • 格式特征利用 :CTF 中 flag 常为固定长度(如 32/42 字符)、含特定符号(如{}),可据此缩小分析范围。
  • 自动化工具辅助:对复杂字符串操作(如多次重组、加密),可使用 IDA 的字符串交叉引用或 Ghidra 的函数流程图快速定位核心逻辑。

五、总结

本题的核心是理解confuseKey的字符串重组逻辑:通过将原始密钥{P1P2P3P4}混淆为{P3P4P1P2},再结合.rodata 中的混淆后字符串反推原始 flag。这类题目考察逆向思维能力,关键在于拆解字符串操作步骤并编写逆向逻辑。

掌握 "定位关键函数→解析操作规则→逆向推导验证" 的流程,可快速解决多数字符串处理类逆向题目。

最终 flag53fc275d81053ed5be8cdaf29f59034938ae4efd

相关推荐
CHANG_THE_WORLD8 小时前
Python 中的循环结构详解
开发语言·python·c#
程序员-周李斌8 小时前
ConcurrentHashMap 源码分析
java·开发语言·哈希算法·散列表·开源软件
飞行增长手记8 小时前
什么是IP纯净度?为什么它能决定账号安全与访问效率?
网络
JS_GGbond9 小时前
JavaScript入门学习路线图
开发语言·javascript·学习
普普通通的南瓜9 小时前
一年期免费IP证书,为公网IP地址提供HTTPS加密
网络·网络协议·tcp/ip·安全·http·金融·https
.YM.Z9 小时前
【数据结构】:排序(二)——归并与计数排序详解
数据结构·算法·排序
武帝为此9 小时前
【数据结构之树状数组】
数据结构·算法
失败才是人生常态9 小时前
算法题归类学习
学习·算法
quikai19819 小时前
python练习第一组
开发语言·python