📊
CTF-All-In-One
  • 简介
  • 前言
  • 一、基础知识篇
    • 1.1 CTF 简介
    • 1.2 学习方法
    • 1.3 Linux 基础
    • 1.4 Web 安全基础
      • 1.4.1 HTML 基础
      • 1.4.2 HTTP 协议基础
      • 1.4.3 JavaScript 基础
      • 1.4.4 常见 Web 服务器基础
      • 1.4.5 OWASP Top Ten Project 漏洞基础
      • 1.4.6 PHP 源码审计基础
    • 1.5 逆向工程基础
      • 1.5.1 C/C++ 语言基础
      • 1.5.2 汇编基础
      • 1.5.3 Linux ELF
      • 1.5.4 Windows PE
      • 1.5.5 静态链接
      • 1.5.6 动态链接
      • 1.5.7 内存管理
      • 1.5.8 glibc malloc
      • 1.5.9 Linux 内核
      • 1.5.10 Windows 内核
      • 1.5.11 jemalloc
    • 1.6 密码学基础
      • 1.6.1 密码学导论
      • 1.6.2 流密码
      • 1.6.3 分组密码
      • 1.6.4 公钥密码
      • 1.6.5 消息认证和哈希函数
      • 1.6.6 数字签名
      • 1.6.7 密码协议
      • 1.6.8 密钥分配与管理
      • 1.6.9 数字货币
    • 1.7 Android 安全基础
      • 1.7.1 Android 环境搭建
      • 1.7.2 Dalvik 指令集
      • 1.7.3 ARM 汇编基础
      • 1.7.4 Android 常用工具
  • 二、工具篇
    • 虚拟化分析环境
      • 2.1.1 VirtualBox
      • 2.1.2 QEMU
      • 2.1.3 Docker
      • 2.1.4 Unicorn
    • 静态分析工具
      • 2.2.1 radare2
      • 2.2.2 IDA Pro
      • 2.2.3 JEB
      • 2.2.4 Capstone
      • 2.2.5 Keystone
      • 2.2.6 Ghidra
    • 动态分析工具
      • 2.3.1 GDB
      • 2.3.2 OllyDbg
      • 2.3.3 x64dbg
      • 2.3.4 WinDbg
      • 2.3.5 LLDB
    • 其他工具
      • 2.4.1 pwntools
      • 2.4.2 zio
      • 2.4.3 metasploit
      • 2.4.4 binwalk
      • 2.4.5 Burp Suite
      • 2.4.6 Wireshark
      • 2.4.7 Cuckoo Sandbox
  • 三、分类专题篇
    • Pwn
      • 3.1.1 格式化字符串漏洞
      • 3.1.2 整数溢出
      • 3.1.3 栈溢出
      • 3.1.4 返回导向编程(ROP)(x86)
      • 3.1.5 返回导向编程(ROP)(ARM)
      • 3.1.6 Linux 堆利用(一)
      • 3.1.7 Linux 堆利用(二)
      • 3.1.8 Linux 堆利用(三)
      • 3.1.9 Linux 堆利用(四)
      • 3.1.10 内核 ROP
      • 3.1.11 Linux 内核漏洞利用
      • 3.1.12 Windows 内核漏洞利用
      • 3.1.13 竞争条件
      • 3.1.14 虚拟机逃逸
    • Reverse
      • 3.2.1 patch 二进制文件
      • 3.2.2 脱壳技术(PE)
      • 3.2.3 脱壳技术(ELF)
      • 3.2.4 反调试技术(PE)
      • 3.2.5 反调试技术(ELF)
      • 3.2.6 指令混淆
    • Web
      • 3.3.1 SQL 注入利用
      • 3.3.2 XSS 漏洞利用
    • Crypto
    • Misc
      • 3.5.1 Lsb
    • Mobile
  • 四、技巧篇
    • 4.1 Linux 内核调试
    • 4.2 Linux 命令行技巧
    • 4.3 GCC 编译参数解析
    • 4.4 GCC 堆栈保护技术
    • 4.5 ROP 防御技术
    • 4.6 one-gadget RCE
    • 4.7 通用 gadget
    • 4.8 使用 DynELF 泄露函数地址
    • 4.9 shellcode 开发
    • 4.10 跳转导向编程(JOP)
    • 4.11 利用 mprotect 修改栈权限
    • 4.12 利用 __stack_chk_fail
    • 4.13 利用 _IO_FILE 结构
    • 4.14 glibc tcache 机制
    • 4.15 利用 vsyscall 和 vDSO
  • 五、高级篇
    • 5.0 软件漏洞分析
    • 5.1 模糊测试
      • 5.1.1 AFL fuzzer
      • 5.1.2 libFuzzer
    • 5.2 动态二进制插桩
      • 5.2.1 Pin
      • 5.2.2 DynamoRio
      • 5.2.3 Valgrind
    • 5.3 符号执行
      • 5.3.1 angr
      • 5.3.2 Triton
      • 5.3.3 KLEE
      • 5.3.4 S²E
    • 5.4 数据流分析
      • 5.4.1 Soot
    • 5.5 污点分析
      • 5.5.1 TaintCheck
    • 5.6 LLVM
      • 5.6.1 Clang
    • 5.7 程序切片
    • 5.8 SAT/SMT
      • 5.8.1 Z3
    • 5.9 基于模式的漏洞分析
    • 5.10 基于二进制比对的漏洞分析
    • 5.11 反编译技术
      • 5.11.1 RetDec
  • 六、题解篇
    • Pwn
      • 6.1.1 pwn HCTF2016 brop
      • 6.1.2 pwn NJCTF2017 pingme
      • 6.1.3 pwn XDCTF2015 pwn200
      • 6.1.4 pwn BackdoorCTF2017 Fun-Signals
      • 6.1.5 pwn GreHackCTF2017 beerfighter
      • 6.1.6 pwn DefconCTF2015 fuckup
      • 6.1.7 pwn 0CTF2015 freenote
      • 6.1.8 pwn DCTF2017 Flex
      • 6.1.9 pwn RHme3 Exploitation
      • 6.1.10 pwn 0CTF2017 BabyHeap2017
      • 6.1.11 pwn 9447CTF2015 Search-Engine
      • 6.1.12 pwn N1CTF2018 vote
      • 6.1.13 pwn 34C3CTF2017 readme_revenge
      • 6.1.14 pwn 32C3CTF2015 readme
      • 6.1.15 pwn 34C3CTF2017 SimpleGC
      • 6.1.16 pwn HITBCTF2017 1000levels
      • 6.1.17 pwn SECCONCTF2016 jmper
      • 6.1.18 pwn HITBCTF2017 Sentosa
      • 6.1.19 pwn HITBCTF2018 gundam
      • 6.1.20 pwn 33C3CTF2016 babyfengshui
      • 6.1.21 pwn HITCONCTF2016 Secret_Holder
      • 6.1.22 pwn HITCONCTF2016 Sleepy_Holder
      • 6.1.23 pwn BCTF2016 bcloud
      • 6.1.24 pwn HITCONCTF2016 House_of_Orange
      • 6.1.25 pwn HCTF2017 babyprintf
      • 6.1.26 pwn 34C3CTF2017 300
      • 6.1.27 pwn SECCONCTF2016 tinypad
      • 6.1.28 pwn ASISCTF2016 b00ks
      • 6.1.29 pwn Insomni'hack_teaserCTF2017 The_Great_Escape_part-3
      • 6.1.30 pwn HITCONCTF2017 Ghost_in_the_heap
      • 6.1.31 pwn HITBCTF2018 mutepig
      • 6.1.32 pwn SECCONCTF2017 vm_no_fun
      • 6.1.33 pwn 34C3CTF2017 LFA
      • 6.1.34 pwn N1CTF2018 memsafety
      • 6.1.35 pwn 0CTF2018 heapstorm2
      • 6.1.36 pwn NJCTF2017 messager
      • 6.1.37 pwn sixstarctf2018 babystack
      • 6.1.38 pwn HITCONCMT2017 pwn200
      • 6.1.39 pwn BCTF2018 house_of_Atum
      • 6.1.40 pwn LCTF2016 pwn200
      • 6.1.41 pwn PlaidCTF2015 PlaidDB
      • 6.1.42 pwn hacklu2015 bookstore
      • 6.1.43 pwn 0CTF2018 babyheap
      • 6.1.44 pwn ASIS2017 start_hard
      • 6.1.45 pwn LCTF2016 pwn100
    • Reverse
      • 6.2.1 re XHPCTF2017 dont_panic
      • 6.2.2 re ECTF2016 tayy
      • 6.2.3 re CodegateCTF2017 angrybird
      • 6.2.4 re CSAWCTF2015 wyvern
      • 6.2.5 re PicoCTF2014 Baleful
      • 6.2.6 re SECCONCTF2017 printf_machine
      • 6.2.7 re CodegateCTF2018 RedVelvet
      • 6.2.8 re DefcampCTF2015 entry_language
    • Web
      • 6.3.1 web HCTF2017 babycrack
    • Crypto
    • Misc
    • Mobile
  • 七、实战篇
    • CVE
      • 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
      • 7.1.2 CVE-2015-0235 glibc __nss_hostname_digits_dots 堆溢出漏洞
      • 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
      • 7.1.4 CVE-2017-13089 wget skip_short_body 栈溢出漏洞
      • 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
      • 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
      • 7.1.7 CVE-2018-6323 GNU binutils elf_object_p 整型溢出漏洞
      • 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
      • 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
    • Malware
  • 八、学术篇
    • 8.1 The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86)
    • 8.2 Return-Oriented Programming without Returns
    • 8.3 Return-Oriented Rootkits: Bypassing Kernel Code Integrity Protection Mechanisms
    • 8.4 ROPdefender: A Detection Tool to Defend Against Return-Oriented Programming Attacks
    • 8.5 Data-Oriented Programming: On the Expressiveness of Non-Control Data Attacks
    • 8.7 What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP Defenses
    • 8.9 Symbolic Execution for Software Testing: Three Decades Later
    • 8.10 AEG: Automatic Exploit Generation
    • 8.11 Address Space Layout Permutation (ASLP): Towards Fine-Grained Randomization of Commodity Softwa
    • 8.13 New Frontiers of Reverse Engineering
    • 8.14 Who Allocated My Memory? Detecting Custom Memory Allocators in C Binaries
    • 8.21 Micro-Virtualization Memory Tracing to Detect and Prevent Spraying Attacks
    • 8.22 Practical Memory Checking With Dr. Memory
    • 8.23 Evaluating the Effectiveness of Current Anti-ROP Defenses
    • 8.24 How to Make ASLR Win the Clone Wars: Runtime Re-Randomization
    • 8.25 (State of) The Art of War: Offensive Techniques in Binary Analysis
    • 8.26 Driller: Augmenting Fuzzing Through Selective Symbolic Execution
    • 8.27 Firmalice - Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware
    • 8.28 Cross-Architecture Bug Search in Binary Executables
    • 8.29 Dynamic Hooks: Hiding Control Flow Changes within Non-Control Data
    • 8.30 Preventing brute force attacks against stack canary protection on networking servers
    • 8.33 Under-Constrained Symbolic Execution: Correctness Checking for Real Code
    • 8.34 Enhancing Symbolic Execution with Veritesting
    • 8.38 TaintEraser: Protecting Sensitive Data Leaks Using Application-Level Taint Tracking
    • 8.39 DART: Directed Automated Random Testing
    • 8.40 EXE: Automatically Generating Inputs of Death
    • 8.41 IntPatch: Automatically Fix Integer-Overflow-to-Buffer-Overflow Vulnerability at Compile-Time
    • 8.42 Dynamic Taint Analysis for Automatic Detection, Analysis, and Signature Generation of Exploits
    • 8.43 DTA++: Dynamic Taint Analysis with Targeted Control-Flow Propagation
    • 8.44 Superset Disassembly: Statically Rewriting x86 Binaries Without Heuristics
    • 8.45 Ramblr: Making Reassembly Great Again
    • 8.46 FreeGuard: A Faster Secure Heap Allocator
    • 8.48 Reassembleable Disassembling
  • 九、附录
    • 9.1 更多 Linux 工具
    • 9.2 更多 Windows 工具
    • 9.3 更多资源
    • 9.4 Linux 系统调用表
    • 9.5 python2到3字符串转换
    • 9.6 幻灯片
