CTF Wiki
  • 简介
  • 如何使用 CTF Wiki
  • introduction
    • CTF 历史
    • CTF 竞赛模式简介
    • CTF 竞赛内容
    • 线下攻防经验小结
    • CGC 超级挑战赛
    • 学习资源
  • misc
    • 杂项简介
    • 取证隐写前置技术
    • 信息搜集技术
    • encode
      • 通信领域常用编码
      • 计算机相关的编码
      • 现实世界中常用的编码
    • picture
      • 图片分析简介
      • JPG
      • PNG
      • GIF
    • audio
      • 音频隐写
    • archive
      • RAR 格式
      • ZIP 格式
    • traffic
      • 流量包分析简介
      • 协议分析概述
        • Wireshark
        • DNS
        • HTTP
        • HTTPS
        • FTP
        • USB
        • WIFI
      • 数据提取
      • PCAP 文件修复
    • disk-memory
      • 磁盘内存分析
      • 题目
    • shellcode
    • other
      • pyc
  • web
    • Web 简介
    • XSS
    • php
      • PHP 代码审计
    • SQL 注入
      • sqlmap绕过脚本
      • 各版本数据库语句备忘
    • CSRF
    • SSRF
  • reverse
    • 软件逆向工程简介
    • identify-encode-encryption
      • 常见加密算法和编码识别
    • language
      • 简介
      • go
        • Golang 逆向入门
      • python
        • Python 逆向入门
      • rust
        • Rust 逆向入门
    • maze
      • 迷宫问题
    • obfuscate
      • 控制流平坦化
      • 花指令
      • movofuscator
      • Self-Modified Code
    • vm
      • 虚拟机分析
    • platform
      • linux
        • Detecting Breakpoints
        • Detecting debugging
        • False Disassembly
        • LD_PRELOAD
      • windows
        • anti-debug
          • CheckRemoteDebuggerPresent
          • 反调试技术例题
          • Heap Flags
          • Interrupt 3
          • IsDebuggerPresent
          • 花指令
          • NtGlobalFlag
          • NtQueryInformationProcess
          • The Heap
          • Thread Local Storage(TLS)
          • ZwSetInformationThread
        • unpack
          • 一步到达 OEP 法
          • ESP 定律法
          • DUMP 及 IAT 重建
          • 最后一次异常法
          • 手动查找 IAT 并使用 ImportREC 重建
          • 内存镜像法
          • 保护壳简介
          • SFX 法
          • 单步跟踪法
          • DLL 文件脱壳
    • tools
      • constraint
        • z3
      • debug
        • gdb
        • ollydbg
        • windbg
        • x64dbg/x32dbg
      • simulate-execution
        • angr
        • Unicorn Engine
      • static-analyze
        • dnspy
        • Ghidra
        • IDA Pro
        • jadx
  • crypto
    • 密码学简介
    • asymmetric
      • 介绍
      • discrete-log
        • 离散对数
        • ECC
        • ElGamal
      • knapsack
        • 背包加密
      • lattice
        • CVP
        • 基本介绍
        • 格基规约算法
        • 格概述
      • rsa
        • RSA 选择明密文攻击
        • RSA 复杂题目
        • Coppersmith 相关攻击
        • 公钥指数相关攻击
        • 模数相关攻击
        • Bleichenbacher's attack
        • RSA 侧信道攻击
        • RSA 介绍
        • d_attacks
          • 私钥 d 相关攻击
          • 扩展维纳攻击
    • attack-summary
      • 简介
      • 比特攻击
      • 中间相遇攻击 - MITM
    • basic
      • 基础数学知识
    • blockcipher
      • AES
      • ARX: Add-Rotate-Xor
      • DES
      • IDEA
      • 块加密
      • Simon and Speck Block Ciphers
      • mode
        • CBC
        • CFB
        • CTR
        • ECB
        • 分组模式
        • OFB
        • Padding Oracle Attack
        • 填充方式
        • PCBC
    • certificate
      • 证书格式
    • classical
      • 古典密码简介
      • 单表代换加密
      • 其它类型加密
      • 多表代换加密
      • 总结
    • hash
      • Hash Attack
      • 综合题目
      • Fowler–Noll–Vo hash function
      • 哈希函数
      • MD5
      • SHA1
    • signature
      • DSA
      • ElGamal
      • 数字签名
      • RSA 数字签名
    • streamcipher
      • 流密码
      • fsr
        • 反馈移位寄存器
        • 线性反馈移位寄存器 - LFSR
        • 非线性反馈移位寄存器
      • lcg
        • 题目
        • 线性同余生成器
      • prng
        • 密码安全伪随机数生成器
        • 伪随机数生成器介绍
        • 题目
      • special
        • RC4
  • pwn
    • MacOS
    • misc-os
    • 概述
      • stackoverflow
        • 执行 Shellcode
        • 栈介绍
        • 栈溢出原理
    • browser
      • Chrome
      • Firefox
      • Safari
    • hardware
      • 简介
        • side-channel
          • prefetch side-channel attack
      • trusted-computing
        • 可信执行环境
    • linux
      • kernel-mode
        • 基础知识
        • Introduction
          • DoS
          • Information Disclosure
          • Introduction
            • Change Others
            • Change Self
        • Introduction
          • Introduction
            • 信息泄漏
            • Misc
          • Introduction
            • Kernel Stack Canary
          • Introduction
            • inner-kernel
              • 内部隔离
            • Introduction
              • KPTI - Kernel Page Table Isolation
              • 用户代码不可执行
              • 用户数据不可访问
          • Introduction
            • FGKASLR
            • KASLR
        • Introduction
          • 编译内核驱动
          • 内核下载与编译
          • Qemu 模拟环境
          • Real Device
        • exploitation
          • heap
            • 内核堆概述
            • buddy
              • Cross-Cache Overflow & Page-level Heap Fengshui
              • Page-level UAF
            • slub
              • freelist 劫持
              • Heap Spray
              • kernel UAF
          • race
            • Double Fetch
            • userfaultfd 的使用
          • rop
            • bypass-smep
            • ret2dir
            • 利用 pt_regs 构造通用内核 ROP
            • ret2usr(已过时)
            • Kernel ROP
          • tricks
            • 在内存中直接搜索 flag
      • user-mode
        • environment
        • fmtstr
          • 检测
          • 例子
          • 利用
          • 原理介绍
        • integeroverflow
          • 整数溢出
        • io-file
          • glibc 2.24下 IO_FILE 的利用
          • 伪造vtable劫持程序流程
          • FSOP
          • FILE结构
        • mitigation
          • Canary
        • race-condition
          • introduction
          • 题目
        • summary
          • 获取地址
          • shell 获取小结
          • 控制程序执行流
        • Type Confusion
        • Uninitialized Memory
        • heap
          • mallocng
          • ptmalloc2
            • Chunk Extend and Overlapping
            • Fastbin Attack
            • 堆概述
            • 堆相关数据结构
            • 堆溢出
            • House Of Einherjar
            • House Of Force
            • House of Lore
            • House of Orange
            • House of Pig
            • House of Rabbit
            • House of Roman
            • 堆利用
            • Large Bin Attack
            • 通过堆进行信息泄漏
            • 堆中的 Off-By-One
            • 堆中的检查
            • tcache makes heap exploitation easy again
            • Unlink
            • Unsorted Bin Attack
            • Use After Free
            • implementation
              • 基础操作
              • 释放内存块
              • 堆初始化
              • malloc_state 相关函数
              • 申请内存块
              • 测试支持
              • 深入理解堆的实现
              • tcache
        • stackoverflow
          • arm
            • 环境搭建
            • Arm ROP
          • mips
            • mips - ROP
          • RISC-V
          • x86
            • 基本 ROP
            • 花式栈溢出技巧
            • 中级ROP
            • 栈介绍
            • 栈溢出原理
            • advanced-rop
              • 高级 ROP
              • ret2dlresolve
              • ret2VDSO
              • SROP
    • sandbox
      • Chroot
      • Docker
      • Namespace
      • python
        • Python 沙盒
      • seccomp
        • C 沙盒逃逸
      • Shell Sandbox
    • virtualization
      • basic-knowledge
        • 虚拟化技术简介
        • CPU 虚拟化
        • IO 虚拟化
        • 内存虚拟化
      • parallels
        • Parallels
      • VirtualBox
      • VMWare
      • qemu
        • basic-knowledge
          • QEMU 设备模拟
          • QEMU 内存管理
        • environment
          • 编写 QEMU 模拟设备
          • QEMU 下载与编译
        • exploitation
          • QEMU 逃逸入门
          • 越界读写
  • Android 安全
    • basic_develop
      • Android 开发基础
    • Android 应用运行机制简述
      • Android 中 Java 层的运行机制
        • dex
          • DEX文件
          • ODEX文件
        • smali
          • Smali
      • native_layer
        • so 介绍
    • basic_reverse
      • Android 关键代码定位
      • Android 逆向基本介绍
      • dynamic
        • Android 动态调试
        • IDA 动态调试原生层程序
        • IDA 动态调试 smali 代码
      • static
        • 静态分析综合题目
        • 静态分析 java 层例子
        • 静态分析原生层程序
  • blockchain
    • Blockchain Security Challenges
    • Blockchain Security Overview
    • ethereum
      • Ethereum Basics
      • Ethereum Overview
      • Ethereum Opcodes
      • 学习资源
      • Smart Contract Reverse
      • Function Selector and Argument Encoding
      • Ethereum Storage
      • attacks
        • Airdrop Hunting
        • Arbitrary Writing
        • CREATE2
        • Delegatecall
        • Introduction
        • Jump Oriented Programming
        • Integer Overflow and Underflow
        • Randomness
        • Re-Entrancy
        • Short Address Attack
        • Uninitialized Storage Pointer
    • publicblockchain
      • Public Blockchain Security Overview
      • Blockchain Weaknesses
  • assembly
    • ARM
    • MIPS
    • x86_x64
  • executable
    • elf
      • 程序加载
      • 程序执行流程
      • linking
        • 程序链接
        • Symbol Reslove
      • structure
        • ELF 文件
        • Code Section
        • Data Related Sections
        • Dynamic Sections
        • Misc Sections
        • Sections
        • String Sections
        • .symtab: Symbol Table
    • pe
      • PE 文件格式
      • 导出表
      • 导入表
      • 基址重定位表
  • ics
    • ICS_CTF 竞赛
    • ICS_CTF 发现
    • ICS_CTF 利用
    • ICS_CTF 学习资源
  • contribute
    • 贡献之前
    • 基本贡献方式
    • 贡献文档要求
    • 翻译
  • write up
    • 浙江工业大学CTF赛事
      • 2023第四届“安恒杯”CTF新生赛题解
