public ByteStream(ByteStream byteStream, int offset) { this.bytes = byteStream.bytes; this.offset = byteStream.offset + offset; }
public t_disasm Disasm(ByteStream src, ulong srcip, int disasmmode) { da = new t_disasm(); int i, j, operand, mnemosize, arg; ulong u; int cxsize; // Prepare disassembler variables and initialize structure disasm. datasize = addrsize = 4; // 32-bit code and data segments only! segprefix = SEG_UNDEF; hasrm = false; hassib = false; dispsize = immsize = 0; bool lockprefix = false; // Non-zero if lock prefix present byte repprefix = 0; // REPxxx prefix or 0 cmd = new ByteStream(src, 0); pfixup = -1; softerror = 0; bool is3dnow = false; da.ip = srcip; da.comment = string.Empty; da.cmdtype = C_BAD; da.nprefix = 0; da.memtype = DEC_UNKNOWN; da.indexed = 0; da.jmpconst = 0; da.jmptable = 0; da.adrconst = 0; da.immconst = 0; da.zeroconst = 0; da.fixupoffset = 0; da.fixupsize = 0; da.warnings = 0; da.error = DAE_NOERR; mode = disasmmode; // No need to use register contents // Correct 80x86 command may theoretically contain up to 4 prefixes belonging // to different prefix groups. This limits maximal possible size of the // command to MAXCMDSIZE=16 bytes. In order to maintain this limit, if // Disasm() detects second prefix from the same group, it flushes first // prefix in the sequence as a pseudocommand. u = 0; bool repeated = false; while (size > 0) { bool isprefix = true; // Assume that there is some prefix switch (cmd[0]) { case 0x26: if (segprefix == SEG_UNDEF) segprefix = SEG_ES; else repeated = true; break; case 0x2E: if (segprefix == SEG_UNDEF) segprefix = SEG_CS; else repeated = true; break; case 0x36: if (segprefix == SEG_UNDEF) segprefix = SEG_SS; else repeated = true; break; case 0x3E: if (segprefix == SEG_UNDEF) segprefix = SEG_DS; else repeated = true; break; case 0x64: if (segprefix == SEG_UNDEF) segprefix = SEG_FS; else repeated = true; break; case 0x65: if (segprefix == SEG_UNDEF) segprefix = SEG_GS; else repeated = true; break; case 0x66: if (datasize == 4) datasize = 2; else repeated = true; break; case 0x67: if (addrsize == 4) addrsize = 2; else repeated = true; break; case 0xF0: if (!lockprefix) repprefix = 0xF0; // lockprefix = 0xF0; else repeated = true; break; case 0xF2: if (repprefix == 0) repprefix = 0xF2; else repeated = true; break; case 0xF3: if (repprefix == 0) repprefix = 0xF3; else repeated = true; break; default: isprefix = false; break; }; if (!isprefix || repeated) break; // No more prefixes or duplicated prefix if (mode >= DISASM_FILE) da.dump.AppendFormat("{0:X}:", cmd[0]); da.nprefix++; cmd.AdjustOffset(1); srcip++; u++; }; // We do have repeated prefix. Flush first prefix from the sequence. if (repeated) { if (mode >= DISASM_FILE) { //da.dump[3] = '\0'; // Leave only first dumped prefix string pname; da.nprefix = 1; switch (cmd[(int)-(long)u]) { case 0x26: pname = segname[SEG_ES]; break; case 0x2E: pname = segname[SEG_CS]; break; case 0x36: pname = segname[SEG_SS]; break; case 0x3E: pname = segname[SEG_DS]; break; case 0x64: pname = segname[SEG_FS]; break; case 0x65: pname = segname[SEG_GS]; break; case 0x66: pname = "DATASIZE"; break; case 0x67: pname = "ADDRSIZE"; break; case 0xF0: pname = "LOCK"; break; case 0xF2: pname = "REPNE"; break; case 0xF3: pname = "REPE"; break; default: pname = "?"; break; }; da.result.AppendFormat("PREFIX {0}:", pname); if (!extraprefix) da.comment = "Superfluous prefix"; }; da.warnings |= DAW_PREFIX; if (lockprefix) da.warnings |= DAW_LOCK; da.cmdtype = C_RARE; return da; // 1; // Any prefix is 1 byte long }; // If lock prefix available, display it and forget, because it has no // influence on decoding of rest of the command. if (lockprefix) { if (mode >= DISASM_FILE) da.result.Append("LOCK "); da.warnings |= DAW_LOCK; }; // Fetch (if available) first 3 bytes of the command, add repeat prefix and // find command in the command table. ulong code = 0; if (cmd.SizeLeft > 0) code = cmd[0]; if (cmd.SizeLeft > 1) code = code + (ulong)(cmd[1] << 8); if (cmd.SizeLeft > 2) code = code + (ulong)(cmd[2] << 16); if (repprefix != 0) // RER/REPE/REPNE is considered to be code = (code << 8) | repprefix; // part of command. t_cmddata pd = cmddata[0]; // Assignment to first entry is to avoid compiler error int pd_index = 0; if (decodevxd && (code & 0xFFFF) == 0x20CD) pd = vxdcmd; // Decode VxD call (Win95/98) else { for (pd_index = 0; pd_index < cmddata.Length; pd_index++) { pd = cmddata[pd_index]; if (((code ^ pd.code) & pd.mask) != 0) continue; if (mode >= DISASM_FILE && shortstringcmds && (pd.arg1 == MSO || pd.arg1 == MDE || pd.arg2 == MSO || pd.arg2 == MDE)) continue; // Search short form of string command break; }; }; if ((pd.type & C_TYPEMASK) == C_NOW) { // 3DNow! commands require additional search. is3dnow = true; j = Get3dnowsuffix(); if (j < 0) da.error = DAE_CROSS; else { //for (; pd.mask != 0; pd++) for (; pd_index < cmddata.Length; pd_index++) { pd = cmddata[pd_index]; if (((code ^ pd.code) & pd.mask) != 0) continue; //if (((uchar*)&(pd.code))[2] == j) if ((int)((pd.code & 0xFF0000) >> 24) == j) break; }; }; }; if (pd.mask == 0) { // Command not found da.cmdtype = C_BAD; if (cmd.SizeLeft < 2) da.error = DAE_CROSS; else da.error = DAE_BADCMD; } else { // Command recognized, decode it da.cmdtype = pd.type; cxsize = datasize; // Default size of ECX used as counter if (segprefix == SEG_FS || segprefix == SEG_GS || lockprefix) da.cmdtype |= C_RARE; // These prefixes are rare if (pd.bits == PR) da.warnings |= DAW_PRIV; // Privileged command (ring 0) else if (pd.bits == WP) da.warnings |= DAW_IO; // I/O command // Win32 programs usually try to keep stack dword-aligned, so INC ESP // (44) and DEC ESP (4C) usually don't appear in real code. Also check for // ADD ESP,imm and SUB ESP,imm (81,C4,imm32; 83,C4,imm8; 81,EC,imm32; // 83,EC,imm8). if (cmd[0] == 0x44 || cmd[0] == 0x4C || (cmd.SizeLeft >= 3 && (cmd[0] == 0x81 || cmd[0] == 0x83) && (cmd[1] == 0xC4 || cmd[1] == 0xEC) && (cmd[2] & 0x03) != 0) ) { da.warnings |= DAW_STACK; da.cmdtype |= C_RARE; }; // Warn also on MOV SEG,... (8E...). Win32 works in flat mode. if (cmd[0] == 0x8E) da.warnings |= DAW_SEGMENT; // If opcode is 2-byte, adjust command. if (pd.len == 2) { if (cmd.SizeLeft == 0) da.error = DAE_CROSS; else { if (mode >= DISASM_FILE) da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); srcip++; }; }; if (cmd.SizeLeft == 0) da.error = DAE_CROSS; // Some commands either feature non-standard data size or have bit which // allows to select data size. if ((pd.bits & WW) != 0 && (cmd[0] & WW) == 0) datasize = 1; // Bit W in command set to 0 else if ((pd.bits & W3) != 0 && (cmd[0] & W3) == 0) datasize = 1; // Another position of bit W else if ((pd.bits & FF) != 0) datasize = 2; // Forced word (2-byte) size // Some commands either have mnemonics which depend on data size (8/16 bits // or 32 bits, like CWD/CDQ), or have several different mnemonics (like // JNZ/JNE). First case is marked by either '&' (mnemonic depends on // operand size) or '$' (depends on address size). In the second case, // there is no special marker and disassembler selects main mnemonic. if (mode >= DISASM_FILE) { string name = string.Empty; if (pd.name[0] == '&') mnemosize = datasize; else if (pd.name[0] == '$') mnemosize = addrsize; else mnemosize = 0; if (mnemosize != 0) { for (i = 0, j = 1; pd.name[j] != '\0'; j++) { if (pd.name[j] == ':') { // Separator between 16/32 mnemonics if (mnemosize == 4) i = 0; else break; } else if (pd.name[j] == '*') { // Substitute by 'W', 'D' or none if (mnemosize == 4 && sizesens != 2) name = name + 'D'; else if (mnemosize != 4 && sizesens != 0) name = name + 'W'; } else name = name + pd.name[j]; }; //name[i] = '\0'; } else { name = pd.name; int comma_index = name.IndexOf(','); if (comma_index >= 0) name = name.Substring(0, comma_index - 1); }; da.result.Append(name); }; // Decode operands (explicit - encoded in command, implicit - present in // mmemonic or assumed - used or modified by command). Assumed operands // must stay after all explicit and implicit operands. Up to 3 operands // are allowed. for (operand = 0; operand < 3; operand++) { if (da.error != 0) break; // Error - no sense to continue // If command contains both source and destination, one usually must not // decode destination to comment because it will be overwritten on the // next step. Global addcomment takes care of this. Decoding routines, // however, may ignore this flag. if (operand == 0 && pd.arg2 != NNN && pd.arg2 < PSEUDOOP) addcomment = false; else addcomment = true; // Get type of next argument. if (operand == 0) arg = pd.arg1; else if (operand == 1) arg = pd.arg2; else arg = pd.arg3; if (arg == NNN) break; // No more operands // Arguments with arg>=PSEUDOOP are assumed operands and are not // displayed in disassembled result, so they require no delimiter. if ((mode >= DISASM_FILE) && arg < PSEUDOOP) { if (operand == 0) da.result.Append(' '); else da.result.Append(','); }; // Decode, analyse and comment next operand of the command. switch (arg) { case REG: // Integer register in Reg field if (cmd.SizeLeft < 2) da.error = DAE_CROSS; else DecodeRG(cmd[1] >> 3, datasize, REG); hasrm = true; break; case RCM: // Integer register in command byte DecodeRG(cmd[0], datasize, RCM); break; case RG4: // Integer 4-byte register in Reg field if (cmd.SizeLeft < 2) da.error = DAE_CROSS; else DecodeRG(cmd[1] >> 3, 4, RG4); hasrm = true; break; case RAC: // Accumulator (AL/AX/EAX, implicit) DecodeRG(REG_EAX, datasize, RAC); break; case RAX: // AX (2-byte, implicit) DecodeRG(REG_EAX, 2, RAX); break; case RDX: // DX (16-bit implicit port address) DecodeRG(REG_EDX, 2, RDX); break; case RCL: // Implicit CL register (for shifts) DecodeRG(REG_ECX, 1, RCL); break; case RS0: // Top of FPU stack (ST(0)) DecodeST(0, 0); break; case RST: // FPU register (ST(i)) in command byte DecodeST(cmd[0], 0); break; case RMX: // MMX register MMx if (cmd.SizeLeft < 2) da.error = DAE_CROSS; else DecodeMX(cmd[1] >> 3); hasrm = true; break; case R3D: // 3DNow! register MMx if (cmd.SizeLeft < 2) da.error = DAE_CROSS; else DecodeNR(cmd[1] >> 3); hasrm = true; break; case MRG: // Memory/register in ModRM byte case MRJ: // Memory/reg in ModRM as JUMP target case MR1: // 1-byte memory/register in ModRM byte case MR2: // 2-byte memory/register in ModRM byte case MR4: // 4-byte memory/register in ModRM byte case MR8: // 8-byte memory/MMX register in ModRM case MRD: // 8-byte memory/3DNow! register in ModRM case MMA: // Memory address in ModRM byte for LEA case MML: // Memory in ModRM byte (for LES) case MM6: // Memory in ModRm (6-byte descriptor) case MMB: // Two adjacent memory locations (BOUND) case MD2: // Memory in ModRM byte (16-bit integer) case MB2: // Memory in ModRM byte (16-bit binary) case MD4: // Memory in ModRM byte (32-bit integer) case MD8: // Memory in ModRM byte (64-bit integer) case MDA: // Memory in ModRM byte (80-bit BCD) case MF4: // Memory in ModRM byte (32-bit float) case MF8: // Memory in ModRM byte (64-bit float) case MFA: // Memory in ModRM byte (80-bit float) case MFE: // Memory in ModRM byte (FPU environment) case MFS: // Memory in ModRM byte (FPU state) case MFX: // Memory in ModRM byte (ext. FPU state) DecodeMR(arg); break; case MMS: // Memory in ModRM byte (as SEG:OFFS) DecodeMR(arg); da.warnings |= DAW_FARADDR; break; case RR4: // 4-byte memory/register (register only) case RR8: // 8-byte MMX register only in ModRM case RRD: // 8-byte memory/3DNow! (register only) if ((cmd[1] & 0xC0) != 0xC0) softerror = DAE_REGISTER; DecodeMR(arg); break; case MSO: // Source in string op's ([ESI]) DecodeSO(); break; case MDE: // Destination in string op's ([EDI]) DecodeDE(); break; case MXL: // XLAT operand ([EBX+AL]) DecodeXL(); break; case IMM: // Immediate data (8 or 16/32) case IMU: // Immediate unsigned data (8 or 16/32) if ((pd.bits & SS) != 0 && (cmd[0] & 0x02) != 0) DecodeIM(1, datasize, arg); else DecodeIM(datasize, 0, arg); break; case VXD: // VxD service (32-bit only) DecodeVX(); break; case IMX: // Immediate sign-extendable byte DecodeIM(1, datasize, arg); break; case C01: // Implicit constant 1 (for shifts) DecodeC1(); break; case IMS: // Immediate byte (for shifts) case IM1: // Immediate byte DecodeIM(1, 0, arg); break; case IM2: // Immediate word (ENTER/RET) DecodeIM(2, 0, arg); if ((da.immconst & 0x03) != 0) da.warnings |= DAW_STACK; break; case IMA: // Immediate absolute near data address DecodeIA(); break; case JOB: // Immediate byte offset (for jumps) DecodeRJ(1, srcip + 2); break; case JOW: // Immediate full offset (for jumps) DecodeRJ((ulong)datasize, (ulong)((int)srcip + datasize + 1)); break; case JMF: // Immediate absolute far jump/call addr DecodeJF(); da.warnings |= DAW_FARADDR; break; case SGM: // Segment register in ModRM byte if (cmd.SizeLeft < 2) da.error = DAE_CROSS; DecodeSG(cmd[1] >> 3); hasrm = true; break; case SCM: // Segment register in command byte DecodeSG(cmd[0] >> 3); if ((da.cmdtype & C_TYPEMASK) == C_POP) da.warnings |= DAW_SEGMENT; break; case CRX: // Control register CRx if ((cmd[1] & 0xC0) != 0xC0) da.error = DAE_REGISTER; DecodeCR(cmd[1]); break; case DRX: // Debug register DRx if ((cmd[1] & 0xC0) != 0xC0) da.error = DAE_REGISTER; DecodeDR(cmd[1]); break; case PRN: // Near return address (pseudooperand) break; case PRF: // Far return address (pseudooperand) da.warnings |= DAW_FARADDR; break; case PAC: // Accumulator (AL/AX/EAX, pseudooperand) DecodeRG(REG_EAX, datasize, PAC); break; case PAH: // AH (in LAHF/SAHF, pseudooperand) case PFL: // Lower byte of flags (pseudooperand) break; case PS0: // Top of FPU stack (pseudooperand) DecodeST(0, 1); break; case PS1: // ST(1) (pseudooperand) DecodeST(1, 1); break; case PCX: // CX/ECX (pseudooperand) DecodeRG(REG_ECX, cxsize, PCX); break; case PDI: // EDI (pseudooperand in MMX extentions) DecodeRG(REG_EDI, 4, PDI); break; default: da.error = DAE_INTERN; // Unknown argument type break; }; }; // Check whether command may possibly contain fixups. if (pfixup != -1 && da.fixupsize > 0) da.fixupoffset = pfixup; // Segment prefix and address size prefix are superfluous for command which // does not access memory. If this the case, mark command as rare to help // in analysis. if (da.memtype == DEC_UNKNOWN && (segprefix != SEG_UNDEF || (addrsize != 4 && pd.name[0] != '$'))) { da.warnings |= DAW_PREFIX; da.cmdtype |= C_RARE; }; // 16-bit addressing is rare in 32-bit programs. If this is the case, // mark command as rare to help in analysis. if (addrsize != 4) da.cmdtype |= C_RARE; }; // Suffix of 3DNow! command is accounted best by assuming it immediate byte // constant. if (is3dnow) { if (immsize != 0) da.error = DAE_BADCMD; else immsize = 1; }; // Right or wrong, command decoded. Now dump it. if (da.error != 0) { // Hard error in command detected if (mode >= DISASM_FILE) da.result.Append("???"); if (da.error == DAE_BADCMD && (cmd[0] == 0x0F || cmd[0] == 0xFF) && size > 0 ) { if (mode >= DISASM_FILE) da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); }; if (size > 0) { if (mode >= DISASM_FILE) da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); }; } else { // No hard error, dump command if (mode >= DISASM_FILE) { da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); if (hasrm) { da.dump.AppendFormat("{0:X}", cmd[0]); } if (hassib) { da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); } if (dispsize != 0) { da.dump.Append(' '); for (i = 0; i < dispsize; i++) { da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); }; }; if (immsize != 0) { da.dump.Append(' '); for (i = 0; i < immsize; i++) { da.dump.AppendFormat("{0:X}", cmd[0]); cmd.AdjustOffset(1); }; }; } else cmd.AdjustOffset(1 + (hasrm ? 1 : 0) + (hassib ? 1 : 0) + dispsize + immsize); }; // Check that command is not a dangerous one. if (mode >= DISASM_DATA) { //t_cmddata pdan; //for (pdan = dangerous; pdan.mask != 0; pdan++) foreach (t_cmddata pdan in dangerous) { if (((code ^ pdan.code) & pdan.mask) != 0) continue; if (pdan.type == C_DANGERLOCK && !lockprefix) break; // Command harmless without LOCK prefix if (iswindowsnt && pdan.type == C_DANGER95) break; // Command harmless under Windows NT // Dangerous command! if (pdan.type == C_DANGER95) da.warnings |= DAW_DANGER95; else da.warnings |= DAW_DANGEROUS; break; }; }; if (da.error == 0 && softerror != 0) da.error = softerror; // Error, but still display command if (mode >= DISASM_FILE) { if (da.error != DAE_NOERR) { switch (da.error) { case DAE_CROSS: da.comment = "Command crosses end of memory block"; break; case DAE_BADCMD: da.comment = "Unknown command"; break; case DAE_BADSEG: da.comment = "Undefined segment register"; break; case DAE_MEMORY: da.comment = "Illegal use of register"; break; case DAE_REGISTER: da.comment = "Memory address not allowed"; break; case DAE_INTERN: da.comment = "Internal OLLYDBG error"; break; default: da.comment = "Unknown error"; break; } } else if ((da.warnings & DAW_PRIV) != 0 && !privileged) da.comment = "Privileged command"; else if ((da.warnings & DAW_IO) != 0 && !iocommand) da.comment = "I/O command"; else if ((da.warnings & DAW_FARADDR) != 0 && !farcalls) { if ((da.cmdtype & C_TYPEMASK) == C_JMP) da.comment = "Far jump"; else if ((da.cmdtype & C_TYPEMASK) == C_CAL) da.comment = "Far call"; else if ((da.cmdtype & C_TYPEMASK) == C_RET) da.comment = "Far return"; } else if ((da.warnings & DAW_SEGMENT) != 0 && !farcalls) da.comment = "Modification of segment register"; else if ((da.warnings & DAW_SHIFT) != 0 && !badshift) da.comment = "Shift constant out of range 1..31"; else if ((da.warnings & DAW_PREFIX) != 0 && !extraprefix) da.comment = "Superfluous prefix"; else if ((da.warnings & DAW_LOCK) != 0 && !lockedbus) da.comment = "LOCK prefix"; else if ((da.warnings & DAW_STACK) != 0 && !stackalign) da.comment = "Unaligned stack operation"; }; return da; }