首页
社区
课程
招聘
[原创]reversing.kr第12题WindowKernel
发表于: 2019-1-17 22:13 3866

[原创]reversing.kr第12题WindowKernel

2019-1-17 22:13
3866

DeviceIoControl如果操作成功完成,DeviceIoControl将返回一个非零值基本上可以看出来,如果指令是0x1000,进入check模式,当指令为0x2000,判断正误

1.peid查壳,程序无壳,然后直接运行程序,发现不需要输入

,直接显示,下面的内容,下面载入ida试试

2.搜索字符串,没有有价值的字符串,下面用od载入试试,还是od给力,下面直接定位关键字符串进行分析。用ida转入对应的关键代码地址处
发现ida中的地址和od中的地址不一样,od载入的基质为0x870000,ida载入的基质为0x400000,只要做ida重新载入程序,更改加载的基质
3.关键代码处f5,看来关键函数就是这个sub_871280()函数了。


4. CreateFile() 创建或打开文件或I/O设备。该函数返回一个句柄,该句柄可用于根据文件或设备以及指定的标志和属性访问文件或设备以获取各种类型的I/O。
HANDLE WINAPI CreateFile(
  _In_     LPCTSTR               lpFileName,
  _In_     DWORD                 dwDesiredAccess,
  _In_     DWORD                 dwShareMode,
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  _In_     DWORD                 dwCreationDisposition,
  _In_     DWORD                 dwFlagsAndAttributes,
  _In_opt_ HANDLE                hTemplateFile
);

 DeviceIoControl是用来控制我们指定设备的输入输出操作,使设备按照我们发的指令去工作。
BOOL DeviceIoControl(
  HANDLE hDevice,
  DWORD dwIoControlCode,
  LPVOID lpInBuffer,
  DWORD nInBufferSize,
  LPVOID lpOutBuffer,
  DWORD nOutBufferSize,
  LPDWORD lpBytesReturned,
  LPOVERLAPPED lpOverlapped );
这里主要了解几个参数
hDevice:要操作的设备的句柄了,
dwIoControlCode: 控制设备的指令了,
lpInBuffer: 设备操控请求数据的缓冲区基址,如果dwIoControlCode 指定了一个操作,该操作不需要输入数据,那么这个参数设为NULL 
nInBufferSize:lplnBuffer的size 
lpOutBuffer:存放输出数据的buffer,同样,如果dwIoControlCode 指定了一个操作,该操作不需要处理输出数据,那么这个参数设为NULL
nOutBufferSize, nOutBuffer的size
lpBytesReturned:实际输出数据的bytes
lpOverlapped:Ignored; set to NULL.忽略不用.

下面来到第二个参数的详解,CTL_CODE的定义与应用:
BOOL DeviceIoControl(
  HANDLE hDevice,
  DWORD dwIoControlCode,
  LPVOID lpInBuffer,
  DWORD nInBufferSize,
  LPVOID lpOutBuffer,
  DWORD nOutBufferSize,
  LPDWORD lpBytesReturned,
  LPOVERLAPPED lpOverlapped );
这里主要了解几个参数
hDevice:要操作的设备的句柄了,
dwIoControlCode: 控制设备的指令了,
lpInBuffer: 设备操控请求数据的缓冲区基址,如果dwIoControlCode 指定了一个操作,该操作不需要输入数据,那么这个参数设为NULL 
nInBufferSize:lplnBuffer的size 
lpOutBuffer:存放输出数据的buffer,同样,如果dwIoControlCode 指定了一个操作,该操作不需要处理输出数据,那么这个参数设为NULL
nOutBufferSize, nOutBuffer的size
lpBytesReturned:实际输出数据的bytes
lpOverlapped:Ignored; set to NULL.忽略不用.

下面来到第二个参数的详解,CTL_CODE的定义与应用:
#define CTL_CODE(DeviceType, Function, Method, Access) (
  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
)
可以看到,这个宏四个参数,自然是一个32位分成了4部分,高16位存储设备类型,14~15位访问权限,2~13位操作功能,最后一个就是确定缓冲区(上面DeviceIOControl中缓冲区的定义哦)是如何与I/O和文件系统数据缓冲区进行数据传递的方式。

