具有重金属风格的调试艺术:如何在IBM大型机上面进行逆向分析

 

相关术语

  • zOS:IBM的大型机操作系统;
  • TSO:分时选择程序,用于对zOS系统进行交互式访问;
  • PDS:zOS文件夹;
  • HLASM:高级汇编语言,z/架构上的汇编语言;
  • TSO TEST: 预装在zOS上的终端调试器,它并非真正用于逆向工程;
  • AMBLIST: 用于映射加载模块和程序对象的批处理程序;
  • JCL:作业控制语言,用于提交批处理程序;
  • USS:Unix子系统,类似wsl,只不过运行在zOS上。

 

简介

在zOS系统上进行逆向工程时,会面临诸多挑战,其中最大的挑战是如何迈出第一步。

首先,zOS系统上可用的工具并不适合逆向工程,因为IBM创建它们的初衷,是为了调试应用程序。在本文中,我选择使用的应用程序是TSO TEST,因为它是免费的(前提是您能访问zOS,我是通过zPDT的许可副本来访问它的),并且会默认安装在每台IBM大型机上(甚至是TK4上面,它是20世纪80年代的开源zOS系统的前身)。

在如何使用TSO TEST和HLASM方面,IBM的文档介绍的相当详尽。然而,IBM并没有提供快速入门指南,所以需要花很多时间去翻阅IBM的文档;所以,本文就应运而生了,我们旨在提供一份逆向分析zOS应用程序的快速入门资料。

 

如何在zOS系统上编译和运行C程序

为了便于学习逆向分析技术,我们首先要准备好一个编译好的程序。为此,我们将以下面的示例代码为例,来演示如何编译和运行C程序;当然,这份代码中明显含有溢出漏洞。

#include <stdio.h>
#include <string.h>

void special()
{
    printf ("H4CK3D TH3 M41NFR4M3");
}


int main()
{
    char buff[15];
    int pass = 0;

    printf("\n Enter the password : \n");
    gets(buff);

    if(strcmp(buff, "fsecure"))
    {
        printf ("\n Wrong Password \n");
    }
    else
    {
        printf ("\n Correct Password \n");
        pass = 1;
    }

    if(pass)
    {
        special();
    }

    return 0;
}

现在,我们有了C语言的源代码,接下来我们要对其进行编译。为此,这里将使用USS,因为我发现它比提交批处理JCL要更容易一些,但两者的作用是一样的。之后,目标文件被编译成一个MVS PDS数据集。

c89 -o "//'JAKE.TSOTEST.LOADE(OVERFLOW)'" overflow.c

下面是通过批处理文件运行的、用于编译、绑定和运行C程序的作业。

//COMPC  JOB (JOBNAME),'XSS',CLASS=A,NOTIFY=&SYSUID
//PROC JCLLIB ORDER=(CBC.SCCNPRC)
//*-------------------------------------------------------------------
//* Compile and bind step
//*-------------------------------------------------------------------
//COMP     EXEC EDCCB,
//         OUTFILE='JAKE.TSOTEST.LOADE(OVERFLOW),DISP=SHR',
//         CPARM='ASM'
//STEPLIB  DD DSN=CBC.SCCNCMP,DISP=SHR
//         DD DSN=CEE.SCEERUN,DISP=SHR
//         DD DSN=CEE.SCEERUN2,DISP=SHR
//COMPILE.SYSIN DD DSN=JAKE.SOURCE.C(OVERFLOW),DISP=SHR
//*-------------------------------------------------------------------
//* Run step
//*-------------------------------------------------------------------
//GO       EXEC PGM=OVERFLOW
//STEPLIB  DD DSN=JAKE.TSOTEST.LOADE,DISP=SHR

现在,我们就可以通过TSO来调用该程序了。

call 'JAKE.TSOTEST.LOADE(OVERFLOW)'

  Enter the password : 
testtest

  Wrong Password

要对该程序进行静态分析,可以使用AMBLIST;同样,我们也可以利用批处理方式来运行它,不过,使用USS命令会更简单一些。

echo " LISTLOAD OUTPUT=MAP MEMBER=(OVERFLOW)" | amblist "//'JAKE.TSOTEST.LOADE'" > /tmp/overflow_amblist

同样,下面的JCL用于处理同样的事情,只不过它使用了批处理方式。

