任务段进行任务切换,Jmp Call指令实现任务切换

时间:2024-04-07 07:37:33

        任务段描述符的格式我们就不说了太基本了,它的Base指向TSS结构的地址,Limit设置为0x68就够了TSS结构的大小就是104个字节,我们之前讲过,通过中断门,调用们提权的时候 ring0的堆栈ESP和SS都是TSS提供的,TSS结构每个核只有一个和KPCR结构一样,每当线程切换的时候会把当前线程的ESP0写入TSS结构中(以后进行线程切换逆向的时候会找到具体的代码),所以我们所Ring3提权到Ring0得到ESP都是当前线程的Ring的栈顶。

       TSS是CPU设计的实现多任务的方式,但是操作系统没有使用TSS作为线程切换,而是自己实现的线程切换,自己设计了一个Trap_Frame结构作为线程切换用于保存寄存器环境的一块内存,每个线程有一个通过线程结构可以找到这个字段,而TSS32位下操作系统只使用ESP0,SS0这两个字段用于门提权的时候获得RIng0的栈,

任务段进行任务切换,Jmp Call指令实现任务切换

   今天我们带领大家通过任务段的方式实现任务的切换,可以使用jmp指令和Call指令

我们之前讲过jmp指令有四种使用方式   这是白皮书卷2jmp指令四种不同的跳转方式原话

任务段进行任务切换,Jmp Call指令实现任务切换

1.jmp近跳,就是我们平常使用的jmp 硬编码后面接4个字节,可以跳转的地址为4GB,也称为段内跳转

2.jmp短跳,跳转的范围只限于当前EIP +128到-127 硬编码后面跟一个1字节

3.jmp远跳,   也称为段间跳转(会修改cs寄存器和eip寄存器的值)格式位  jmp  0x23:0x12345678  

4.任务切换,就是使用TSS结构中的值替换到当前寄存器,当我们把TSS 中的cs设置成0环的代码段就能实现提权

所以jmp指令是可以提权的,jmp指令任务切换的格式和jmp远跳的格式完全相同,有一点含义不同

jmp  0x48:0x12345678  如果此时0x48指向的是一个代码段的段描述符,此时就是jmp远跳,如果此时0x48指向的是一个任务段描述符,则此时是进行任务切换(会把这个加载任务段描述符到tr寄存器),jmp的远跳不能提权,jmp任务切换可以,

听过一位大佬说过非常经典的一段话,所有的指令要么操作内存,要么修改寄存器,jmp远跳修改的是cs和eip,而任务切换只是把tss里那些寄存器一下子全都替换而已

首先试试提权jmp任务切换提权

#include "stdafx.h"
#include <Windows.h>
int   taolaoda=0;
char  Ret[6]={0};
void  _declspec(naked) print(){
#if 0    
    __asm{
        mov eax,0x66666666
        mov taolaoda,eax
        mov eax,eax
        mov ebx,ebx
        pushfd
        pop eax
        or  eax,0x4000
        push eax
        popfd
        iretd
    }
#else
    __asm{
        int 3
         mov eax,0x66666666
        mov taolaoda,eax
        jmp fword ptr Ret
    }
#endif
}
int main(int argc, char* argv[]){
    char Buf[6]={0};

    char arrr[0x1000]={0};
    WORD  tr;
    DWORD Cr3=0;
    DWORD Pro=0;
    /*在0x1000 0000开辟一页的内存权限为可读可写*/
    int* P=(int*)VirtualAlloc((void*)0x10000000,0x1000,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);
    if(NULL==P){
        MessageBox(NULL,"开辟虚拟内存失败",0,0);
    }
    __asm{
        str tr                                                       //读取tr寄存器的值到tr变量中
    }
    *(WORD*)&Ret[4]=tr;
    *(WORD*)&Buf[4]=0x48;
    printf("请输入CR3:");
    scanf("%x",&Cr3);
    P[1]=((int)arrr+0x100);
    P[2]=0x10;    
    P[7]=Cr3;
    P[8]=(int)print;            //EIP
    P[0xA]=1;
    P[0xB]=2;
    P[0xC]=3;
    P[0xD]=4;
    P[0xE]=((int)arrr+0x200);            //ESP

    P[0x12]=0x23;                //ES
    P[0x13]=0x8;                //CS
    P[0x14]=0x10;                //SS
    P[0x15]=0x23;                //DS
    P[0x16]=0x30;                //FS
    P[0x19]=0x20ac0000;    

    __asm{
        mov eax,0x00000000
        mov ecx,0x11117777
        mov edx,0x33333333
        mov ebx,0x11111111
        mov edi,0x22222222
        mov esi,0x33333333
        mov ebp,0x44449999

        jmp  fword ptr Buf;
    }
    printf("%p\n",taolaoda);
    getchar();
    return 0;
}

在构造一个任务段描述符 Base=0X10000000  Limlit=0x68

 

任务段进行任务切换,Jmp Call指令实现任务切换

此时查看80042000处的TSS存储的值0x28选择子对应的也是一个任务段描述符 ,有人会奇怪一个是eb,一个是e9

e9是因为这个TSS段没有使用,eb是因为这个段描述符加载到了tr寄存器中,正在别使用,等会我们切换任务后就能看到变化了

任务段进行任务切换,Jmp Call指令实现任务切换

找到当前进程的进行Cr3

任务段进行任务切换,Jmp Call指令实现任务切换

