3

To clarify, I understand how ADC works. It's the same as a regular ADD but with an extra 1 if the Carry Flag was set.

Its use in the snippet below makes perfect sense to me since an overflow is most likely going to occur, setting the CF flag, when adding two 64-bit values in a 32-bit context:

Assume EDX:EAX and EBX:ECX pairs hold two 64 bit values that are to be added together.

add     eax, ecx
adc     edx, ebx
mov     dword [ebp+Some64BitValue], eax
mov     dword [ebp+Some64BitValue+4], edx

What I fail to understand the meaning of is when ADC is used consecutively on the same variable/register like this:

add     eax, dword [esi]
adc     eax, dword [esi+0x4]
adc     eax, dword [esi+0x8]
adc     eax, dword [esi+0x10]
...
adc     eax, 0

Generally speaking what would be the purpose of doing this? What is meant to be of EAX?

*update:

see my answer below

1
  • can you post a bigger snippet? e.g. how eax is initialized?
    – Igor Skochinsky
    Aug 22, 2019 at 8:42

2 Answers 2

4

I found the answer to my question.

EAX is arbitrary, as are the values added to it from [ESI].

The function I'm reversing is a calculating a 16-bit checksum after adding together data, in the manner you see above, and then folding the result in EAX to AX.

I did some more research and found this article on Additive Checksums. The one I'm looking at is similar to The Internet Checksum described there.

The purpose of consecutive ADC instructions in this case looks like an optimisation made available only at the assembly level. The checksum is performing ones compliment addition multiple times. Another way to perform the same operations, only with ADD, would be to let the carry bits accumulate in the upper half of the register and then add them back to the lower half at the end.

3

EDIT 2

without resorting to inline assembly / seperate assembly module i was able to generate consecutive adc instruction using the intrinsic _addcarry_uxx (16,32,64) (with full optimizations on /O1 switch in msvc cl.exe )

relevant code and its disassembly below

#include <stdio.h>
#include <intrin.h>
#define BUSIZ  16
#define UNROLL 8
unsigned int dat[BUSIZ] = 
{
    0x10001337, 0xffffffff, 0x10001337 , 0xffffffff,
    0x00001337, 0xdeadbeef, 0xbeadbed5 , 0xdad15dad,
    0xba5eba11, 0xf001b055, 0xc001b055 , 0x501eb055,
    0xba11ba75, 0xbadba115, 0xbed15bad , 0xdaff0d11
};    
int main (void) 
{
    unsigned int chksum = 0;
    unsigned char carry = 0;
    for(int i=0; i< BUSIZ; i += UNROLL )
    {
        carry = _addcarry_u32(carry,chksum,dat[i+0],&chksum);
        carry = _addcarry_u32(carry,chksum,dat[i+1],&chksum);
        carry = _addcarry_u32(carry,chksum,dat[i+2],&chksum);
        carry = _addcarry_u32(carry,chksum,dat[i+3],&chksum);        
        carry = _addcarry_u32(carry,chksum,dat[i+4],&chksum);
        carry = _addcarry_u32(carry,chksum,dat[i+5],&chksum);
        carry = _addcarry_u32(carry,chksum,dat[i+6],&chksum);
        carry = _addcarry_u32(carry,chksum,dat[i+7],&chksum);
    }
    printf("FINAL CHECKSUM = %8x\n" , chksum + carry );  
    return 0;
}

executed it prints out

:\>addcarry.exe
FINAL CHECKSUM = 616ba476

python script to chk veracity of result

:\>cat chksum.py
dat = [
    0x10001337, 0xffffffff, 0x10001337 , 0xffffffff,
    0x00001337, 0xdeadbeef, 0xbeadbed5 , 0xdad15dad,
    0xba5eba11, 0xf001b055, 0xc001b055 , 0x501eb055,
    0xba11ba75, 0xbadba115, 0xbed15bad , 0xdaff0d11
]
chksum33bit = 0
for i in range (0,16,1):
    chksum33bit = chksum33bit + dat[i]
chklow = chksum33bit & 0xffffffff
chkhig = chksum33bit >> 32
chkfin = chklow+chkhig
print (hex(chkfin))

:\>python chksum.py
0x616ba476L

disassembling we see compiler has generated consecutive adc instructions

:\>cdb -c "uf addcarry!main;q" addcarry.exe | grep -B 31 quit
0:000> cdb: Reading initial command 'uf addcarry!main;q'
addcarry!main:
01151029 33c9            xor     ecx,ecx
0115102b b804901901      mov     eax,offset addcarry!dat+0x4 (01199004)
01151030 8ad1            mov     dl,cl

