SAP license + password checking functions...

While returning to my installed in VMware SAP IDES, I figured out that the installation is expired and I cannot login anymore.

First extremely good news is that full disp+work.pdb file is here, it contain almost everything: function names, structures, types, local variable and argument names, etc. What a lavish gift!

I got TYPEINFODUMP utility for converting PDB files into something readable and grepable.
Here is an example of function information + its arguments + its local variables:

FUNCTION ThVmcSysEvent 
  Address:         10143190  Size:      675 bytes  Index:    60483  TypeIndex:    60484 
  Type: int NEAR_C ThVmcSysEvent (unsigned int, unsigned char, unsigned short*)
Flags: 0 
PARAMETER events 
  Address: Reg335+288  Size:        4 bytes  Index:    60488  TypeIndex:    60489 
  Type: unsigned int
Flags: d0 
PARAMETER opcode 
  Address: Reg335+296  Size:        1 bytes  Index:    60490  TypeIndex:    60491 
  Type: unsigned char
Flags: d0 
PARAMETER serverName 
  Address: Reg335+304  Size:        8 bytes  Index:    60492  TypeIndex:    60493 
  Type: unsigned short*
Flags: d0 
STATIC_LOCAL_VAR func 
  Address:         12274af0  Size:        8 bytes  Index:    60495  TypeIndex:    60496 
  Type: wchar_t*
Flags: 80 
LOCAL_VAR admhead 
  Address: Reg335+304  Size:        8 bytes  Index:    60498  TypeIndex:    60499 
  Type: unsigned char*
Flags: 90 
LOCAL_VAR record 
  Address: Reg335+64  Size:      204 bytes  Index:    60501  TypeIndex:    60502 
  Type: AD_RECORD
Flags: 90 
LOCAL_VAR adlen 
  Address: Reg335+296  Size:        4 bytes  Index:    60508  TypeIndex:    60509 
  Type: int
Flags: 90 

And here is an example of some structure:

STRUCT DBSL_STMTID 
Size: 120  Variables: 4  Functions: 0  Base classes: 0
MEMBER moduletype 
  Type:  DBSL_MODULETYPE
  Offset:        0  Index:        3  TypeIndex:    38653
MEMBER module 
  Type:  wchar_t module[40]
  Offset:        4  Index:        3  TypeIndex:      831
MEMBER stmtnum 
  Type:  long
  Offset:       84  Index:        3  TypeIndex:      440
MEMBER timestamp 
  Type:  wchar_t timestamp[15]
  Offset:       88  Index:        3  TypeIndex:     6612

Wow!

OK, now grepping that file for "license":

cat "disp+work.pdb.d" | grep FUNCTION | grep -i license | sort | uniq
FUNCTION check_license 
FUNCTION CheckProductLicense 
FUNCTION DeleteLicense 
FUNCTION GetAllLicenses 
FUNCTION InstallLicense 
FUNCTION InstallTempLicense 
FUNCTION likey_delete_licenses 
FUNCTION likey_get_all_licenses 
FUNCTION likeyadm_cleanup_all_licenses 
FUNCTION likeyadm_delete_licenses 
FUNCTION likeyadm_get_all_licenses 
FUNCTION likeyche_fill_licenses_list 
FUNCTION likeyind_fill_licenses_entry 
FUNCTION SlicAddLicense 
....
FUNCTION SlLikeyAbInstallTempLicense 
FUNCTION warning_license_expiration 

Starting with check_license() function, I also noticed a lot of debugging DpTrc() calls there:

.text:00000001402D948F                 lea     rdx, [rsp+88h+s1] ; passwdU16
.text:00000001402D9494                 lea     rcx, [rsp+88h+dayU] ; dayU
.text:00000001402D9499                 call    SlicPwForDay
.text:00000001402D949E                 lea     rdx, aRpoump1r  ; "RPOUMP1R"
.text:00000001402D94A5                 lea     rcx, [rsp+88h+s1] ; s1
.text:00000001402D94AA                 mov     r8, rbp         ; n
.text:00000001402D94AD                 call    memcmpU16
.text:00000001402D94B2                 mov     rbp, [rsp+88h+arg_10]
.text:00000001402D94BA                 test    eax, eax
.text:00000001402D94BC                 jnz     short loc_1402D94EA
.text:00000001402D94BE
.text:00000001402D94BE loc_1402D94BE:                          ; DATA XREF: .pdata:00000001456DDD04o
.text:00000001402D94BE                                         ; .pdata:00000001456DDD10o
.text:00000001402D94BE                 cmp     cs:ct_level, 2
.text:00000001402D94C5                 mov     cs:do_license_check, eax
.text:00000001402D94CB                 jl      short loc_1402D94EA
.text:00000001402D94CD                 call    DpLock
.text:00000001402D94D2                 mov     rcx, cs:hdl     ; hdl
.text:00000001402D94D9                 lea     rdx, aLicenseCheckDi ; "License check disabled.\n"
.text:00000001402D94E0                 call    DpTrc
.text:00000001402D94E5                 call    DpUnlock
.text:00000001402D94EA
.text:00000001402D94EA loc_1402D94EA:                          ; CODE XREF: check_license+61j

These debugging calls are very usable. Here you can also notice ct_level global variable.
Here about it:
http://help.sap.com/saphelp_nwpi71/helpdata/en/46/962416a5a613e8e1000000...

SlicPwForDay()? Password for a day? Huh? "RPOUMP1R"? If I understood correctly, this is related to temporary license.
I also noticed function get_hwkey() - I didn't knew SAP use hardware dongles.
But well, the only thing here I need is do_license_check global variable. Setting it to 0 in executable file is enough to disable license check.