5.下面继续对函数进行分析

DeviceIoControl如果操作成功完成,DeviceIoControl将返回一个非零值基本上可以看出来,如果指令是0x1000,进入check模式,当指令为0x2000,判断正误


6.运行程序,点击enable的时候会弹出两个对话框,第一个是函数sub_871280()里面文件操作失败,点击确定后会弹出第二个对话框
这里不知到时哪里的调用,用od搜索关键字符串 Device Error,ida搜索不到
然后用ida定位关键字符串,就在刚才的代码的下面

没有思路了
在ida中找了个字符串,然后转到字符串的位置(双击字符串就好了),有意外的发现,跳转到这里去看看。然而并没有发现什么有价值的东西。下面动态调试试试,

7.运行了下程序,发现32位和64位环境下运行程序没有区别,那就直接在64位环境下运行吧(毕竟实体机器是64位)还是没啥发现,参考别人的writeup,写个自己复现的思路吧

8.下面ida载入WinKer.sys文件进行分析。函数sub_11288处发现比较感兴趣的字符0x1000,0x2000
0x1000处是进行初始化,0x2000处是check 返回,
IoCompleteRequest 例程表示调用者的已经完成了对指定I/O请求的所有处理操作,并且向I/O管理器返回指定的IRP报文。 Irp就是返回的内容
v3->type可能是返回的内容,因为在不同的情况下被赋值为不同的内容。

查看dword_13024处的内容,发现对其改变的交叉引用,
int __stdcall sub_110D0(char a1)
{
  int result; // eax
  char v2; // cl
  bool v3; // zf

  result = dword_13034 - 200;
  v2 = a1 ^ 5;
  switch ( dword_13034 )
  {
    case 200:
    case 202:
    case 204:
    case 206:
      goto LABEL_2;
    case 201:
      v3 = v2 == -76;
      goto LABEL_4;
    case 203:
    case 205:
      v3 = v2 == -113;
LABEL_4:
      if ( v3 )
        goto LABEL_2;
      goto LABEL_10;
    case 207:
      if ( v2 != -78 )
        goto LABEL_10;
      dword_13024 = 1;    //success
LABEL_2:
      ++dword_13034;
      break;
    case 208:
      dword_13024 = 0;     //fail
LABEL_10:
      dword_1300C = 1;
      break;
    default:
      return result;
  }
  return result;
}

向上继续找交叉引用,
void __stdcall sub_11266(struct _KDPC *Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
  char v4; // al

  v4 = READ_PORT_UCHAR((PUCHAR)0x60);
  sub_111DC(v4);
}
READ_PORT_UCHAR这个API是从端口中获取字节信息,0x60是PS2键盘的数据端口
调用流程就是DriverEntry->sub_11266->sub_111dc(a1)->sub_11156(a1)->sub_110D0(a1)
由于代码断比较少,我就直接查看代码的反编译代码
找到a1的交叉引用
所以整个流程大体是:
驱动接受到0x1000指令后,开始记录键盘输入并比对,直到接收0x2000指令返回值
int __stdcall sub_11288(int a1, PIRP Irp)
{
  int v2; // edx
  _IRP *v3; // eax

  v2 = *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12);
  v3 = Irp->AssociatedIrp.MasterIrp;
  if ( v2 == 0x1000 )         //初始值
  {
    *(_DWORD *)&v3->Type = 1;
    dword_13030 = 1;
    dword_13034 = 0;
    dword_13024 = 0;
    dword_1300C = 0;
  }
  else if ( v2 == 0x2000 )  //返回
  {
    dword_13030 = 0;
    *(_DWORD *)&v3->Type = dword_13024;
  }
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 4;
  IofCompleteRequest(Irp, 0);
  return 0;
}
那么 只需要慢慢分析一下sub_111dc 到sub_110d0就可以了