addcarry!main+0x9:
01151032 80c2ff          add     dl,0FFh
01151035 1348fc          adc     ecx,dword ptr [eax-4]
01151038 1308            adc     ecx,dword ptr [eax]
0115103a 134804          adc     ecx,dword ptr [eax+4]
0115103d 134808          adc     ecx,dword ptr [eax+8]
01151040 13480c          adc     ecx,dword ptr [eax+0Ch]
01151043 134810          adc     ecx,dword ptr [eax+10h]
01151046 134814          adc     ecx,dword ptr [eax+14h]
01151049 134818          adc     ecx,dword ptr [eax+18h]
0115104c 0f92c2          setb    dl
0115104f 83c020          add     eax,20h
01151052 3d44901901      cmp     eax,offset addcarry!__scrt_default_matherr (01199044)
01151057 7cd9            jl      addcarry!main+0x9 (01151032)

addcarry!main+0x30:
01151059 0fb6c2          movzx   eax,dl
0115105c 03c1            add     eax,ecx
0115105e 50              push    eax
0115105f 6890011901      push    offset addcarry!`string' (01190190)
01151064 e805000000      call    addcarry!printf (0115106e)
01151069 59              pop     ecx
0115106a 59              pop     ecx
0115106b 33c0            xor     eax,eax
0115106d c3              ret
quit:

END OF EDIT 2

EDIT 1:

even though i could generate several adc's
i couldn't successfully generate consecutive adc using c compiler
so i looked around windows\system32 directory if any of the binaries has consecutive adc's
located one in imagehlp.dll
and some googling later it appears it is RFC1071 IP Header Checksum
probably written using handwritten assembly (found some assembly codes for motorola , cray etc in the link also here is a google group link that discusses the Implementation of ChkSum there is also an old Dave Cutler authored ChkSum() from a NT imagehelp toolkit sdk that was released by ms around 1993 )

below is win7 sp1 32 bit imagehlp.dll adc sequence

grep -obUaPs "\x13\x46\x04\x13\x46\x08" --include=*.dll *

imagehlp.dll:17883:‼F♦‼F
imagehlp.dll:17917:‼F♦‼F
imagehlp.dll:17963:‼F♦‼F
imagehlp.dll:18021:‼F♦‼F

xxd -c 12 -g 3 -s 18021 -l 99 imagehlp.dll

0004665: 134604 134608 13460c 134610  .F..F..F..F.
0004671: 134614 134618 13461c 134620  .F..F..F..F
000467d: 134624 134628 13462c 134630  .F$.F(.F,.F0
0004689: 134634 134638 13463c 134640  .F4.F8.F<.F@
0004695: 134644 134648 13464c 134650  .FD.FH.FL.FP
00046a1: 134654 134658 13465c 134660  .FT.FX.F\.F`
00046ad: 134664 134668 13466c 134670  .Fd.Fh.Fl.Fp
00046b9: 134674 134678 13467c 83d000  .Ft.Fx.F|...
00046c5: 81c680                       ...

dumpbin /headers imagehlp.dll | grep -i "section header #1" -A 5

SECTION HEADER #1
   .text name
   23249 virtual size
    1000 virtual address (41871000 to 41894248)
   23400 size of raw data
     600 file pointer to raw data (00000600 to 000239FF)

cdb -c "ln (imagehlp + 1000 + (0n18021-0x600));q" -z imagehlp.dll | grep -i -A 1 reading

0:000> cdb: Reading initial command 'ln (imagehlp + 1000 + (0n18021-0x600));q'

(41874f7b)   imagehlp!ChkSum+0xea   |  (41875105)   imagehlp!_SEH_prolog4_GS

END OF EDIT 1

there may be several adc's because the compilation was not optimized is one of the reason

assuming you have some code like this

#include <stdio.h>
#include <windows.h>

int main (void) {
    DWORD64 a = 0x100002000;
    DWORD64 b = 0x00000000ffffffff;
    DWORD64 c = a + b;    
    printf("%I64x\n" , a);
    printf("%I64x\n" , b);
    printf("%I64x\n" , c);
    c = c + a;
    printf("%I64x\n" , c);
    c = c + b;
    printf("%I64x\n" , c);
    c = c + c;
    printf("%I64x\n" , c);    
    return 0;
}

if you compile this without optimization
disassemble the function main and look for adc instructions
you will see several adc instructions including some that use the same register

cl /Zi /EHsc /W4 /Od /nologo /analyze adc.cpp /link /release /nologo adc.cpp  