Another problem I got is that SAPGUI cannot login anymore and it says "Password logon no longer possible - too many failed attempts".

Let's do grepping again:

cat "disp+work.pdb.d" | grep FUNCTION | grep -i password

We got:

FUNCTION rcui::AgiPassword::DiagISelection 
FUNCTION ssf_password_encrypt 
FUNCTION ssf_password_decrypt 
FUNCTION password_logon_disabled 
FUNCTION dySignSkipUserPassword 
FUNCTION migrate_password_history 
FUNCTION password_is_initial 
FUNCTION rcui::AgiPassword::IsVisible 
FUNCTION password_distance_ok 
FUNCTION get_password_downwards_compatibility 
FUNCTION dySignUnSkipUserPassword 
FUNCTION rcui::AgiPassword::GetTypeName 
FUNCTION `rcui::AgiPassword::AgiPassword'::`1'::dtor$2 
FUNCTION `rcui::AgiPassword::AgiPassword'::`1'::dtor$0 
FUNCTION `rcui::AgiPassword::AgiPassword'::`1'::dtor$1 
FUNCTION usm_set_password 
FUNCTION rcui::AgiPassword::TraceTo 
FUNCTION days_since_last_password_change 
FUNCTION rsecgrp_generate_random_password 
FUNCTION rcui::AgiPassword::`scalar deleting destructor' 
FUNCTION password_attempt_limit_exceeded 
FUNCTION handle_incorrect_password 
FUNCTION `rcui::AgiPassword::`scalar deleting destructor''::`1'::dtor$1 
FUNCTION calculate_new_password_hash 
FUNCTION shift_password_to_history 
FUNCTION rcui::AgiPassword::GetType 
FUNCTION found_password_in_history 
FUNCTION `rcui::AgiPassword::`scalar deleting destructor''::`1'::dtor$0 
FUNCTION rcui::AgiObj::IsaPassword 
FUNCTION password_idle_check 
FUNCTION SlicHwPasswordForDay 
FUNCTION rcui::AgiPassword::IsaPassword 
FUNCTION rcui::AgiPassword::AgiPassword 
FUNCTION delete_user_password 
FUNCTION usm_set_user_password 
FUNCTION Password_API 
FUNCTION get_password_change_for_SSO 
FUNCTION password_in_USR40 
FUNCTION rsec_agrp_abap_generate_random_password 

After playing for a little with these functions, I quickly noticed that the problem is in password_attempt_limit_exceeded(). It is called from chckpass() - one of the password checking functions.

First, I would like to be sure we are at the correct point:

Run my generic tracer:

gt64.exe -a:disp+work.exe bpf=.*!chckpass,args:3,unicode,rt:0
PID=2236|TID=2248|(0) disp+work.exe!chckpass (0x202c770, L"Brewered1                               ", 0x41) (called from 0x1402f1060 (disp+work.exe!usrexist+0x3c0))
PID=2236|TID=2248|(0) disp+work.exe!chckpass -> 0x35

Call path is syssigni -> DyISigni -> dychkusr -> usrexist -> chckpass.

0x35 is error returning in chckpass() in that point:

.text:00000001402ED567 loc_1402ED567:                          ; CODE XREF: chckpass+B4j
.text:00000001402ED567                 mov     rcx, rbx        ; usr02
.text:00000001402ED56A                 call    password_idle_check
.text:00000001402ED56F                 cmp     eax, 33h
.text:00000001402ED572                 jz      loc_1402EDB4E
.text:00000001402ED578                 cmp     eax, 36h
.text:00000001402ED57B                 jz      loc_1402EDB3D
.text:00000001402ED581                 xor     edx, edx        ; usr02_readonly
.text:00000001402ED583                 mov     rcx, rbx        ; usr02
.text:00000001402ED586                 call    password_attempt_limit_exceeded
.text:00000001402ED58B                 test    al, al
.text:00000001402ED58D                 jz      short loc_1402ED5A0
.text:00000001402ED58F                 mov     eax, 35h
.text:00000001402ED594                 add     rsp, 60h
.text:00000001402ED598                 pop     r14
.text:00000001402ED59A                 pop     r12
.text:00000001402ED59C                 pop     rdi
.text:00000001402ED59D                 pop     rsi
.text:00000001402ED59E                 pop     rbx
.text:00000001402ED59F                 retn

Fine, let's check:

gt64.exe -a:disp+work.exe bpf=.*!password_attempt_limit_exceeded,args:4,unicode,rt:0
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded (0x202c770, 0, 0x257758, 0) (called from 0x1402ed58b (disp+work.exe!chckpass+0xeb))
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded -> 1
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded (0x202c770, 0, 0, 0) (called from 0x1402e9794 (disp+work.exe!chngpass+0xe4))
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded -> 1
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0

Voila! I can login now.

By the way, if I try to pretend I forgot the password, fixing chckpass() function return value at zero is enough to bypass check:

gt64.exe -a:disp+work.exe bpf=.*!chckpass,args:3,unicode,rt:0
PID=2744|TID=360|(0) disp+work.exe!chckpass (0x202c770, L"bogus                                   ", 0x41) (called from 0x1402f1060 (disp+work.exe!usrexist+0x3c0))
PID=2744|TID=360|(0) disp+work.exe!chckpass -> 0x35
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0

It is really great. Function names are very clear, much clearer than in Oracle RDBMS. There are some portions of disp+work process written in C++. It was probably rewritten some time ago to C/C++?

By the way, my generic tracer 0.4 contained error in PDB files loader, I fixed it and reuploaded it again. For experiments with SAP win64 you should get newest gt.

P.S. Need any reverse engineering / small modification work? -> dennis@conus.info