解析2025强网拟态Icall
好的,我现在需要帮用户总结一篇关于CTF逆向题目的文章,控制在100字以内。首先,我得仔细阅读用户提供的文章内容,了解其主要结构和重点。 文章详细记录了名为“Icall”的CTF逆向题目的分析过程。题目涉及反调试技术、自定义RC4流密码和仿射密码等技术。目标是通过逆向分析获取隐藏的flag。 用户要求总结内容,不需要特定的开头,直接描述文章内容。我需要抓住关键点:题目名称、涉及的技术、分析过程、解密步骤以及最终结果。 首先,题目名称是“Icall”,这是一个CTF逆向题目。接下来,分析过程中提到了反调试技术的绕过,这可能包括检测调试器并终止程序的机制。然后是加密算法的逆向,包括RC4变种和仿射密码的处理。 解密流程分为三轮RC4解密和仿射密码解密。最终得到flag为“flag{r0uNd_Rc4_Aff1neEnc1yp7!}”,揭示了题目的核心技术。 总结时要简洁明了,涵盖这些要点:题目名称、涉及的技术、分析步骤和结果。确保在100字以内,并且直接描述内容。 可能的结构:文章详细分析了“Icall”CTF逆向题目的反调试技术、自定义RC4流密码和仿射密码,通过动态调试和静态分析绕过反调试机制,提取密文并逆向加密算法,最终解密得到隐藏的flag“flag{r0uNd_Rc4_Aff1neEnc1yp7!}”。 检查字数是否符合要求,并确保所有关键点都被涵盖。 </think> 文章详细分析了"Icall" CTF逆向题目中的反调试技术、自定义RC4流密码和仿射密码。通过动态调试与静态分析绕过反调试机制,提取密文并逆向加密算法,最终解密得到隐藏的flag"flag{r0uNd_Rc4_Aff1neEnc1yp7!}"。 2025-11-5 02:59:3 Author: www.freebuf.com(查看原文) 阅读量:1 收藏

前言

本文详细记录了一道名为"Icall"的CTF逆向题目的完整分析过程。作为一道综合性逆向题目,它涉及反调试技术、自定义RC4流密码、仿射密码等多项技术,非常适合学习现代逆向工程的思路和方法。

**题目提示:**Icall call where?

**目标:**通过逆向分析获取隐藏的flag

一、初步侦察 - 了解目标

1.1 文件基本信息

首先对目标文件进行基础信息收集:

file Icall

输出结果:

Icall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 3.2.0, stripped

关键信息解读:

  • ELF 64-bit:Linux平台64位可执行文件

  • stripped:符号表已被移除,增加了分析难度,函数名、变量名等信息不可见

  • dynamically linked:使用动态链接,会调用外部库函数

1.2 查看文件结构

使用readelf查看程序的段信息和入口点:

readelf -h Icall

关键输出:

入口点地址:0x401040

查看段结构:

readelf -S Icall | grep -E "Name|init_array|data|rodata"

发现关键段:

  • .init_array:初始化函数数组,在main之前执行

  • .data:数据段,可能包含密文

  • .rodata:只读数据段,可能包含S-box

1.3 字符串分析

strings Icall | head -30

发现了关键函数名:

pthread_create
pthread_detach
prctl
getpid
kill

**立即警觉:**这些函数的组合强烈暗示程序实现了反调试机制!

  • pthread_create:创建线程

  • prctl:进程控制,常用于反调试

  • getpid+ kill:可能用于自杀式反调试

二、反调试机制分析与绕过

2.1 定位初始化函数

查看.init_array段的内容:

readelf -x .init_array Icall

输出:

".init_array"节的十六进制输出:
  0x00405dd0 20114000 00000000 30114000 00000000

转换为地址(小端序):

  • 0x401120

  • 0x401130← 这是我们的重点

2.2 反汇编分析

使用objdump查看0x401130函数:

objdump -d Icall | grep "401130:" -A 50

关键代码片段:

401130:  push   %rbp
401131:  mov    %rsp,%rbp
401134:  sub    $0x40,%rsp
...
401173:  movabs $0x401290,%r8    # 线程函数地址
...
40118b:  call   *%r8              # pthread_create调用

继续查看线程函数0x401290:

objdump -d Icall | grep "401290:" -A 80

发现关键调用:

4012ce:  mov    $0x4,%edi         # PR_SET_TRACER
4012e9:  call   *%r9              # prctl调用

反调试原理:

  1. 主线程在init阶段创建一个子线程

  2. 子线程调用prctl(PR_SET_PTRACER, ...)设置跟踪权限

  3. 读取/proc/self/status文件检查TracerPid字段

  4. 如果TracerPid不为0,说明正在被调试,程序调用kill自杀

2.3 绕过反调试 - 实战操作

方法:使用GDB的return命令直接跳过反调试函数

创建GDB脚本bypass_antidebug.gdb

set pagination off
set confirm off

# 在反调试函数入口设置断点
b *0x401130

run <<< "test_input"

# 直接返回,不执行反调试代码
return

# 继续执行到main
b *0x402000
continue

quit

执行脚本:

gdb -x bypass_antidebug.gdb Icall

**成功绕过!**程序可以正常运行到main函数。

原理说明:

  • return命令会让函数立即返回,跳过所有反调试检测代码

  • 这比修改二进制文件更灵活,且不会破坏程序完整性

三、定位main函数与核心逻辑

3.1 找到main函数

查看程序入口点代码:

objdump -d Icall | grep -A 30 "^0000000000401040"

关键指令:

401061:  mov    $0x402000,%rdi    # main函数地址
401068:  call   *0x4f6a(%rip)     # __libc_start_main

确认main函数地址:0x402000

3.2 分析main函数框架

objdump -d Icall | grep "402000:" -A 200 | head -250

通过分析汇编代码,发现main函数的基本流程:

1. 函数序言(保存栈帧)
2. 调用scanf/fgets读取用户输入
3. 调用strlen计算输入长度
4. 多次调用同一个函数(疑似加密函数)
5. 调用memcmp比较结果
6. 根据比较结果输出成功/失败信息

3.3 提取密文数据

使用readelf查看数据段:

readelf -x .data Icall

在地址0x406020发现30字节的数据:

406020  f788c329 36646329 c77f1cab 71e00349  ...)6dc)....q..I
406030  73cb0aaf 0c87848e 5a64c7ac 2a670000  s.......Zd..*g..

提取密文(Python):

enc = [0xF7, 0x88, 0xC3, 0x29, 0x36, 0x64, 0x63, 0x29, 0xC7, 0x7F,
       0x1C, 0xAB, 0x71, 0xE0, 0x03, 0x49, 0x73, 0xCB, 0x0A, 0xAF,
       0x0C, 0x87, 0x84, 0x8E, 0x5A, 0x64, 0xC7, 0xAC, 0x2A, 0x67]

确认这是30字节的目标密文,正好对应flag长度!

四、加密算法逆向分析

4.1 动态调试观察加密过程

使用GDB在加密函数处设置断点:

# 绕过反调试
b *0x401130
run
return

# 在可能的加密调用处断点
b *0x402073
b *0x402102
b *0x40219b

continue

观察到程序连续三次调用加密函数,每次传入不同的参数:

  • 第一次:参数为 0x0C(12)

  • 第二次:参数为 0x1E(30)

  • 第三次:参数为 0x2A(42)

4.2 识别RC4算法特征

通过反汇编加密函数,发现典型的RC4算法特征:

特征1:256字节的S-box数组

# 访问256字节数组
mov    (%rax,%rcx,1),%dl    # S[i]
mov    (%rax,%rdx,1),%r8b   # S[j]

特征2:经典的交换操作

xchg   %dl,%r8b              # swap(S[i], S[j])

特征3:密钥流生成

add    %dl,%r8b              # t = S[i] + S[j]
movzbl %r8b,%edx
mov    (%rax,%rdx,1),%cl    # K = S[t]

特征4:XOR加密

xor    %cl,%bl               # plaintext ^ keystream

4.3 RC4变种分析

通过详细分析发现,这不是标准的RC4,而是一个定制版本:

变种特点1:自定义S-box初始值

提取S-box(通过动态调试或静态分析):