Powered by GitBook
On this page
  • 从源代码到可执行文件
  • 预编译
  • 编译
  • 汇编
  • 链接
  • gcc 技巧
  • C 语言标准库
  • 整数表示
  • 格式化输出函数
  • 格式字符串
  • 例子
  • 关于 C++

Was this helpful?

  1. 一、基础知识篇
  2. 1.5 逆向工程基础

1.5.1 C/C++ 语言基础

Previous1.5 逆向工程基础Next1.5.2 汇编基础

Last updated 3 years ago

Was this helpful?

从源代码到可执行文件

我们以经典著作《The C Programming Language》中的第一个程序 “Hello World” 为例,讲解 Linux 下 GCC 的编译过程。

#include <stdio.h>
main()
{
    printf("hello, world\n");
}
$gcc hello.c
$./a.out
hello world

以上过程可分为4个步骤:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。

预编译

gcc -E hello.c -o hello.i
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
......
extern int printf (const char *__restrict __format, ...);
......
main() {
 printf("hello, world\n");
}

预编译过程主要处理源代码中以 “#” 开始的预编译指令:

  • 将所有的 “#define” 删除,并且展开所有的宏定义。

  • 处理所有条件预编译指令,如 “#if”、“#ifdef”、“#elif”、“#else”、“#endif”。

  • 处理 “#include” 预编译指令,将被包含的文件插入到该预编译指令的位置。注意,该过程递归执行。

  • 删除所有注释。

  • 添加行号和文件名标号。

  • 保留所有的 #pragma 编译器指令。