Powered by GitBook
On this page
  • 简介
  • 例题:N1CTF2020 - oflo
  • 逆向分析
  • 求解
  1. reverse
  2. obfuscate

花指令

简介

花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。比较经典的花指令技巧有利用 jmp 、call、ret 指令改变执行流,从而使得反汇编器解析出与运行时不相符的错误代码。

例题:N1CTF2020 - oflo

逆向分析

惯例拖入 IDA,发现 main() 函数无法被反编译,查看汇编代码发现在 0x400BB1 处存在一个 原地 jmp 使得反汇编出错:

.text:0000000000400B54 ; int __fastcall main(int, char **, char **)
.text:0000000000400B54 main:                                   ; DATA XREF: start+1D↑o
.text:0000000000400B54                                         ; .text:0000000000400C21↓o
.text:0000000000400B54 ; __unwind {
.text:0000000000400B54                 push    rbp
.text:0000000000400B55                 mov     rbp, rsp
.text:0000000000400B58                 sub     rsp, 240h
.text:0000000000400B5F                 mov     rax, fs:28h
.text:0000000000400B68                 mov     [rbp-8], rax
.text:0000000000400B6C                 xor     eax, eax
.text:0000000000400B6E                 lea     rdx, [rbp-210h]
.text:0000000000400B75                 mov     eax, 0
.text:0000000000400B7A                 mov     ecx, 40h ; '@'
.text:0000000000400B7F                 mov     rdi, rdx
.text:0000000000400B82                 rep stosq
.text:0000000000400B85                 mov     qword ptr [rbp-230h], 0
.text:0000000000400B90                 mov     qword ptr [rbp-228h], 0
.text:0000000000400B9B                 mov     qword ptr [rbp-220h], 0
.text:0000000000400BA6                 mov     qword ptr [rbp-218h], 0
.text:0000000000400BB1
.text:0000000000400BB1 loc_400BB1:                             ; CODE XREF: .text:loc_400BB1↑j
.text:0000000000400BB1                 jmp     short near ptr loc_400BB1+1
.text:0000000000400BB3 ; ---------------------------------------------------------------------------
.text:0000000000400BB3                 ror     byte ptr [rax-70h], 90h
.text:0000000000400BB7                 call    loc_400BBF
.text:0000000000400BB7 ; ---------------------------------------------------------------------------
.text:0000000000400BBC                 db 0E8h, 0EBh, 12h
.text:0000000000400BBF ; ---------------------------------------------------------------------------

将 0x400BB1 处第一个字节改为 0x90 (nop),继续进行反汇编,接下来来到一个奇怪的调用:

.text:0000000000400BB1                 nop
.text:0000000000400BB2                 inc     eax
.text:0000000000400BB4                 xchg    rax, rax
.text:0000000000400BB6                 nop
.text:0000000000400BB7                 call    loc_400BBF
.text:0000000000400BB7 ; ---------------------------------------------------------------------------
.text:0000000000400BBC                 db 0E8h, 0EBh, 12h
.text:0000000000400BBF ; ---------------------------------------------------------------------------
.text:0000000000400BBF
.text:0000000000400BBF loc_400BBF:                             ; CODE XREF: .text:0000000000400BB7↑j
.text:0000000000400BBF                 pop     rax
.text:0000000000400BC0                 add     rax, 1
.text:0000000000400BC4                 push    rax
.text:0000000000400BC5                 mov     rax, rsp
.text:0000000000400BC8                 xchg    rax, [rax]
.text:0000000000400BCB                 pop     rsp
.text:0000000000400BCC                 mov     [rsp], rax
.text:0000000000400BD0                 retn

call 指令会将下一条指令的地址(0x400BBC)压入栈上,而在代码片段 0x400BBF 中返回地址会被从栈上弹出,值被加一后又压回栈上并 retn,因此这里实际的执行流从 0x400BBD 开始。因此这里我们可以将 call 指令与代码片段 0x400BBF 都 patch 为 nop:

import idc

for i in range(0x400BB7, 0x400BBC + 1):
    idc.patch_byte(i, 0x90)

for i in range(0x400BBF, 0x400BD0 + 1):
    idc.patch_byte(i, 0x90)

patch 后的逻辑比较简单,就是跳到 0x400BD1,这里会调用 sub_4008B9() 后直接 exit():

.text:0000000000400BB6                 nop
.text:0000000000400BB7                 nop
.text:0000000000400BB8                 nop
.text:0000000000400BB9                 nop
.text:0000000000400BBA                 nop
.text:0000000000400BBB                 nop
.text:0000000000400BBC                 nop
.text:0000000000400BBD                 jmp     short loc_400BD1
.text:0000000000400BBF ; ---------------------------------------------------------------------------
.text:0000000000400BBF
.text:0000000000400BBF loc_400BBF:                             ; CODE XREF: .text:0000000000400BB7↑j
.text:0000000000400BBF                 nop
.text:0000000000400BC0                 nop
.text:0000000000400BC1                 nop
.text:0000000000400BC2                 nop
.text:0000000000400BC3                 nop
.text:0000000000400BC4                 nop
.text:0000000000400BC5                 nop
.text:0000000000400BC6                 nop
.text:0000000000400BC7                 nop
.text:0000000000400BC8                 nop
.text:0000000000400BC9                 nop
.text:0000000000400BCA                 nop
.text:0000000000400BCB                 nop
.text:0000000000400BCC                 nop
.text:0000000000400BCD                 nop
.text:0000000000400BCE                 nop
.text:0000000000400BCF                 nop
.text:0000000000400BD0                 nop
.text:0000000000400BD1 ; ---------------------------------------------------------------------------
.text:0000000000400BD1
.text:0000000000400BD1 loc_400BD1:                             ; CODE XREF: .text:0000000000400BBD↑j
.text:0000000000400BD1                 lea     rax, [rbp-210h]
.text:0000000000400BD8                 mov     rdi, rax
.text:0000000000400BDB                 call    sub_4008B9
.text:0000000000400BE0                 cmp     eax, 0FFFFFFFFh
.text:0000000000400BE3                 jnz     short loc_400BEF
.text:0000000000400BE5                 mov     edi, 0
.text:0000000000400BEA                 call    exit

此时 main() 函数还是无法 F5 ,我们继续向下看是否还存在花指令,发现在 0x400CB5 处存在一个和前面一样的混淆:

.text:0000000000400CB5                 call    loc_400CBD
.text:0000000000400CB5 ; ---------------------------------------------------------------------------
.text:0000000000400CBA                 dw 0EBE8h
.text:0000000000400CBC                 db 12h
.text:0000000000400CBD ; ---------------------------------------------------------------------------
.text:0000000000400CBD
.text:0000000000400CBD loc_400CBD:                             ; CODE XREF: .text:0000000000400CB5↑j
.text:0000000000400CBD                 pop     rax
.text:0000000000400CBE                 add     rax, 1
.text:0000000000400CC2                 push    rax
.text:0000000000400CC3                 mov     rax, rsp
.text:0000000000400CC6                 xchg    rax, [rax]
.text:0000000000400CC9                 pop     rsp
.text:0000000000400CCA                 mov     [rsp], rax
.text:0000000000400CCE                 retn

直接 patch 为 nop:

import idc

for i in range(0x400CB5, 0x400CBA + 1):
    idc.patch_byte(i, 0x90)

for i in range(0x400CBD, 0x400CCE + 1):
    idc.patch_byte(i, 0x90)

获得一个到 0x400CCF 的跳转:

.text:0000000000400CB9                 nop
.text:0000000000400CBA                 nop
.text:0000000000400CBB                 jmp     short loc_400CCF
.text:0000000000400CBD ; ---------------------------------------------------------------------------
.text:0000000000400CBD
.text:0000000000400CBD loc_400CBD:                             ; CODE XREF: .text:0000000000400CB5↑j
.text:0000000000400CBD                 nop
.text:0000000000400CBE                 nop
.text:0000000000400CBF                 nop
.text:0000000000400CC0                 nop

继续向下看,在 0x400D04 又发现一个非常经典的花指令,还是直接将第一个字节 patch 为 nop 即可:

.text:0000000000400D04 loc_400D04:                             ; CODE XREF: .text:0000000000400CEE↑j
.text:0000000000400D04                                         ; .text:loc_400D04↑j
.text:0000000000400D04                 jmp     short near ptr loc_400D04+1
.text:0000000000400D04 ; ---------------------------------------------------------------------------
.text:0000000000400D06                 db 0C0h
.text:0000000000400D07                 db  48h ; H
.text:0000000000400D08                 db  90h
.text:0000000000400D09                 db  90h
.text:0000000000400D0A                 db 0BFh

现在看起来就是一个非常正常的函数末尾了:

.text:0000000000400D04 loc_400D04:                             ; CODE XREF: .text:0000000000400CEE↑j
.text:0000000000400D04                 nop
.text:0000000000400D05                 inc     eax
.text:0000000000400D07                 xchg    rax, rax
.text:0000000000400D09                 nop
.text:0000000000400D0A                 mov     edi, 0
.text:0000000000400D0F                 call    exit
.text:0000000000400D14 ; ---------------------------------------------------------------------------
.text:0000000000400D14                 nop
.text:0000000000400D15                 mov     rax, [rbp-8]
.text:0000000000400D19                 xor     rax, fs:28h
.text:0000000000400D22                 jz      short locret_400D29
.text:0000000000400D24                 call    ___stack_chk_fail
.text:0000000000400D29 ; ---------------------------------------------------------------------------
.text:0000000000400D29
.text:0000000000400D29 locret_400D29:                          ; CODE XREF: .text:0000000000400D22↑j
.text:0000000000400D29                 leave
.text:0000000000400D2A                 retn
.text:0000000000400D2A ; } // starts at 400B54