s_box = [
    0xCD, 0xE1, 0x65, 0xC6, 0xB3, 0x05, 0x63, 0x50, 0x07, 0x36,
    0x0B, 0x10, 0x87, 0x49, 0x40, 0x0F, 0xF0, 0xB5, 0xE9, 0xD2,
    # ... 共256个字节
    0x62, 0xA3, 0x79, 0x4C, 0xFE, 0xFF
]

变种特点2:多轮PRGA

标准RC4每生成一个密钥流字节只执行一次PRGA循环,但这个变种对每个字节执行N次PRGA循环,然后取最后一次的结果:

def get_keystream_byte(n_rounds):
    for _ in range(n_rounds):  # 重复n次
        i = (i + 1) % 256
        j = (j + s_box[i]) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
        k = s_box[(s_box[i] + s_box[j]) % 256]
    return k  # 只返回最后一次的结果

变种特点3:链式XOR加密(CFB模式)

加密时不仅与密钥流XOR,还与前一个密文字节XOR:

# 第一个字节特殊处理
C[0] = P[0] ^ S_box[0] ^ K[0]

# 后续字节形成链式依赖
for i in range(1, len(P)):
    C[i] = P[i] ^ C[i-1] ^ K[i]

这种模式类似于CFB(Cipher Feedback)模式,使得相同的明文在不同位置产生不同的密文。

变种特点4:三轮加密

程序对输入进行三轮加密,每轮使用不同的循环次数:

轮次循环次数十六进制
第1轮120x0C
第2轮300x1E
第3轮420x2A

五、仿射密码层的发现

5.1 意外的发现

在动态调试时,我发现一个奇怪的现象:在输入"AAAA..."后,进入RC4加密前,数据已经发生了变化!

输入:  AAAA AAAA ...
RC4前: LLLL LLLL ...  # 字符已经改变!

这说明在RC4加密之前还有一层预处理。

5.2 定位变换函数

通过在main函数中仔细查找,发现在scanf之后立即调用了一个字符处理函数。反汇编这个函数后发现:

# 判断字符类型
cmp    $'0',%al
jl     .not_digit
cmp    $'9',%al
jg     .not_digit

# 对数字进行变换
movzbl %al,%eax
sub    $0x30,%eax        # x = c - '0'
imul   $0x7,%eax,%edx    # y = 7 * x
add    $0xb,%edx         # y = y + 11
mov    $0xa,%ecx
cdq
idiv   %ecx              # y = y % 10
add    $0x30,%edx        # result = y + '0'

这是仿射密码!

5.3 仿射密码原理

仿射密码的加密公式:

E(x) = (a * x + b) mod m

解密公式:

D(y) = a^(-1) * (y - b) mod m

其中a^(-1)a在模m下的乘法逆元。

5.4 提取参数

通过分析汇编代码,提取出各字符类型的参数:

字符类型基准模数(m)乘数(a)加数(b)逆元(a⁻¹)
数字 0-9'0'107113
大写 A-Z'A'26171115
小写 a-z'a'26171115

验证乘法逆元:

# 对于数字:7 * 3 = 21 ≡ 1 (mod 10) ✓
# 对于字母:17 * 15 = 255 ≡ 1 (mod 26) ✓

六、完整解密流程实现

6.1 解密策略

加密流程:

原始flag → [仿射变换] → [RC4轮1(0x0C)] → [RC4轮2(0x1E)] → [RC4轮3(0x2A)] → 密文

解密流程(逆序):

密文 → [RC4轮3逆(0x2A)] → [RC4轮2逆(0x1E)] → [RC4轮1逆(0x0C)] → [仿射逆变换] → 原始flag

6.2 RC4解密脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 密文数据(从0x406020提取)
new_enc = [0xF7, 0x88, 0xC3, 0x29, 0x36, 0x64, 0x63, 0x29, 0xC7, 0x7F,
           0x1C, 0xAB, 0x71, 0xE0, 0x03, 0x49, 0x73, 0xCB, 0x0A, 0xAF,
           0x0C, 0x87, 0x84, 0x8E, 0x5A, 0x64, 0xC7, 0xAC, 0x2A, 0x67]