编译

gcc -S hello.c -o hello.s
        .file   "hello.c"
        .section        .rodata
.LC0:
        .string "hello, world"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 7.2.0"
        .section        .note.GNU-stack,"",@progbits

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。

汇编

$ gcc -c hello.s -o hello.o
或者
$gcc -c hello.c -o hello.o
$ objdump -sd hello.o

hello.o:     file format elf64-x86-64

Contents of section .text:
 0000 554889e5 488d3d00 000000e8 00000000  UH..H.=.........
 0010 b8000000 005dc3                      .....].
Contents of section .rodata:
 0000 68656c6c 6f2c2077 6f726c64 00        hello, world.
Contents of section .comment:
 0000 00474343 3a202847 4e552920 372e322e  .GCC: (GNU) 7.2.
 0010 3000                                 0.
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 17000000 00410e10 8602430d  .........A....C.
 0030 06520c07 08000000                    .R......

Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # b <main+0xb>
   b:   e8 00 00 00 00          callq  10 <main+0x10>
  10:   b8 00 00 00 00          mov    $0x0,%eax
  15:   5d                      pop    %rbp
  16:   c3                      retq

汇编器将汇编代码转变成机器可以执行的指令。

链接

gcc hello.o -o hello
$ objdump -d -j .text hello
......
000000000000064a <main>:
 64a:   55                      push   %rbp
 64b:   48 89 e5                mov    %rsp,%rbp
 64e:   48 8d 3d 9f 00 00 00    lea    0x9f(%rip),%rdi        # 6f4 <_IO_stdin_used+0x4>
 655:   e8 d6 fe ff ff          callq  530 <puts@plt>
 65a:   b8 00 00 00 00          mov    $0x0,%eax
 65f:   5d                      pop    %rbp
 660:   c3                      retq
 661:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 668:   00 00 00
 66b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