9.现在思路清晰了,现在分析sub_110dc(a1)函数
int __stdcall sub_111DC(char a1)     //第一次检查
{
  int result; // eax
  bool v2; // zf

  result = 1;
  if ( dword_1300C != 1 )
  {
    switch ( dword_13034 )     //初始化的时候dword_13034=0,参考sub_11288
    {
      case 0:
      case 2:
      case 4:
      case 6:
        goto LABEL_3;
      case 1:
        v2 = a1 == -91;
        goto LABEL_6;
      case 3:
        v2 = a1 == -110;
        goto LABEL_6;
      case 5:
        v2 = a1 == -107;
LABEL_6:
        if ( !v2 )
          goto LABEL_7;
LABEL_3:
        ++dword_13034;
        break;
      case 7:
        if ( a1 == -80 )
          dword_13034 = 100;
        else
LABEL_7:
          dword_1300C = 1;
        break;
      default:
        result = sub_11156(a1);
        break;
    }
  }
  return result;
}
1)第一次输入的时候,dword_13034初始化值为0,直接将 dword_13034  加一,然后break,说明开始输入可以为任意的值
2)第二次输入 dword_13034 =1  ,判断是否为0xa5,不是的话直接将全局变量dword_1300c=1,无法进行判断了,说明第二个为0xa5
3)依次类推,第三个为0x92
4)第四个任意
5)第五个为0x95
6)第六个任意
7)第七个为0xb0,并将dword_13034=100
      *\0xa5*\0x92\*\0x95\*\0xb0


10 下面进入第二个判断:sub_11156.
int __stdcall sub_11156(char a1)
{
  int result; // eax
  bool v2; // zf
  char v3; // [esp+8h] [ebp+8h]

  result = dword_13034;
  v3 = a1 ^ 0x12;
  switch ( dword_13034 )
  {
    case 100:
    case 102:
    case 104:
    case 106:
      goto LABEL_2;
    case 101:
      v2 = v3 == -78;
      goto LABEL_5;
    case 103:
      v2 = v3 == -123;
      goto LABEL_5;
    case 105:
      v2 = v3 == -93;
LABEL_5:
      if ( !v2 )
        goto LABEL_6;
LABEL_2:
      ++dword_13034;
      break;
    case 107:
      if ( v3 == -122 )
        dword_13034 = 200;
      else
LABEL_6:
        dword_1300C = 1;
      break;
    default:
      result = sub_110D0(v3);
      break;
  }
  return result;
}
v3=a1^0x12,  dword_13034=100
步骤一样的:
1)第一个任意, dword_13034 =101
2)第二个=0xb2  ,0xb2 ^ 0x12=0xa0, dword_13034=102
3)  第三个任意, dword_13034 = 103
4)第四个=0x85,0x85^0x12=0x97, dword_13034 =104
5)第五个任意, dword_13034 =105
6)第六个=0xa3, 0xa3 ^0x12=0xb1, dword_13034 =106
7) 第七个任意,
8)第8个=0x86,0x86^0x12=0x94, dword_13034 = 200
    所以结果为 *\0xa0\*\0x97\*\0xb1\*\0x94

11.sub_110d0函数,第三个判断函数
int __stdcall sub_110D0(char a1)
{
  int result; // eax
  char v2; // cl
  bool v3; // zf

  result = dword_13034 - 200;
  v2 = a1 ^ 5;
  switch ( dword_13034 )
  {
    case 200:
    case 202:
    case 204:
    case 206:
      goto LABEL_2;
    case 201:
      v3 = v2 == -76;
      goto LABEL_4;
    case 203:
    case 205:
      v3 = v2 == -113;
LABEL_4:
      if ( v3 )
        goto LABEL_2;
      goto LABEL_10;
    case 207:
      if ( v2 != -78 )
        goto LABEL_10;
      dword_13024 = 1;
LABEL_2:
      ++dword_13034;
      break;
    case 208:
      dword_13024 = 0;
LABEL_10:
      dword_1300C = 1;
      break;
    default:
      return result;
  }
  return result;
}
同理
这里要注意,这里给的值是前面已经xor 12的值,所以这里需要在异或一次,所以这里少了一个,在后面的代码中显示。
v2=a1^5
1)第一个任意
2)第二个=0xb4  ^ 5^0x12=0xa3
3)第三个=0x8f^5 ^0x12 =0x98
4)第四个任意
5)第五个=0x8f^0x5 ^0x12 =0x98
6)第六个任意
7)第七个=0xb2^0x5 ^0x12 =0xa5
      *\0xb1*\0x8a\*\0x8a\*\0xb7

