OpenVMS Source Code Demos
READ_SYSUAF
1000 %title "read_sysuaf_xxx.bas" !
%ident "version 102.1" !
declare string constant k_version = "102.1" , ! &
k_program = "read_sysuaf" !
!====================================================================================================
! title : read_sysuaf_xxx.bas
! author : Neil Rieck (https://neilrieck.net)
! : Waterloo, Ontario, Canada.
! created: 2014-01-30
! purpose: directly access SYSUAF for auditing purposes only
! ver who when what
! --- --- ------ ------------------------------------------------------------------------------------
! 100 NSR 140130 1. original hack
! NSR 140131 2. added a function to demo VMS date-time conversion (that's all for now;
! My wife reminded me that I am supposed to be on vacation this week) :-)
! 101 NSR 140808 1. a little more hacking to flesh out something for an idea I am working on
! 102 NSR 140811 1. added code to fetch information via sys$getuai (just so we can compare)
!====================================================================================================
! notes : 1) WARNING: never attempt to write to SYSUAF directly. You will corrupt something.
! 2) Start by analyzing the RMS structure of SYSUAF from DCL like so:
!
! $ analyze/rms/fdl sys$system:SYSUAF.DAT
! $ type sysuaf.fdl (to see what analyze discovered about the data file)
!
! SYS$SYSROOT:[SYSEXE]SYSUAF.FDL;1
!
! IDENT FDL_VERSION 02 "30-JAN-2014 07:26:33 OpenVMS ANALYZE/RMS_FILE Utility"
!
! SYSTEM
! SOURCE OpenVMS
!
! FILE
! ALLOCATION 1120
! BEST_TRY_CONTIGUOUS no
! BUCKET_SIZE 3
! CLUSTER_SIZE 16
! CONTIGUOUS no
! EXTENSION 10
! FILE_MONITORING no
! NAME "SYS$COMMON:[SYSEXE]SYSUAF.DAT;4437"
! ORGANIZATION indexed
! OWNER [1,1]
! PROTECTION (system:RWED, owner:RWED, group:, world:)
! GLOBAL_BUFFER_COUNT 0
! GLBUFF_CNT_V83 0
! GLBUFF_FLAGS_V83 none
!
! RECORD
! BLOCK_SPAN yes
! CARRIAGE_CONTROL none
! FORMAT variable
! SIZE 1412
!
! AREA 0
! ALLOCATION 1109
! BUCKET_SIZE 3
! EXTENSION 10
!
! KEY 0
! CHANGES no
! DATA_KEY_COMPRESSION yes
! DATA_RECORD_COMPRESSION yes
! DATA_AREA 0
! DATA_FILL 100
! DUPLICATES no
! INDEX_AREA 0
! INDEX_COMPRESSION yes
! INDEX_FILL 100
! LEVEL1_INDEX_AREA 0
! NAME ""
! NULL_KEY no
! PROLOG 3
! SEG0_LENGTH 32
! SEG0_POSITION 4
! TYPE string
!
! KEY 1
! CHANGES yes
! DATA_KEY_COMPRESSION no
! DATA_AREA 0
! DATA_FILL 100
! DUPLICATES yes
! INDEX_AREA 0
! INDEX_COMPRESSION no
! INDEX_FILL 100
! LEVEL1_INDEX_AREA 0
! NAME ""
! NULL_KEY no
! SEG0_LENGTH 4
! SEG0_POSITION 36
! TYPE bin4
!
! KEY 2
! CHANGES yes
! DATA_KEY_COMPRESSION no
! DATA_AREA 0
! DATA_FILL 100
! DUPLICATES yes
! INDEX_AREA 0
! INDEX_COMPRESSION no
! INDEX_FILL 100
! LEVEL1_INDEX_AREA 0
! NAME ""
! NULL_KEY no
! SEG0_LENGTH 8
! SEG0_POSITION 36
! TYPE bin8
!
! KEY 3
! CHANGES yes
! DATA_KEY_COMPRESSION no
! DATA_AREA 0
! DATA_FILL 100
! DUPLICATES yes
! INDEX_AREA 0
! INDEX_COMPRESSION no
! INDEX_FILL 100
! LEVEL1_INDEX_AREA 0
! NAME ""
! NULL_KEY no
! SEG0_LENGTH 8
! SEG0_POSITION 44
! TYPE bin8
!
! ANALYSIS_OF_AREA 0
! RECLAIMED_SPACE 0
!
! ANALYSIS_OF_KEY 0
! DATA_FILL 51
! DATA_KEY_COMPRESSION 69
! DATA_RECORD_COMPRESSION 60
! DATA_RECORD_COUNT 876
! DATA_SPACE_OCCUPIED 909
! DEPTH 2
! INDEX_COMPRESSION 65
! INDEX_FILL 47
! INDEX_SPACE_OCCUPIED 15
! LEVEL1_RECORD_COUNT 303
! MEAN_DATA_LENGTH 644
! MEAN_INDEX_LENGTH 34
! LONGEST_RECORD_LENGTH 644
!
! ANALYSIS_OF_KEY 1
! DATA_FILL 47
! DATA_KEY_COMPRESSION 0
! DATA_RECORD_COUNT 871
! DATA_SPACE_OCCUPIED 42
! DEPTH 1
! DUPLICATES_PER_SIDR 0
! INDEX_COMPRESSION 0
! INDEX_FILL 6
! INDEX_SPACE_OCCUPIED 3
! LEVEL1_RECORD_COUNT 14
! MEAN_DATA_LENGTH 12
! MEAN_INDEX_LENGTH 6
!
! ANALYSIS_OF_KEY 2
! DATA_FILL 47
! DATA_KEY_COMPRESSION 0
! DATA_RECORD_COUNT 871
! DATA_SPACE_OCCUPIED 57
! DEPTH 1
! DUPLICATES_PER_SIDR 0
! INDEX_COMPRESSION 0
! INDEX_FILL 13
! INDEX_SPACE_OCCUPIED 3
! LEVEL1_RECORD_COUNT 19
! MEAN_DATA_LENGTH 16
! MEAN_INDEX_LENGTH 10
!
! ANALYSIS_OF_KEY 3
! DATA_FILL 72
! DATA_KEY_COMPRESSION 0
! DATA_RECORD_COUNT 5
! DATA_SPACE_OCCUPIED 15
! DEPTH 1
! DUPLICATES_PER_SIDR 174
! INDEX_COMPRESSION 0
! INDEX_FILL 1
! INDEX_SPACE_OCCUPIED 3
! LEVEL1_RECORD_COUNT 1
! MEAN_DATA_LENGTH 1104
! MEAN_INDEX_LENGTH 10
!====================================================================================================
option type=explicit ! no kid stuff
set no prompt !
!
%include "starlet" %from %library "sys$library:basic$starlet" ! system services (+ basic$quadword)
%include "$ssdef" %from %library "sys$library:basic$starlet" ! ss$
%include "$syidef" %from %library "sys$library:basic$starlet" ! syi$
%include "$uaidef" %from %library "sys$library:basic$starlet" ! uai$
%include "$rmsdef" %from %library "sys$library:basic$starlet" ! rms$
%include "lib$routines" %from %library "sys$library:basic$starlet" ! lib$spawn
%include "$libdef" %from %library "sys$library:basic$starlet" ! eg. lib$_normal
%include "$iledef" %from %library "sys$library:basic$starlet" ! ile3$ (Item List Entry 3 structures)
!
record ItemRec ! structure of item record
variant !
case !
group zero ! new code
ILE3 myILE3 ! from sys$library:basic$starlet
end group zero !
case !
group one ! legacy code
word BuffLen ! length
word ItemCode ! code
long BuffAddr ! address of buffer
long RtnLenAdr ! address of word for returned length
end group one !
case !
group two !
long List_Terminator !
long Junk1 !
long Junk2 !
end group two !
end variant !
end record ItemRec !
!
! home brewed functiond
!
external string function bin_to_asc(basic$quadword) !
!
! constants
!
declare long constant k_max_rec = 9999 !
!
! variables
!
declare long read_count, handler_error !
declare long x, y, offset !
declare string junk$ , &
filter$
!
! this reverse-engineered map overlays were produced by analyzing the contents of the associated FDL
!
! observations:
! 1) bin8 (8 bytes) is probably unsigned but BASIC "quad" will yield int8 (signed)
! 2) bin4 (4 bytes) is probably unsigned but BASIC "long" will yield int4 (signed)
! 3) notice that key-1 and key-2 overlap
! key-1 appears to be the lower 32-bits of UIC-member (little endian)
! key-2 appears to be 64-bit UIC-member
! 4) key-3 appears to be 64-bit UIC-group
! 5) UIC-group comes after UIC-member because we are little endian
! 6) key-0 appears to be a segmented key consisting of USERNAME + encoded PASSWORD
!
map(re) string uaf_whole = 1412 , ! from: SIZE in FDL &
string uaf_alignZ = 0 !
map(re) string fill$ = 52 , ! &
string uaf_alignM = 0 ! force a compile-time error if I'm wrong
map(re) string fill$ = 4 , ! &
string uaf_key0 = 32 , ! P:4 L:32 (from: key#0) &
long uaf_key1 , ! P:36 L:4 (from: key#1) &
string fill$ = 4 , ! 4 &
quad uaf_key3 , ! P:44 L:8 (from: key#3) &
string uaf_alignM = 0 ! we should be at position 52
map(re) string fill$ = 4 , ! &
string uaf_key0 = 32 , ! P:4 L:32 (from: key#0) &
quad uaf_key2 , ! P:36 L:8 (from: key#2) &
quad uaf_key3 , ! P:44 L:8 (from: key#3) &
string uaf_alignM = 0 ! we should be at position 52
!
! aassumptions
!
map(re) string fill$ = 4 , ! &
string uaf_k0_user_pwd = 32 , ! username (short + long) &
long uaf_k1_uic_mem_L , ! uic-member (lower word) &
string fill$ = 4 , ! &
quad uaf_k3_uic_grp_Q , ! &
string uaf_alignM = 0 !
map(re) string fill$ = 4 , ! &
string uaf_k0_user_pwd = 32 , ! &
quad uaf_k2_uic_mem_Q , ! uic-member &
quad uaf_k3_uic_grp_Q , ! uic_grp &
string uaf_alignM = 0 !
!
! this map was produced by observing/hackingthe output of this program...
! ...AS WELL AS FROM DCL LIKE SO:
! $dump sys$system:sysuaf.dat/page
! +++--- starting offset
map(re) string uaf_00 = 1 , ! 0 ?? &
string uaf_01 = 1 , ! 1 &
string uaf_02 = 1 , ! 2 &
string uaf_03 = 1 , ! 3 &
string uaf_username12 = 12 , ! 4 username &
string uaf_username20 = 20 , ! 16 username future growth? &
word uaf_member , ! 36 uic:member &
word uaf_group , ! 38 uic:group &
string fill$ = 12 , ! 40 ?? &
string uaf_account = 32 , ! 52 &
byte uaf_owner_len , ! 84 &
string uaf_owner = 31 , ! 85 &
byte uaf_dev_len , !116 &
string uaf_dev = 31 , !117 &
byte uaf_dir_len , !148 &
string uaf_dir = 63 , !149 &
byte uaf_LGICMD_len , !212 &
string uaf_LGICMD = 63 , !213 &
byte uaf_cli_len , !276 &
string uaf_cli = 31 , !277 &
byte uaf_cli_tbl_len , !308 &
string uaf_cli_tbl = 31 , !309 &
quad uaf_primary_pw , !340 account password &
quad uaf_secondary_pw , !348 secondary "" &
word uaf_login_fails , !356 counter &
word uaf_salt , !358 encryption salt &
byte uaf_pri_pw_enc_type , !360 pw encryption type &
byte uaf_sec_pw_enc_type , !361 secondary "" &
byte uaf_min_pw_length , !362 minimum pw length &
string fill$ = 1 , !363 ??? &
basic$quadword uaf_account_expiration , !364 64-bit timestamp &
basic$quadword uaf_password_lifetime , !372 64-bit timestamp &
basic$quadword uaf_pri_pw_changed , !380 64-bit timestamp &
basic$quadword uaf_sec_pw_changed , !388 64-bit timestamp &
basic$quadword uaf_last_login_inter , !396 64-bit timestamp &
basic$quadword uaf_last_login_noninter , !404 64-bit timestamp &
basic$quadword uaf_prv_auth , !412 authorized priv bits &
basic$quadword uaf_prv_def , !420 default priv bits &
string fill$ = 40 , !428 ??? &
long uaf_login_flags , !468 &
byte uaf_net_acc_pri_days(2) , !472 &
byte uaf_net_acc_sec_days(2) , !475 &
byte uaf_bat_acc_pri_days(2) , !478 &
byte uaf_bat_acc_sec_days(2) , !481 &
byte uaf_loc_acc_pri_days(2) , !484 &
byte uaf_loc_acc_sec_days(2) , !487 &
byte uaf_dia_acc_pri_days(2) , !490 &
byte uaf_dia_acc_sec_days(2) , !493 &
byte uaf_rem_acc_pri_days(2) , !496 &
byte uaf_rem_acc_sec_days(2) , !499 &
string fill$ = 12 , !502 &
byte uaf_prime_days , !514 &
string fill$ = 1 , !515 &
byte uaf_base_prior , !516 &
byte uaf_max_que_prior , !517 &
word uaf_proc_limit , !518 &
word uaf_max_jobs , !520 &
word uaf_det_lim , !522 &
word uaf_sub_lim , !524 &
word uaf_bio_lim , !526 &
word uaf_timer_lim , !528 &
word uaf_ast_lim , !530 &
string re_align = 0 !532 (to check my math)
map (re)string re_buffer = 532 , ! &
string re_align = 0 ! (to check my math)
!
! stuff used in sys$getuai
!
declare basic$quadword uai_hashed_pw , ! &
pass1_hash , ! &
pass4_hash , ! &
quad_time , ! &
ItemRec myItems(9) , ! &
long rc_bits% , ! &
long uai_flags1% , ! &
uai_flags2% , ! &
word uai_salt , ! &
byte uai_encrytion_type% , ! &
uai_min_pwd_length% , ! &
string user4$ , ! &
long rc% , ! &
junk% , ! &
quad quad_copy !
!
record switcheroo ! another hacking tool
variant
case
group zero
basic$quadword bquad0 ! unsigned quad word (system calls)
end group
case
group one
string quad$ = 8
end group
case
group two
quad quad0 ! signed quad word (native basic)
end group
case
group three
byte byte0
byte byte1
byte byte2
byte byte3
byte byte4
byte byte5
byte byte6
byte byte7
end group
end variant
end record
declare switcheroo quad_hack !
!============================================================================================
! main
!============================================================================================
main:
print k_program +"_"+ k_version !
print string$(len(k_program +"_"+ k_version), asc("=")) !
input "username filter? (blank=NO FILTERING) "; filter$ !
junk$ = filter$ !
junk$ = edit$(junk$,32+2) !
if filter$ <> junk$ then !
filter$ = junk$ !
print "-i-filter changed to: "+ filter$ !
sleep 1 !
end if !
!
read_count = 0 !
when error in !
!
! cavat: you need lots of privs to open "this file"
!
%let %indexedopen=0 !
%if %indexedopen=1 %then !
!
! this open doesn't work just yet (we need a bin8 data type but BASIC
! can only provide us with an int8 data type; so a fully functional
! BASIC version of this program may not be possible)
!
print "-i-attempting indexed file open"
open "sys$system:SYSUAF.DAT" for input as #99 ! &
,access read ! we only want to read &
,allow modify ! don't block other processes &
,organization indexed variable &
,map re &
,primary key uaf_k0_user_pwd &
,alternate key uaf_k1_uic_mem_L duplicates changes &
,alternate key uaf_k2_uic_mem_Q duplicates changes &
,alternate key uaf_k3_uic_grp_Q duplicates changes
%else
print "-i-attempting raw file open" !
open "sys$system:SYSUAF.DAT" for input as #99 ! &
,access read ! we only want to read &
,allow modify ! don't block other processes &
,organization undefined ! &
,recordtype any ! &
,map re
%end %if
while 1 !
get #99, regardless ! read record without lock
read_count = read_count + 1 ! count the reads
gosub analyze_record !
cause error 11 if read_count >= k_max_rec ! only print x records in this hack
next !
use !
handler_error = err !
print "================================="
print "-i-status: "+ str$(handler_error) !
print "-i-text : "+ ert$(handler_error) !
print "-i-count : "+ str$(read_count) !
end when !
!
select handler_error !
case 11 ! EOF is expected
goto fini !
end select !
!
goto fini !
!---------------------------------------------------------------
! parse various fields in the UAF record
! entry: i = record count
! map(re) contains valid data
!---------------------------------------------------------------
analyze_record:
when error in !
if filter$ <> "" then !
user4$ = edit$(uaf_username12,32+2) ! upcase, no white-space
return if pos(user4$,filter$,1) = 0 !
end if !
!
print "########################################"
print "record: "+str$(read_count)
%let %enablehack=0 !
%if %enablehack=1 %then !
if uaf_username12 = "<System+Pass" then ! hmmm...
print "encountered TEMPLATE" !
junk$ = uaf_whole !
for x = 0 to 99 !
print str$(mod(x,10)); !
next x !
print ! EOL
for y = 0 to 13 !
offset = y * 100 !
for x = 0 to 99 !
print mid$(junk$,offset+x+1, 1); !
next x !
print ! EOL
next y !
print "------------------------------------" !
iterate !
end if !
%end %if
!
! could the first four bytesre present a layout version number?
!
! 0000: template account
! 1100: normal account for OpenVMS-8.4
!
print " header bytes: ";asc(uaf_00);" ";asc(uaf_01);" ";asc(uaf_02);" ";asc(uaf_03);
if asc(uaf_00)=1 and asc(uaf_01)=1 and asc(uaf_02)=0 and asc(uaf_03)=0 then
print " :: valid VMS-8.4 account" !
goto display_record !
end if
if asc(uaf_00)=0 and asc(uaf_01)=0 and asc(uaf_02)=0 and asc(uaf_03)=0 then
print " :: template record" !
sleep 1
goto display_record !
end if !
print " :: unknown prefix" !
sleep 1
display_record:
!
print " username : "; uaf_username12
print " group (dec) : "; str$(uaf_group)
print " member (dec) : "; str$(uaf_member)
print " account : "; uaf_account
print " owner : "; left$(uaf_owner, integer(uaf_owner_len))
print " device : "; left$(uaf_dev, integer(uaf_dev_len))
print " directory : "; left$(uaf_dir, integer(uaf_dir_len))
print " lgicmd : "; left$(uaf_lgicmd, integer(uaf_lgicmd_len))
print " cli : "; left$(uaf_cli, integer(uaf_cli_len))
print " ud cli table : "; left$(uaf_cli_tbl, integer(uaf_cli_tbl_len))
print " login flags : "; str$(uaf_login_flags)
print " min pw len :"; uaf_min_pw_length
print " salt :"; uaf_salt
print " encryption type:"; uaf_pri_pw_enc_type
print " last intr login: "; bin_to_asc(uaf_last_login_inter)
gosub ask_vms !
use !
print "-e-error: "+ str$(err) +" in block: ANALYZE RECORD" !
end when !
return !
!=======================================================================
! ask VMS about some details
! entry: user4
!=======================================================================
ask_vms:
!
! prep for call to sys$getuai (fetch hashed password of user)
!
myItems(0)::BuffLen = 4 ! size of uai_encrytion_type% in bytes
myItems(0)::ItemCode = UAI$_ENCRYPT !
myItems(0)::BuffAddr = loc(uai_encrytion_type%) ! addr of uai_encrytion_type%
myItems(0)::RtnLenAdr = 0 ! location of bytes returned (don't care)
!
myItems(1)::BuffLen = 8 ! size of hashed_password in bytes (64-bit)
myItems(1)::ItemCode = UAI$_PWD !
myItems(1)::BuffAddr = loc(uai_hashed_pw) ! addr of hashed_password
myItems(1)::RtnLenAdr = 0 ! location of bytes returned (don't care)
!
myItems(2)::BuffLen = 2 ! size of uai_salt in bytes
myItems(2)::ItemCode = UAI$_SALT !
myItems(2)::BuffAddr = loc(uai_salt) ! addr of uai_salt
myItems(2)::RtnLenAdr = 0 ! location of bytes returned (don't care)
!
myItems(3)::BuffLen = 4 ! size of uai_flags1 in bytes (UAI$S_FLAGS)
myItems(3)::ItemCode = UAI$_FLAGS !
myItems(3)::BuffAddr = loc(uai_flags1%) ! addr of uai_flags1
myItems(3)::RtnLenAdr = 0 ! location of bytes returned (don't care)
!
myItems(4)::List_Terminator = 0 ! signal: no more items
!
! SYS$GETUAI [nullarg] ,[contxt] ,usrnam ,itmlst ,[nullarg] ,[nullarg] ,[nullarg]
!
rc% = sys$getuai(,,user4$,myItems(0),,,) !
rc_bits% = (rc% and 7%) !
if rc_bits% <> 1% then !
select rc% !
case RMS$_RNF !
print "-e-user: ";user4$;" not found in SYSUAF" !
case else !
print "-e-getuai-status:";rc%;" lookup up: ";user4$;" in SYSUAF"
end select !
else !
!
! see if stuff from sys$getuai matches fields on the disk
!
print "-i-SYSUAF lookup successful ----------"
print "-i-uai salt :"; uai_salt !
print "-i-uai enc type :"; uai_encrytion_type% !
quad_hack::bquad0 = uai_hashed_pw ! xfer for next test
if quad_hack::quad0 = uaf_primary_pw then !
print "-i-passwords match"
else
print "-e-passwords do not match"
end if
end if !
return !
!
! that's all she wrote
!
30000 fini:
print "-i-Adios..."
end !
!
!####################################################################################################
! external functions
!####################################################################################################
31000 !
! function: vms_time_bin_to_asc()
!
! notes: 17-NOV-1858 00:00:00.0 = NEVER
!
function string bin_to_asc(basic$quadword p1)
option type=explicit
declare long rc%
%include "starlet" %from %library "sys$library:basic$starlet" ! system services (including basic$quadword)
!
map (LocalSysMap) string Sys_buf = 22
map (LocalSysMap) string Sys_day = 2, &
Sys_dash1 = 1, &
Sys_month = 3, &
Sys_dash2 = 1, &
Sys_year = 4, &
Sys_Time = 11
rc% = sys$asctim(,Sys_buf,p1,) !
if (rc% and 7%) = 1% then
if Sys_year = "1858" then
bin_to_asc = "Never"
else
bin_to_asc = sys_buf
end if
else
bin_to_asc = "-e-err:"+str$(rc%)
end if
end function