RPISEC/MBE: writeup lab01 (Reverse Engineering)

RPISEC is the resident computer security club at Rensselaer Polytechnic Institute. They developed a university course to teach skills in vulnerability research, reverse engineering and binary exploitation. The course material can be found on github including a detailed explanation on how to run the provided VM: https://github.com/RPISEC/MBE.

This article contains my writeup for the first lab (lab01). The lab’s topic is Reverse Engineering and it consists of the following levels:
–> lab1C
–> lab1B
–> lab1A


lab1C

We start by connecting to the first level using ssh. There are three levels within each lab ranging from C to A. The username for the first level is lab1C with the password lab01start:

gameadmin@warzone:~$ sudo ssh lab1C@localhost
lab1C@localhost's password: (lab01start)
        ____________________.___  _____________________________                
        \______   \______   \   |/   _____/\_   _____/\_   ___ \               
         |       _/|     ___/   |\_____  \  |    __)_ /    \  \/               
         |    |   \|    |   |   |/        \ |        \\     \____              
         |____|_  /|____|   |___/_______  //_______  / \______  /              
                \/                      \/         \/         \/               
 __      __  _____ ____________________________    _______  ___________
/  \    /  \/  _  \\______   \____    /\_____  \   \      \ \_   _____/
\   \/\/   /  /_\  \|       _/ /     /  /   |   \  /   |   \ |    __)_ 
 \        /    |    \    |   \/     /_ /    |    \/    |    \|        \
  \__/\  /\____|__  /____|_  /_______ \\_______  /\____|__  /_______  /
       \/         \/       \/        \/        \/         \/        \/ 

        --------------------------------------------------------        

                       Challenges are in /levels                        
                   Passwords are in /home/lab*/.pass                    
            You can create files or work directories in /tmp            
                    
         -----------------[ contact@rpis.ec ]-----------------          

Last login: Thu Jan 18 10:28:34 2018 from 192.168.187.130

The challenges are located at /levels/lab0X/:

lab1C@warzone:~$ pwd
/home/lab1C
lab1C@warzone:~$ cd /levels/lab01/
lab1C@warzone:/levels/lab01$ ls -al
total 40
drwxr-xr-x  2 root    root  4096 Jun 21  2015 .
drwxr-xr-x 14 root    root  4096 Sep 28  2015 ..
-r-sr-x---  1 lab1end lab1A 9672 Jun 21  2015 lab1A
-r-sr-x---  1 lab1A   lab1B 9672 Jun 21  2015 lab1B
-r-sr-x---  1 lab1B   lab1C 7414 Jun 21  2015 lab1C
lab1C@warzone:/levels/lab01$ file lab1
lab1A  lab1B  lab1C  
lab1C@warzone:/levels/lab01$ file lab1C
lab1C: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1522352e3de50fd6d180831ba18e2bca16be4204, not stripped

Let’s examine the binary using radare2:

lab1C@warzone:/levels/lab01$ r2 lab1C 
 -- Do not try to sploit that binary - that's impossible. Instead, only try to realize the truth: the is no binary.
[0x080485b0]>

The first thing I do is to let r2 analyze all (aaa) and list all functions (afl):

[0x080485b0]> aaa
[0x080485b0]> afl
0x080485b0  34  1  entry0
0x08048590  6  1  sym.imp.__libc_start_main
0x08048596  10  2  fcn.08048596
0x08048540  12  1  section..plt
0x0804854c  10  1  sub.printf_12_54c
0x08048556  10  1  fcn.08048556
0x08048560  6  1  sym.imp.puts
0x08048566  10  1  fcn.08048566
0x08048570  6  1  sym.imp.system
0x08048576  10  1  fcn.08048576
0x08048580  6  1  sym.imp.__gmon_start__
0x08048586  10  1  fcn.08048586
0x080485a0  6  1  sym.imp.__isoc99_scanf
0x080485a6  10  1  fcn.080485a6
0x080485e0  4  1  sym.__x86.get_pc_thunk.bx
0x080485f0  42  4  sym.deregister_tm_clones
0x0804861a  61  4  fcn.0804861a
0x08048657  39  3  fcn.08048657
0x08048680  45  8  sym.frame_dummy
0x080486ad  138  4  sym.main
0x08048740  97  4  sym.__libc_csu_init
0x080487a1  17  2  fcn.080487a1
0x080487b2  22  1  section_end..text
0x0804851c  35  3  section..init
0x0804853f  13  1  section_end..init

The main-function (sym.main) sounds like a good point to start. Let’s have a loot at it using print disassembled function (pdf):

[0x080485b0]> pdf @ sym.main
╒ (fcn) sym.main 138
│          ; DATA XREF from 0x080485c7 (entry0)
│          ;-- main:
│          ;-- sym.main:
│          0x080486ad    55             push ebp
│          0x080486ae    89e5           mov ebp, esp
│          0x080486b0    83e4f0         and esp, 0xfffffff0
│          0x080486b3    83ec20         sub esp, 0x20
│          0x080486b6    c70424d08704.  mov dword [esp], str._____________________________  ; [0x80487d0:4]=0x2d2d2d2d  ; "-----------------------------" @ 0x80487d0
│          0x080486bd    e89efeffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x080486c2    c70424ee8704.  mov dword [esp], str.____RPISEC___CrackMe_v1.0____  ; [0x80487ee:4]=0x202d2d2d  ; "--- RPISEC - CrackMe v1.0 ---" @ 0x80487ee
│          0x080486c9    e892feffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x080486ce    c70424d08704.  mov dword [esp], str._____________________________  ; [0x80487d0:4]=0x2d2d2d2d  ; "-----------------------------" @ 0x80487d0
│          0x080486d5    e886feffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x080486da    c704240c8804.  mov dword [esp], str._nPassword:  ; [0x804880c:4]=0x7361500a  ; str._nPassword:
│          0x080486e1    e86afeffff     call sym.imp.printf             ; sub.printf_12_54c+0x4
│            ^- sub.printf_12_54c() ; sym.imp.printf
│          0x080486e6    8d44241c       lea eax, [esp + 0x1c]           ; 0x1c
│          0x080486ea    89442404       mov dword [esp + 4], eax        ; [0x4:4]=0x10101
│          0x080486ee    c70424188804.  mov dword [esp], 0x8048818      ; [0x8048818:4]=0xa006425  ; "%d" @ 0x8048818
│          0x080486f5    e8a6feffff     call sym.imp.__isoc99_scanf
│            ^- sym.imp.__isoc99_scanf()
│          0x080486fa    8b44241c       mov eax, dword [esp + 0x1c]     ; [0x1c:4]=52
│          0x080486fe    3d9a140000     cmp eax, 0x149a
│      ┌─< 0x08048703    751f           jne 0x8048724
│      │   0x08048705    c704241b8804.  mov dword [esp], str._nAuthenticated_  ; [0x804881b:4]=0x7475410a  ; str._nAuthenticated_
│      │   0x0804870c    e84ffeffff     call sym.imp.puts
│      │     ^- sym.imp.puts()
│      │   0x08048711    c704242b8804.  mov dword [esp], str._bin_sh    ; [0x804882b:4]=0x6e69622f  ; "/bin/sh" @ 0x804882b
│      │   0x08048718    e853feffff     call sym.imp.system
│      │     ^- sym.imp.system()
│      │   0x0804871d    b800000000     mov eax, 0
│     ┌──< 0x08048722    eb11           jmp 0x8048735
│     │└   ; JMP XREF from 0x08048703 (sym.main)
│     │└─> 0x08048724    c70424338804.  mov dword [esp], str._nInvalid_Password___  ; [0x8048833:4]=0x766e490a  ; str._nInvalid_Password___
│     │    0x0804872b    e830feffff     call sym.imp.puts
│     │      ^- sym.imp.puts()
│     │    0x08048730    b801000000     mov eax, 1
│     └    ; JMP XREF from 0x08048722 (sym.main)
│     └──> 0x08048735    c9             leave
╘          0x08048736    c3             ret

After a few calls to puts a password-prompt (“Password: “) is displayed using printf (line 19-20). The user input is read by a call to scanf (line 25). The arguments passed to scanf are a string (line 24) located at 0x8048818 (which is “%d” as already displayed by r2 right after the address) and a local variable at esp+0x1c (line 23).

On line 28 the user input stored in eax is compared to 0x149a. If the comparison fails, the jne to 0x8048724 is taken resulting in the output “Invalid Password!!!”. If the comparison succeeds the jump is not taken. In this case the string “Authenticated!” is printed and the function system is called with "/bin/sh" as argument (line 33-34).

Thus we only have to enter 0x149a as decimal and we get a shell 🙂

In r2 you can run shell commands using !!. This way we can easily convert the hex value to decimal using rax2:

[0x080485b0]> !!rax2 0x149a
5274

Now we only have to run the binary and enter the password we found:

lab1C@warzone:/levels/lab01$ ./lab1C 
-----------------------------
--- RPISEC - CrackMe v1.0 ---
-----------------------------

Password: 5274

Authenticated!
$ cat /home/lab1B/.pass	
n0_str1ngs_n0_pr0bl3m
$ exit
lab1C@warzone:/levels/lab01$

Done 🙂 The password for the next level is n0_str1ngs_n0_pr0bl3m.


lab1B

With the password we gained in the last level we can connect to the next level using ssh again. This time the username is lab1B with the password n0_str1ngs_n0_pr0bl3m:

gameadmin@warzone:~$ sudo ssh lab1B@localhost
lab1B@localhost's password: (n0_str1ngs_n0_pr0bl3m)
        ____________________.___  _____________________________          
        \______   \______   \   |/   _____/\_   _____/\_   ___ \               
         |       _/|     ___/   |\_____  \  |    __)_ /    \  \/               
         |    |   \|    |   |   |/        \ |        \\     \____              
         |____|_  /|____|   |___/_______  //_______  / \______  /              
                \/                      \/         \/         \/               
 __      __  _____ ____________________________    _______  ___________
/  \    /  \/  _  \\______   \____    /\_____  \   \      \ \_   _____/
\   \/\/   /  /_\  \|       _/ /     /  /   |   \  /   |   \ |    __)_ 
 \        /    |    \    |   \/     /_ /    |    \/    |    \|        \
  \__/\  /\____|__  /____|_  /_______ \\_______  /\____|__  /_______  /
       \/         \/       \/        \/        \/         \/        \/ 

        --------------------------------------------------------        

                       Challenges are in /levels                        
                   Passwords are in /home/lab*/.pass                    
            You can create files or work directories in /tmp            
                    
         -----------------[ contact@rpis.ec ]-----------------          

Last login: Thu Jan 18 10:44:33 2018 from 192.168.187.130
lab1B@warzone:~$ cd /levels/lab01/
lab1B@warzone:/levels/lab01$ file lab1B
lab1B: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=c9f07b581bd8d97cdc7c0ff1a288e20aea2df0f5, stripped

Let’s start up radare2 again, …

lab1B@warzone:/levels/lab01$ r2 lab1B
 -- Press 'C' in visual mode to toggle colors

… analyze all (aaa) …

[0x08048850]> aaa

… and list all functions (afl):

[0x08048850]> afl
0x08048850  34  1  entry0
0x08048820  6  1  sym.imp.__libc_start_main
0x08048826  10  2  fcn.08048826
0x08048760  12  1  section..plt
0x0804876c  10  1  sub.strcmp_12_76c
0x08048776  10  1  fcn.08048776
0x08048780  6  1  sym.imp.printf
0x08048786  10  1  fcn.08048786
0x08048790  6  1  sym.imp.fflush
0x08048796  10  1  fcn.08048796
0x080487a0  6  1  sym.imp.getchar
0x080487a6  10  1  fcn.080487a6
0x080487b0  6  1  sym.imp.time
0x080487b6  10  1  fcn.080487b6
0x080487c0  6  1  sym.imp.__stack_chk_fail
0x080487c6  10  1  fcn.080487c6
0x080487d0  6  1  sym.imp.puts
0x08048830  6  1  sym.imp.rand
0x08048836  10  1  fcn.08048836
0x08048840  6  1  sym.imp.__isoc99_scanf
0x08048846  10  1  fcn.08048846
0x08048880  4  1  fcn.08048880
0x08048890  42  4  fcn.08048890
0x080488ba  61  4  fcn.080488ba
0x080488f7  39  3  fcn.080488f7
0x08048920  45  8  fcn.08048920
0x0804894d  34  5  sym.clear_stdin
0x0804896f  55  1  sym.get_unum
0x080489a6  17  1  sym.prog_timeout
0x080489b7  189  11  sym.decrypt
0x08048a74  368  3  sym.test
0x08048be4  133  3  sym.main
0x08048800  6  1  sym.imp.srand
0x08048806  10  1  fcn.08048806
0x08048810  6  1  sym.imp.strlen
0x08048816  10  1  fcn.08048816
0x08048c70  97  4  sym.__libc_csu_init
0x08048cd1  17  2  fcn.08048cd1
0x08048ce2  22  1  section_end..text
0x08048738  35  3  section..init
0x080487f0  6  1  sym.imp.__gmon_start__
0x080487f6  10  1  fcn.080487f6
0x0804875b  17  1  section_end..init
0x080487e0  6  1  sym.imp.system
0x080487e6  10  1  fcn.080487e6

Yet again the main-function (sym.main) is a good point to start:

[0x08048850]> pdf @ sym.main
╒ (fcn) sym.main 133
│          ; DATA XREF from 0x08048867 (entry0)
│          ;-- main:
│          ;-- sym.main:
│          0x08048be4    55             push ebp
│          0x08048be5    89e5           mov ebp, esp
│          0x08048be7    83e4f0         and esp, 0xfffffff0
│          0x08048bea    83ec20         sub esp, 0x20
│          0x08048bed    50             push eax
│          0x08048bee    31c0           xor eax, eax
│      ┌─< 0x08048bf0    7403           je 0x8048bf5                  
│      │   0x08048bf2    83c404         add esp, 4
│      └   ; JMP XREF from 0x08048bf0 (sym.main)
│      └─> 0x08048bf5    58             pop eax
│          0x08048bf6    c70424000000.  mov dword [esp], 0
│          0x08048bfd    e8aefbffff     call sym.imp.time
│            ^- sym.imp.time()
│          0x08048c02    890424         mov dword [esp], eax
│          0x08048c05    e8f6fbffff     call sym.imp.srand
│            ^- sym.imp.srand()
│          0x08048c0a    c70424888d04.  mov dword [esp], 0x8048d88      ; [0x8048d88:4]=0x2d2d2d2e 
│          0x08048c11    e8bafbffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048c16    c70424a68d04.  mov dword [esp], str.____RPISEC___CrackMe_v2.0____  ; [0x8048da6:4]=0x202d2d7c  ; "|-- RPISEC - CrackMe v2.0 --|" @ 0x8048da6
│          0x08048c1d    e8aefbffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048c22    c70424c48d04.  mov dword [esp], str._____________________________  ; [0x8048dc4:4]=0x2d2d2d27  ; "'---------------------------'" @ 0x8048dc4
│          0x08048c29    e8a2fbffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048c2e    c70424e28d04.  mov dword [esp], str._nPassword:  ; [0x8048de2:4]=0x7361500a  ; str._nPassword:
│          0x08048c35    e846fbffff     call sym.imp.printf
│            ^- sym.imp.printf()
│          0x08048c3a    8d44241c       lea eax, [esp + 0x1c]           ; 0x1c 
│          0x08048c3e    89442404       mov dword [esp + 4], eax        ; [0x4:4]=0x10101 
│          0x08048c42    c70424ee8d04.  mov dword [esp], 0x8048dee      ; [0x8048dee:4]=0x6425 
│          0x08048c49    e8f2fbffff     call sym.imp.__isoc99_scanf
│            ^- sym.imp.__isoc99_scanf()
│          0x08048c4e    8b44241c       mov eax, dword [esp + 0x1c]     ; [0x1c:4]=52
│          0x08048c52    c74424040dd0.  mov dword [esp + 4], 0x1337d00d  ; [0x1337d00d:4]=-1
│          0x08048c5a    890424         mov dword [esp], eax
│          0x08048c5d    e812feffff     call sym.test
│            ^- sym.test()
│          0x08048c62    b800000000     mov eax, 0
│          0x08048c67    c9             leave
╘          0x08048c68    c3             ret

The usual password-prompt is displayed by a call to printf (line 31-32). Right after this scanf is called on line 37. The content of the string passed to the function (line 36) located at 0x8048dee is not automatically displayed by r2. We can print it using print string (ps):

[0x08048850]> ps @ 0x8048dee
%d

The string contains %d meaning that the input to scanf is interpreted as an integer, which is stored in a local variable at esp+0x1c (line 35 main-function).

After the call to scanf follows a call to the user-defined function sym.test on line 42. Before the call two arguments are moved on the stack. Remember that arguments are pushed on the stack in reverse order. Thus the first argument of the function is the local variable stored at esp+0x1c on line 41. This is the variable formerly passed to scanf (the integer the user entered). The second argument is the constant 0x1337d00d on line 40.

Thus the relevant part of our main-function looks like this:

int userinput;
printf("Password: ");
scanf("%d", &userinput);
test(userinput, 0x1337d00d);

Now we can proceed with the function sym.test:

[0x08048850]> pdf @ sym.test
╒ (fcn) sym.test 368
│           ; arg int arg_2        @ ebp+0x8
│           ; arg int arg_3        @ ebp+0xc
│           ; arg int arg_5_1      @ ebp+0x15
│           ; var int local_3      @ ebp-0xc
│           ; CALL XREF from 0x08048c5d (sym.main)
│           ;-- sym.test:
│           0x08048a74    55             push ebp
│           0x08048a75    89e5           mov ebp, esp
│           0x08048a77    83ec28         sub esp, 0x28
│           0x08048a7a    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a7d    8b550c         mov edx, dword [ebp + 0xc]     ; [0xc:4]=0
│           0x08048a80    29c2           sub edx, eax
│           0x08048a82    89d0           mov eax, edx
│           0x08048a84    8945f4         mov dword [ebp-local_3], eax
│           0x08048a87    837df415       cmp dword [ebp-local_3], 0x15  ; [0x15:4]=0x50000000 
│       ┌─< 0x08048a8b    0f8744010000   ja 0x8048bd5                 
│       │   0x08048a91    8b45f4         mov eax, dword [ebp-local_3]
│       │   0x08048a94    c1e002         shl eax, 2
│       │   0x08048a97    05308d0408     add eax, 0x8048d30
│       │   0x08048a9c    8b00           mov eax, dword [eax]
│       │   0x08048a9e    ffe0           jmp eax
│       │   0x08048aa0    8b45f4         mov eax, dword [ebp-local_3]
│       │   0x08048aa3    890424         mov dword [esp], eax
│       │   0x08048aa6    e80cffffff     call sym.decrypt
│       │     ^- sym.decrypt()
│      ┌──< 0x08048aab    e932010000     jmp 0x8048be2                
│      ││   0x08048ab0    8b45f4         mov eax, dword [ebp-local_3]
│      ││   0x08048ab3    890424         mov dword [esp], eax
│      ││   0x08048ab6    e8fcfeffff     call sym.decrypt
│      ││     ^- sym.decrypt()