所以总的应该为: *\0xa5*\0x92\*\0x95\*\0xb0
                              *\0xa0\*\0x97\*\0xb1\*\0x94
                                *\0xa3*\0x98\*\0x98\*\0xa5
由于这些有些不能直接用ascii表示, 并且奇数输入都为任意值 猜想端口输入应该包括了按键按下和弹起的命令,并且有另外一套对应码表.

0x60端口的数据中 低7位代表扫描码,高1位代表状态 0表示按下,称为通码 1表示弹起,称为断码
搜索键盘扫描码,键盘码参考这个网站:
https://www.supfree.net/search.asp?id=6386
当按下一键时,产生 mark 码,产生一次 IRQ1 中断。 ,这里对mark进行匹配就可以了。

解码代码如下:
a=' *\0xa5*\0x92\*\0x95\*\0xb0*\0xa0\*\0x97\*\0xb1\*\0x94*\0xb1*\0x8a\*\0x8a\*\0xb7 '
b=a.replace('*','')
a=b.replace('\\','')
b=a.replace('\x00','')
a=b.replace('x','').strip()
b=a
a=b.decode('hex')

for i in range(len(a)):
    print (hex(ord(a[i])&127)),



result:0x25 0x12 0x15 0x30 0x20 0x17 0x31 0x14 0x31 0xa 0xa 0x37

对应表得到结果为:
key:keybdinthook

大功告成:学到一个函数:
READ_PORT_UCHAR这个API是从端口中获取字节信息,0x60是PS2键盘的数据端口
第一次分析


这里对windows kernel分析没接触过,所以后面大部分分析都是参照下面的这个博客
参考博客:https://www.52pojie.cn/thread-648863-1-1.html


#define CTL_CODE(DeviceType, Function, Method, Access) (
  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
)
可以看到,这个宏四个参数,自然是一个32位分成了4部分,高16位存储设备类型,14~15位访问权限,2~13位操作功能,最后一个就是确定缓冲区(上面DeviceIOControl中缓冲区的定义哦)是如何与I/O和文件系统数据缓冲区进行数据传递的方式。

5.下面继续对函数进行分析

DeviceIoControl如果操作成功完成,DeviceIoControl将返回一个非零值基本上可以看出来,如果指令是0x1000,进入check模式,当指令为0x2000,判断正误


6.运行程序,点击enable的时候会弹出两个对话框,第一个是函数sub_871280()里面文件操作失败,点击确定后会弹出第二个对话框
这里不知到时哪里的调用,用od搜索关键字符串 Device Error,ida搜索不到
然后用ida定位关键字符串,就在刚才的代码的下面

没有思路了
在ida中找了个字符串,然后转到字符串的位置(双击字符串就好了),有意外的发现,跳转到这里去看看。然而并没有发现什么有价值的东西。下面动态调试试试,

7.运行了下程序,发现32位和64位环境下运行程序没有区别,那就直接在64位环境下运行吧(毕竟实体机器是64位)还是没啥发现,参考别人的writeup,写个自己复现的思路吧

8.下面ida载入WinKer.sys文件进行分析。函数sub_11288处发现比较感兴趣的字符0x1000,0x2000
0x1000处是进行初始化,0x2000处是check 返回,
IoCompleteRequest 例程表示调用者的已经完成了对指定I/O请求的所有处理操作,并且向I/O管理器返回指定的IRP报文。 Irp就是返回的内容
v3->type可能是返回的内容,因为在不同的情况下被赋值为不同的内容。