现在我们回到 main 的开头,p 一下重新建立函数,之后就可以正常 F5 反编译了,main() 逻辑如下:

  • 首先调用 sub_4008B9()

  • 接下来从输入读取 19 字节

  • 调用 mprotect() 修改 main & 0xFFFFC000 处权限为 r | w | x,由于权限控制粒度为内存页,因此这里实际上会修改一整张内存页的权限

  • 修改 sub_400A69() 开头的 10 个字节

  • 调用 sub_400A69() 检查 flag

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int v3; // ecx
  int v4; // er8
  int v5; // er9
  int i; // [rsp+4h] [rbp-23Ch]
  __int64 v7[4]; // [rsp+10h] [rbp-230h] BYREF
  char v8[520]; // [rsp+30h] [rbp-210h] BYREF
  unsigned __int64 v9; // [rsp+238h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  memset(v8, 0, 0x200uLL);
  v7[0] = 0LL;
  v7[1] = 0LL;
  v7[2] = 0LL;
  v7[3] = 0LL;
  if ( (unsigned int)sub_4008B9(v8, a2, v8) == -1 )
    exit(0LL);
  read(0LL, v7, 19LL);
  qword_602048 = (__int64)sub_400A69;
  mprotect((unsigned int)main & 0xFFFFC000, 16LL, 7LL);
  for ( i = 0; i <= 9; ++i )
  {
    v3 = i % 5;
    *(_BYTE *)(qword_602048 + i) ^= *((_BYTE *)v7 + i % 5);
  }
  if ( (unsigned int)sub_400A69((unsigned int)v8, (unsigned int)v7 + 5, (unsigned int)v8, v3, v4, v5) )
    write(1LL, "Cong!\n", 6LL);
  exit(0LL);
}