def get_key(a4):
    """
    生成RC4密钥流
    参数a4: PRGA循环次数
    """
    # 自定义S-box初始值(256字节)
    s_box = [
        0xCD, 0xE1, 0x65, 0xC6, 0xB3, 0x05, 0x63, 0x50, 0x07, 0x36,
        0x0B, 0x10, 0x87, 0x49, 0x40, 0x0F, 0xF0, 0xB5, 0xE9, 0xD2,
        0x14, 0x15, 0x16, 0x17, 0xED, 0xDB, 0x77, 0xE4, 0x1C, 0x57,
        0xC7, 0x6D, 0x66, 0xA9, 0xB2, 0x3E, 0x24, 0xCB, 0xDC, 0xC0,
        0xA8, 0x29, 0xFD, 0xF6, 0x76, 0x2D, 0x92, 0x8F, 0xF9, 0x68,
        0xEE, 0x64, 0x34, 0x7C, 0x71, 0xCA, 0xB1, 0x52, 0x3A, 0x98,
        0xE7, 0x4D, 0x5D, 0x03, 0x82, 0x37, 0x9B, 0x91, 0x6F, 0x55,
        0xE8, 0xEA, 0x43, 0x74, 0x0D, 0x38, 0x4B, 0xFC, 0x67, 0x0E,
        0x47, 0x94, 0x7E, 0x78, 0x54, 0x61, 0xB9, 0xB6, 0x33, 0x09,
        0xDD, 0x5B, 0x26, 0x7A, 0x83, 0x7B, 0x3F, 0xF5, 0x02, 0xD6,
        0xDA, 0xD1, 0xA0, 0x80, 0x4E, 0xAE, 0x6A, 0x6B, 0x8E, 0xCC,
        0x39, 0x44, 0x5A, 0x48, 0x72, 0xC4, 0x20, 0x13, 0x2C, 0x8B,
        0xE0, 0x32, 0x7F, 0x89, 0x19, 0x0A, 0x1F, 0x22, 0x31, 0x04,
        0xDF, 0xE6, 0xA2, 0x85, 0x86, 0x30, 0x06, 0x12, 0xBE, 0x51,
        0x8D, 0x8C, 0xD5, 0x2B, 0x90, 0x18, 0xB8, 0x0C, 0xD4, 0x95,
        0x96, 0x9A, 0xD9, 0x9C, 0x2F, 0xEF, 0x99, 0xE3, 0x9E, 0xB0,
        0x70, 0xAD, 0xB7, 0x1E, 0x28, 0x45, 0xF3, 0xCE, 0x27, 0x69,
        0xAA, 0xF2, 0xE5, 0xA5, 0xD3, 0xE2, 0xAF, 0x9D, 0x35, 0x3D,
        0xB4, 0xAB, 0xC8, 0xA4, 0x9F, 0x6E, 0xBA, 0x2A, 0xBC, 0x84,
        0x97, 0x08, 0xA7, 0xD7, 0xC2, 0x2E, 0x6C, 0x81, 0x4A, 0x5F,
        0xA1, 0xC9, 0xFA, 0x21, 0x73, 0x00, 0xC3, 0x11, 0x58, 0x60,
        0x56, 0x4F, 0x1A, 0xFB, 0x1B, 0x01, 0xA6, 0x88, 0x59, 0xD0,
        0x5C, 0xC1, 0x3C, 0xAC, 0xC5, 0x7D, 0xF1, 0x41, 0xBB, 0x8A,
        0x75, 0xDE, 0x42, 0xEC, 0x93, 0xEB, 0xBD, 0x3B, 0x53, 0x46,
        0xBF, 0x1D, 0x5E, 0xD8, 0xF4, 0xCF, 0x23, 0xF7, 0xF8, 0x25,
        0x62, 0xA3, 0x79, 0x4C, 0xFE, 0xFF
    ]

    xor = []
    i = 0
    j = 0
    v7 = 0

    # 对每个字节生成密钥流
    for byte_pos in range(30):
        # 执行a4次PRGA循环
        for _ in range(a4):
            i = (i + 1) % 256
            j = (j + s_box[i]) % 256
            # 交换S[i]和S[j]
            s_box[i], s_box[j] = s_box[j], s_box[i]
            # 生成密钥流字节
            t_idx = (s_box[i] + s_box[j]) % 256
            v7 = s_box[t_idx]
        xor.append(v7)

    return xor


