Try   HackMD

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:

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:

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 (outputs cleaner code, but more artifacts) and unluac.

Decompile with luadec:

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:

  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:


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:

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:

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

{$'\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:

{$\'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:

[[ $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:

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

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:

  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

Let's generate a string from which we will start when brutforce

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

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:

ELF x86 - VM

I just used angr lol