Image of NetSPI testing LPAR

One of the benefits of working for NetSPI is access to our own testing LPAR. This proves handy when we’re mid-engagement and need to quickly create a tool, allowing us to test it before trying it on a live environment. For example, during a recent engagement, I couldn’t run the TSO command PARMLIB or the operator command “DISPLAY IKJTSO”, both of which provide similar information we use when conducting pentests.

Image demonstrating the parmlib command cannot be run by my user

Fortunately, a series of in-memory tables (i.e., IKJEFTE2, IKJEFTE8, IKJEFTAP, and IKJEFTNS) store the information we need. These tables contain the authorized commands, programs, etc., typically defined in the TSO configuration file (IKJTSOxx parmlib member). The TSO/E System Diagnosis: Data Areas manual documents how to locate these tables. 

On z/OS, we refer to these tables as control blocks1. Typically chained and anchored, we can find all control blocks by starting at the base control block (usually the CVT or common vector table) and traversing the pointers and tables until we obtain the desired information. While these tables are sometimes well-documented (as in this case), we sometimes must figure them out on our own.  

If we lack permission to run the tools or commands to dump this information, we can manually retrieve the contents of the in-memory tables. To do this, we need to traverse a series of pointers and offsets in memory:  

  1. We start by looking at the CVT2, which is always located at offset 0x10.  
  2. At offset 0x9C in the CVT we find the address of the TSO vector table (TSVT) 3, labeled CVTTVT 
  3. At offset 0x4C in the TSO vector table (TSVT) is the address of the TSO/E Parameters Vector Table (TPVT), labeled TSVTTPVT 
  4. At offset 0x14 in the TSO/E Parameters Vector Table (TPVT)4 is the address of the TSO/E Command Tables Location Table (CTLT), labeled TPVTCTLT 5 

The Command Tables Location Table (CTLT) contains the address and other information we need to find our target data. 

In essence, we’ve done this:  

Mapping of the control block and their offsets in z/OS memory

Although the contents of the Command Tables Location Table (CTLT) are well documented, I wanted to visualize the table in memory and make it readable. This approach allows me to access this information in future situations, regardless of access to the commands or programs typically used to read it.

Putting together a quick REXX script is my preferred method, though you could just as easily use ISRDDN to do it (which I’ll leave as an exercise to the reader). 

/* REXX */                                
CVT = STORAGE(10,4)                       
TSVT = STORAGE(D2X(C2D(CVT)+X2D(9C)),4)   
TPVT = STORAGE(D2X(C2D(TSVT)+X2D(4C)),4)  
CTLT = STORAGE(D2X(C2D(TPVT)+X2D(14)),4)  
SAY "CVT:" C2X(CTV)                       
SAY "TSVT:" C2X(TSTV)                     
SAY "TPVT:" C2X(TPVT)                     
SAY "CTLT:" C2X(CTLT)                     
SAY "CTLT CONTENTS"                       
CTLTHEX = STORAGE(C2X(CTLT),100)          
OUTPUT = ''                               
DO I=1 TO 60                              
 BYTE = C2X(SUBSTR(CTLTHEX,I,1))          
 OUTPUT = OUTPUT BYTE                     
 IF I // 16 = 0 THEN DO                   
  SAY OUTPUT                              
  OUTPUT = ''                             
 END                                      
IF OUTPUT \= '' THEN SAY OUTPUT

After saving this REXX script to z/OS and executing it, we obtain the following hex dump of the CTLT table: 

Screenshot showing the output of the above REXX script

