Short Summary:
This article discusses the benefits of having access to a testing LPAR at NetSPI, particularly during penetration testing engagements. It details how to retrieve information from in-memory tables on z/OS when certain commands cannot be executed. The author shares a REXX script to visualize and access the contents of the Command Tables Location Table (CTLT), which aids in uncovering potential security vulnerabilities.
Key Points:
- Access to a testing LPAR is beneficial for quick tool creation during penetration tests.
- In-memory tables (e.g., IKJEFTE2, IKJEFTE8) store vital information for pentesting on z/OS.
- Control blocks can be traversed to retrieve necessary data when permissions are limited.
- A REXX script is provided to visualize and enumerate the contents of the Command Tables Location Table (CTLT).
- The script helps uncover privilege escalation paths that may be overlooked.
- Continuous innovation in tools and techniques enhances penetration testing capabilities.
- NetSPI offers mainframe penetration testing services to bolster security.
MITRE ATT&CK TTPs – created by AI
- Technique Name: Credential Dumping ID: T1003
- Procedure: Use REXX scripts to access in-memory tables to retrieve sensitive information.
- Technique Name: Privilege Escalation ID: T1068
- Procedure: Uncover privilege escalation paths through analysis of control blocks and command tables.
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.
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:
- We start by looking at the CVT2, which is always located at offset 0x10.
- At offset 0x9C in the CVT we find the address of the TSO vector table (TSVT) 3, labeled CVTTVT
- At offset 0x4C in the TSO vector table (TSVT) is the address of the TSO/E Parameters Vector Table (TPVT), labeled TSVTTPVT
- 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:
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:
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.
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:
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:
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.
The post Mapping Mainframe Memory Made Easy appeared first on NetSPI.