......

目标文件需要链接一大堆文件才能得到最终的可执行文件(上面只展示了链接后的 main 函数,可以和 hello.o 中的 main 函数作对比)。链接过程主要包括地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定向(Relocation)等。

gcc 技巧

通常在编译后只会生成一个可执行文件,而中间过程生成的 .i、.s、.o 文件都不会被保存。我们可以使用参数 -save-temps 永久保存这些临时的中间文件。

$ gcc -save-temps hello.c
$ ls
a.out hello.c  hello.i  hello.o  hello.s

这里要注意的是,gcc 默认使用动态链接,所以这里生成的 a.out 实际上是共享目标文件。

$ file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=533aa4ca46d513b1276d14657ec41298cafd98b1, not stripped

使用参数 --verbose 可以输出 gcc 详细的工作流程。

gcc hello.c -static --verbose

东西很多,我们主要关注下面几条信息:

$ /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/cc1 -quiet -v hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -o /tmp/ccj1jUMo.s

as -v --64 -o /tmp/ccAmXrfa.o /tmp/ccj1jUMo.s

/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/collect2 -plugin /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/lto-wrapper -plugin-opt=-fresolution=/tmp/cc1l5oJV.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lc --build-id --hash-style=gnu -m elf_x86_64 -static /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib/crt1.o /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib/crti.o /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/crtbeginT.o -L/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0 -L/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../.. /tmp/ccAmXrfa.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/crtend.o /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib/crtn.o