...

│ ────────< 0x08048bc6    eb1a           jmp 0x8048be2                
│ │││││││   0x08048bc8    8b45f4         mov eax, dword [ebp-local_3]
│ │││││││   0x08048bcb    890424         mov dword [esp], eax
│ │││││││   0x08048bce    e8e4fdffff     call sym.decrypt
│ │││││││     ^- sym.decrypt()
│ ────────< 0x08048bd3    eb0d           jmp 0x8048be2                
│ ││││││└   ; JMP XREF from 0x08048a8b (sym.test)
│ ││││││└─> 0x08048bd5    e856fcffff     call sym.imp.rand
│ ││││││└     ^- sym.imp.rand()
│ ││││││    0x08048bda    890424         mov dword [esp], eax
│ ││││││    0x08048bdd    e8d5fdffff     call sym.decrypt
│ ││││││      ^- sym.decrypt()
│ └└└└└└──> 0x08048be2    c9             leave
╘           0x08048be3    c3             ret

At first we should locate the arguments passed to the function. You can already see that r2 identified ebp+0x8 and ebp+0xc as arg_2 and arg_3 (line 3-4). Let’s quickly review why this should be the formerly passed arguments having a look at the stack-layout:

ebp+0x0  [     saved ebp     ]  <-- pushed ebp (line 9)
ebp+0x4  [   return address  ]  <-- return address pushed by call-instrunction
ebp+0x8  [ integer userinput ]  <-- 1st argument
ebp+0xc  [constant 0x1337d00d]  <-- 2nd argument

As the stack grows from the bottom (high addresses) to the top (low addresses) the first element moved to the stack is the constant 0x1337d00d followed by the local integer. After this the function test is called. The call-instruction implictly pushes the return address on the stack in order to proceed code execution after the function call. The first thing that happens in the function test is that the base pointer (ebp) is saved on the stack. After this ebp is set to the value of the stack pointer (esp) on line 10. Thus we can access our arguments with ebp+0x8 and ebp+0xc.

So what does the function do?

│           0x08048a7a    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a7d    8b550c         mov edx, dword [ebp + 0xc]     ; [0xc:4]=0
│           0x08048a80    29c2           sub edx, eax
│           0x08048a82    89d0           mov eax, edx
│           0x08048a84    8945f4         mov dword [ebp-local_3], eax
│           0x08048a87    837df415       cmp dword [ebp-local_3], 0x15  ; [0x15:4]=0x50000000 
│       ┌─< 0x08048a8b    0f8744010000   ja 0x8048bd5                 

In the lines 12-13 the passed arguments are stored in eax (userinput) and edx (constant). After this eax is subtracted from edx (line 14). The result is stored in eax (line 15) and then moved to a local variable (ebp-local_3) on line 16. This local variable is compared to 0x15 on line 17 followed by a jump above (ja) to 0x8048bd5 on line 18. So far the function looks like this:

void test(int userinput, int constant) {

  int result;

  result = constant - userinput;

  if (result <= 0x15) {

  }
  else {

0x8048bd5:
  ...

  }

  return;
}

If the jump on line 18 is not taken meaning that the difference of the constant 0x1337d00d and the userinput is less than or equal to 0x15, the difference is moved to eax (line 19). Then the value is left-shifted 2 bits (line 20) and 0x8048d30 is added (line 21). On line 22 the resulting value is used as a reference to a DWORD, which is stored in eax. The following jump lets the code execution proceed at the stored DWORD in eax (line 23):

...
  if (result <= 0x15) {

    void *ptr = (void*)(result << 2);
    ptr += 0x8048d30;
    goto *ptr;

  }
...

Before we have a closer look at the addresses where the code execution may proceed, notice the instructions which follow from line 24 onwards. I truncated the output of the function sym.test because the 4 instructions beginning at 0x08048aa0 (lines 24-28) repeat a few times:

│       │   0x08048aa0    8b45f4         mov eax, dword [ebp-local_3]
│       │   0x08048aa3    890424         mov dword [esp], eax
│       │   0x08048aa6    e80cffffff     call sym.decrypt
│       │     ^- sym.decrypt()
│      ┌──< 0x08048aab    e932010000     jmp 0x8048be2   

The instructions place the local variable ebp-local_3 as an argument on the stack and call the user-defined function sym.decrypt. After the function call a jump to 0x8048be2 follows, which is the end of the function sym.test.

Now let’s have a look at the addresses where the code execution may proceed when the difference of the subtraction is less than or equal to 0x15:

result result << 2 ptr (+0x8048d30)
0x15 0x54 0x8048d84
0x14 0x50 0x8048d80
0x13 0x4c 0x8048d7c
0x12 0x48 0x8048d78
0x01 0x04 0x8048d34
0x00 0x00 0x8048d30

Note that the value is used as a pointer to the address we jump to. Thus we have to look which addresses are stored at these location. As we can see in the right column these locations range from 0x8048d30 (result = 0x00) to 0x8048d84 (result = 0x15). In r2 we can print words using print hexdump of words (pwx):

