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
.
Very detailed, I’ve learned a lot things from your articles. thanks a lot.
Thanks, I am glad to hear that 🙂
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.
Thank you! Great to hear that 🙂
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?
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).
that makes sense! thanks a lot 🙂