Mapping Mainframe Memory Made Easy
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.
Authors:
Explore more blog posts
Part 1: Ready for Red Teaming? Intelligence-Driven Planning for Effective Scenarios
Take time for dedicated planning and evaluation ahead of red team testing to prepare your organisation for effective red team exercises.
The Strategic Value of Platformization for Proactive Security
Read about NetSPI’s latest Platform milestone, enabling continuous threat exposure management (CTEM) with consolidated proactive security solutions.
Backdooring Azure Automation Account Packages and Runtime Environments
Azure Automation Accounts can allow an attacker to persist in the associated packages that support runbooks. Learn how attackers can maintain access to an Automation Account.