Welcome to HBH! If you have tried to register and didn't get a verification email, please using the following link to resend the verification email.

The-Scarecrow's Avatar
Member
755 19

This is a little tricky without spoilers. But.

I can see the password string is pushed through this function. It’s really difficult to see what’s going on. It appears to do something with hexadecimal.

char * DoSomethingToPass(longlong passString)

{
  ulonglong *puVar1;
  longlong in_r13;
  ulonglong i;
  ulonglong auStack152 [16];
  longlong local_18;
  
  local_18 = *(longlong *)(in_r13 + -0x7010);
  .opd.FUN_1002a640(auStack152,(ulonglong *)&DAT_100a1cd0,0x80);
  puVar1 = .opd.FUN_10026640(0x11);
  .opd.FUN_1002aa40(puVar1,0,0x11);
  i = 0;
  while( true ) {
    if (0xf < i) break;
    *(undefined *)((longlong)puVar1 + i) = *(undefined *)(passString + auStack152[i]);
    i = i + 1;
  }
  if (local_18 != *(longlong *)(in_r13 + -0x7010)) {
                    /* WARNING: Subroutine does not return */
    .opd.FUN_100358d0();
  }
  return (char *)puVar1;
}

That returned variable is then called in a function like this;

likelyAnswer = StringMurder((ulonglong)pcVar3,(longlong)passWordString,passLength);

That function looks like this.

ulonglong * StringMurder(ulonglong param_1,longlong passWordString,ulonglong StringLength)

{
  longlong param1holder;
  ulonglong *likelyAnswer;
  ulonglong *puVar1;
  longlong lVar2;
  longlong stringLengthOffset;
  longlong in_r13;
  ulonglong i;
  
  lVar2 = *(longlong *)(in_r13 + -0x7010);
  i = 0;
  param1holder = ::StringLength(param_1);
  stringLengthOffset = StringLength + param1holder;
  likelyAnswer = .opd.FUN_10026640(stringLengthOffset + 1);
  .opd.FUN_1002aa40(likelyAnswer,0,stringLengthOffset + 1);
  puVar1 = .opd.FUN_10026640(stringLengthOffset + 1);
  .opd.FUN_1002aa40(puVar1,0,stringLengthOffset + 1);
  FUN_100003a0();
  FUN_10000380();
  while( true ) {
    if (param1holder + StringLength <= i) break;
    *(byte *)((longlong)likelyAnswer + i) =
         *(byte *)((longlong)puVar1 + i) ^
         *(byte *)(passWordString + (i - (i / StringLength) * StringLength));
    i = i + 1;
  }
  Clear((longlong)puVar1);
  if (lVar2 != *(longlong *)(in_r13 + -0x7010)) {
                    /* WARNING: Subroutine does not return */
    .opd.FUN_100358d0();
  }
  return likelyAnswer;
}

I feel like the first param doesn’t actually exist. But you can see the for loop is manipulating the bytes one by one using the length of the string.

So my thoughts are, each character of that password string changed to hex then each byte of the hex manipulated using that (pwlength(i-(i/length)*length)) algorithm.

Also how is (i / StringLength) not throwing an exception? is this some lower language/byte stuff where it just disregards decimals? also the C operator ^ is some pretty advanced stuff, is that ghidra sending me another curve ball or is this @Futility coding slightly above machine language? lol.

Following along generally is one thing but I’m having a lot of trouble coming out the other side with the correct password string.

Am I in the right ball park here?

Edited for some fancy colours in the code and secondary thoughts.