三条指令分别是 cc1、as 和 collect2,cc1 是 gcc 的编译器,将 .c 文件编译为 .s 文件,as 是汇编器命令,将 .s 文件汇编成 .o 文件,collect2 是链接器命令,它是对命令 ld 的封装。静态链接时,gcc 将 C 语言运行时库的 5 个重要目标文件 crt1.o、crti.o、crtbeginT.o、crtend.o、crtn.o 和 -lgcc、-lgcc_eh、-lc 表示的 3 个静态库链接到可执行文件中。

更多的内容我们会在 1.5.3 中专门对 ELF 文件进行讲解。

C 语言标准库

C 运行库(CRT)是一套庞大的代码库,以支撑程序能够正常地运行。其中 C 语言标准库占据了最主要地位。

常用的标准库文件头:

  • 标准输入输出(stdio.h)

  • 字符操作(ctype.h)

  • 字符串操作(string.h)

  • 数学函数(math.h)

  • 实用程序库(stdlib.h)

  • 时间/日期(time.h)

  • 断言(assert.h)

  • 各种类型上的常数(limits.h & float.h)

  • 变长参数(stdarg.h)

  • 非局部跳转(setjmp.h)

glibc 即 GNU C Library,是为 GNU 操作系统开发的一个 C 标准库。glibc 主要由两部分组成,一部分是头文件,位于 /usr/include;另一部分是库的二进制文件。二进制文件部分主要是 C 语言标准库,有动态和静态两个版本,动态版本位于 /lib/libc.so.6,静态版本位于 /usr/lib/libc.a。

整数表示

默认情况下,C 语言中的数字是有符号数,下面我们声明一个有符号整数和无符号整数:

int var1 = 0;
unsigned int var2 = 0;
  • 有符号整数

    • 可以表示为正数或负数

    • int 的范围:-2,147,483,648 ~ 2,147,483,647

  • 无符号整数

    • 只能表示为零或正数

    • unsigned int 的范围:0 ~ 4,294,967,295

signed 或者 unsigned 取决于整数类型是否可以携带标志 +/-:

  • Signed

    • int

    • signed int

    • long

  • Unsigned

    • uint

    • unsigned int

    • unsigned long

在 signed int 中,二进制最高位被称作符号位,符号位被设置为 1 时,表示值为负,当设置为 0 时,值为非负:

  • 0x7FFFFFFF = 2147493647

    • 01111111111111111111111111111111

  • 0x80000000 = -2147483648

    • 10000000000000000000000000000000

  • 0xFFFFFFFF = -1

    • 11111111111111111111111111111111

二进制补码以一种适合于二进制加法器的方式来表示负数,当一个二进制补码形式表示的负数和与它的绝对值相等的正数相加时,结果为 0。首先以二进制方式写出正数,然后对所有位取反,最后加 1 就可以得到该数的二进制补码:

eg: 0x00123456
  = 1193046
  = 00000000000100100011010001010110
 ~= 11111111111011011100101110101001
 += 11111111111011011100101110101010
  = -1193046 (0xFFEDCBAA)

编译器需要根据变量类型信息编译成相应的指令:

  • 有符号指令

    • IDIV:带符号除法指令

    • IMUL:带符号乘法指令

    • SAL:算术左移指令(保留符号)

    • SAR:算术右移指令(保留符号)

    • MOVSX:带符号扩展传送指令

    • JL:当小于时跳转指令

    • JLE:当小于或等于时跳转指令

    • JG:当大于时跳转指令

    • JGE:当大于或等于时跳转指令

  • 无符号指令

    • DIV:除法指令

    • MUL:乘法指令

    • SHL:逻辑左移指令

    • SHR:逻辑右移指令

    • MOVZX:无符号扩展传送指令

    • JB:当小于时跳转指令

    • JBE:当小于或等于时跳转指令

    • JA:当大于时跳转指令

    • JAE:当大于或等于时跳转指令

32 位机器上的整型数据类型,不同的系统可能会有不同:

C 数据类型
最小值
最大值
最小大小

char

-128

127

8 bits

short

-32 768

32 767

16 bits

int

-2 147 483 648

2 147 483 647

16 bits

long

-2 147 483 648

2 147 483 647

32 bits

long long

-9 223 372 036 854 775 808

9 223 372 036 854 775 807

64 bits