Let’s analyze the bytes in CTLT CONTENTS and refer to IBM’s documentation in the TSO/E System Diagnosis: Data Areas, under KJCTLT information. (As of this blog post, this information is also available at: https://www.ibm.com/docs/en/zos/3.1.0?topic=information-ikjctlt-mapping).

The first 4 bytes (C3 E3 D3 E3, which is EBCDIC for ‘CTLT’) represent what IBM calls an eye catcher, essentially a road marker indicating you’re in the right spot. The next two bytes show the size of this table (00 3C), which is 60, followed by a version byte (02) and a reserved byte. 

The table becomes particularly interesting after these initial bytes. We find 4 entries, each 12 bytes long, followed by a flag byte and three more reserved bytes. 

Image showing the CTLT memory region with descriptions

Let’s examine these four entries more closely. I’ve separated them out here:

20 37 03 88 00 00 02 A8 00 53 00 08 (AUTHCMD/IKJEFTE2) 
20 37 02 60 00 00 01 28 00 23 00 08 (AUTHPGM/IKJEFTE8) 
20 3B 00 00 00 00 00 42 00 05 00 0A (NOTBKGND/IKJEFTNS) 
20 38 50 58 00 00 00 38 00 05 00 08 (AUTHTSF/IKJEFTAP) 

Referring back to the documentation, we can parse each entry as follows: 

  • Four bytes: memory address 
  • Four bytes: size of the table in bytes 
  • Two bytes: number of entries 
  • Two bytes: size of each entry 

So, for example, AUTHPGM can be broken down as:

AUTHPGM

Now that we understand the structure of each entry in the CTLT, we can create a REXX script to enumerate all four structures:  

/* REXX */  
                                          
SAY 'ENUMERATING CTLT' 

  /*                         */ 
 /* Walk the control blocks */ 
/*                         */           

CVT =  C2X(STORAGE(10,4))                                  
TSVT = _STORAGE(CVT,9C)                                    
TPVT = _STORAGE(TSVT,4C)                                   
CTLT = _STORAGE(TPVT,14)                                   
SAY CVT ": CVT" TSVT ": TSVT" TPVT ": TPVT" CTLT ": CTLT"   

/* Get the CTLT size */ 
CTLT_SIZE = C2D(STORAGE(D2X(X2D(CTLT)+4),2))                

  /*                         */ 
 /* Loop through each entry */ 
/*                         */ 

DO I = 8 TO CTLT_SIZE - 5 BY 12                            
 TABLE   = _STORAGE(CTLT,D2X(I))                           
 SIZE    = C2D(STORAGE(D2X(X2D(CTLT)+I+6),2))              
 ENTRIES = C2D(STORAGE(D2X(X2D(CTLT)+I+8),2))              
 LENGTH  = C2D(STORAGE(D2X(X2D(CTLT)+I+10),2))             
 NAME = STORAGE(TABLE,8)                                   
 SAY ;SAY NAME "ENTRIES:" ENTRIES-1; SAY                     
 OUTPUT = ''      

    /*                                         */ 
   /* Print the entries in the table skipping */ 
  /*  over ' PARMLIB'                        */ 
 /*                                         */ 

 DO J = 2 TO ENTRIES                                       
  ENT = STORAGE(D2X(X2D(TABLE) + (LENGTH*J)),LENGTH)  
  OUTPUT = OUTPUT ENT                                
  IF LENGTH(OUTPUT) > 60 THEN DO                     
   SAY OUTPUT                                        
   OUTPUT = ''                                       
  END                                                
 END                                                 
 IF OUTPUT /= '' THEN SAY OUTPUT                     
END                                                  
RETURN                                                                                                   

_STORAGE:                                            
 PARSE ARG ADDR, DISP                                
 RETURN C2X(STORAGE(D2X(X2D(ADDR)+X2D(DISP)),4))

And after uploading this script to z/OS and executing it, we get: 

Screenshot showing the output from the updated REXX script
Screenshot showing the output from the updated REXX script

Success! We can now easily view the contents of the tables we needed. While there’s room for further refinement, particularly in handling the length of the IKJEFTNS entries, the script provides us with the necessary information to overcome the obstacle we faced. 

This work proved its value during a recent engagement. Using this script, we uncovered a privilege escalation path that might have gone undetected otherwise. 

To streamline future pentests, I’ve integrated this functionality as option “TSOT” in my z/OS enumeration REXX script, available here:  

This REXX script is just one example of how we at NetSPI continuously innovate to enhance our penetration testing capabilities. By developing new tools and techniques, we’re able to provide more comprehensive and effective security assessments, particularly in complex mainframe environments. 

Are you looking to bolster your mainframe security? Our expert team is ready to apply these advanced techniques and more to your systems. Click here to learn about our mainframe penetration testing services and schedule a consultation. Let’s work together to secure your critical mainframe infrastructure.