cdb -c "g adc!main;uf .;q" adc.exe | grep -i "adc "
000a102b 134dec          adc     ecx,dword ptr [ebp-14h]
000a107c 1345f4          adc     eax,dword ptr [ebp-0Ch]
000a10a3 134dec          adc     ecx,dword ptr [ebp-14h]
000a10ca 1355fc          adc     edx,dword ptr [ebp-4]

but if you optimize compiler is smart enough to remove all Adc instructions

cl /Zi /EHsc /W4 /Ox /nologo /analyze adc.cpp /link /release /nologo adc.cpp

cdb -c "g adc!main;uf .;q" adc.exe | grep -i "adc "

adc.exe
100002000
ffffffff
200001fff
300003fff
400003ffe
800007ffc

the code you have in question might be result of a subroutine that performs pure addition and returns the result as below

#include <stdio.h>
#include <windows.h>
#pragma optimize ( "t" , off)
DWORD64  madd (DWORD64 a, DWORD64 b) 
{
    DWORD64 c = 0;
    c = a + b;  
    c = c + a;    
    c = c + b;
    c = c + c;
    return c;    
}
#pragma optimize ( "t" , on)
int main (void) {
    DWORD64 a = 0x100002000;
    DWORD64 b = 0x00000000ffffffff;
    printf("%I64x\n" , madd(a,b));    
    return 0;
}

here is the disassembly of the functions that adds multiple64 bit values and returns the result

disassembly of function madd

0:000> uf adc!madd
adc!madd [adc.cpp @ 6]:
    6 00291000 55              push    ebp
    6 00291001 8bec            mov     ebp,esp
    6 00291003 51              push    ecx
    6 00291004 51              push    ecx
    7 00291005 0f57c0          xorps   xmm0,xmm0
    7 00291008 660f1345f8      movlpd  qword ptr [ebp-8],xmm0
    8 0029100d 8b4508          mov     eax,dword ptr [ebp+8]
    8 00291010 034510          add     eax,dword ptr [ebp+10h]
    8 00291013 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
    8 00291016 134d14          adc     ecx,dword ptr [ebp+14h]
    8 00291019 8945f8          mov     dword ptr [ebp-8],eax
    8 0029101c 894dfc          mov     dword ptr [ebp-4],ecx
    9 0029101f 8b45f8          mov     eax,dword ptr [ebp-8]
    9 00291022 034508          add     eax,dword ptr [ebp+8]
    9 00291025 8b4dfc          mov     ecx,dword ptr [ebp-4]
    9 00291028 134d0c          adc     ecx,dword ptr [ebp+0Ch]
    9 0029102b 8945f8          mov     dword ptr [ebp-8],eax
    9 0029102e 894dfc          mov     dword ptr [ebp-4],ecx
   10 00291031 8b45f8          mov     eax,dword ptr [ebp-8]
   10 00291034 034510          add     eax,dword ptr [ebp+10h]
   10 00291037 8b4dfc          mov     ecx,dword ptr [ebp-4]
   10 0029103a 134d14          adc     ecx,dword ptr [ebp+14h]
   10 0029103d 8945f8          mov     dword ptr [ebp-8],eax
   10 00291040 894dfc          mov     dword ptr [ebp-4],ecx
   11 00291043 8b45f8          mov     eax,dword ptr [ebp-8]
   11 00291046 0345f8          add     eax,dword ptr [ebp-8]
   11 00291049 8b4dfc          mov     ecx,dword ptr [ebp-4]
   11 0029104c 134dfc          adc     ecx,dword ptr [ebp-4]
   11 0029104f 8945f8          mov     dword ptr [ebp-8],eax
   11 00291052 894dfc          mov     dword ptr [ebp-4],ecx
   12 00291055 8b45f8          mov     eax,dword ptr [ebp-8]
   12 00291058 8b55fc          mov     edx,dword ptr [ebp-4]
   14 0029105b c9              leave
   14 0029105c c3              ret

the function uses the same register ecx for adding multiple carryovers

0:000> # adc*, adc!madd l 60
adc!madd+0x16 [adc.cpp @ 8]:
00291016 134d14          adc     ecx,dword ptr [ebp+14h]  
adc!madd+0x28 [adc.cpp @ 9]:
00291028 134d0c          adc     ecx,dword ptr [ebp+0Ch]
adc!madd+0x3a [adc.cpp @ 10]:
0029103a 134d14          adc     ecx,dword ptr [ebp+14h]
adc!madd+0x4c [adc.cpp @ 11]:
0029104c 134dfc          adc     ecx,dword ptr [ebp-4]
0:000>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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