[0x08048850]> pxw 88 @ 0x8048d30
0x08048d30  0x08048bd5 0x08048aa0 0x08048ab0 0x08048ac0  ................
0x08048d40  0x08048ad0 0x08048ae0 0x08048af0 0x08048b00  ................
0x08048d50  0x08048b10 0x08048b20 0x08048b30 0x08048b40  .... ...0...@...
0x08048d60  0x08048b50 0x08048b60 0x08048b6d 0x08048b7a  P...`...m...z...
0x08048d70  0x08048b87 0x08048b94 0x08048ba1 0x08048bae  ................
0x08048d80  0x08048bbb 0x08048bc8                      ........        

The 88 specifies that we want to print 88 bytes (= 22 dwords). The addresses stored here look similar somehow! These are offsets into the function sym.test. We can disassemble the addressed locations with print disassembled (pd). Let’s begin with 0x8048d30 (result = 0x00):

[0x08048850]> pd 5 @ [0x8048d30]
           ; JMP XREF from 0x08048a8b (sym.test)
│          0x08048bd5    e856fcffff     call sym.imp.rand
│            ^- sym.imp.rand(unk)
│          0x08048bda    890424         mov dword [esp], eax
│          0x08048bdd    e8d5fdffff     call sym.decrypt
│            ^- sym.decrypt()
│          0x08048be2    c9             leave
╘          0x08048be3    c3             ret

The square brackets around the 0x8048d30 tell r2 not to disassemble the code at 0x8048d30 but rather take the value stored at 0x8048d30 and disassamble the code at that value. Thus for 0x8048d30 the code at 0x08048bd5 is diassembled. In the disassembled code a call to sym.imp.rand is made to generate a random value which is then passed as argument for the following call to sym.decrypt.

The other addresses for result = 0x01, 0x02, 0x03, ... look like this:

[0x08048850]> pd 4 @ [0x8048d34]
│          0x08048aa0    8b45f4         mov eax, dword [ebp-local_3]
│          0x08048aa3    890424         mov dword [esp], eax
│          0x08048aa6    e80cffffff     call sym.decrypt
│            ^- sym.decrypt()
│      ┌─< 0x08048aab e932010000 jmp 0x8048be2 [0x08048850]> pd 4 @ [0x8048d38]
│          0x08048ab0    8b45f4         mov eax, dword [ebp-local_3]
│          0x08048ab3    890424         mov dword [esp], eax
│          0x08048ab6    e8fcfeffff     call sym.decrypt
│            ^- sym.decrypt()
│      ┌─< 0x08048abb e922010000 jmp 0x8048be2 [0x08048850]> pd 4 @ [0x8048d3c]
│          0x08048ac0    8b45f4         mov eax, dword [ebp-local_3]
│          0x08048ac3    890424         mov dword [esp], eax
│          0x08048ac6    e8ecfeffff     call sym.decrypt
│            ^- sym.decrypt()
│      ┌─< 0x08048acb    e912010000     jmp 0x8048be2                 

The disassambled instructions are the 4 instructions we have already seen in the function sym.test. The function sym.decrypt is called and the local variable ebp-local_3 is passed as argument. After the call there is a jump to the end of the function sym.test (0x8048be2).

So let’s have a look at the function sym.decrypt:

[0x08048850]> pdf @ sym.decrypt
╒ (fcn) sym.decrypt 189
│          ; arg int arg_2        @ ebp+0x8
│          ; arg int arg_215506719_1 @ ebp+0x33617c7d
│          ; arg int arg_433691864 @ ebp+0x67667360
│          ; arg int arg_492773204_1 @ ebp+0x757c7d51
│          ; arg int arg_517577951_2 @ ebp+0x7b66737e
│          ; var int local_3      @ ebp-0xc
│          ; var int local_3_1    @ ebp-0xd
│          ; var int local_7_1    @ ebp-0x1d
│          ; var int local_9      @ ebp-0x24
│          ; var int local_10     @ ebp-0x28
│          ; CALL XREF from 0x08048bdd (sym.test)
│          ;-- sym.decrypt:
│          0x080489b7    55             push ebp
│          0x080489b8    89e5           mov ebp, esp
│          0x080489ba    83ec38         sub esp, 0x38
│          0x080489bd    65a114000000   mov eax, dword gs:[0x14]        ; [0x14:4]=1
│          0x080489c3    8945f4         mov dword [ebp-local_3], eax
│          0x080489c6    31c0           xor eax, eax
│          0x080489c8    c745e3517d7c.  mov dword [ebp-local_7_1], 0x757c7d51  ; [0x757c7d51:4]=-1
│          0x080489cf    c745e7607366.  mov dword [ebp - 0x19], 0x67667360  ; [0x67667360:4]=-1
│          0x080489d6    c745eb7e7366.  mov dword [ebp - 0x15], 0x7b66737e  ; [0x7b66737e:4]=-1
│          0x080489dd    c745ef7d7c61.  mov dword [ebp - 0x11], 0x33617c7d  ; [0x33617c7d:4]=-1
│          0x080489e4    c645f300       mov byte [ebp-local_3_1], 0
│          0x080489e8    50             push eax
│          0x080489e9    31c0           xor eax, eax
│      ┌─< 0x080489eb    7403           je 0x80489f0                  
│      │   0x080489ed    83c404         add esp, 4
│      └   ; JMP XREF from 0x080489eb (sym.decrypt)
│      └─> 0x080489f0    58             pop eax
│          0x080489f1    8d45e3         lea eax, [ebp-local_7_1]
│          0x080489f4    890424         mov dword [esp], eax
│          0x080489f7    e814feffff     call sym.imp.strlen
│            ^- sym.imp.strlen()
│          0x080489fc    8945dc         mov dword [ebp-local_9], eax
│          0x080489ff    c745d8000000.  mov dword [ebp-local_10], 0
│     ┌──< 0x08048a06    eb20           jmp 0x8048a28                 
│    ┌     ; JMP XREF from 0x08048a2e (sym.decrypt)
│    ┌───> 0x08048a08    8d55e3         lea edx, [ebp-local_7_1]
│    ││    0x08048a0b    8b45d8         mov eax, dword [ebp-local_10]
│    ││    0x08048a0e    01d0           add eax, edx
│    ││    0x08048a10    0fb600         movzx eax, byte [eax]
│    ││    0x08048a13    89c2      44   mov edx, eax
│    ││    0x08048a15    8b4508         mov eax, dword [ebp + 8]        ; [0x8:4]=0
│    ││    0x08048a18    31d0      46   xor eax, edx
│    ││    0x08048a1a    8d4de3         lea ecx, [ebp-local_7_1]
│    ││    0x08048a1d    8b55d8    48   mov edx, dword [ebp-local_10]
│    ││    0x08048a20    01ca           add edx, ecx
│    ││    0x08048a22    8802      50   mov byte [edx], al
│    ││    0x08048a24    8345d801       add dword [ebp-local_10], 1
│    │└    ; JMP XREF from 0x08048a06 (sym.decrypt)
│    │└──> 0x08048a28    8b45d8         mov eax, dword [ebp-local_10]
│    │     0x08048a2b    3b45dc    54   cmp eax, dword [ebp-local_9]
│    └───< 0x08048a2e    72d8           jb 0x8048a08                  
│          0x08048a30    c7442404038d.  mov dword [esp + 4], str.Congratulations_  ; [0x8048d03:4]=0x676e6f43  ; "Congratulations!" @ 0x8048d03
│          0x08048a38    8d45e3    58   lea eax, [ebp-local_7_1]
│          0x08048a3b    890424         mov dword [esp], eax
│          0x08048a3e    e82dfdffff     call sym.imp.strcmp             ; sub.strcmp_12_76c+0x4
│            ^- sub.strcmp_12_76c() ; sym.imp.strcmp
│          0x08048a43    85c0           test eax, eax
│   ┌────< 0x08048a45    750e           jne 0x8048a55                 
│   │      0x08048a47    c70424148d04.  mov dword [esp], str._bin_sh    ; [0x8048d14:4]=0x6e69622f  ; "/bin/sh" @ 0x8048d14
│   │      0x08048a4e    e88dfdffff     call sym.imp.system
│   │        ^- sym.imp.system()
│  ┌─────< 0x08048a53    eb0c           jmp 0x8048a61                 
│  │└      ; JMP XREF from 0x08048a45 (sym.decrypt)
│  │└────> 0x08048a55    c704241c8d04.  mov dword [esp], str._nInvalid_Password_  ; [0x8048d1c:4]=0x766e490a  ; str._nInvalid_Password_
│  │       0x08048a5c    e86ffdffff     call sym.imp.puts
│  │         ^- sym.imp.puts()
│  └       ; JMP XREF from 0x08048a53 (sym.decrypt)
│  └─────> 0x08048a61    8b45f4         mov eax, dword [ebp-local_3]
│          0x08048a64    653305140000.  xor eax, dword gs:[0x14]
│          0x08048a6b    7405           je 0x8048a72                  
│          0x08048a6d    e84efdffff     call sym.imp.__stack_chk_fail
│            ^- sym.imp.__stack_chk_fail()
│          ; JMP XREF from 0x08048a6b (sym.decrypt)
│          0x08048a72    c9             leave
╘          0x08048a73    c3             ret

As we have already seen the function takes one argument which can be accessed with ebp+0x8. On lines 21-24 four dwords (4 byte) and single 1 byte (line 24) are moved on the stack. The output above is a little bit confusing because r2 replaced the offset to ebp with the local_ aliases. The corresponding lines look like this:

│          0x080489c8    c745e3517d7c.  mov dword [ebp - 0x1d], 0x757c7d51  ; [0x757c7d51:4]=-1
│          0x080489cf    c745e7607366.  mov dword [ebp - 0x19], 0x67667360  ; [0x67667360:4]=-1
│          0x080489d6    c745eb7e7366.  mov dword [ebp - 0x15], 0x7b66737e  ; [0x7b66737e:4]=-1
│          0x080489dd    c745ef7d7c61.  mov dword [ebp - 0x11], 0x33617c7d  ; [0x33617c7d:4]=-1
│          0x080489e4    c645f300       mov byte  [ebp - 0xd], 0

The values are placed sequently on the stack. After the executing of the instructions the memory region looks like the following:

ebp-0x1d: 517d7c75607366677e73667b7d7c613300

Looks like a null-terminated string:

[0x08048850]> !!python -c 'print("517d7c75607366677e73667b7d7c613300".decode("hex"))'
Q}|u`sfg~sf{}|a3

We will keep this in mind and continue reversing the function sym.decrypt.

On line 34 the function strlen is called. The passed argument is a reference the string we just found (ebp-local_7_1):

│          0x080489f1    8d45e3         lea eax, [ebp-local_7_1]
│          0x080489f4    890424         mov dword [esp], eax
│          0x080489f7    e814feffff     call sym.imp.strlen
│            ^- sym.imp.strlen()

The returned length of the string stored in eax is moved to a local variable (ebp-local_9). Another local variable (ebp-local_10) is initialized with 0:

│          0x080489fc    8945dc         mov dword [ebp-local_9], eax
│          0x080489ff    c745d8000000.  mov dword [ebp-local_10], 0

On lines 38-55 follows a loop iterating every character in the string:

│     ┌──< 0x08048a06    eb20           jmp 0x8048a28                 
│    ┌     ; JMP XREF from 0x08048a2e (sym.decrypt)
│    ┌───> 0x08048a08    8d55e3         lea edx, [ebp-local_7_1]
│    ││    0x08048a0b    8b45d8         mov eax, dword [ebp-local_10]
│    ││    0x08048a0e    01d0           add eax, edx
│    ││    0x08048a10    0fb600         movzx eax, byte [eax]
│    ││    0x08048a13    89c2      44   mov edx, eax
│    ││    0x08048a15    8b4508         mov eax, dword [ebp + 8]        ; [0x8:4]=0
│    ││    0x08048a18    31d0      46   xor eax, edx
│    ││    0x08048a1a    8d4de3         lea ecx, [ebp-local_7_1]
│    ││    0x08048a1d    8b55d8    48   mov edx, dword [ebp-local_10]
│    ││    0x08048a20    01ca           add edx, ecx
│    ││    0x08048a22    8802      50   mov byte [edx], al
│    ││    0x08048a24    8345d801       add dword [ebp-local_10], 1
│    │└    ; JMP XREF from 0x08048a06 (sym.decrypt)
│    │└──> 0x08048a28    8b45d8         mov eax, dword [ebp-local_10]
│    │     0x08048a2b    3b45dc    54   cmp eax, dword [ebp-local_9]
│    └───< 0x08048a2e    72d8           jb 0x8048a08                 

At first (line 38) there is a jump to the loop-condition beginning on line 53. These three instructions simply test if ebp-local_10 (loop-counter) has reached the value of ebp-local_9 (string length) and jump to the loop-body on line 40 when the loop-counter is still smaller.

The loop-body loads the baseaddress of the string (line 40) and adds the loop-counter. After the execution of line 44 edx contains a single character of the formerly stored string at the position indicated by the loop-counter. This character is XORed with the value stored at ebp+0x8 (line 46). Do You remember what value that was? Yes! The argument passed to the function sym.decrypt. On lines 47-50 the XORed character is written back to the string. At last (line 51) the loop-counter is incremented.

Until now the decrypt function looks like the following:

void decrypt(int v) {

  char str[] = "Q}|u`sfg~sf{}|a3";

  int len = strlen(str);

  for (int i = 0; i < len; i++) {

    str[i] = str[i] ^ v;

  }
  ...
}

After the loop there is a call to strcmp:

│          0x08048a30    c7442404038d.  mov dword [esp + 4], str.Congratulations_  ; [0x8048d03:4]=0x676e6f43  ; "Congratulations!" @ 0x8048d03
│          0x08048a38    8d45e3         lea eax, [ebp-local_7_1]
│          0x08048a3b    890424         mov dword [esp], eax
│          0x08048a3e    e82dfdffff     call sym.imp.strcmp             ; sub.strcmp_12_76c+0x4
│            ^- sub.strcmp_12_76c() ; sym.imp.strcmp

r2 already displays that the first string being compared contains Congratulations!. The second string stored at ebp-local_7_1 is the string that has just been XORed in the loop. After the call to strcmp the result is evaluted:

│          0x08048a43    85c0           test eax, eax
│   ┌────< 0x08048a45    750e           jne 0x8048a55                 
│   │      0x08048a47    c70424148d04.  mov dword [esp], str._bin_sh    ; [0x8048d14:4]=0x6e69622f  ; "/bin/sh" @ 0x8048d14
│   │      0x08048a4e    e88dfdffff     call sym.imp.system
│   │        ^- sym.imp.system()
│  ┌─────< 0x08048a53    eb0c           jmp 0x8048a61                 
│  │└      ; JMP XREF from 0x08048a45 (sym.decrypt)
│  │└────> 0x08048a55    c704241c8d04.  mov dword [esp], str._nInvalid_Password_  ; [0x8048d1c:4]=0x766e490a  ; str._nInvalid_Password_
│  │       0x08048a5c    e86ffdffff     call sym.imp.puts

If the strings are not equal Invalid Password! is printed and the function returns. If the strings match a call to system spawns our desired shell. Thus we can complete your decrypt function:

void decrypt(int v) {
  ...

  // str contains "Q}|u`sfg~sf{}|a3" XORed with v

  if (strcmp("Congratulations!", str) == 0) {

    system("/bin/sh");

  }
  else {

    puts("Invalid Password!");

  }
}

Summing it all up we have to find a value which turns the string Q}|u`sfg~sf{}|a3 into Congratulations! when being XORed with this value. This is quite easy because we can just XOR both strings and see what the result is:

lab1B@warzone:~$ python
...
>>> s1 = "Q}|u`sfg~sf{}|a3"
>>> s2 = "Congratulations!"
>>> [ord(a) ^ ord(b) for a,b in zip(s1,s2)]
[18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18]

So the value we are looking for is 18 (= 0x12). As we already figured out the value passed to the function decrypt is calculated by subtracting our input value from 0x1337d00d. Thus we need to enter:

0x1337d00d - 0x12 = 0x1337cffb = 322424827
lab1B@warzone:/levels/lab01$ ./lab1B 
.---------------------------.
|-- RPISEC - CrackMe v2.0 --|
'---------------------------'

Password: 322424827
$ whoami
lab1A
$ cat /home/lab1A/.pass
1337_3nCRyptI0n_br0

Done 🙂 The password for the next level is 1337_3nCRyptI0n_br0.


lab1A

Now we can proceed to the last level of this lab. The username is lab1A with the password 1337_3nCRyptI0n_br0:

gameadmin@warzone:~$ sudo ssh lab1A@localhost
lab1A@localhost's password: (1337_3nCRyptI0n_br0)
        ____________________.___  _____________________________          
        \______   \______   \   |/   _____/\_   _____/\_   ___ \               
         |       _/|     ___/   |\_____  \  |    __)_ /    \  \/               
         |    |   \|    |   |   |/        \ |        \\     \____              
         |____|_  /|____|   |___/_______  //_______  / \______  /              
                \/                      \/         \/         \/               
 __      __  _____ ____________________________    _______  ___________
/  \    /  \/  _  \\______   \____    /\_____  \   \      \ \_   _____/
\   \/\/   /  /_\  \|       _/ /     /  /   |   \  /   |   \ |    __)_ 
 \        /    |    \    |   \/     /_ /    |    \/    |    \|        \
  \__/\  /\____|__  /____|_  /_______ \\_______  /\____|__  /_______  /
       \/         \/       \/        \/        \/         \/        \/ 

        --------------------------------------------------------        

                       Challenges are in /levels                        
                   Passwords are in /home/lab*/.pass                    
            You can create files or work directories in /tmp            
                    
         -----------------[ contact@rpis.ec ]-----------------          

Last login: Fri Jan 19 01:45:32 2018 from localhost
lab1A@warzone:~$

As usual we start up r2:

lab1A@warzone:~$ cd /levels/lab01
lab1A@warzone:/levels/lab01$ r2 lab1A

Have a quick look at the functions within the binary:

[0x08048880]> aaa
[0x08048880]> afl
0x08048880  34  1  entry0
0x08048840  6  1  sym.imp.__libc_start_main
0x08048846  10  2  fcn.08048846
...
0x08048a0f  309  15  sym.auth
0x08048b44  298  8  sym.main
0x08048c70  97  4  sym.__libc_csu_init
0x08048cd1  17  2  fcn.08048cd1
0x08048ce2  22  1  section_end..text
0x0804876c  35  3  section..init
0x08048830  6  1  sym.imp.__gmon_start__
0x08048836  10  1  fcn.08048836
0x0804878f  13  1  section_end..init
0x08048810  6  1  sym.imp.puts
0x08048816  10  1  fcn.08048816
0x08048820  6  1  sym.imp.system
0x08048826  10  1  fcn.08048826

And start analyzing the main-function:

[0x08048880]> pdf @ sym.main
╒ (fcn) sym.main 298
│          ; arg int arg_3        @ ebp+0xc
│          ; DATA XREF from 0x08048897 (entry0)
│          ;-- main:
│          ;-- sym.main:
│          0x08048b44    55             push ebp
│          0x08048b45    89e5           mov ebp, esp
│          0x08048b47    83e4f0         and esp, 0xfffffff0
│          0x08048b4a    83ec40         sub esp, 0x40
│          0x08048b4d    8b450c         mov eax, dword [ebp + 0xc]      ; [0xc:4]=0
│          0x08048b50    8944240c       mov dword [esp + 0xc], eax      ; [0xc:4]=0
│          0x08048b54    65a114000000   mov eax, dword gs:[0x14]        ; [0x14:4]=1
│          0x08048b5a    8944243c       mov dword [esp + 0x3c], eax     ; [0x3c:4]=0x8048034 section_end.ehdr ; '<'
│          0x08048b5e    31c0           xor eax, eax
│          0x08048b60    50             push eax
│          0x08048b61    31c0           xor eax, eax
│      ┌─< 0x08048b63    7403           je 0x8048b68                  
│      │   0x08048b65    83c404         add esp, 4
│      └   ; JMP XREF from 0x08048b63 (sym.main)
│      └─> 0x08048b68    58             pop eax
│          0x08048b69    c70424738d04.  mov dword [esp], str..___________________________.  ; [0x8048d73:4]=0x2d2d2d2e  ; ".---------------------------." @ 0x8048d73
│          0x08048b70    e89bfcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048b75    c70424918d04.  mov dword [esp], str.____________RPISEC___________  ; [0x8048d91:4]=0x2d2d2d7c  ; "|---------  RPISEC  --------|" @ 0x8048d91
│          0x08048b7c    e88ffcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048b81    c70424af8d04.  mov dword [esp], str.___SECURE_LOGIN_SYS_v._3.0___  ; [0x8048daf:4]=0x53202b7c  ; "|+ SECURE LOGIN SYS v. 3.0 +|" @ 0x8048daf
│          0x08048b88    e883fcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048b8d    c70424cd8d04.  mov dword [esp], 0x8048dcd      ; [0x8048dcd:4]=0x2d2d2d7c 
│          0x08048b94    e877fcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048b99    c70424eb8d04.  mov dword [esp], str.____Enter_your_Username:_____  ; [0x8048deb:4]=0x202d7e7c  ; "|~- Enter your Username:  ~-|" @ 0x8048deb
│          0x08048ba0    e86bfcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048ba5    c70424098e04.  mov dword [esp], str._____________________________  ; [0x8048e09:4]=0x2d2d2d27  ; "'---------------------------'" @ 0x8048e09
│          0x08048bac    e85ffcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048bb1    a160b00408     mov eax, dword [sym.stdin]      ; [0x804b060:4]=0x3a434347  ; "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" @ 0x804b060
│          0x08048bb6    89442408       mov dword [esp + 8], eax        ; [0x8:4]=0
│          0x08048bba    c74424042000.  mov dword [esp + 4], 0x20       ; [0x20:4]=0x2168  ; "h!" 0x00000020 
│          0x08048bc2    8d44241c       lea eax, [esp + 0x1c]           ; 0x1c 
│          0x08048bc6    890424         mov dword [esp], eax
│          0x08048bc9    e802fcffff     call sym.imp.fgets
│            ^- sym.imp.fgets()
│          0x08048bce    c70424738d04.  mov dword [esp], str..___________________________.  ; [0x8048d73:4]=0x2d2d2d2e  ; ".---------------------------." @ 0x8048d73
│          0x08048bd5    e836fcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048bda    c70424278e04.  mov dword [esp], str._____NEW_ACCOUNT_DETECTED____  ; [0x8048e27:4]=0x2121207c  ; "| !! NEW ACCOUNT DETECTED !!|" @ 0x8048e27
│          0x08048be1    e82afcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048be6    c70424cd8d04.  mov dword [esp], 0x8048dcd      ; [0x8048dcd:4]=0x2d2d2d7c 
│          0x08048bed    e81efcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048bf2    c70424458e04.  mov dword [esp], str.____Input_your_serial:_______  ; [0x8048e45:4]=0x202d7e7c  ; "|~- Input your serial:    ~-|" @ 0x8048e45
│          0x08048bf9    e812fcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048bfe    c70424098e04.  mov dword [esp], str._____________________________  ; [0x8048e09:4]=0x2d2d2d27  ; "'---------------------------'" @ 0x8048e09
│          0x08048c05    e806fcffff     call sym.imp.puts
│            ^- sym.imp.puts()
│          0x08048c0a    8d442418       lea eax, [esp + 0x18]           ; 0x18 
│          0x08048c0e    89442404       mov dword [esp + 4], eax        ; [0x4:4]=0x10101 
│          0x08048c12    c70424008d04.  mov dword [esp], 0x8048d00      ; [0x8048d00:4]=0xa007525 
│          0x08048c19    e842fcffff     call sym.imp.__isoc99_scanf
│            ^- sym.imp.__isoc99_scanf()
│          0x08048c1e    8b442418       mov eax, dword [esp + 0x18]     ; [0x18:4]=0x8048880 entry0
│          0x08048c22    89442404       mov dword [esp + 4], eax        ; [0x4:4]=0x10101 
│          0x08048c26    8d44241c       lea eax, [esp + 0x1c]           ; 0x1c 
│          0x08048c2a    890424         mov dword [esp], eax
│          0x08048c2d    e8ddfdffff     call sym.auth
│            ^- sym.auth()
│          0x08048c32    85c0           test eax, eax
│     ┌──< 0x08048c34    751f           jne 0x8048c55                 
│     │    0x08048c36    c70424638e04.  mov dword [esp], str.Authenticated_  ; [0x8048e63:4]=0x68747541  ; "Authenticated!" @ 0x8048e63
│     │    0x08048c3d    e8cefbffff     call sym.imp.puts
│     │      ^- sym.imp.puts()
│     │    0x08048c42    c70424728e04.  mov dword [esp], str._bin_sh    ; [0x8048e72:4]=0x6e69622f  ; "/bin/sh" @ 0x8048e72
│     │    0x08048c49    e8d2fbffff     call sym.imp.system
│     │      ^- sym.imp.system()
│     │    0x08048c4e    b800000000     mov eax, 0
│    ┌───< 0x08048c53    eb05           jmp 0x8048c5a                 
│    │└    ; JMP XREF from 0x08048c34 (sym.main)
│    │└──> 0x08048c55    b801000000     mov eax, 1
│    └     ; JMP XREF from 0x08048c53 (sym.main)
│    └───> 0x08048c5a    8b54243c       mov edx, dword [esp + 0x3c]     ; [0x3c:4]=0x8048034 section_end.ehdr ; '<'
│          0x08048c5e    653315140000.  xor edx, dword gs:[0x14]
│          0x08048c65    7405           je 0x8048c6c                  
│          0x08048c67    e894fbffff     call sym.imp.__stack_chk_fail
│            ^- sym.imp.__stack_chk_fail()
│          ; JMP XREF from 0x08048c65 (sym.main)
│          0x08048c6c    c9             leave
╘          0x08048c6d    c3             ret

