此汇编x86代码的反编译(C)代码结构是什么?

时间:2019-04-19 09:28:11

标签: c assembly x86 reverse-engineering decompiling

此代码将字符串的每个字符(位于ebp+arg_0)与不同的常量(ASCII字符)(例如“ I”,“ o”和“ S”)进行比较。我想,基于其他代码部分,该代码最初是用C编写的。

assembly

这个比较代码部分看起来效率很低。 我的问题,您如何看待这段代码在C语言中的外观?最初使用什么代码构造?到目前为止我的想法

  • 这不是 for循环。因为我看不到任何向上跳跃和停止的情况。

  • 这不是while / case / switch代码构造

  • 我最好的猜测是,这是许多连续的if / else语句。 你能帮忙吗?

是的,这是挑战的一部分,我已经有了标志/解决方案,对此无后顾之忧。只是为了更好地理解代码。

2 个答案:

答案 0 :(得分:4)

  

这不是for循环。因为我看不到任何向上跳跃和停止的情况。

正确。

  

不是一会儿/案例/切换代码的​​构造

不能,它比较数组的不同索引。

  

我最好的猜测是,这是许多连续的if / elses。你能帮忙吗?

看起来可能是这样的代码:

void f(const char* arg_0) {
    if(arg_0[4] == 'I' && arg_0[5] == 'o' && arg_0[6] == 'S') {
        printf("Gratz man :)");
        exit(0); //noreturn, hence your control flow ends here in the assembly
    }
    puts("Wrong password"); // Or `printf("Wrong password\n");` which gets optimized to `puts`
    // leave, retn
}

This is how gcc compiles it without optimizations

.LC0:
        .string "Gratz man :)"
.LC1:
        .string "Wrong password"
f(char const*):
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     eax, DWORD PTR [ebp+8]
        add     eax, 4
        movzx   eax, BYTE PTR [eax]
        cmp     al, 73
        jne     .L2
        mov     eax, DWORD PTR [ebp+8]
        add     eax, 5
        movzx   eax, BYTE PTR [eax]
        cmp     al, 111
        jne     .L2
        mov     eax, DWORD PTR [ebp+8]
        add     eax, 6
        movzx   eax, BYTE PTR [eax]
        cmp     al, 83
        jne     .L2
        sub     esp, 12
        push    OFFSET FLAT:.LC0
        call    printf
        add     esp, 16
        sub     esp, 12
        push    0
        call    exit
.L2:
        sub     esp, 12
        push    OFFSET FLAT:.LC1
        call    puts
        add     esp, 16
        nop
        leave
        ret

看起来与您的反汇编代码非常相似。

  

这个比较代码部分看起来效率很低

看起来它没有经过优化就被编译。启用优化后,gcc compiled the code to

.LC0:
        .string "Gratz man :)"
.LC1:
        .string "Wrong password"
f(char const*):
        sub     esp, 12
        mov     eax, DWORD PTR [esp+16]
        cmp     BYTE PTR [eax+4], 73
        jne     .L2
        cmp     BYTE PTR [eax+5], 111
        je      .L5
.L2:
        mov     DWORD PTR [esp+16], OFFSET FLAT:.LC1
        add     esp, 12
        jmp     puts
.L5:
        cmp     BYTE PTR [eax+6], 83
        jne     .L2
        sub     esp, 12
        push    OFFSET FLAT:.LC0
        call    printf
        mov     DWORD PTR [esp], 0
        call    exit

不确定gcc为什么决定跳下再跳而不是jne的直线。此外,ret不见了,您的printf得到了尾部呼叫优化,即jmp printf代替了call printf,之后是ret

答案 1 :(得分:2)

第一个参数(arg_0)是指向给定密码字符串(例如const char *arg_0)的指针。将该指针(第一个字符的地址)加载到eax寄存器(mov eax, [ebp+arg_0])中,然后添加当前字符的索引以将其前进到该索引(add eax, 4等等。)。然后将该地址处的单个字节加载到eaxmovzx eax, byte ptr [eax])中。

然后将该字节/字符与正确的字节/字符(cmp eax, 'I'等)进行比较。如果结果不为零(即,如果它们不相等),则程序将跳转到“错误密码”分支(jnz-如果不为零,则跳转),否则它将继续进行下一个比较(并最终进行比较)成功)。

因此,最接近的直接C等效项是:

void check(const char *arg_0) {
    // presumably comparisons 0-3 are omitted
    if (arg_0[4] != 'I') goto fail;
    if (arg_0[5] != 'o') goto fail;
    if (arg_0[6] != 'S') goto fail;
    printf("Gratz man :)");
    exit(0);
fail:
    puts("Wrong password");
}

(当然,实际的C代码不太可能看起来像这样,因为goto fail的排列并不常见。)