# Rootme bytecode rev writeups ## PE DotNet - Basic Crackme Throw in dnspy and find the following piece of code, which write `bytes2` to a file and then execute it: ```csharp File.WriteAllBytes(string.Concat(new string[] { CS$<>8__locals1.CurrentDirectoryy[0], "\\", CS$<>8__locals1.CurrentDirectoryy[1], "\\", Environment.UserName, "\\", CS$<>8__locals1.CurrentDirectoryy[2], "\\", CS$<>8__locals1.CurrentDirectoryy[3], "\\", CS$<>8__locals1.CurrentDirectoryy[4], "\\", CS$<>8__locals1.fileName, CS$<>8__locals1.CurrentDirectoryy[5] }), bytes2); Process process = new Process(); process.Exited += CS$<>8__locals1.<Main>g__p_Exited|0; process.StartInfo.FileName = string.Concat(new string[] { CS$<>8__locals1.CurrentDirectoryy[0], "\\", CS$<>8__locals1.CurrentDirectoryy[1], "\\", Environment.UserName, "\\", CS$<>8__locals1.CurrentDirectoryy[2], "\\", CS$<>8__locals1.CurrentDirectoryy[3], "\\", CS$<>8__locals1.CurrentDirectoryy[4], "\\", CS$<>8__locals1.fileName, CS$<>8__locals1.CurrentDirectoryy[5] }); process.EnableRaisingEvents = true; process.Start(); ``` Let's set a breakpoint after writing to the file, extract it and throw it into dnspy. We will see exactly the same code, so we will do this procedure again. Once again we throw the decrypted exe into dnspy and find the nonobfuscated gui code. In the form code, we find the flag comparison algorithm: ```csharp private void Button1_Click(object sender, EventArgs e) { bool flag = Operators.CompareString(this.TextBox1.Text, "W0wD0tNetREveRsE3asy", false) == 0; if (flag) { Interaction.MsgBox("Bien joué ! Vous pouvez valider le challenge avec ce mot de passe", MsgBoxStyle.OkOnly, null); } else { Interaction.MsgBox("Bad hacker", MsgBoxStyle.OkOnly, null); } } ``` ## Lua - Bytecode For decompilation, I used [luadec](https://github.com/viruscamp/luadec) (outputs cleaner code, but more artifacts) and [unluac](https://sourceforge.net/projects/unluac/). Decompile with luadec: ```lua check = function(l_1_0) start_array = {} if (string.len)(l_1_0) ~= 23 then return false end for l_1_4 = 1, #l_1_0 do start_array[l_1_4] = (string.byte)(l_1_0:sub(l_1_4, l_1_4)) end local l_1_5 = {} -- DECOMPILER ERROR at PC48: No list found for R1 , SetList fails -- DECOMPILER ERROR at PC51: Overwrote pending register: R2 in 'AssignReg' -- DECOMPILER ERROR at PC52: Overwrote pending register: R3 in 'AssignReg' -- ... end_array, l_1_5 = l_1_5, {33, 1, 84, -104, -65, 46, -28, -49, 15, 110, -18, 40, -59, -25, 0, 22, -46, -88, -33, -13, 88, -50, 129} l_1_5 = 1 iterator = l_1_5 while 1 do l_1_5 = iterator if l_1_5 <= (string.len)(l_1_0) then l_1_5 = iterator l_1_5 = l_1_5 % 2 if l_1_5 == 1 then l_1_5 = start_array l_1_5 = l_1_5[iterator] l_1_5 = l_1_5 + shift_array[iterator] if l_1_5 ~= end_array[iterator] then l_1_5 = false return l_1_5 end else l_1_5 = start_array l_1_5 = l_1_5[iterator] l_1_5 = l_1_5 - shift_array[iterator] if l_1_5 ~= end_array[iterator] then l_1_5 = false return l_1_5 end end l_1_5 = iterator l_1_5 = l_1_5 + 1 iterator = l_1_5 end l_1_5 = true return l_1_5 end print("Welcome to the Lua Crackme Challenge! Find the correct password to validate the challenge:") s = (io.read)("*l") if check(s) then print("Good Game! You can validate the challenge with this password :-)") else print("nope, wrong password!") end ``` Sometimes luadec has problems like this: ```lua local l_1_5 = {} -- DECOMPILER ERROR at PC48: No list found for R1 , SetList fails -- DECOMPILER ERROR at PC51: Overwrote pending register: R2 in 'AssignReg' -- DECOMPILER ERROR at PC52: Overwrote pending register: R3 in 'AssignReg' -- ... end_array, l_1_5 = l_1_5, {33, 1, 84, -104, -65, 46, -28, -49, 15, 110, -18, 40, -59, -25, 0, 22, -46, -88, -33, -13, 88, -50, 129} ``` This is where unluac comes to the rescue, which does not compress the creation of the array: ![](https://i.imgur.com/d5dH3aE.png) From here we extract the contents of the array and a new name for it - `shift_array` Using the knowledge gained, reduce code even more: ```lua function check(input) local start_array = {} if string.len(input) ~= 23 then return false end for i = 1, #input do start_array[i] = (string.byte)(input:sub(i, i)) -- input[i] end local end_array = {99, 96, 192, 201, 45, 53, 73, 144, 99, 1, 92, 55, 22, 142, 111, 89, 33, 199, 78, 92, 167, 155, 162} local shift_array = {33, 1, 84, -104, -65, 46, -28, -49, 15, 110, -18, 40, -59, -25, 0, 22, -46, -88, -33, -13, 88, -50, 129} local iterator = 1 while 1 do if iterator<= (string.len)(input) then if iterator % 2 == 1 then if start_array[iterator] + shift_array[iterator] ~= end_array[iterator] then return false end else if start_array[iterator] - shift_array[iterator] ~= end_array[iterator] then return false end end iterator += 1 end end return true end print("Welcome to the Lua Crackme Challenge! Find the correct password to validate the challenge:") s = io.read("*l") if check(s) then print("Good Game! You can validate the challenge with this password :-)") else print("nope, wrong password!") end ``` Remains to solve the `check`: ```python flag = "" end_array = [99, 96, 192, 201, 45, 53, 73, 144, 99, 1, 92, 55, 22, 142, 111, 89, 33, 199, 78, 92, 167, 155, 162] shift_array = [33, 1, 84, -104, -65, 46, -28, -49, 15, 110, -18, 40, -59, -25, 0, 22, -46, -88, -33, -13, 88, -50, 129] for i in range(1,24): if i%2 == 1: char = end_array[i-1] - shift_array[i-1] else: char = end_array[i-1] + shift_array[i-1] flag += chr(char) print(flag) ``` ## Bash VM We have a code of the kind ```bash {$'\x65\x76\x61\x6c',$'\x48\x3d\x70\x72\x69\x6e\x74\x66'}&&$H '\e[30m\n'&& [[ $- =~ x ]] && x=1; X=$x $'\x62\141's${-:$(()):01} -c "$($'\x65\x63\x68\x6f' -e \\$#${##}$((${##}....#}))" ``` Let's deobfuscate: ```bash {$\'eval\',$\'H=printf\'}&&$H \'\\e[30m\n\'&& [[ $- =~ x ]] && x=1; X=$x bash -c "$($\'echo\' -e \\$#${#...}###})" ``` The script tries to execute sequence of `$#{(` via `bash -c "echo -e '$#{(...#})'"`. Let's try to decrypt it with `echo -e`: ```bash [[ $X ]] && exit; echo -en "\e[0m"; M=( 42 29978 47 59 44 213 42 219 40 59 57 42 4058 40 59 12 59 21 56 29 42 40 41 43 40 3 41 44 41 42 32 57 56 59 57 2 32 56 37 40 0 43 44 43 43 35 22 41 43 42 40 43 40 43 41 44 41 37 32 2 58 40 56 37 40 43 40 43 40 43 40 43 40 43 40 43 40 43 40 43 40 43 40 43 40 29 42 40 41 44 41 117 32 203 43 40 29 42 40 41 44 41 72 32 203 43 40 29 42 40 41 44 41 30 32 203 43 40 29 42 40 41 44 41 89 32 203 58 40 29 42 40 41 44 41 67 32 203 43 40 29 42 40 41 44 41 117 32 203 43 40 29 42 40 41 44 41 70 32 203 42 42 46 43 46 43 40 29 42 40 41 44 41 26 32 203 44 46 47 32 137 43 40 29 42 40 41 44 41 92 32 203 43 40 29 42 40 41 44 41 25 32 203 43 40 43 40 43 40 43 40 43 40 29 42 40 41 44 41 98 32 203 59 195 56 2 32 42 280 40 59 11 213 2 32 42 297 40 59 11 56 111 68 94 79 88 10 90 75 89 89 93 69 88 78 10 16 10 42 124 109 109 10 16 10 83 69 95 10 73 75 68 10 95 89 79 10 94 66 79 10 90 75 89 89 93 69 88 78 10 94 69 10 92 75 70 67 78 75 94 79 10 16 7 3 42 126 88 83 10 66 75 88 78 79 88 10 68 26 26 72 11 11 11 0 30000 5 17 332 17 0 255 0 0 4 0 324 3 0 42 2 55 0 4 1 43 1 2 3 1 0 4 1 4 6 4 324 10 341 18 ); ((P=0x144)); {$'\x65\x76\x61\x6c',$'\x6a\x3d\x27\x70\x72\x69\x6e\x74\x66\x20\x27'}; # {$'eval',$'j='printf ''}; # $j = printf o="$j %d '"; p="%03o "; function _0 {((R[M[P+2]]=M[P+1],P+=3));}; function _1 {((R[M[P+1]]++,P+=2));}; function _2 {((R[M[p+1]]=M[P+2],P+=3));}; function _22 {((R[M[p+2]]=R[M[P+1]],P+=3));}; function _3 {((g=M[P+2]+R[M[P+3]],M[g]=R[M[P+1]],P+=4));}; function _55 {((R[M[P+3]]=M[M[P+1]+R[M[P+2]]],P+=4));}; function _6 {((y=R[M[P+1]],z=M[P+2],P+=3));}; function _9 {((y<z))&&((P=M[P+1]))||((P+=2));}; function _10 {((y!=z))&&((P=M[P+1]))||((P+=2));}; function _17 {((R[5]--,M[R[5]]=P+2,P=M[P+1]));}; function _18 {((P=M[R[5]],R[5]++));}; function _14 {((R[5]--,M[R[5]]=M[P+1],P+=2));}; function _15 {((R[5]--,M[R[5]]=R[M[P+1]],P+=2));}; function _16 {((R[M[P+1]]=M[R[5]],R[5]++,P+=2));}; function _40 {(());$j\\$($j$p${M[P+1]});((P+=2));}; function _41 {(());$j\\$($j$p${R[M[P+1]]});((P+=2));}; function _42 {(());{read,-n1,t};R[M[P+1]]=$(${o}${t});((P+=2));}; function _43 {((R[M[P+1]]^=R[M[P+2]],P+=3));}; function _44 {((g=M[P+2]+R[M[P+3]],R[M+1]^=M[g],P+=4));}; function _255 {(());exit;}; while ((1)); do _${M[P]}; done ``` Extract the following from the code: ``` M - mutable bytecode R[x] - Rx register P - intruction pointer, rip y,z,g - special registers _${M[P]} - vm dispatcher ``` Let's analyze vm handlers: ```bash function _0 {((R[M[P+2]]=M[P+1],P+=3));}; # R[M[P+2]] = M[P+1] function _1 {((R[M[P+1]]++,P+=2));}; # R[M[P+1]] += 1 function _2 {((R[M[p+1]]=M[P+2],P+=3));}; # R[M[p+1]] = M[P+2] function _22 {((R[M[p+2]]=R[M[P+1]],P+=3));}; # R[M[p+2]] = R[M[P+1]] function _3 {((g=M[P+2]+R[M[P+3]],M[g]=R[M[P+1]],P+=4));}; # g= M[P+2] + R[M[P+3]] ; M[g] = R[M[P+1]] function _55 {((R[M[P+3]]=M[M[P+1]+R[M[P+2]]],P+=4));}; # R[M[P+3]] = M[ M[P+1] + R[M[P+2]] ] function _6 {((y=R[M[P+1]],z=M[P+2],P+=3));}; # y= R[M[P+1]]; z = M[P+2] function _9 {((y<z))&&((P=M[P+1]))||((P+=2));}; # JL # JMP to M[P+1] if y<z function _10 {((y!=z))&&((P=M[P+1]))||((P+=2));}; # JNE function _17 {((R[5]--,M[R[5]]=P+2,P=M[P+1]));}; # R[5]-- ; M[R[5]] = P+2; P=M[P+1] # JMP M[P+1] function _18 {((P=M[R[5]],R[5]++));}; # P=M[R[5]] ; R[5]++ # JMP M[R[5]] function _14 {((R[5]--,M[R[5]]=M[P+1],P+=2));}; # R[5]-- ; M[R[5]] = M[P+1] function _15 {((R[5]--,M[R[5]]=R[M[P+1]],P+=2));}; # R[5]-- ; M[R[5]] = R[M[P+1]] function _16 {((R[M[P+1]]=M[R[5]],R[5]++,P+=2));}; # R[M[P+1]] = M[R[5]]; R[5]++ function _40 {(());$j\\$($j$p${M[P+1]});((P+=2));}; # print( M[P+1] ) function _41 {(());$j\\$($j$p${R[M[P+1]]});((P+=2));}; # print( R[M[P+1]] ) function _42 {(());{read,-n1,t};R[M[P+1]]=$(${o}${t});((P+=2));}; # R[M[P+1]] = getchar() function _43 {((R[M[P+1]]^=R[M[P+2]],P+=3));}; # R[M[P+1]] ^= R[M[P+2]] # SOME_XOR function _44 {((g=M[P+2]+R[M[P+3]],R[M+1]^=M[g],P+=4));}; # g = M[P+2] + R[M[P+3] ; R[M+1] ^= M[g] # SOME_XOR function _255 {(());exit;}; # EXIT_VM ``` Rewrite in python with the addition of a disassembler ```python import tty, sys, termios M = {} m=[42,29978,47,59,44,213,42,219,40,59,57,42,4058,40,59,12,59,21,56,29,42,40,41,43,40,3,41,44,41,42,32,57,56,59,57,2,32,56,37,40,0,43,44,43,43,35,22,41,43,42,40,43,40,43,41,44,41,37,32,2,58,40,56,37,40,43,40,43,40,43,40,43,40,43,40,43,40,43,40,43,40,43,40,43,40,29,42,40,41,44,41,117,32,203,43,40,29,42,40,41,44,41,72,32,203,43,40,29,42,40,41,44,41,30,32,203,43,40,29,42,40,41,44,41,89,32,203,58,40,29,42,40,41,44,41,67,32,203,43,40,29,42,40,41,44,41,117,32,203,43,40,29,42,40,41,44,41,70,32,203,42,42,46,43,46,43,40,29,42,40,41,44,41,26,32,203,44,46,47,32,137,43,40,29,42,40,41,44,41,92,32,203,43,40,29,42,40,41,44,41,25,32,203,43,40,43,40,43,40,43,40,43,40,29,42,40,41,44,41,98,32,203,59,195,56,2,32,42,280,40,59,11,213,2,32,42,297,40,59,11,56,111,68,94,79,88,10,90,75,89,89,93,69,88,78,10,16,10,42,124,109,109,10,16,10,83,69,95,10,73,75,68,10,95,89,79,10,94,66,79,10,90,75,89,89,93,69,88,78,10,94,69,10,92,75,70,67,78,75,94,79,10,16,7,3,42,126,88,83,10,66,75,88,78,79,88,10,68,26,26,72,11,11,11,0,30000,5,17,332,17,0,255,0,0,4,0,324,3,0,42,2,55,0,4,1,43,1,2,3,1,0,4,1,4,6,4,324,10,341,18,] for i in range(len(m)): M[i] = m[i] P = 0x144 y,z,g =0,0,0 R = 2000*[0] def getch(): # for 1 char input fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch DEBUG=True def d(*args): if DEBUG: print(str(P)+'>', *args) while True: op = M[P] if op == 255: d('EXIT') break elif op == 0: R[M[P+2]] = M[P+1] d(f"R[{M[P+2]}] = {M[P+1]}") P+=3 elif op == 1: R[M[P+1]] += 1 d(f"R[{M[P+1]}] += 1") P+=2 elif op == 2: R[M[p+1]] = M[P+2] d(f"R{M[p+1]} = {M[P+2]}") P+=3 elif op == 22: R[M[p+2]] = R[M[P+1]] d(f'R[{M[p+2]}] = R[{M[P+1]}]') P+=3 elif op == 3: g= M[P+2] + R[M[P+3]] d(f'g = {M[P+2]} + R[{M[P+3]}] = {g}') d(f"M[{g}] = R[{M[P+1]}]") M[g] = R[M[P+1]] P+=4 elif op == 55: R[M[P+3]] = M[ M[P+1] + R[M[P+2]] ] d(f"R[{M[P+3]}] = M[ {M[P+1]} + R[{M[P+2]}] ] = M[{M[P+1] + R[M[P+2]]}]") P+=4 elif op == 6: y = R[M[P+1]] z = M[P+2] d(f"y = R[{M[P+1]}]") d(f"z = {M[P+2]}") P+=3 elif op == 9: d(f"JL {M[P+1]} # IF {y} < {z}") if y < z: P = M[P+1] else: P += 2 elif op == 10: d(f"JNE {M[P+1]} # IF {y} != {z}") if y != z: P = M[P+1] else: P += 2 elif op == 17: d(f"R[5]-= 1") d(f"M[{R[5]}] = {P+2}") d(f"P = M[{P+1}]") R[5] -= 1 M[R[5]] = P+2 P = M[P+1] elif op == 18: d(f"P = M[{R[5]}]") d(f"R[5] += 1") P = M[R[5]] R[5] += 1 elif op == 14: d(f"R[5] -= 1") d(f"M[{R[5]}] = M[{P+1}]") R[5] -= 1 M[R[5]] = M[P+1] P+=2 elif op == 15: d(f"R[5] -= 1") d(f"M[{R[5]}] = R[{M[P+1]}]") R[5] -= 1 M[R[5]] = R[M[P+1]] P+=2 elif op == 16: d(f"R[{M[P+1]}] = M[{R[5]}]") d(f"R[5] += 1") R[M[P+1]] = M[R[5]] R[5] += 1 P+=2 elif op == 40: d(f"print M[{P+1}]") print(chr(M[P+1]),end='') P+=2 elif op == 41: d(f"print R[{M[P+1]}]") print(chr(R[M[P+1]]),end='') P+=2 elif op == 42: inp_chr = getch() R[M[P+1]] = ord(inp_chr) d(f"R[{M[P+1]}] = ord(\"{inp_chr}\") = {ord(inp_chr)}") P+=2 elif op == 43: d(f"R[{M[P+1]}] ^= R[{M[P+2]}]") R[M[P+1]] ^= R[M[P+2]] P += 3 elif op == 44: d(f"g = M[{P+2}] + R[{M[P+3]}] ") d(f"R[{M+1}] ^= M[{g}]") g = M[P+2] + R[M[P+3]] R[M+1] ^= M[g] P += 4 else: print(f'ERROR at P = {P}') break ``` Let's notice a some things: 1) String length is 15: ![](https://i.imgur.com/bJgC8Pt.png) 2) The string is compared character by character. On a mismatch, a `JMP 225` occurs with the output `Try harder noob`. Accordingly, we can make a string of length 15 and fit each character to the one that will be compared at `JNE 22 # IF inp_chr != need_chr` ![](https://i.imgur.com/6Vf2Lrt.png) Let's generate a string from which we will start when brutforce ```python st = "".join(map(chr, range(60, 75))) # <=>?@ABCDE_GHIJ ``` And we will edit the handler of the getchar instruction so that it is more convenient to brutforce the string ```python st = input() i=-1 while True: ... elif op == 42: # inp_chr = getch() i+=1 inp_chr = st[i] ... ... ``` We look at what the character from our string is compared with and change it to the desired one. Going through character by character, we get the coveted result: ![](https://i.imgur.com/uBvFInQ.png) ## ELF x86 - VM I just used angr lol