输入TSS的cr3字段,注意cr3非常的重要,(我们知道每一个进行都有4GB虚拟内存),而每个进程都有一个0x10000000虚拟地址,这些0x10000000对应的物理内存肯定不同,CPU如何知道我们要找就是哪一个进程的?就是根据Cr3寄存器中的值查找的

MMU(存储管理单元,会根据Cr3和虚拟地址自动拆分,找到对应的物理页),这里需要一丢丢页的知识就能理解

任务段进行任务切换,Jmp Call指令实现任务切换

任务切换就是把当TSS中的寄存器全都替换了,那肯定得把自己切换前的环境保存起来了,否则(我环境不就丢了)

任务段进行任务切换,Jmp Call指令实现任务切换

返回地址就是0x40FCD0

任务段进行任务切换,Jmp Call指令实现任务切换

继续F11可以看到Windug断下来了可以看到,它把当前环境保存到了前一个TSS中了就是0x28处的那个TSS

任务段进行任务切换,Jmp Call指令实现任务切换

看一个当前TSS我们自己构造的  Previous Task  Link前一个任务段链接,没有填充这里的话如果使用Jmp不会填充这个字段,但是如果使用Call会在这里填充上一个TSS段的选择子,Jmp任务切换和Call任务切换的返回方式不一样,Jmp是切换提权,然后Jmp又切换回去

任务段进行任务切换,Jmp Call指令实现任务切换

可以看到在切换任务之前tr加载的是0x28处的任务段描述符,指向jmp切换任务后 tr加载了0x48处的任务段描述符,并且把b位置为1了表示正在使用这个任务段,而前一个任务段的TSS就是0x28的Base指向的TSS中保存了切换任务之前的环境,便于我们等会在切换任务把那个环境恢复切换回去,

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

可以看到使用jmp和call(切换任务的时候都会把切换前的环境保存到上一个TSS中(call的我就不贴出来了))把call和jmp任务切换不同的贴不来

 

 

call指令的任务切换,还是当前进程的找到Cr3

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

F11继续,可以看到Call切换任务的时候,Previous Task  Link存储的前一个链接

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

按下去立马就挂了(这不像是蓝屏错误信息都没有,像是重启)这就是call指令任务切换不同的地方

Call指令任务切换的返回指令是iretd指令,中断门的时候返回指令及时iretd,当时说的是iretd指令从堆栈中弹出5值给 eip  cs  elfag  esp   ss,其实iretd指令会怎么执行依赖于elfag寄存器的NT位如果NT位=1就会从当前TSS结构中的Previous Task  Link找到上一个TSS的选择子,通过选择子找到上一个TSS,直接过如果单步Call里面就挂了

任务段进行任务切换,Jmp Call指令实现任务切换

如果直接过就ok

任务段进行任务切换,Jmp Call指令实现任务切换
直接F5就过了

任务段进行任务切换,Jmp Call指令实现任务切换

 

直接进入call直接F5过 也能过  Previous Task  Link,jmp,call任务切换的时候他们都将切换前的环境保存在了前一个TSS中,

jmp指令如果jmp切换回去,call指令iretd  如果单步会将eflag寄存器nt设置为0,这个一定要主要,否定一定挂如果nt=0,iretd会从堆栈弹出5个值给那些寄存器,堆栈根本没有东西所以

获得Cr3

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

可以看到Call指令填充了

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

 

为了让大家跟加了解任务切换,我们从A进程切换到B进程

texttest进程有一个楼函数print地址0x401005

任务段进行任务切换,Jmp Call指令实现任务切换

我们将Cr3改成texttest进程的

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

一按下就蓝了,应该TSS是设置的堆栈在texttest进程的没挂物理页,这就需要全都设置一下,在试试一次

设置好重新设置好esp为exttest进程控制堆栈

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

可以看到把堆栈设置后就过了,Windug给了一个警告,我们切换的进程不是当前进程就是cr3和当前进程的cr3不一样了

任务段进行任务切换,Jmp Call指令实现任务切换

环境也保存在了前一个TSS里面

任务段进行任务切换,Jmp Call指令实现任务切换

没执行一条指令就弹出一个警告

任务段进行任务切换,Jmp Call指令实现任务切换

任务段进行任务切换,Jmp Call指令实现任务切换

然后返回的时候有挂了,一想我就知道了,TSS的base是0x10000000是TSS进程的,使用texttest的cr3是找不到另外一个进程TSS(读其他进程的空间就是通过那个进程Cr3去读的哦,比如readprocessmemory函数writeprocessmemory),texttest的cr3去访问直接就挂了,如果在texttest的相同位置也搞个tss,或者挂相同的物理页,原理就是这样子了,这就是切换任务,写完这篇帖子感觉理解更加的深刻了,需要追到的是这是CPU提供切换任务的方式,操作系统没有用,但是本质是一样,切换Cr3切换ESP,任务就切换了至于其他寄存器就是回答现场的问题了,比如eip啊,(后面逆向线程切换的时候会带家详细的了解),学习内核的话没有捷径可以走,只有做实验,其他的什么都是扯淡,什么都是扯淡,除了自己动手做,TSS结构里面写入的堆栈必须是一个局部变量开辟在堆栈里面的,我试过用VirtualAlloc开辟块内存,让ESP指向这里面,和全局数据内存当做堆栈,都是直接挂,我们想线程有两个堆栈,是有默认大小的,而局部变量内存时在堆栈开辟的,所有可定不会有问题,而VirtualAlloc和全局数据的地址很大了,可能就溢出了,所以直接挂,我这里可能蓝屏了50次才想到这个原因,所有除了实验之外没有捷径可以走,太多太多的坑了。