查看dword_13024处的内容,发现对其改变的交叉引用,
int __stdcall sub_110D0(char a1)
{
  int result; // eax
  char v2; // cl
  bool v3; // zf

  result = dword_13034 - 200;
  v2 = a1 ^ 5;
  switch ( dword_13034 )
  {
    case 200:
    case 202:
    case 204:
    case 206:
      goto LABEL_2;
    case 201:
      v3 = v2 == -76;
      goto LABEL_4;
    case 203:
    case 205:
      v3 = v2 == -113;
LABEL_4:
      if ( v3 )
        goto LABEL_2;
      goto LABEL_10;
    case 207:
      if ( v2 != -78 )
        goto LABEL_10;
      dword_13024 = 1;    //success
LABEL_2:
      ++dword_13034;
      break;
    case 208:
      dword_13024 = 0;     //fail
LABEL_10:
      dword_1300C = 1;
      break;
    default:
      return result;
  }
  return result;
}

int __stdcall sub_110D0(char a1)
{
  int result; // eax
  char v2; // cl
  bool v3; // zf

  result = dword_13034 - 200;
  v2 = a1 ^ 5;
  switch ( dword_13034 )
  {
    case 200:
    case 202:
    case 204:
    case 206:
      goto LABEL_2;
    case 201:
      v3 = v2 == -76;
      goto LABEL_4;
    case 203:
    case 205:
      v3 = v2 == -113;
LABEL_4:
      if ( v3 )
        goto LABEL_2;
      goto LABEL_10;
    case 207:
      if ( v2 != -78 )
        goto LABEL_10;
      dword_13024 = 1;    //success
LABEL_2:
      ++dword_13034;
      break;
    case 208:
      dword_13024 = 0;     //fail
LABEL_10:
      dword_1300C = 1;
      break;
    default:
      return result;
  }
  return result;
}

向上继续找交叉引用,
void __stdcall sub_11266(struct _KDPC *Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
  char v4; // al

  v4 = READ_PORT_UCHAR((PUCHAR)0x60);
  sub_111DC(v4);
}
READ_PORT_UCHAR这个API是从端口中获取字节信息,0x60是PS2键盘的数据端口
调用流程就是DriverEntry->sub_11266->sub_111dc(a1)->sub_11156(a1)->sub_110D0(a1)
由于代码断比较少,我就直接查看代码的反编译代码
void __stdcall sub_11266(struct _KDPC *Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
  char v4; // al

  v4 = READ_PORT_UCHAR((PUCHAR)0x60);
  sub_111DC(v4);
}
READ_PORT_UCHAR这个API是从端口中获取字节信息,0x60是PS2键盘的数据端口
调用流程就是DriverEntry->sub_11266->sub_111dc(a1)->sub_11156(a1)->sub_110D0(a1)
由于代码断比较少,我就直接查看代码的反编译代码
找到a1的交叉引用
所以整个流程大体是:
驱动接受到0x1000指令后,开始记录键盘输入并比对,直到接收0x2000指令返回值
int __stdcall sub_11288(int a1, PIRP Irp)
{
  int v2; // edx
  _IRP *v3; // eax

  v2 = *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12);
  v3 = Irp->AssociatedIrp.MasterIrp;
  if ( v2 == 0x1000 )         //初始值
  {
    *(_DWORD *)&v3->Type = 1;
    dword_13030 = 1;
    dword_13034 = 0;
    dword_13024 = 0;
    dword_1300C = 0;
  }
  else if ( v2 == 0x2000 )  //返回
  {
    dword_13030 = 0;
    *(_DWORD *)&v3->Type = dword_13024;
  }
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 4;
  IofCompleteRequest(Irp, 0);
  return 0;
}
int __stdcall sub_11288(int a1, PIRP Irp)
{
  int v2; // edx
  _IRP *v3; // eax

  v2 = *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12);
  v3 = Irp->AssociatedIrp.MasterIrp;
  if ( v2 == 0x1000 )         //初始值
  {
    *(_DWORD *)&v3->Type = 1;
    dword_13030 = 1;
    dword_13034 = 0;
    dword_13024 = 0;
    dword_1300C = 0;
  }
  else if ( v2 == 0x2000 )  //返回
  {
    dword_13030 = 0;
    *(_DWORD *)&v3->Type = dword_13024;
  }
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 4;
  IofCompleteRequest(Irp, 0);
  return 0;
}
那么 只需要慢慢分析一下sub_111dc 到sub_110d0就可以了

