2

I have the following code:

0000000000400526 <main>:
  400526:   55                      push   rbp
  400527:   48 89 e5                mov    rbp,rsp
  40052a:   48 83 ec 20             sub    rsp,0x20
  40052e:   89 7d ec                mov    DWORD PTR [rbp-0x14],edi
  400531:   48 89 75 e0             mov    QWORD PTR [rbp-0x20],rsi
  400535:   c7 45 f4 4d 3c 2b 1a    mov    DWORD PTR [rbp-0xc],0x1a2b3c4d
  40053c:   48 8d 45 f4             lea    rax,[rbp-0xc]
  400540:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  400544:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0
  40054b:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40054f:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400552:   0f be c0                movsx  eax,al
  400555:   c1 e0 18                shl    eax,0x18
  400558:   89 c2                   mov    edx,eax
  40055a:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40055e:   48 83 c0 01             add    rax,0x1
  400562:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400565:   0f be c0                movsx  eax,al
  400568:   c1 e0 10                shl    eax,0x10
  40056b:   09 c2                   or     edx,eax
  40056d:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400571:   48 83 c0 02             add    rax,0x2
  400575:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400578:   0f be c0                movsx  eax,al
  40057b:   c1 e0 08                shl    eax,0x8
  40057e:   09 c2                   or     edx,eax
  400580:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400584:   48 83 c0 03             add    rax,0x3
  400588:   0f b6 00                movzx  eax,BYTE PTR [rax]
  40058b:   0f be c0                movsx  eax,al
  40058e:   09 d0                   or     eax,edx
  400590:   89 45 f0                mov    DWORD PTR [rbp-0x10],eax
  400593:   8b 55 f0                mov    edx,DWORD PTR [rbp-0x10]
  400596:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  400599:   89 c6                   mov    esi,eax
  40059b:   bf 44 06 40 00          mov    edi,0x400644 ; "a = %#x\nb = %#x\n"
  4005a0:   b8 00 00 00 00          mov    eax,0x0
  4005a5:   e8 56 fe ff ff          call   400400 <printf@plt>
  4005aa:   b8 00 00 00 00          mov    eax,0x0
  4005af:   c9                      leave
  4005b0:   c3                      ret
  4005b1:   66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  4005b8:   00 00 00
  4005bb:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

This is a segment of x64 assembly code, and I would like to rewrite this code into C. I've been reading Assembly books all day, and I'm still having some difficulty. I just want to understand what this code is doing. From messing around, I think that it performs some operations on an int (that's why I think the DWORD is there) and a long (that's why the QWORD is there). I think that this is true because I recompiled C code with those data structures and those words appeared in the Assembly equivalent, but I could be wrong.

Any help is appreciated in decoding this code.'


For Amigag: second segment of code

0000000000400966 <my_tolower>:
  400966:   55                      push   rbp
  400967:   48 89 e5                mov    rbp,rsp
  40096a:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  40096e:   eb 2d                   jmp    40099d <my_tolower+0x37>
  400970:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400974:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400977:   3c 40                   cmp    al,0x40
  400979:   7e 1d                   jle    400998 <my_tolower+0x32>
  40097b:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40097f:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400982:   3c 5a                   cmp    al,0x5a
  400984:   7f 12                   jg     400998 <my_tolower+0x32>
  400986:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40098a:   0f b6 00                movzx  eax,BYTE PTR [rax]
  40098d:   83 c0 20                add    eax,0x20
  400990:   89 c2                   mov    edx,eax
  400992:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400996:   88 10                   mov    BYTE PTR [rax],dl
  400998:   48 83 45 f8 01          add    QWORD PTR [rbp-0x8],0x1
  40099d:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  4009a1:   0f b6 00                movzx  eax,BYTE PTR [rax]
  4009a4:   84 c0                   test   al,al
  4009a6:   75 c8                   jne    400970 <my_tolower+0xa>
  4009a8:   90                      nop
  4009a9:   5d                      pop    rbp
  4009aa:   c3                      ret    
  4009ab:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

1 Answer 1

7

I believe the best tool for rewriting assembly to C is IDA Graph View, which is toggled with space.
It let you see the function as Basic Blocks, connected by control flow instructions. In this specific function, I cannot spot any jumps so you will see one long block.

The first thing you usually see in a function is the function prologue which sets up the stack frame.

  400526:   55                      push   rbp
  400527:   48 89 e5                mov    rbp,rsp
  40052a:   48 83 ec 20             sub    rsp,0x20

second, as 64 bit calling convention suggests, the first parameters are passed by registers, other are passed on the stack.
The used registers are os-dependent (see table 5 at Agner Fog calling conventions).
You an see that the function probably gets 2 parameters (edi for 32 bit variable for argc and 64 bit for argv)

You can see that a "magic" value (0x1a2b3c4) is saved in a local variable and a pointer to it is created. Note that when it's saved, it is stored as little-endian, which means the order of bytes is reversed.

  400535:   c7 45 f4 4d 3c 2b 1a    mov    DWORD PTR [rbp-0xc],0x1a2b3c4d
  40053c:   48 8d 45 f4             lea    rax,[rbp-0xc]
  400540:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax

And his first byte is read to eax as a signed byte and multiplied by 2^0x18 (=2^24). The fact that it's signed doesn't affect anything in this case, because the sign bit is always off (as 0x1a, 0x2b, 0x3c and 0x4d are all below 128)

  40054f:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400552:   0f be c0                movsx  eax,al
  400555:   c1 e0 18                shl    eax,0x18

And in a similar way, a value is calculated using next bytes, multiplied by 0x10 (=16), 8 and 1 (implicitly) respectively. results are stored in edi, and ored with the previous value.

We can conclude that our function is something like that:

void main(int argc, char *argv[])
{
     int calculated_value;  // represents the use of edi to store the result
     int magic_value = 0x1A2B3C4D;  // note that although i am using int, i mean uint32_t, a variable that has 4 bytes - a DWORD.
     char *magic_ptr = (char *) &magic_value;  // the values are read byte-by-byte, or char-by-char
     calculated_value = magic_ptr[0] << 24;
     calculated_value |= magic_ptr[1] << 16;
     calculated_value |= magic_ptr[2] << 8;
     calculated_value |= magic_ptr[3];  // note that at the last or, the result is saved at eax as edi will soon be used to pass the first parameter to printf

     printf("a = %#x\nb = %#x\n", magic_value, calculated_value);
}

So, what we can see is that the magic value is read back to a variable, while saving the little-endianness, which means we will get the reversed byte order if the magic_value.
Thus, we can expect the output to be:

a = 0x1a2b3c4d
b = 0x4d3c2b1a


Also, as a general note, this code have could utilize loops to perform the read.

void main(int argc, char *argv[])
{
     int i;
     int calculated_value = 0;  // represents the use of edi to store the result
     int magic_value = 0x1A2B3C4D;  // note that although i am using int, i mean uint32_t, a variable that has 4 bytes - a DWORD.
     char *magic_ptr = (char *) &magic_value;  // the values are read byte-by-byte, or char-by-char
     for(int i = 0; i < 4; i++)
     {
         calculated_value <<= 8;
         calculated_value = magic_ptr[i];
     }
     printf("a = %#x\nb = %#x\n", magic_value, calculated_value);
}

EDIT: As to your second code. Here we can see jumps, so I created a graph view of the code. it makes reading it much easier.
A bit of info on the graph: a green line means that the jump happens if a condition is met. A red line means that the jump happens if the condition is false. A blue line means that the jump is unconditional, it will always jump.
Let's go through it and see what happens.

A graph view of the second code

The first thing we get to see after the function prologue is a single parameter is saved at rbp-0x8.
On the block of 0x40099d we can see that the input is probably char *ptr, it dereference the pointer and read it's value.
From the test al, al we can assume that the value is a string (and not just binary data, it is probably related to user input) and we stop once we have read the null terminator (\x00 = 0).

The next block we will check is 0x400970. All it does is checking if the char pointed by rbp-0x8 is smaller or equal to 0x40 (0x40 is ascii for '@', 0x41 is 'A'). If it is, it continues (the single-line block at 0x400998).

So far, our function is something like:

void my_tolower(char *str)
{
    while(str[i] != '\x00')
    {
        if(str[i] < 'A')  // as opposed to <= '@'. I can't remember I saw a code ever caring about '@'.
        {
            str += 1;  // skipping the current character
            continue;
        }
        // unknown code for now
    }
}

Looking at 0x40097b, we can see a similar code, but it checks the character is smaller than 0x5A (=ascii of Z). so we can write those conditions in a single if:

if(str[i] < 'A' || str[i] > 'Z')

Last block (0x400986). We now know that str[i] contains an upper-case letter.
The code takes the character and add 0x20 to it. 0x20 is ascii for (space) and (a - A). It saves the result back to the string and continues.

  400992:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400996:   88 10                   mov    BYTE PTR [rax],dl

Looking at 0x4009a8, no result is passed to rax, that means the function probably doesn't return a value.

So, out functions look something like that:

void my_tolower(char *str)
{
    while(str[i] != '\x00')
    {
        if(str[i] < 'A' || str[i] > 'Z')  // as opposed to <= '@'. I can't remember I saw a code ever caring about '@'.
        {
            continue;
        }
        str[i] = str[i] + 'a' - 'A';
        str += 1;
    }
}

We could also rewrite the function to show the single block that increments the pointer, which i believe is how the original code looked like. it makes more sense logically that we change the string if a condition was met, not if a condition is not met.

void my_tolower(char *str)  // name was taken from 4009a6 and the first line of function
{
    while(str[i] != '\x00')
    {
        if(str[i] >= 'A' && str[i] <= 'Z')  // as opposed to <= '@'. I can't remember I saw a code ever caring about '@'.
        {
            str[i] = str[i] + 'a' - 'A';
        }
        str += 1;  // this is the block at 400998, that always happen
    }
}
2
  • Thank you so much!! There is just one last segment of code that I'm having trouble deciphering. I've accepted your answer, though. I added it to the original post to see if you can help me? I do not have IDA Graph View (though, I saw it as a suggested application earlier) as it is pay-per-download, and I plan on using Assembly again.
    – Joey
    Oct 10, 2018 at 0:55
  • @stackofhay42 i've edited the answer
    – user22970
    Oct 10, 2018 at 10:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.