sub_4008B9() 会调用 fork() 分出父子进程,其中子进程会请求父进程调试执行 /bin/cat /proc/version:

__int64 __fastcall sub_4008B9(__int64 a1)
{
  unsigned int v2; // [rsp+14h] [rbp-5Ch] BYREF
  int v3; // [rsp+18h] [rbp-58h]
  unsigned int v4; // [rsp+1Ch] [rbp-54h]
  __int64 v5; // [rsp+20h] [rbp-50h]
  __int64 v6; // [rsp+28h] [rbp-48h]
  __int64 v7; // [rsp+30h] [rbp-40h]
  __int64 v8; // [rsp+38h] [rbp-38h]
  __int64 v9; // [rsp+40h] [rbp-30h] BYREF
  __int64 v10[4]; // [rsp+50h] [rbp-20h] BYREF

  v10[3] = __readfsqword(0x28u);
  v4 = fork();
  if ( (v4 & 0x80000000) != 0 )
    v2 = -1;
  if ( !v4 )
  {
    v10[0] = (__int64)&unk_400DB8;
    v10[1] = (__int64)"/proc/version";
    v10[2] = 0LL;
    v9 = 0LL;
    ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
    execve("/bin/cat", v10, &v9);
    exit(127LL);
  }

父进程以单个系统调用作为步长进行单步调试,这里的 PTRACE_PEEKUSER 会根据提供的偏移值取出子进程对应寄存器的值,偏移值与寄存器间关系参见内核源码 /arch/x86/include/asm/user_64.h 中的 user_regs_struct 结构体.

这里偏移 120 取的是 orig_rax,即**系统调用号,也就是说直到系统调用号为 1(即 write)时父进程才会进入核心逻辑:获取 rsi (偏移 104)与 rdx (偏移 96)并调用 sub_4007D1() :

  v3 = 0;
  v5 = a1;
  while ( 1 )
  {
    wait4(v4, &v2, 0LL, 0LL);
    if ( (v2 & 0x7F) == 0 )
      break;
    v6 = ptrace(PTRACE_PEEKUSER, v4, 120LL, 0LL);
    if ( v6 == 1 )
    {
      if ( v3 )
      {
        v3 = 0;
      }
      else
      {
        v3 = 1;
        v7 = ptrace(PTRACE_PEEKUSER, v4, 104LL, 0LL);
        v8 = ptrace(PTRACE_PEEKUSER, v4, 96LL, 0LL);
        sub_4007D1(v4, v7, v5, v8);
        v5 += v8;
      }
    }
    ptrace(PTRACE_SYSCALL, v4, 0LL, 0LL);
  }
  return v2;
}