Futility's Avatar
:(
80 122

Gonna start in the weeds and then give general tips

  • “I feel like the first param doesn’t actually exist” - You can see it being strlen‘d early on and that length being used later. It is highly unlikely that this is a fluke. What makes you think it doesn’t exist?
  • “each character of that password string changed to hex then each byte of the hex manipulated “ - Might be nitpicky, but nothing is being “changed to hex”. A program is a pile of bits. Those strings are just piles of bits. When the pile’o’bits is sent to something like printf, you have to tell it “these bits right here, these should be displayed as ASCII, or as their hex representation or as an int or just group them up into bytes or whatever”. You can try this with a simple program yourself (below). Notice that they’re all equivalent- 0x74736574 is 1953719668 in decimal and treating 0x74736574 as ascii characters gets you the string test (backwards, in this case, due to endianness). All this to say- I’ve changed nothing, I’m just iterating through the string and treating its bytes as… well… bytes.
  • “Also how is (i / StringLength) not throwing an exception?” - Integer division doesn’t throw an exception if it gets a non-integer answer. It simply drops the remainder portion (think floor()). Again you can try this yourself using the shell below by uncommenting the final printf.
  • “also the C operator ^ is some pretty advanced stuff” - Not sure what we mean by this. The ^ is akin to the | or & operators, which are all basic bitwise operators. In this case I’m just doing an XOR, which sort of a fundamental building block of many cryptographic algorithms.

Anyway- so far we’re looking (very!) good. You’ve managed to narrow down the search space considerably and you’re well on the way to understanding what’s happening. Just a few hiccups. I would make the following suggestions:

  1. I would check exactly where the output from your StringMurder function is going. How important is it to know exactly what’s going on here?
  2. If you don’t understand the math that’s happening ((pwlength(i-(i/length)*length))), maybe making a small program to iterate through it a couple of times could help? That is, give dummy values to the variables and then print out what the answer is at each step. Maybe you’ll see a pattern or something? This is a good general hint overall- if something looks too complicated to work out in your head, feel free to pop in a few test values and run it through to see what pops out.

Keep it up!

#include <stdio.h>

/*
 *  tester.c
 *  compile with something like: gcc tester.c -o thing
 *  output:
 *    str: test
 *    num: 1953719668
 *    hex: 0x74736574
 */
int main(int argc, char** argv){
  char hello[4] = "test";

  printf("str: %s\n", hello);
  printf("num: %d\n", *((int*) hello));
  printf("hex: 0x%x\n", *((int*) hello));
  //printf("%d\n", 5/2);

  exit(0);
}

The-Scarecrow's Avatar
Member
755 19

Still working on this one.

I see what you are saying about not ‘changing’ a type but outputting its bytes in different ways. I think you are hinting that if you think of a string as bytes/number/int its easily manipulated.

This/these functions are confusing to me. The loop runs 16 times. It adds the password string to each element of that array then moves it to dynamic memory?

ulonglong * SomethingToPassString(longlong passString)

{
  ulonglong *malloc;
  longlong in_r13;
  ulonglong i;
  ulonglong auStack152 [16];
  longlong local_18;
  
  local_18 = *(longlong *)(in_r13 + -0x7010);
  .opd.FUN_1002a640(auStack152,(ulonglong *)&DAT_100a1cd0,0x80);
  malloc = .opd.FUN_10026640(0x11);
  .opd.FUN_1002aa40(malloc,0,0x11);
  i = 0;
  while( true ) {
    if (0xf < i) break;
    *(undefined *)((longlong)malloc + i) = *(undefined *)(passString + auStack152[i]);
    i = i + 1;
  }
  if (local_18 != *(longlong *)(in_r13 + -0x7010)) {
                    /* WARNING: Subroutine does not return */
    .opd.FUN_100358d0();
  }
  return malloc;
}

.opd.FUN_1002a640(auStack152,(ulonglong *)&DAT_100a1cd0,0x80);

Really struggling with this function. I feel like its a default function from C. But I don’t understand how it fills the array. If that’s what it does at all.

And the var malloc is just 17 bytes of space yeah? How will that string possibly fit into that?


Futility's Avatar
:(
80 122

It’s not a default C function. That’s all me, baby. And again, maybe writing a small sample function to do what you’re seeing here and then running it a couple times could help show what’s going on.

Also, maybe just look again at what you’ve said. it seems like you’re probably a bit closer than you realize.

The loop runs 16 times. || And the var malloc is just 17 bytes of space yeah? How will that string possibly fit into that? || It adds the password string to each(?) element of that array then moves it to dynamic memory?