# 三轮解密(逆序:从0x2A开始)
rounds = [0x2a, 0x1e, 0xc]

print("=" * 60)
print("开始RC4三轮解密")
print("=" * 60)

for round_num in range(3):
    print(f"\n【第{round_num + 1}轮解密】")
    print(f"  循环次数: {rounds[round_num]} (0x{rounds[round_num]:02x})")

    # 生成本轮密钥流
    xor = get_key(rounds[round_num])
    print(f"  第一个密钥流字节: 0x{xor[0]:02x}")

    # 逆向链式XOR:从后往前解密
    for i in range(len(new_enc) - 1, -1, -1):
        if i != 0:
            # C[i] = P[i] ^ C[i-1] ^ K[i]
            # 解密:P[i] = C[i] ^ C[i-1] ^ K[i]
            new_enc[i] = new_enc[i] ^ new_enc[i - 1] ^ xor[i]
        else:
            # 第一个字节特殊处理
            new_enc[i] = new_enc[i] ^ 0xCD ^ xor[i]

    print(f"  解密后前10字节: {bytes(new_enc[:10])}")

print("\n" + "=" * 60)
print("RC4解密完成!")
print("=" * 60)
print(f"\n仿射密文: {bytes(new_enc)}")
print(f"十六进制: {bytes(new_enc).hex()}")

运行结果:

==============================================================
开始RC4三轮解密
==============================================================

【第1轮解密】
  循环次数: 42 (0x2a)
  第一个密钥流字节: 0x83
  解密后前10字节: b'\x08\x9e\x11V\x1c'

【第2轮解密】
  循环次数: 30 (0x1e)
  第一个密钥流字节: 0x97
  解密后前10字节: b'=6G\x08\x1d'

【第3轮解密】
  循环次数: 12 (0x0c)
  第一个密钥流字节: 0x5b
  解密后前10字节: b'uklb{a1vYg'

==============================================================
RC4解密完成!
==============================================================

仿射密文: b'uklb{a1vYg_Az9_Luu8ynNyz8xm0!}'
十六进制: 756b6c627b61317659675f417a395f4c757538796e4e797a38786d30217d

6.3 仿射密码解密脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def decrypt_char(c):
    """
    解密单个字符
    使用仿射密码的逆变换
    """
    if '0' <= c <= '9':
        # 数字:a=7, b=11, m=10, a^(-1)=3
        base, mod, inv = ord('0'), 10, 3
    elif 'A' <= c <= 'Z':
        # 大写字母:a=17, b=11, m=26, a^(-1)=15
        base, mod, inv = ord('A'), 26, 15
    elif 'a' <= c <= 'z':
        # 小写字母:a=17, b=11, m=26, a^(-1)=15
        base, mod, inv = ord('a'), 26, 15
    else:
        # 非字母数字字符不变(如花括号、感叹号)
        return c

    # 解密公式:x = a^(-1) * (y - b) mod m
    y = ord(c) - base
    x = (inv * (y - 11)) % mod
    return chr(x + base)


def decrypt_string(s):
    """解密整个字符串"""
    return ''.join(decrypt_char(c) for c in s)


if __name__ == "__main__":
    # 从RC4解密得到的仿射密文
    shifted_flag = "uklb{a1vYg_Az9_Luu8ynNyz8xm0!}"

    print("=" * 60)
    print("仿射密码解密")
    print("=" * 60)
    print(f"\n输入(仿射密文):{shifted_flag}")

    # 解密
    final_flag = decrypt_string(shifted_flag)

    print(f"\n输出(最终flag):{final_flag}")
    print("\n" + "=" * 60)
    print("解密成功!")
    print("=" * 60)

运行结果:

==============================================================
仿射密码解密
==============================================================

输入(仿射密文):uklb{a1vYg_Az9_Luu8ynNyz8xm0!}

输出(最终flag):flag{r0uNd_Rc4_Aff1neEnc1yp7!}

==============================================================
解密成功!
==============================================================

七、Flag含义解析

最终得到的flag为:

flag{r0uNd_Rc4_Aff1neEnc1yp7!}

Flag的命名非常贴切,揭示了题目的核心技术:

  • r0uNd_Rc4:多轮(Round)RC4加密

  • Aff1ne:仿射(Affine)密码

  • Enc1yp7:加密(Encrypt)的1337写法