sub_4007D1() 比较简单,主要就是将子进程调用 write() 的输出结果拷贝回父进程:

__int64 __fastcall sub_4007D1(unsigned int a1, __int64 a2, __int64 a3, __int64 a4)
{
  __int64 result; // rax
  int v6; // [rsp+20h] [rbp-20h]
  int v7; // [rsp+24h] [rbp-1Ch]

  v6 = 0;
  v7 = a4 / 8;
  while ( v6 < v7 )
  {
    *(_QWORD *)(4 * v6 + a3) = ptrace(PTRACE_PEEKDATA, a1, a2 + 4 * v6, 0LL);
    ++v6;
  }
  result = a4 % 8;
  if ( (unsigned int)(a4 % 8) )
  {
    result = ptrace(PTRACE_PEEKDATA, a1, a2 + 4 * v6, 0LL);
    *(_QWORD *)(4 * v6 + a3) = result;
  }
  return result;
}

接下来我们回到 main() 中,由于 sub_400A69() 会在运行时被修改因此我们需要将修改结果直接应用到 IDA 中以获得正确的反汇编结果,其会取 flag 的前 5 字节与代码段进行运算,而 flag 的前 5 字节恒定为 n1ctf,因此我们这样修复 sub_400A69() :

import idc

s = 'n1ctf'