This time the first thing the program asks for is not a password but a username by calling puts (line 34-35). This username is read using fgets on line 45. The first argument passed (=last argument pushed) on line 44 is the address of the string where the user-input should be stored (line 43: esp+0x1c). The second argument is the maximum count of bytes to read (line 42). In this case the value is set to 0x20 = 32 bytes. The third argument is the stream from where to read. In this case this is simply stdin (lines 40-41).

After the username-prompt there is another prompt asking for a serial (lines 56-57). The user-input is read using scanf (lines 62-65). The first argument passed to scanf on line 64 is the format string to be used (0x8048d00):

[0x08048880]> ps @ 0x8048d00
%u

The format-string %u specifies that an unsigned integer is read. This is stored at the address passed as the second argument. As can be seen in lines 62-63 the unsigned integer will be stored at esp+0x18.

After the username and the serial are read the function sym.auth is called on line 71. There are two arguments passed to the function (lines 67-70): esp+0x1c and esp+0x18. As we have already figured out esp+0x1c holds the username entered and esp+0x18 contains the serial read by scanf.

After the call to sym.auth the instruction test eax, eax on line 73 checks if the return value of sym.auth stored in eax is 0. When sym.auth does not return 0 the jne on line 74 is taken and the function is quit. If sym.auth returns 0 "Authenticated!" is printed and we get a shell (line 78-79).

Summing this up the main-function looks like this:

int main() {

  ...

  char username[32];
  unsigned int serial;

  puts("Enter username: ");
  fgets(username, 32, stdin);

  puts("Enter serial: ");
  scanf("%u", &serial);

  if (auth(username, serial) == 0) {

    puts("Authenticated!");
    system("/bin/sh");

  }

}

Thus we have to find a username / serial combination which makes auth return 0:

[0x08048880]> pdf @ sym.auth
╒ (fcn) sym.auth 309
│           ; arg int arg_1_1      @ ebp+0x5
│           ; arg int arg_2        @ ebp+0x8
│           ; arg int arg_3        @ ebp+0xc
│           ; var int local_3      @ ebp-0xc
│           ; var int local_4      @ ebp-0x10
│           ; var int local_5      @ ebp-0x14
│           ; CALL XREF from 0x08048c2d (sym.main)
│           ;-- sym.auth:
│           0x08048a0f    55             push ebp
│           0x08048a10    89e5           mov ebp, esp
│           0x08048a12    83ec28         sub esp, 0x28
│           0x08048a15    c7442404038d.  mov dword [esp + 4], 0x8048d03  ; [0x8048d03:4]=10
│           0x08048a1d    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a20    890424         mov dword [esp], eax
│           0x08048a23    e878fdffff     call sym.imp.strcspn           ; sub.strcspn_12_79c+0x4
│             ^- sub.strcspn_12_79c() ; sym.imp.strcspn
│           0x08048a28    8b5508         mov edx, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a2b    01d0           add eax, edx
│           0x08048a2d    c60000         mov byte [eax], 0
│           0x08048a30    c74424042000.  mov dword [esp + 4], 0x20      ; [0x20:4]=0x2168  ; "h!" 0x00000020 
│           0x08048a38    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a3b    890424         mov dword [esp], eax
│           0x08048a3e    e80dfeffff     call sym.imp.strnlen
│             ^- sym.imp.strnlen()
│           0x08048a43    8945f4         mov dword [ebp-local_3], eax
│           0x08048a46    50             push eax
│           0x08048a47    31c0           xor eax, eax
│       ┌─< 0x08048a49    7403           je 0x8048a4e                 
│       │   0x08048a4b    83c404         add esp, 4
│       └   ; JMP XREF from 0x08048a49 (sym.auth)
│       └─> 0x08048a4e    58             pop eax
│           0x08048a4f    837df405       cmp dword [ebp-local_3], 5     ; [0x5:4]=257
│      ┌──< 0x08048a53    7f0a           jg 0x8048a5f                 
│      │    0x08048a55    b801000000     mov eax, 1
│     ┌───< 0x08048a5a    e9e3000000     jmp 0x8048b42                
│     │└    ; JMP XREF from 0x08048a53 (sym.auth)
│     │└──> 0x08048a5f    c744240c0000.  mov dword [esp + 0xc], 0       ; [0xc:4]=0
│     │     0x08048a67    c74424080100.  mov dword [esp + 8], 1         ; [0x8:4]=0
│     │     0x08048a6f    c74424040000.  mov dword [esp + 4], 0         ; [0x4:4]=0x10101 
│     │     0x08048a77    c70424000000.  mov dword [esp], 0
│     │     0x08048a7e    e8edfdffff     call sym.imp.ptrace
│     │       ^- sym.imp.ptrace()
│     │     0x08048a83    83f8ff         cmp eax, 0xff
│    ┌────< 0x08048a86    752e           jne 0x8048ab6                
│    ││     0x08048a88    c70424088d04.  mov dword [esp], str._e_32m.___________________________.  ; [0x8048d08:4]=0x32335b1b  ; str._e_32m.___________________________.
│    ││     0x08048a8f    e87cfdffff     call sym.imp.puts
│    ││       ^- sym.imp.puts()
│    ││     0x08048a94    c704242c8d04.  mov dword [esp], str._e_31m_____TAMPERING_DETECTED______  ; [0x8048d2c:4]=0x31335b1b  ; str._e_31m_____TAMPERING_DETECTED______
│    ││     0x08048a9b    e870fdffff     call sym.imp.puts
│    ││       ^- sym.imp.puts()
│    ││     0x08048aa0    c70424508d04.  mov dword [esp], str._e_32m_____________________________  ; [0x8048d50:4]=0x32335b1b  ; str._e_32m_____________________________
│    ││     0x08048aa7    e864fdffff     call sym.imp.puts
│    ││       ^- sym.imp.puts()
│    ││     0x08048aac    b801000000     mov eax, 1
│   ┌─────< 0x08048ab1    e98c000000     jmp 0x8048b42                
│   │└      ; JMP XREF from 0x08048a86 (sym.auth)
│   │└────> 0x08048ab6    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│   │ │     0x08048ab9    83c003         add eax, 3
│   │ │     0x08048abc    0fb600         movzx eax, byte [eax]
│   │ │     0x08048abf    0fbec0         movsx eax, al
│   │ │     0x08048ac2    3537130000     xor eax, 0x1337
│   │ │     0x08048ac7    05eded5e00     add eax, 0x5eeded
│   │ │     0x08048acc    8945f0         mov dword [ebp-local_4], eax
│   │ │     0x08048acf    c745ec000000.  mov dword [ebp-local_5], 0
│  ┌──────< 0x08048ad6    eb4e           jmp 0x8048b26                
│           ; JMP XREF from 0x08048b2c (sym.auth)
│ ────────> 0x08048ad8    8b55ec         mov edx, dword [ebp-local_5]
│  ││ │     0x08048adb    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│  ││ │     0x08048ade    01d0           add eax, edx
│  ││ │     0x08048ae0    0fb600         movzx eax, byte [eax]
│  ││ │     0x08048ae3    3c1f           cmp al, 0x1f
│ ┌───────< 0x08048ae5    7f07           jg 0x8048aee                 
│ │││ │     0x08048ae7    b801000000     mov eax, 1
│ ────────< 0x08048aec    eb54           jmp 0x8048b42                
│ └         ; JMP XREF from 0x08048ae5 (sym.auth)
│ └───────> 0x08048aee    8b55ec         mov edx, dword [ebp-local_5]
│  ││ │     0x08048af1    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│  ││ │     0x08048af4    01d0           add eax, edx
│  ││ │     0x08048af6    0fb600         movzx eax, byte [eax]
│  ││ │     0x08048af9    0fbec0         movsx eax, al
│  ││ │     0x08048afc    3345f0         xor eax, dword [ebp-local_4]
│  ││ │     0x08048aff    89c1           mov ecx, eax
│  ││ │     0x08048b01    ba2b3b2388     mov edx, 0x88233b2b
│  ││ │     0x08048b06    89c8           mov eax, ecx
│  ││ │     0x08048b08    f7e2           mul edx
│  ││ │     0x08048b0a    89c8           mov eax, ecx
│  ││ │     0x08048b0c    29d0           sub eax, edx
│  ││ │     0x08048b0e    d1e8           shr eax, 1
│  ││ │     0x08048b10    01d0           add eax, edx
│  ││ │     0x08048b12    c1e80a         shr eax, 0xa
│  ││ │     0x08048b15    69c039050000   imul eax, eax, 0x539
│  ││ │     0x08048b1b    29c1           sub ecx, eax
│  ││ │     0x08048b1d    89c8           mov eax, ecx
│  ││ │     0x08048b1f    0145f0         add dword [ebp-local_4], eax
│  ││ │     0x08048b22    8345ec01       add dword [ebp-local_5], 1
│  └        ; JMP XREF from 0x08048ad6 (sym.auth)
│  └──────> 0x08048b26    8b45ec         mov eax, dword [ebp-local_5]
│   │ │     0x08048b29    3b45f4         cmp eax, dword [ebp-local_3]
│ ────────< 0x08048b2c    7caa           jl 0x8048ad8                 
│   │ │     0x08048b2e    8b450c         mov eax, dword [ebp + 0xc]     ; [0xc:4]=0
│   │ │     0x08048b31    3b45f0         cmp eax, dword [ebp-local_4]
│ ────────< 0x08048b34    7407           je 0x8048b3d                 
│   │ │     0x08048b36    b801000000     mov eax, 1
│   │ │     0x08048b3b    eb05           jmp 0x8048b42                
│           ; JMP XREF from 0x08048b34 (sym.auth)
│ ────────> 0x08048b3d    b800000000     mov eax, 0
│   └ └     ; JMP XREF from 0x08048b3b (sym.auth)
│   └ └     ; JMP XREF from 0x08048aec (sym.auth)
│   └ └     ; JMP XREF from 0x08048ab1 (sym.auth)
│   └ └     ; JMP XREF from 0x08048a5a (sym.auth)
│ ──└─└───> 0x08048b42    c9             leave
╘           0x08048b43    c3             ret