八、技术总结与收获

8.1 反调试技术

实现原理:

  1. 使用pthread_create在主线程启动前创建监控线程

  2. 监控线程通过prctl和读取/proc/self/status检测调试状态

  3. 检测到调试器时使用kill终止进程

绕过方法:

  • 方法1(推荐):GDB的return命令直接跳过函数

  • 方法2:Hook系统调用(LD_PRELOAD)

  • 方法3:修改二进制文件,NOP掉检测代码

实战经验:

  • 反调试函数通常在.init_array段注册

  • 多线程反调试比单线程更难绕过

  • 动态绕过比静态patch更灵活

8.2 RC4变种分析

识别要点:

  1. 查找256字节数组(S-box)

  2. 寻找典型的交换操作

  3. 观察模256运算

  4. 识别XOR操作

本题变种特点:

  • 自定义S-box初始值

  • 多轮PRGA生成单个密钥流字节

  • CFB模式的链式XOR

  • 三轮独立加密

安全性分析:

  • 多轮PRGA大幅增强了密钥流的随机性

  • CFB模式消除了相同明文的模式特征

  • 三轮加密增加了暴力破解的复杂度

8.3 仿射密码应用

数学基础:

  • 模运算

  • 乘法逆元(扩展欧几里得算法)

  • 同余方程

实战技巧:

  • 快速识别仿射变换(imul+ add+ idiv

  • 验证互质性(gcd(a, m) = 1

  • 计算乘法逆元

在CTF中的作用:

  • 作为预处理层增加分析难度

  • 保持字符类型不变(数字仍是数字)

  • 与强加密算法结合使用

8.4 逆向工程方法论

静态分析:

  1. 文件信息收集(file, readelf, strings)

  2. 段结构分析

  3. 反汇编关键函数

  4. 提取硬编码数据

动态分析:

  1. 运行程序观察行为

  2. GDB设置断点跟踪执行流

  3. 观察寄存器和内存变化

  4. Hook关键函数

综合分析:

  1. 结合静态和动态信息

  2. 识别算法特征

  3. 编写验证脚本

  4. 迭代优化理解

九、思考

如果我是出题人,如何增强难度?

反调试层面:

  1. 使用内核级反调试(ptrace自身)

  2. 添加时间检测(RDTSC指令)

  3. 检测虚拟机环境

  4. 实现代码完整性校验

加密层面:

  1. 使用真正的密钥派生函数(PBKDF2)

  2. 添加HMAC完整性保护

  3. 实现白盒加密

  4. 使用更复杂的分组密码

混淆层面:

  1. 控制流平坦化

  2. 虚假控制流注入

  3. 字符串加密

  4. 虚拟机保护(VMProtect)

十、工具与资源

10.1 本文使用的工具

工具用途命令示例
file文件类型识别file Icall
readelfELF文件分析readelf -h Icall
objdump反汇编objdump -d Icall
strings字符串提取strings Icall
GDB动态调试gdb Icall
Python脚本编写python3 decrypt.py

10.2 推荐的学习资源

逆向工程:

  • 《Practical Reverse Engineering》

  • 《The IDA Pro Book》

  • 《Reversing: Secrets of Reverse Engineering》

密码学:

  • 《Applied Cryptography》- Bruce Schneier

  • 《Cryptography Engineering》

  • RFC 4345 (RC4)

在线资源:

  • CrackMe网站

  • CTFtime - CTF竞赛平台

  • GitHub上的逆向工程工具集

十一、结语

这道"Icall"题目通过多层技术的巧妙结合,展示了现代软件保护的常见手法。

逆向工程是一门需要耐心、细心和创造力的技术。每一次成功的分析都是对技术理解的深化,每一个困难的克服都是能力的提升。

希望本文能帮助读者理解CTF逆向题目的解题思路,在今后的学习和实战中有所收获。


研究声明

本文档基于CTF竞赛题目进行研究,所有技术仅用于合法授权的安全测试和教育目的。研究者应遵守当地法律法规和道德准则。


文章来源: https://www.freebuf.com/articles/others-articles/455724.html
如有侵权请联系:admin#unsafe.sh