for i in range(0x400A69, 0x400A69 + 10):
    c = idc.get_db_byte(i)
    c ^= ord(s[(i - 0x400A69) % 5])
    idc.patch_byte(i, c)

在 sub_400A69() 当中还存在一个伪花指令,这里直接将 0x400AC4 的 jmp 给 patch 为 nop 即可:

.text:0000000000400A69 sub_400A69      proc near               ; CODE XREF: main+193↓p
.text:0000000000400A69                                         ; DATA XREF: main+B4↓o
.text:0000000000400A69
.text:0000000000400A69 var_40          = qword ptr -40h
.text:0000000000400A69
.text:0000000000400A69 ; __unwind {
.text:0000000000400A69                 push    rbp
.text:0000000000400A6A                 mov     rbp, rsp
.text:0000000000400A6D                 sub     rsp, 40h
.text:0000000000400A71                 mov     [rbp-38h], rdi
.text:0000000000400A75                 mov     [rbp-40h], rsi
.text:0000000000400A79                 mov     rax, fs:28h
.text:0000000000400A82                 mov     [rbp-8], rax
.text:0000000000400A86                 xor     eax, eax
.text:0000000000400A88                 mov     byte ptr [rbp-20h], 35h ; '5'
.text:0000000000400A8C                 mov     byte ptr [rbp-1Fh], 2Dh ; '-'
.text:0000000000400A90                 mov     byte ptr [rbp-1Eh], 11h
.text:0000000000400A94                 mov     byte ptr [rbp-1Dh], 1Ah
.text:0000000000400A98                 mov     byte ptr [rbp-1Ch], 49h ; 'I'
.text:0000000000400A9C                 mov     byte ptr [rbp-1Bh], 7Dh ; '}'
.text:0000000000400AA0                 mov     byte ptr [rbp-1Ah], 11h
.text:0000000000400AA4                 mov     byte ptr [rbp-19h], 14h
.text:0000000000400AA8                 mov     byte ptr [rbp-18h], 2Bh ; '+'
.text:0000000000400AAC                 mov     byte ptr [rbp-17h], 3Bh ; ';'
.text:0000000000400AB0                 mov     byte ptr [rbp-16h], 3Eh ; '>'
.text:0000000000400AB4                 mov     byte ptr [rbp-15h], 3Dh ; '='
.text:0000000000400AB8                 mov     byte ptr [rbp-14h], 3Ch ; '<'
.text:0000000000400ABC                 mov     byte ptr [rbp-13h], 5Fh ; '_'
.text:0000000000400AC0                 jz      short loc_400AC9
.text:0000000000400AC2                 jnz     short loc_400AC9
.text:0000000000400AC4                 jmp     near ptr 801AC9h

在 0x400B0E 处还是存在一处和前面一样的花指令,继续 patch:

.text:0000000000400B0E                 call    loc_400B16
.text:0000000000400B0E ; ---------------------------------------------------------------------------
.text:0000000000400B13                 db 0E8h
.text:0000000000400B14                 db 0EBh
.text:0000000000400B15                 db  12h
.text:0000000000400B16 ; ---------------------------------------------------------------------------
.text:0000000000400B16
.text:0000000000400B16 loc_400B16:                             ; CODE XREF: sub_400A69+A5↑j
.text:0000000000400B16                 pop     rax
.text:0000000000400B17                 add     rax, 1
.text:0000000000400B1B                 push    rax
.text:0000000000400B1C                 mov     rax, rsp
.text:0000000000400B1F                 xchg    rax, [rax]
.text:0000000000400B22                 pop     rsp
.text:0000000000400B23                 mov     [rsp+40h+var_40], rax
.text:0000000000400B27                 retn

接下来就能正常地反编译 sub_400A69() 了,核心逻辑其实非常简单,需要注意的是在 main() 中传入的 flag 从 n1ctf 往后开始:

__int64 __fastcall sub_400A69(__int64 a1, __int64 a2)
{
  __int64 v2; // rbp
  int i; // [rsp+14h] [rbp-2Ch]
  char v5[8]; // [rsp+18h] [rbp-28h]
  _BYTE v6[6]; // [rsp+20h] [rbp-20h] BYREF
  unsigned __int64 v7; // [rsp+30h] [rbp-10h]
  __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = v2;
  v7 = __readfsqword(0x28u);
  v5[0] = 53;
  v5[1] = 45;
  v5[2] = 17;
  v5[3] = 26;
  v5[4] = 73;
  v5[5] = 125;
  v5[6] = 17;
  v5[7] = 20;
  qmemcpy(v6, "+;>=<_", sizeof(v6));
  for ( i = 0; i <= 13; ++i )
  {
    if ( v5[i] != ((*(char *)(i + a1) + 2) ^ *(char *)(i + a2)) )
      return 0LL;
  }
  return 1LL;
}

求解

由于 /proc/version 的前 14 字节恒定为 "Linux version " ,因此我们很容易便能得到 flag 内容:

s = "5-\x11\x1AI}\x11\x14+;>=<_"
b = "Linux version "
ans = ""

for i in range(14):
    ans += chr(ord(s[i]) ^ (ord(b[i]) + 2))
print(ans)
# {Fam3_is_NULL}
Previous控制流平坦化Nextmovofuscator

Last updated 1 year ago