As we have already pointed out during the other levels, the arguments passed to the function are accessed with ebp+0x8 (username) and ebp+0xc (serial). On line 17 the function strcspn is called. This function takes two arguments. The first argument is a string, in which the first occurence of a character being part of the string passed as second argument is searched. In the call on line 17 the username (esp+0x8) and a string located at 0x8048d03 is passed (lines 14-16). Let’s examine what this string contains:

[0x08048880]> ps @ 0x8048d03


[0x08048880]>

Oh, seems not to be an alphanumeric character:

[0x08048880]> px 1 @ 0x8048d03
- offset -   0 1   2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x08048d03  0a                                        .

Okay, it is a newline (0xa). Let’s review what happens after the call to strcspn:

│           0x08048a28    8b5508         mov edx, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a2b    01d0           add eax, edx
│           0x08048a2d    c60000         mov byte [eax], 0

Because the username has been read using fgets there is a newline at the end of the string (fgets does not truncate the newline). The call to strcspn returns the index of this newline character in eax. On line 19 the address of the username-string is moved to edx, which is then added to eax (line 20). Thus eax points to the newline character within the string. On line 21 the newline character is overwritten with a null-byte. This way the newline character is truncated. After this there is a call to strnlen on line 25:

│           0x08048a30    c74424042000.  mov dword [esp + 4], 0x20      ; [0x20:4]=0x2168  ; "h!" 0x00000020 
│           0x08048a38    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│           0x08048a3b    890424         mov dword [esp], eax
│           0x08048a3e    e80dfeffff     call sym.imp.strnlen
│           0x08048a3e    e80dfeffff     call sym.imp.strnlen
│             ^- sym.imp.strnlen()
│           0x08048a43    8945f4         mov dword [ebp-local_3], eax

The passed arguments are the username-string (ebp+0x8) and a max-length of 0x20 (32). The return-value of the call (eax) is stored in a local variable (ebp-local_3). This value is compared to 5 on line 34:

│           0x08048a4f    837df405       cmp dword [ebp-local_3], 5     ; [0x5:4]=257
│      ┌──< 0x08048a53    7f0a           jg 0x8048a5f                 
│      │    0x08048a55    b801000000     mov eax, 1
│     ┌───< 0x08048a5a    e9e3000000     jmp 0x8048b42                
│     │└    ; JMP XREF from 0x08048a53 (sym.auth)

If the string length is less than or equal to 5, the jg on line 35 is not taken, 1 is moved to eax (line 36) and the function is quit on line 37 (0x8048b42 is the address of the function’s epilogue). Remember that the return value is stored in eax and we want to achieve that the function returns 0 and not 1. If the string length is greater than 5 the code execution proceeds on line 39:

│     │└──> 0x08048a5f    c744240c0000.  mov dword [esp + 0xc], 0       ; [0xc:4]=0
│     │     0x08048a67    c74424080100.  mov dword [esp + 8], 1         ; [0x8:4]=0
│     │     0x08048a6f    c74424040000.  mov dword [esp + 4], 0         ; [0x4:4]=0x10101 
│     │     0x08048a77    c70424000000.  mov dword [esp], 0
│     │     0x08048a7e    e8edfdffff     call sym.imp.ptrace
│     │       ^- sym.imp.ptrace()
│     │     0x08048a83    83f8ff         cmp eax, 0xff
│    ┌────< 0x08048a86    752e           jne 0x8048ab6                
│    ││     0x08048a88    c70424088d04.  mov dword [esp], str._e_32m.___________________________.  ; [0x8048d08:4]=0x32335b1b  ; str._e_32m.___________________________.
│    ││     0x08048a8f    e87cfdffff     call sym.imp.puts
│    ││       ^- sym.imp.puts()
│    ││     0x08048a94    c704242c8d04.  mov dword [esp], str._e_31m_____TAMPERING_DETECTED______  ; [0x8048d2c:4]=0x31335b1b  ; str._e_31m_____TAMPERING_DETECTED______
│    ││     0x08048a9b    e870fdffff     call sym.imp.puts
│    ││       ^- sym.imp.puts()
│    ││     0x08048aa0    c70424508d04.  mov dword [esp], str._e_32m_____________________________  ; [0x8048d50:4]=0x32335b1b  ; str._e_32m_____________________________
│    ││     0x08048aa7    e864fdffff     call sym.imp.puts
│    ││       ^- sym.imp.puts()
│    ││     0x08048aac    b801000000     mov eax, 1
│   ┌─────< 0x08048ab1    e98c000000     jmp 0x8048b42                  

On line 43 there is a call to ptrace which is a common anti-debugging technique. A debugger typically uses ptrace to attach to a process. Since there can only be one attached process at a time, the ptrace call will fail if there is already a debugger attached. In this case the function would return 0xff and the jne on line 46 would not be taken. This would lead to the ouput "TAMPERING DETECTED" followed by a jump to the function epilogue returning 1 (line 56-57). To bypass this we could copy the binary and patch the code with NOPs (0x90). For learning purpose we proceed to statically reverse the binary:

│   │└────> 0x08048ab6    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│   │ │     0x08048ab9    83c003         add eax, 3
│   │ │     0x08048abc    0fb600         movzx eax, byte [eax]
│   │ │     0x08048abf    0fbec0         movsx eax, al
│   │ │     0x08048ac2    3537130000     xor eax, 0x1337
│   │ │     0x08048ac7    05eded5e00     add eax, 0x5eeded
│   │ │     0x08048acc    8945f0         mov dword [ebp-local_4], eax
│   │ │     0x08048acf    c745ec000000.  mov dword [ebp-local_5], 0      

