裸函数
void test()
{
}
int _tmain(int argc, _TCHAR* argv[])
{
test();
return 0;
}
空函数,编译器替我们生成了基本结构
测试代码
//_declspec(naked) 不需要编译器替我们生成基本结构
void _declspec(naked) test()
{
}
int _tmain(int argc, _TCHAR* argv[])
{
test();
return 0;
}
函数下断点,转反汇编,F11进去
发现这是编译器给我们开启了增量链接,Release版本是自动关闭的
所以我们手动关闭即可
裸函数时,编译器是不会给你生成任何的代码.
但你至少写一个ret因为在我们调用函数的时候,call xxx,push 了函数的下一条指令,所以我们在调用完函数时,要ret回去.如果不写的话,操作堆栈的时候会报错.
裸函数最大的特点就是: 所有的结构和规则是自己定的,所以我们可以自定义规则hook,效率会比一般函数高.
shellcode 可以推荐使用裸函数
一般函数都是esp或者ebp去寻址.
64位系统没有裸函数
在裸函数中去定义局部变量是会报错的,因为寻址方式没有定义,没有配置函数环境,提升堆栈,回收堆栈等问题
所以要想定义局部变量,就要手动的去配置堆栈环境
void _declspec(naked) test()
{
__asm
{
push ebp;
mov ebp, esp;
sub esp, 0C0h;
}
int n;
n = 0;
__asm
{
mov esp, ebp;
pop ebp;
ret;
}
}
注意: 函数范围中不允许存在初始化的局部变量
具体规则:规则和限制裸的功能
段权限检查
三个概念:
(1)RPL(Request Privilege Level) 请求特权级别,段选择子的后两位。
(2)DPL(Descriptor Privilege Level) 段描述特权级别,13位与14位。
(3)CPL(Current Privilege Level)当前特权级别,当前工作在CS\SS段的RPL。
区分:RPL、CPL、DPL
RPL:访问权限等级:Request Privilege Level,请求特权级别;
mov ax,001B
mov ds,ax
看我们提供的001B,转成二进制后的最后2位 //RLP是针对段选择子而言的,可以自己随便写
CPL:CPU当的特权级 看CS段SS段 //当前程序CS和SS段的权限(代码执行一定会用堆栈,就一定会用SS)
DPL:DPL段描述权限,访问该段所需要的特权级别 看段描述符的DPL字段 //DPL存储在段描述符中,规定了【访问该段所需要的特权级别是什么】
RPL访问权限等级 请求的特权级
ds段后两位
ss段后两位
DPL 段描述特权级别
CPL当前程序的特权级
当前权限等级Current Privilege Level
看CS和SS,一般看一个就够了,因为是一样的.
CS SS 最后两位决定你在哪个环上
CS和SS中,存储的【段选择子后2位】
如:
001B: 0000 0000 0001 1011 后两位为11,值为3–>当前的程序处于3环
0008: 0000 0000 0000 0100 后两位为00,值为0–>当前程序处于0环
一、实验数据段的 权限
条件:CPL <= DPL (当前程序权限是否足够) 并且RPL<=DPL (段选择子请求的权限是否足够) (数值越大,权限越小)
执行以下该代码
不会发生异常
二、实验堆栈段的权限
条件:CPL = RPL = DPL
**代码段条件也一样 **
执行以下该代码
发生异常
三、实验代码段权限
条件:CPL = RPL = DPL
假设此处是代码段,不是调用门之类的 区分是一致代码段还是非一致代码段
①非一致代码段要求:CPL=DPL且RPL<=DPL
②一致代码段要求: CPL>=DPL
能修改,但不会提权,也不会抛异常
四、实验 jmp call ret
jmp
void _declspec(naked) test1()
{
__asm
{
//jmp eax;
//retf;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
__asm
{
}
system("pause");
return 0;
}
打印的地址是随机的,我们要将该设置关闭
void _declspec(naked) test1()
{
__asm
{
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
__asm
{
//微软并不支持该写法
// jmp far 0x48:401000
}
system("pause");
return 0;
}
void _declspec(naked) test1()
{
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
char bufcode[] = {0,0,0,0,0x48,0};
*(int *)&bufcode[0] = (ULONG)test1;
__asm
{
//微软并不支持该写法
// jmp far 0x48:401000
jmp fword ptr bufcode;
}
system("pause");
return 0;
}
CS段修改成功
那么如何返回去呢?
void _declspec(naked) test1()
{
__asm
{
pop eax;
jmp eax;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
char bufcode[] = {0,0,0,0,0x48,0};
*(int *)&bufcode[0] = (ULONG)test1;
__asm
{
lea eax,[haha]
push eax;
jmp fword ptr bufcode;
haha:
}
system("pause");
return 0;
}
如何证明提权?
如果你能访问到内核地址,那么你就提权了,
不能提权的原因是:不能同时修改CS和SS
void _declspec(naked) test1()
{
__asm
{
mov eax,0x80b99000;
mov eax,dword ptr [eax];
//mov eax,dword ptr ds:[0x80b99000];
pop eax;
jmp eax;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
char bufcode[] = {0,0,0,0,0x48,0};
*(int *)&bufcode[0] = (ULONG)test1;
__asm
{
lea eax,[haha]
push eax;
jmp fword ptr bufcode;
haha:
}
system("pause");
return 0;
}
跨段不提权远跳转
void _declspec(naked) test1()
{
__asm
{
ret;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
char bufcode[]={0,0,0,0,0x48,0};
*(int *)&bufcode[0] = (ULONG)test1;
__asm
{
call fword ptr bufcode;
haha:
}
system("pause");
return 0;
}
正常来说 esp提升堆栈应该是0012FE4C,那为什么是0012FE48呢?
多了个1B
多压了个CS段
导致堆栈不平衡
使用retf可以实现原跳转,不会报错
void _declspec(naked) test1()
{
__asm
{
retf;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%x\r\n",test1);
char bufcode[]={0,0,0,0,0x48,0};
*(int *)&bufcode[0] = (ULONG)test1;
__asm
{
call fword ptr bufcode;
haha:
}
system("pause");
return 0;
}