固定大小的数据类型:

  • int [# of bits]_t

    • int8_t, int16_t, int32_t

  • uint[# of bits]_t

    • uint8_t, uint16_t, uint32_t

  • 有符号整数

  • 无符号整数

更多信息在 stdint.h 和 limits.h 中:

man stdint.h
cat /usr/include/stdint.h
man limits.h
cat /usr/include/limits.h

了解整数的符号和大小是很有用的,在后面的相关章节中我们会介绍整数溢出的内容。

格式化输出函数

C 标准中定义了下面的格式化输出函数(参考 man 3 printf):

#include <stdio.h>

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

#include <stdarg.h>

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • fprintf() 按照格式字符串的内容将输出写入流中。三个参数为流、格式字符串和变参列表。

  • printf() 等同于 fprintf(),但是它假定输出流为 stdout。

  • sprintf() 等同于 fprintf(),但是输出不是写入流而是写入数组。在写入的字符串末尾必须添加一个空字符。

  • snprintf() 等同于 sprintf(),但是它指定了可写入字符的最大值 size。当 size 大于零时,输出字符超过第 size-1 的部分会被舍弃而不会写入数组中,在写入数组的字符串末尾会添加一个空字符。

  • dprintf() 等同于 fprintf(),但是它输出不是流而是一个文件描述符 fd。

  • vfprintf()、vprintf()、vsprintf()、vsnprintf()、vdprintf() 分别与上面的函数对应,只是它们将变参列表换成了 va_list 类型的参数。

格式字符串

格式字符串是由普通字符(ordinary character)(包括 %)和转换规则(conversion specification)构成的字符序列。普通字符被原封不动地复制到输出流中。转换规则根据与实参对应的转换指示符对其进行转换,然后将结果写入输出流中。

一个转换规则有可选部分和必需部分组成:

%[ 参数 ][ 标志 ][ 宽度 ][ .精度 ][ 长度 ] 转换指示符
  • (必需)转换指示符

字符
描述

d, i

有符号十进制数值 int。'%d' 与 '%i' 对于输出是同义;但对于 scanf() 输入二者不同,其中 %i 在输入值有前缀 0x 或 0 时,分别表示 16 进制或 8 进制的值。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空

u

十进制 unsigned int。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空

f, F

double 型输出 10 进制定点表示。'f' 与 'F' 差异是表示无穷与 NaN 时,'f' 输出 'inf', 'infinity' 与 'nan';'F' 输出 'INF', 'INFINITY' 与 'NAN'。小数点后的数字位数等于精度,最后一位数字四舍五入。精度默认为 6。如果精度为 0 且没有 # 标记,则不出现小数点。小数点左侧至少一位数字

e, E

double 值,输出形式为 10 进制的([-]d.ddd e[+/-]ddd). E 版本使用的指数符号为 E(而不是e)。指数部分至少包含 2 位数字,如果值为 0,则指数部分为 00。Windows 系统,指数部分至少为 3 位数字,例如 1.5e002,也可用 Microsoft 版的运行时函数 _set_output_format 修改。小数点前存在 1 位数字。小数点后的数字位数等于精度。精度默认为 6。如果精度为 0 且没有 # 标记,则不出现小数点

g, G

double 型数值,精度定义为全部有效数字位数。当指数部分在闭区间 [-4,精度] 内,输出为定点形式;否则输出为指数浮点形式。'g' 使用小写字母,'G' 使用大写字母。小数点右侧的尾数 0 不被显示;显示小数点仅当输出的小数部分不为 0

x, X

16 进制 unsigned int。'x' 使用小写字母;'X' 使用大写字母。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空

o

8 进制 unsigned int。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空

s

如果没有用 l 标志,输出 null 结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了 l 标志,则对应函数参数指向 wchar_t 型的数组,输出时把每个宽字符转化为多字节字符,相当于调用 wcrtomb 函数

c

如果没有用 l 标志,把 int 参数转为 unsigned char 型输出;如果用了 l 标志,把 wint_t 参数转为包含两个元素的 wchart_t 数组,其中第一个元素包含要输出的字符,第二个元素为 null 宽字符

p

void * 型,输出对应变量的值。printf("%p", a) 用地址的格式打印变量 a 的值,printf("%p", &a) 打印变量 a 所在的地址

a, A

double 型的 16 进制表示,"[−]0xh.hhhh p±d"。其中指数部分为 10 进制表示的形式。例如:1025.010 输出为 0x1.004000p+10。'a' 使用小写字母,'A' 使用大写字母

n

不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量

%

'%' 字面值,不接受任何除了 参数 以外的部分

  • (可选)参数

字符
描述

n$

n 是用这个格式说明符显示第几个参数;这使得参数可以输出多次,使用多个格式说明符,以不同的顺序输出。如果任意一个占位符使用了 参数,则其他所有占位符必须也使用 参数。例:printf("%2$d %2$#x; %1$d %1$#x",16,17) 产生 "17 0x11; 16 0x10"

  • (可选)标志

字符
描述

+

总是表示有符号数值的 '+' 或 '-' 号,缺省情况是忽略正数的符号。仅适用于数值类型

空格

使得有符号数的输出如果没有正负号或者输出 0 个字符,则前缀 1 个空格。如果空格与 '+' 同时出现,则空格说明符被忽略

-

左对齐。缺省情况是右对齐

#

对于 'g' 与 'G',不删除尾部 0 以表示精度。对于 'f', 'F', 'e', 'E', 'g', 'G', 总是输出小数点。对于 'o', 'x', 'X', 在非 0 数值前分别输出前缀 0, 0x 和 0X表示数制

0

如果 宽度 选项前缀为 0,则在左侧用 0 填充直至达到宽度要求。例如 printf("%2d", 3) 输出 "3",而 printf("%02d", 3) 输出 "03"。如果 0 与 - 均出现,则 0 被忽略,即左对齐依然用空格填充

  • (可选)宽度

是一个用来指定输出字符的最小个数的十进制非负整数。如果实际位数多于定义的宽度,则按实际位数输出;如果实际位数少于定义的宽度则补以空格或 0。

  • (可选)精度

精度是用来指示打印字符个数、小数位数或者有效数字个数的非负十进制整数。对于 d、i、u、x、o 的整型数值,是指最小数字位数,不足的位要在左侧补 0,如果超过也不截断,缺省值为 1。对于 a, A, e, E, f, F 的浮点数值,是指小数点右边显示的数字位数,必要时四舍五入;缺省值为 6。对于 g, G 的浮点数值,是指有效数字的最大位数。对于 s 的字符串类型,是指输出的字节的上限,超出限制的其它字符将被截断。如果域宽为 *,则由对应的函数参数的值为当前域宽。如果仅给出了小数点,则域宽为 0。

  • (可选)长度

字符
描述

hh

对于整数类型,printf 期待一个从 char 提升的 int 整型参数

h

对于整数类型,printf 期待一个从 short 提升的 int 整型参数

l

对于整数类型,printf 期待一个 long 整型参数。对于浮点类型,printf 期待一个 double 整型参数。对于字符串 s 类型,printf 期待一个 wchar_t 指针参数。对于字符 c 类型,printf 期待一个 wint_t 型的参数

ll

对于整数类型,printf 期待一个 long long 整型参数。Microsoft 也可以使用 I64

L

对于浮点类型,printf 期待一个 long double 整型参数

z

对于整数类型,printf 期待一个 size_t 整型参数

j

对于整数类型,printf 期待一个 intmax_t 整型参数

t

对于整数类型,printf 期待一个 ptrdiff_t 整型参数

例子

printf("Hello %%");           // "Hello %"
printf("Hello World!");       // "Hello World!"
printf("Number: %d", 123);    // "Number: 123"
printf("%s %s", "Format", "Strings");   // "Format Strings"

printf("%12c", 'A');          // "           A"
printf("%16s", "Hello");      // "          Hello!"

int n;
printf("%12c%n", 'A', &n);    // n = 12
printf("%16s%n", "Hello!", &n); // n = 16

printf("%2$s %1$s", "Format", "Strings"); // "Strings Format"
printf("%42c%1$n", &n);       // 首先输出41个空格,然后输出 n 的低八位地址作为一个字符

这里我们对格式化输出函数和格式字符串有了一个详细的认识,后面的章节中我们会介绍格式化字符串漏洞的内容。

关于 C++

在漏洞利用的过程中,通常我们通过计算目标函数地址相对于已知函数地址在同一个 libc 中的偏移,来获得目标函数的虚拟地址,这时我们需要让本地的 libc 版本和远程的 libc 版本相同,可以先泄露几个函数的地址,然后在 中进行搜索来得到。

libcdb.com
从源代码到可执行文件
C 语言标准库
整数表示
格式化输出函数
关于 C++
img
img
img