On line 59 the address of the username string is moved to eax. On the next line (60) the value 3 is added and the resulting address is used as a byte pointer, which targeted byte is moved to eax using the instruction movzx. This instructions is called move with zero-extend meaning that the smaller value (in this case 1 byte) is moved to eax (4 byte) and all upper bytes are set to 0. The next instruction on line 62 is called move with sign-extension and is quite equal to movzx. The difference is that the upper bytes are not filled with 0 but with the sign-extension of the source. Since we are dealing with an ASCII string which bytes range from 0x00 to 0x7f the sign-extension is always 0. On line 63 the value stored in eax (3rd byte of the username string) is XORed with 0x1337 and 0x5eeded is added on line 64. The result is stored in a local variable (ebp-local_4). Another local variable (ebp-local_5) is set to 0. Time to quickly reassemble our function code so far:

int auth(char *username, unsigned int serial) {

  int idx = strcspn(username, '\n');
  username[idx] = 0x00;

  int len = strnlen(username, 32);
  if (len <= 5) return 1;

  if (ptrace(0, 0, 1, 0) == 0xff) {
    puts("TAMPERING DETECTED!");
    return 1;
  }

  unsigned int v1 = (username[3] ^ 0x1337) + 0x5eeded;

  int i = 0;

  ...

}

The assembly proceeds on line 67:

│  ┌──────< 0x08048ad6    eb4e           jmp 0x8048b26                
│           ; JMP XREF from 0x08048b2c (sym.auth)
│ ────────> 0x08048ad8    8b55ec         mov edx, dword [ebp-local_5]
│  ││ │     0x08048adb    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│  ││ │     0x08048ade    01d0           add eax, edx
│  ││ │     0x08048ae0    0fb600         movzx eax, byte [eax]
│  ││ │     0x08048ae3    3c1f           cmp al, 0x1f
│ ┌───────< 0x08048ae5    7f07           jg 0x8048aee                 
│ │││ │     0x08048ae7    b801000000     mov eax, 1
│ ────────< 0x08048aec    eb54           jmp 0x8048b42                
│ └         ; JMP XREF from 0x08048ae5 (sym.auth)
│ └───────> 0x08048aee    8b55ec         mov edx, dword [ebp-local_5]
│  ││ │     0x08048af1    8b4508         mov eax, dword [ebp + 8]       ; [0x8:4]=0
│  ││ │     0x08048af4    01d0           add eax, edx
│  ││ │     0x08048af6    0fb600         movzx eax, byte [eax]
│  ││ │     0x08048af9    0fbec0         movsx eax, al
│  ││ │     0x08048afc    3345f0         xor eax, dword [ebp-local_4]
│  ││ │     0x08048aff    89c1           mov ecx, eax
│  ││ │     0x08048b01    ba2b3b2388     mov edx, 0x88233b2b
│  ││ │     0x08048b06    89c8           mov eax, ecx
│  ││ │     0x08048b08    f7e2           mul edx
│  ││ │     0x08048b0a    89c8           mov eax, ecx
│  ││ │     0x08048b0c    29d0           sub eax, edx
│  ││ │     0x08048b0e    d1e8           shr eax, 1
│  ││ │     0x08048b10    01d0           add eax, edx
│  ││ │     0x08048b12    c1e80a         shr eax, 0xa
│  ││ │     0x08048b15    69c039050000   imul eax, eax, 0x539
│  ││ │     0x08048b1b    29c1           sub ecx, eax
│  ││ │     0x08048b1d    89c8           mov eax, ecx
│  ││ │     0x08048b1f    0145f0         add dword [ebp-local_4], eax
│  ││ │     0x08048b22    8345ec01       add dword [ebp-local_5], 1
│  └        ; JMP XREF from 0x08048ad6 (sym.auth)
│  └──────> 0x08048b26    8b45ec         mov eax, dword [ebp-local_5]
│   │ │     0x08048b29    3b45f4         cmp eax, dword [ebp-local_3]
│ ────────< 0x08048b2c    7caa           jl 0x8048ad8                              

On line 67 there is a jmp to the end of the output above (line 99). On lines 99-101 the local variables ebp-local_5 and ebp-local_3 are compared. If ebp-local_5 is less than ebp-local_3 the jl to line 69 is taken. This is simply a for-loop:

  ...

  for (int i = 0; i < len; i++) {

    ...

  }

In the loop-body the character indexed by ebp-local_5 is moved to eax (lines 69-72) and compared to 0x1f (line 73). If the value is less than or equal to 0x1f the function returns 1. This way control-characters within the username are obviated.

In lines 78-82 the character indexed by ebp-local_5 is moved to eax again. Then on line 83 the character is XORed with the local variable ebp-local_4, which contains the previously manipulated 3rd character. In lines 84-95 follow a few more mathematical operations and moves. One instruction which could need some explanation is mul (line 88). While you can guess that mul stands for multiply, you are may wondering why there is only one operand (edx). The reason is that mul implicitly uses the eax register. The operand is multiplied by eax and the result is stored in eax and the operand register because the product of two 32-bit registers may exceed 32-bit. The lower bits are stored in eax and the upper bits are stored in the operand register. Taking this into account the reversed loop looks like this:

  ...

  for (int i = 0; i < len; i++) {
  
    unsigned int v2, v3, v4;

    if (username[i] <= 0x1f) return 1;
    v2 = username[i];
    v2 ^= v1;
    v3 = (0x88233b2b * v2) >> 32;
    v4 = v2 - v3;
    v4 = v4 >> 1;
    v4 += v3;
    v4 = v4 >> 10;
    v4 *= 0x539;
    v4 = v2 - v4;

    v1 += v4;
	
  }

As you may have noticed only the upper bits of the mul instruction on line 87 are actually used. In order to get those upper bits we can simply right-shift the product 32 bits. The other instructions should be already familiar.

Right after the loop there are only a few lines left:

│   │ │     0x08048b2e    8b450c         mov eax, dword [ebp + 0xc]     ; [0xc:4]=0
│   │ │     0x08048b31    3b45f0         cmp eax, dword [ebp-local_4]
│ ────────< 0x08048b34    7407           je 0x8048b3d                 
│   │ │     0x08048b36    b801000000     mov eax, 1
│   │ │     0x08048b3b    eb05           jmp 0x8048b42                
│           ; JMP XREF from 0x08048b34 (sym.auth)
│ ────────> 0x08048b3d    b800000000     mov eax, 0
│   └ └     ; JMP XREF from 0x08048b3b (sym.auth)
│   └ └     ; JMP XREF from 0x08048aec (sym.auth)
│   └ └     ; JMP XREF from 0x08048ab1 (sym.auth)
│   └ └     ; JMP XREF from 0x08048a5a (sym.auth)
│ ──└─└───> 0x08048b42    c9             leave
╘           0x08048b43    c3             ret

The comparison on line 103 tests if ebp+0xc (the serial entered) and ebp-local_4 (the calculated serial) match. Depending on the result of this comparison the function returns 1 (failed) or 0 (match).

With the reversed function fragments we can now write a little key-generator:

#include <stdio.h>
#include <stdint.h>

int genKey(char *username) {
  
  int idx = strcspn(username, "\n");
  username[idx] = 0x00;
  
  int len = strnlen(username, 32);
  if (len <= 5) return 0;
  
  unsigned int v1 = (username[3] ^ 0x1337) + 0x5eeded;

  int i = 0;  

  for (i = 0; i < len; i++) {
   
    unsigned int v2, v3, v4;
 
    if (username[i] <= 0x1f) return 0;
    v2 = username[i];
    v2 ^= v1;
    v3 = (uint64_t)v2*0x88233b2b >> 32;
    v4 = v2 - v3;
    v4 = v4 >> 1;
    v4 += v3;
    v4 = v4 >> 10;
    v4 *= 0x539;
    v4 = v2 - v4;
     
    v1 += v4;
    v1 &= 0xffffffff;
     
  }
   
  return v1;
}
 
 
int main() {
  
  char username[32];
  unsigned int serial;
  
  printf("Enter username: ");
  fgets(username, 32, stdin);
   
  serial = genKey(username);
  if (serial > 0) printf("serial: %u\n", serial);
  
}

Now we can generate your own serial:

root@kali:~$ gcc lab1A.c -o lab1A
root@kali:~$ ./lab1A
Enter username: dummyuser
serial: 6235758

And enter it to get a shell:

lab1A@warzone:/levels/lab01$ ./lab1A
.---------------------------.
|---------  RPISEC  --------|
|+ SECURE LOGIN SYS v. 3.0 +|
|---------------------------|
|~- Enter your Username:  ~-|
'---------------------------'
dummyuser
.---------------------------.
| !! NEW ACCOUNT DETECTED !!|
|---------------------------|
|~- Input your serial:    ~-|
'---------------------------'
6235758
Authenticated!
$ whoami
lab1end
$ cat /home/lab1end/.pass
1uCKy_Gue55

Done 🙂 The final password is 1uCKy_Gue55.

7 Replies to “RPISEC/MBE: writeup lab01 (Reverse Engineering)”

  1. Great article, you are by far one of the best and most detailed I have found, in a very refreshing way. Love your work, keep it up.

  2. Hey, nice writeup! But I’m a bit confused about lab1A, with code
    v3 = (uint64_t)v2*0x88233b2b >> 32;
    Why do we need a forced type conversion to uint64_t here to get correct answer?

    1. Hey Smile,
      thanks for dropping a comment. The type-cast is indeed confusing. It’s been a while, but I assume that it was necessary for the compiler to carry out the multiplication. When multiplying two 32-bit integers there might be an overflow (exceeding 32-bit), which can be taken into account by storing the result in a 64-bit integer (uint64_t).

Comments are closed.