0xLaugh Rev Challenges ======================= nano ----- nano is an elf 64bit file so we will use linux with ida in analyzing it It comes with simple antidisassembly as discussed in PMA lab15 we can replace the bytes from 134E to 1352 with NOPs replace 74 03 75 01 E8 with 90's .. image:: Screenshot_1.png .. code-block:: python3 fixed= open("nano", "rb").read().replace(b"\x74\x03\x75\x01\xe8", b"\x90\x90\x90\x90\x90") open("cleaned_nano","wb").write(fixed) first is a cheesy check function .. image:: Screenshot_2.png it is XOR and we are given key and flag and if we xor we get the flag? .. image:: Screenshot_4.png No we get watch a rickroll video .. image:: Screenshot_5.png Looking again at ida we see it attempts to read from address zero with gives SIGSEGV (segmentation fault) .. image:: Screenshot_6.png Which we get when we run gdb-peda with breaking at check function `break check` .. image:: Screenshot_7.png Looking at ida, note that the check happens in a child process .. image:: Screenshot_8.png And the parent process catches that exception using waitpid status loc, if there is error break and if child exited with specific status do the following The status is WTERMSIG(status) which checks SEGSEGV from `check2 `_ .. image:: Screenshot_9.png it calculates some values (note pid[1] is just zero) the calls a ptrace syscall which has ability to trace a pid and get registers and set registers .. image:: Screenshot_10.png .. image:: Screenshot_11.png following same or'ed value in ptrace calls we see the value is set r12 .. image:: Screenshot_12.png .. image:: Screenshot_13.png Which is the register that contains our KEY, so we write a python script to replicate the replacing of KEY bytes then xor it with the FLAG .. code-block:: python3 import pwn key="" flag = "0C 5C 60 20 69 63 64 0F 4F 1E 33 3A 68 2A 7C D9 D5 D0 C9 E7 C3 F0 BC AB 9B D7 98 8B AF B0 F8 47 49 16 49 68" for i in range(1,37): v9 = (i>>5)|(8*i)^0xCA v9 ^=0xFE v9 &=0xFF #to constrain for 8 bit key =key +format(int(str(hex(v9))[2:],16),"02X") + " " print(pwn.xor(bytes.fromhex(key),bytes.fromhex(flag))) .. image:: Screenshot_15.png easy login ---------- We are given two files, both are elf 64bit .. image:: Screenshot_16.png in the easy login binary we get user pass token .. image:: Screenshot_17.png so we check the other binary .. image:: Screenshot_18.png | In ida we see set our goal to get final `Correct tokan!` | So first we see the first token part (v4) and fixed byte array is passed to sub 1159 which changes it | Then check token parts to if ( v4 == 4068142527 && v5 == 3976246892 ) .. image:: Screenshot_19.png inside the function is bunch of calcs then set in our first token part, so we reverse the process with final value being if ( v4 == 4068142527 && v5 == 3976246892 ) .. image:: Screenshot_20.png .. code-block:: c++ #include unsigned int reverse_sub_6519F8FD0159(unsigned int *a1, unsigned int *a2) { unsigned int v4 = 3337565984; int i; printf("%u",a1[1]); for (i = 31; i >= 0; --i) { a1[1] -= ((*a1 >> 5) + a2[3]) ^ (*a1 + v4) ^ (16 * *a1 + a2[2]); v4 += 1640531527; *a1 -= (a1[1] + v4) ^ (16 * a1[1] + *a2) ^ ((a1[1] >> 5) + a2[1]); } return *a1; } int main() { unsigned int v4 = 3337565984; unsigned int v5 = 0xED00B66C; unsigned int a1 = 0xF27AEDBF; unsigned int a2[] = {19088743, 2309737967, 4275878552, 1985229328}; v4 = reverse_sub_6519F8FD0159(&a1, a2); puts("Token generation result:"); printf("%u_%u\n", v4 , v5 ); return 0; } | Now we got the token we still need the user and pass, back to easy login binary | we get this code, where the user isn't used, and the pass and token are passed to function then onto second function which base64 .. image:: Screenshot_21.png Which is then compared to `pDG/SbSehGM2l16sRzFmxRDZNCti2PNXzY9Z` .. image:: Screenshot_23.png .. image:: Screenshot_22.png opening the first function used we see it is a typical RC4 To recognize RC4 use tools like capa ,sigsrch or its pattern as following:- | There’s usually 3 loops | 2x 0x100 iterations: one filling the buffer with values from 0 to ff | One going over the array swapping values | And a 3rd producing the keystream | It is a very recognisable pattern .. image:: Screenshot_24.png .. image:: Screenshot_25.png then we use the token as rc4 key and we get the flag .. image:: Screenshot_26.png .. image:: Screenshot_27.png dance ------ It also has antidisassembly so we will use same code .. code-block:: python3 fixed= open("dance", "rb").read().replace(b"\x74\x03\x75\x01\xe8", b"\x90\x90\x90\x90\x90") open("cleaned_dance","wb").write(fixed) it will pass our argument (flag) to function sub_13C7 to a child process with the parent ready to handle exception with ptrace, another nanomite .. image:: Screenshot_29.png there is a lot of functions, so running capa we find crc32 is used at 2511 .. image:: Screenshot_30.png .. image:: Screenshot_31.png going into 13C7 we first need to fix ptrace argument to know where it set regs using ida enum .. image:: Screenshot_32.png and where it is setting registers .. image:: Screenshot_33.png | another fork, in the child starts with ptrace traceme we can expect exception handling here | So what happens here is , it creates a file descriptor then passes "Hello, that is one key for you.." and `nice_move_:)` to sub_23FA which then passed to sub_246D through v3 .. image:: Screenshot_34.png Going into sub_246D, its chacha! (i used chatgpt here to recoginze the algorithm xD) .. image:: Screenshot_35.png googling its implementation `check `_ | we see it is first initialized with key 2nd arg , nonce 3rd arg .. code-block:: c ChaChaInitialize(&chaInfo, key, nonce, &counter, NUMROUNDS); Then call the encryption/decryption .. code-block:: c ChaChaEncrypt(&chaInfo, plainBufferLen, buffer); and we got the clear picture .. image:: Screenshot_36.png Going to 50C0 address and getting the data .. image:: Screenshot_37.png and we get an elf file .. image:: Screenshot_38.png the elf file contains a lot of cc (int 3 ) which will cause sigtrap and that where the ptrace comes in from the parent .. image:: Screenshot_39.png back to the parent, we get this beutiful code where it waits for the exception cc to happen .. image:: Screenshot_40.png lets dissect it, first check v19 & v18 and function sub_1255 with passed third argument bunch of cc .. image:: Screenshot_42.png | checking sub_1255, we see it uses PTRACE_POKEDATA which from `check_here `_ `Copy the word data to the address addr in the tracee's memory.` | So in the loop it starts from address a2 which is v18 that was passed till a4 which is v19 and writes bunch of CC's .. image:: Screenshot_43.png | we can infer that this is a write to pid function that write data from given address to another address, it is overwriting the previous address with CC's | it is antidebugging technique so we can't dump the written data it is overwriting instructions as it goes .. image:: Screenshot_44.png Then it calls ptrace with GETREGS then calls crc32 .. image:: Screenshot_45.png | we need to calculate what is gotten with ptrace, so we know the ptrace GETREGS call's last argument is a registery structure .. code-block:: c ptrace(PTRACE_GETREGS, child, NULL, ®s); | we get its address relative to stack in assembly .. image:: Screenshot_46.png | The RVA to rbp of registry structure is 544, then after the call to ptrace it get rbp-416 | the differene is 544-416=128, and each value is 8 byte (64 bit), 128/8 =16, so it got the registery at offset 16 | to get what resides at that offset run .. code-block:: bash cat /usr/include/x86_64-linux-gnu/sys/reg.h and we get the rip registery .. image:: Screenshot_47.png | Now we can get the full picture, it gets the rip where the exception happened masks it with 0xFFF, runs crc32 to it | Then the index starting from 0 , the loop will search for the correct index where ** v14 != *(_DWORD *)&unk_88A0[24 * some_index]** Condition is not true where v14 = crc32(rip) , then write data using that index offset from 88A0 , with size (last argument) into where the rip points to .. image:: Screenshot_48.png set 88A0 type to `unsigned __int8[16512]` to fix the indexing, also fixing my mistake of curr_address to size .. image:: Screenshot_49.png | To understand the 88A0 structure, if we set some_index to zero from `v14 != *(_DWORD *)&byte_88A0[24 * some_index]` , | we get that v14 (crc32(rip)) is first element in 88A0 structure, and we get its size is 4 bytes from this `writeToPID(pid, rip_reg - 1, &byte_88A0[24 * some_index + 5], byte_88A0[24 * some_index + 4]);` As next element is after 4 bytes then after 5 bytes | so The size to be written is at after 4 bytes and to be written is after 5 bytes .. image:: Screenshot_50.png so 41 57 is an instruction which is push r15 .. image:: Screenshot_51.png we got all data we need, we just need to write code to get this 88A0 fixes the order of instructions and replace those with the CC's in the elf file we got .. code-block:: python3 import struct import io from zlib import crc32 from pwn import u64, p32 file= open("cleaned_dance", "rb") file.seek(0x78a0) crc_table = {} table_88a0 = {} #Rainbow Table for crc32(rip) for i in range(0xfff+1): crc_table[crc32(p32(i))] = i #>>> p32(5) # b'\x05\x00\x00\x00' to bytes while 1: # "<" indicates little-endian byte order. This means the least significant byte is stored first in memory. # I represents an unsigned integer of 4 bytes. # B represents an unsigned char of 1 byte. # 19B represents 19 unsigned chars (each of 1 byte) # intotal 24 where each part is 24 bytes &byte_88A0[24 * some_index + 5] code =struct.unpack("