9.现在思路清晰了,现在分析sub_110dc(a1)函数
int __stdcall sub_111DC(char a1)     //第一次检查
{
  int result; // eax
  bool v2; // zf

  result = 1;
  if ( dword_1300C != 1 )
  {
    switch ( dword_13034 )     //初始化的时候dword_13034=0,参考sub_11288
    {
      case 0:
      case 2:
      case 4:
      case 6:
        goto LABEL_3;
      case 1:
        v2 = a1 == -91;
        goto LABEL_6;
      case 3:
        v2 = a1 == -110;
        goto LABEL_6;
      case 5:
        v2 = a1 == -107;
LABEL_6:
        if ( !v2 )
          goto LABEL_7;
LABEL_3:
        ++dword_13034;
        break;
      case 7:
        if ( a1 == -80 )
          dword_13034 = 100;
        else
LABEL_7:
          dword_1300C = 1;
        break;
      default:
        result = sub_11156(a1);
        break;
    }
  }
  return result;
}
1)第一次输入的时候,dword_13034初始化值为0,直接将 dword_13034  加一,然后break,说明开始输入可以为任意的值
2)第二次输入 dword_13034 =1  ,判断是否为0xa5,不是的话直接将全局变量dword_1300c=1,无法进行判断了,说明第二个为0xa5
3)依次类推,第三个为0x92
4)第四个任意
5)第五个为0x95
6)第六个任意
7)第七个为0xb0,并将dword_13034=100
      *\0xa5*\0x92\*\0x95\*\0xb0


10 下面进入第二个判断:sub_11156.
int __stdcall sub_11156(char a1)
{
  int result; // eax
  bool v2; // zf
  char v3; // [esp+8h] [ebp+8h]

  result = dword_13034;
  v3 = a1 ^ 0x12;
  switch ( dword_13034 )
  {
    case 100:
    case 102:
    case 104:
    case 106:
      goto LABEL_2;
    case 101:
      v2 = v3 == -78;
      goto LABEL_5;
    case 103:
      v2 = v3 == -123;
      goto LABEL_5;
    case 105:
      v2 = v3 == -93;
LABEL_5:
      if ( !v2 )
        goto LABEL_6;
LABEL_2:
      ++dword_13034;
      break;
    case 107:
      if ( v3 == -122 )
        dword_13034 = 200;
      else
LABEL_6:
        dword_1300C = 1;
      break;
    default:
      result = sub_110D0(v3);
      break;
  }
  return result;
}
v3=a1^0x12,  dword_13034=100
步骤一样的:
1)第一个任意, dword_13034 =101
2)第二个=0xb2  ,0xb2 ^ 0x12=0xa0, dword_13034=102
3)  第三个任意, dword_13034 = 103
4)第四个=0x85,0x85^0x12=0x97, dword_13034 =104
5)第五个任意, dword_13034 =105
6)第六个=0xa3, 0xa3 ^0x12=0xb1, dword_13034 =106
7) 第七个任意,
8)第8个=0x86,0x86^0x12=0x94, dword_13034 = 200
    所以结果为 *\0xa0\*\0x97\*\0xb1\*\0x94

11.sub_110d0函数,第三个判断函数
int __stdcall sub_110D0(char a1)
{
  int result; // eax
  char v2; // cl
  bool v3; // zf

  result = dword_13034 - 200;
  v2 = a1 ^ 5;
  switch ( dword_13034 )
  {
    case 200:
    case 202:
    case 204:
    case 206:
      goto LABEL_2;
    case 201:
      v3 = v2 == -76;
      goto LABEL_4;
    case 203:
    case 205:
      v3 = v2 == -113;
LABEL_4:
      if ( v3 )
        goto LABEL_2;
      goto LABEL_10;
    case 207:
      if ( v2 != -78 )
        goto LABEL_10;
      dword_13024 = 1;
LABEL_2:
      ++dword_13034;
      break;
    case 208:
      dword_13024 = 0;
LABEL_10:
      dword_1300C = 1;
      break;
    default:
      return result;
  }
  return result;
}
同理
这里要注意,这里给的值是前面已经xor 12的值,所以这里需要在异或一次,所以这里少了一个,在后面的代码中显示。
v2=a1^5
1)第一个任意
2)第二个=0xb4  ^ 5^0x12=0xa3
3)第三个=0x8f^5 ^0x12 =0x98
4)第四个任意
5)第五个=0x8f^0x5 ^0x12 =0x98
6)第六个任意
7)第七个=0xb2^0x5 ^0x12 =0xa5
      *\0xb1*\0x8a\*\0x8a\*\0xb7