//AMBLIST JOB (ACCT),MSGCLASS=H,NOTIFY=&SYSUID
//AMBL       EXEC   PGM=AMBLIST,REGION=64M
//SYSPRINT   DD     DSN=JAKE.AMBLIST(OVERFLOW),DISP=OLD
//AMBLIB     DD     DSN=JAKE.TSOTEST.LOADE,DISP=SHR
//SYSIN      DD     *
    LISTLOAD  DDN=AMBLIB,OUTPUT=MAP,MEMBER=OVERFLOW
/*

实际上,这些输出内容是非常冗长的,但这里仅摘录了重要信息的片段,例如使用了哪些外部函数,以及函数在编译后的overflow.c二进制文件中的位置;这里显示的是关于SPECIAL和MAIN函数的信息。

-**    END OF MAP AND CROSS-REFERENCE LISTING                                                                            
1                                  *  M O D U L E   S U M M A R Y  *                                             
0    MEMBER NAME:  OVERFLOW                                                               MAIN ENTRY POINT:    00000000  
0    LIBRARY:      SYSLIB                                                                 AMODE OF MAIN ENTRY POINT: 31  

-                 CONTROL SECTION                                        ENTRY                                           
                   LMOD LOC     NAME      LENGTH  TYPE  RMODE             LMOD LOC  CSECT LOC      NAME                                
                       A0    @ST00001       348   SD    31                                                              
                                                                              138        98      SPECIAL                 
                                                                              1B8       118      MAIN
                       338    CEEMAIN         0C   SD    31                                                                        
                      15A8    gets            0A   SD    31                                                              
                                                                             15A8        00      GETS                            
0                     15B8    printf          0A   SD    31                                                              
                                                                             15B8        00      PRINTF                                                    
0LENGTH OF LOAD MODULE    1D58

现在开始实际调试;为此,可以从TSO运行下面的命令:

test 'JAKE.TSOTEST.LOADE(OVERFLOW)'

现在,我们位于TEST终端;要想离开该终端,可以键入“end”命令。当我们被一条指令卡住的时候,可以按PA1来取消该指令。

 

TSO测试指南

下面是一个TSO测试的例子。

在TEST中,有多种方法来表示地址:

  • 15r:寄存器15中的地址;
  • OVERFLOW.MAIN:使用符号表示地址;
  • +12:相对于基地址的偏移量(以字节为单位;同时,可以使用qualify来改变基地址);以入口点为起始位置;
  • 1FAA12F8:绝对地址。

另外,下面是常用的TEST子命令,我们最好熟悉一下:

  • LIST <ADDRESS> <DATA_TYPE> m(<multiple>)
    其中,比较重要的数据类型为i(指令)、b(二进制)、x(十六进制)、c(字符),这里的m表示想显示多少种数据类型。
  • LISTPSW
    显示PSW,以查看条件标志。
  • AT <ADDRESS> , AT <ADDRESS:ADDRESS>
    在一个地址或多个地址上设置断点;对于多个地址,对应位置存放的必须都是指令。
  • `off <address> , off <address:address>.
    删除断点
  • GO
    运行程序,直到出现断点为止。
  • QUALIFY <ADDRESS>
    改变基地址。
  • WHERE
    输出当前位置以及您所在的函数。

 

逆向分析之旅

寻找密码

以下是HLASM中的寄存器的典型用途:

  • 寄存器1→参数列表指针
  • 寄存器13→指向调用方提供的寄存器保存区的指针
  • 寄存器14→返回地址
  • 寄存器15→子程序的地址

首先,我们需要在MAIN函数处设置一个断点并跳转到该函数。同时,让我们修改一下基地址,具体如下所示:

at OVERFLOW.MAIN
go
qualify OVERFLOW.MAIN

让我们看看接下来要运行哪个指令:

list +0 i
        +0    BC      15,36(,R15)

TSO TEST在显示指令时,竟然使用了十进制,但地址通常都是用十六进制表示的。这说明这是一个条件分支指令,掩码1111(总是这个值)用于处理0x24+寄存器15的值。所以,让我们将基地址设为从main函数的起始地址+24字节处,并显示main函数中的所有汇编指令。

qualify +24
list +0 i m(200)
        +0    STM     R14,R5,12(R13)
        +4    L       R14,76(,R13)
        +8    LA      R0,208(,R14)
        +C    CL      R0,788(,R12)
       +10    BRC     2,*-32
       +14    L       R15,640(,R12)
       +18    STM     R15,R0,72(R14)
       +1C    MVI     0(R14),16
       +20    ST      R13,4(,R14)
       +24    LR      R13,R14
       +26    LARL    R3,*+210
       +2C    LARL    R5,*+216
       +32    MVHI    176(R13),0
       +38    L       R15,0(,R3)
       +3C    LA      R0,22(,R5)
       +40    LA      R1,152(,R13)
       +44    ST      R0,152(,R13)
       +48    BASR    R14,R15
       +4A    LA      R0,160(,R13)
       +4E    L       R15,4(,R3)
       +52    LA      R1,152(,R13)
       +56    ST      R0,152(,R13)
       +5A    BASR    R14,R15
       +5C    LA      R2,160(,R13)
       +60    LA      R1,48(,R5)
       +64    LA      R0,0
       +68    CLST    R2,R1
       +6C    LA      R0,0
       +70    ST      R2,180(,R13)
       +74    ST      R1,184(,R13)
       +78    ST      R0,188(,R13)
       +7C    BRC     8,*+30
       +80    L       R2,180(,R13)
       +84    L       R1,184(,R13)
       +88    LLC     R0,0(,R2)
       +8E    LLC     R1,0(,R1)
       +94    SLR     R0,R1
       +96    ST      R0,188(,R13)
       +9A    L       R0,188(,R13)
       +9E    LTR     R0,R0
       +A0    BRC     8,*+26
       +A4    L       R15,0(,R3)
       +A8    LA      R0,56(,R5)
       +AC    LA      R1,152(,R13)
       +B0    ST      R0,152(,R13)
       +B4    BASR    R14,R15
       +B6    BRC     15,*+28
       +BA    L       R15,0(,R3)
       +BE    LA      R0,76(,R5)
       +C2    LA      R1,152(,R13)
       +C6    ST      R0,152(,R13)
       +CA    BASR    R14,R15
       +CC    MVHI    176(R13),1
       +D2    L       R0,176(,R13)
       +D6    LTR     R0,R0
       +D8    BRC     8,*+10
       +DC    L       R15,8(,R3)
       +E0    BASR    R14,R15
       +E2    LA      R15,0
       +E6    LR      R0,R13
       +E8    L       R13,4(,R13)
       +EC    L       R14,12(,R13)
       +F0    LM      R2,R5,28(R13)
       +F4    BALR    R1,R14
       +F6    BCR     0,R7
       +F8    SLR     R10,R0
       +FA    LDR     FR6,FR0
       +FC    SLR     R10,R0
       +FE    LDR     FR5,FR0
      +100    SLR     R10,R0
      +102    LCR     R3,R0
      +104    LPD     R15,978(R12),964(R15)
      +10A    STH     R14,2291(R3,R12)
      +10E    STH     R13,1265(R4,R15)
      +112    CLC     2548(199,R13),1267(R13)
 IKJ57245I INVALID INSTRUCTION CODE AT +118

为了更好地理解程序中发生的事情,让我们看看对外部函数的调用。下面的代码的意思是跳转到寄存器15中存放的地址处,并将函数的返回存储在寄存器14中。

+48 BASR R14,R15

所以,让我们在这个地址上设一个断点,并找出寄存器15中的内容。

at +48
go
list 15r
 15R  1FA02860

好了,我们知道它跳转到什么位置了,现在设置一个断点,然后运行“where”指令,来看看最后进入哪个函数中。

at 1FA02860.
go
where
  1FA02860. LOCATED AT +0      IN OVERFLOW.printf   UNDER TCB LOCATED AT 8B9E88.

所以,我们知道这是printf函数,为此,可以在下一个地址处设置一个断点,以便进行相应的验证。

at +4A
go
  Enter the password :

让我们对下一个函数做同样的处理,我们的研究发现,会进行如下所示的函数调用:

+5A    BASR    R14,R15

接下来的C函数称为strcmp,它的情况略有不同。实际上,HLASM有点像CISC,它有一条用于比较字符串的汇编指令“CLST”,如果字符串相同,则设置条件代码。下面的汇编代码显示了要比较的字符串,如果它们相等,就进行条件分支转移。

+68    CLST    R2,R1
+6C    LA      R0,0
+70    ST      R2,180(,R13)
+74    ST      R1,184(,R13)
+78    ST      R0,188(,R13)
+7C    BRC     8,*+30

让我们在CLST指令上设置断点,以便看看正在比较的两个字符串。

at +68
list 1r:2r
  1R  1FA01508   2R  1FAA02E8


list 1FA01508. c m(30)
 1FA01508.  f                     
 1FA01509.  s
 1FA0150A.  e
 1FA0150B.  c
 1FA0150C.  u
 1FA0150D.  r
 1FA0150E.  e
 1FA0150F.  .

list 1FAA02E8. c m(30)
 1FAA02E8.  a               
 1FAA02E9.  b
 1FAA02EA.  c
 1FAA02EB.  d

从这里我们看到,它将我们输入的字符串“abcd”与“fsecure”字符串进行了比较。

让我们再次调用该程序,并尝试将fsecure作为输入的密码。

call 'JAKE.TSOTEST.LOADE(OVERFLOW)'

  Enter the password : 
fsecure

  Correct Password 
 H4CK3D TH3 M41NFR4M3

缓冲区溢出

现在,让我们看看如何在没有输入正确的密码的情况下,如何攻破这个大型机。显然,这里将利用缓冲区利用漏洞,不过在此之前,先让我们看看它在HLASM中是如何工作的。

为此,我们可以在main函数的每条指令上设置一个断点。这样的话,输入go指令,就能实现单步跳过(step over)功能。

at +0:+112

注意下面的指令,无论密码是错是对,它都会被运行。MVHI是“MoVe fullword from Halfword Immediate”的意思,并将pass的值设置为0,随后如果程序将该内存加载到寄存器中,运行“LTR”来加载并测试寄存器,将第二个寄存器的值放入第一个寄存器,并检查其内容是否为0。

+32    MVHI    176(R13),0
...
+D2    L       R0,176(,R13)
+D6    LTR     R0,R0

如果密码正确,就将该变量的值设置为1。

+CC    MVHI   176(R13),1

然后,让我们转到D2,来看看寄存器13的值是多少。

at +D2 
go
list 13r
 13R  1FAA1248

现在,让我们让1FAA1248与176相加,得到1FAA12F8,并查看这个地址所在的二进制到代码。正如我们所看到的,当密码输入正确时,该值被设置为1。

list 1FAA12F8. B m(4)
 1FAA12F8.  00000000    
 1FAA12F9.  00000000
 1FAA12FA.  00000000
 1FAA12FB.  00000001

从上一节我们知道,我们获取的数据存储在1FAA12E8处。由于每个字符占1个字节,所以要覆盖这个数据,我们需要由1FAA12FB – 1FAA12E8 = 20个字符组成的密码。

call 'JAKE.TSOTEST.LOADE(OVERFLOW)'

  Enter the password : 
aaaaaaaaaaaaaaaaaaaa

  Wrong Password 
 H4CK3D TH3 M41NFR4M3

注意事项1:在整个过程中,绝对地址可能已经改变了。

注意事项2:zOS编译器似乎没有实现金丝雀、ASLR或NX位等内存保护机制,但虚拟地址的工作方式为zOS提供了很多保护。对于大多数地址间的通信,用户都被要求处于Modeset 0状态,这是一个用户可以拥有的最大特权。面是关于zOS的虚拟内存映射的进一步信息,请参阅http://zseries.marist.edu/pdfs/ztidbitz/29%20zNibbler%20%28zOS%27%20Address%20Space%20%20-%20Virtual%20Storage%20Layout%29.pdf。

注意事项3:zOS系统并没有提供堆栈。按照惯例,zOS系统上的C和其他IBM编译器,会创建相应的DSA(https://www.ibm.com/docs/en/zos/2.2.0?topic=conventions-language-environment-dynamic-storage-area-non-xplink),就像其他系统中的每个函数都有一个堆栈一样。

 

未来的工作

为了实现权限升级,我们将需要滥用管理程序状态。在zOS系统上,这可以通过某些方法来实现,即SVC、APF库和跨内存服务。在接下来的文章中,将介绍如何利用这些方法实现提权。

 

参考资料

POoP(操作原则):

  • Introduction to Assembler Programming SHARE Boston 2013
  • Mainframe [z/OS] reverse engineering and exploit development
(完)