所以总的应该为: *\0xa5*\0x92\*\0x95\*\0xb0
                              *\0xa0\*\0x97\*\0xb1\*\0x94
                                *\0xa3*\0x98\*\0x98\*\0xa5
由于这些有些不能直接用ascii表示, 并且奇数输入都为任意值 猜想端口输入应该包括了按键按下和弹起的命令,并且有另外一套对应码表.

0x60端口的数据中 低7位代表扫描码,高1位代表状态 0表示按下,称为通码 1表示弹起,称为断码
搜索键盘扫描码,键盘码参考这个网站:
https://www.supfree.net/search.asp?id=6386
当按下一键时,产生 mark 码,产生一次 IRQ1 中断。 ,这里对mark进行匹配就可以了。

解码代码如下:
a=' *\0xa5*\0x92\*\0x95\*\0xb0*\0xa0\*\0x97\*\0xb1\*\0x94*\0xb1*\0x8a\*\0x8a\*\0xb7 '
b=a.replace('*','')
a=b.replace('\\','')
b=a.replace('\x00','')
a=b.replace('x','').strip()
b=a
a=b.decode('hex')

for i in range(len(a)):
    print (hex(ord(a[i])&127)),



result:0x25 0x12 0x15 0x30 0x20 0x17 0x31 0x14 0x31 0xa 0xa 0x37

对应表得到结果为:
key:keybdinthook

大功告成:学到一个函数:
READ_PORT_UCHAR这个API是从端口中获取字节信息,0x60是PS2键盘的数据端口
int __stdcall sub_111DC(char a1)     //第一次检查
{
  int result; // eax
  bool v2; // zf

  result = 1;
  if ( dword_1300C != 1 )
  {
    switch ( dword_13034 )     //初始化的时候dword_13034=0,参考sub_11288
    {
      case 0:
      case 2:
      case 4:
      case 6:
        goto LABEL_3;
      case 1:
        v2 = a1 == -91;
        goto LABEL_6;
      case 3:
        v2 = a1 == -110;
        goto LABEL_6;
      case 5:
        v2 = a1 == -107;
LABEL_6:
        if ( !v2 )
          goto LABEL_7;
LABEL_3:
        ++dword_13034;
        break;
      case 7:
        if ( a1 == -80 )
          dword_13034 = 100;
        else
LABEL_7:
          dword_1300C = 1;
        break;
      default:
        result = sub_11156(a1);
        break;
    }
  }
  return result;
}
1)第一次输入的时候,dword_13034初始化值为0,直接将 dword_13034  加一,然后break,说明开始输入可以为任意的值
2)第二次输入 dword_13034 =1  ,判断是否为0xa5,不是的话直接将全局变量dword_1300c=1,无法进行判断了,说明第二个为0xa5

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-1-18 16:00 被wwzzww编辑 ,原因:
上传的附件:
收藏
免费 3
支持
分享
最新回复 (1)
雪    币: 13650
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

2.搜索字符串,没有有价值的字符串,下面用od载入试试,还是od给力,下面直接定位关键字符串进行分析。用ida转入对应的关键代码地址处

判断字符串有没有价值,能不能建立个字典?
否则光靠人工判断的话,偶然性蛮大的。
有些题目虽然能做出来,但是细抠的话还是有些文章的。
2019-1-17 23:42
0
游客
登录 | 注册 方可回帖
返回
//