#!/usr/bin/perl # Filename: sim86 # Author: David Ljung Madison # See License: http://MarginalHacks.com/License/ # Description: Incomplete x86 binary simulator, thanks to Disassemble::X86 # References: # Dis: Disassemble::X86 thanks to Bob Mathews! # Insts: http://www.itis.mn.it/linux/quarta/x86/index.htm # Jumps: http://www.unixwiz.net/techtips/x86-jumps.html # Regs: http://www.campusprogram.com/reference/en/wikipedia/x/x8/x86_assembly_language.html # Regs: http://cs.wwc.edu/~aabyan/Unix/x86.html # ISA: http://x86.org/intel.doc/386manuals.htm use strict; use Disassemble::X86; # For debug #use Data::Dump qw(dump); ################################################## # Setup the variables ################################################## my $PROGNAME = $0; $PROGNAME =~ s|.*/||; ################################################## # Usage ################################################## sub fatal { foreach my $msg (@_) { print STDERR "[$PROGNAME] ERROR: $msg\n"; } exit(-1); } sub usage { foreach my $msg (@_) { print STDERR "ERROR: $msg\n"; } print STDERR < Simulates an x86 executable -q Be quiet -v Be verbose -V Be very verbose -do Start by running command file -batch Run a batch command file then exit USAGE exit -1; } sub parse_args { my %opt; while (my $arg=shift(@ARGV)) { if ($arg =~ /^-h$/) { usage(); } if ($arg =~ /^-d$/) { $MAIN::DEBUG=1; next; } if ($arg =~ /^-q$/) { $opt{quiet}=1; next; } if ($arg =~ /^-v$/) { $opt{verbose}=1; next; } if ($arg =~ /^-V$/) { $opt{verbose}=2; next; } if ($arg =~ /^-do$/) { source(\%opt,shift(@ARGV)); next; } if ($arg =~ /^-b(atch)?$/) { $opt{batch}=1; source(\%opt,shift(@ARGV)); next; } if ($arg =~ /^-ip$/) { $opt{start}{ip}=0+shift @ARGV; next; } if ($arg =~ /^-cs$/) { $opt{start}{cs}=0+shift @ARGV; next; } if ($arg =~ /^-/) { usage("Unknown option: $arg"); } usage("Too many files specified [$arg and $opt{file}]") if $opt{file}; $opt{file}=$arg; } usage("No file defined") unless $opt{file}; \%opt; } sub debug { return unless $MAIN::DEBUG; foreach my $msg (@_) { print STDERR "[$PROGNAME] $msg\n"; } } ################################################## # Memory ################################################## sub set_mem_byte { my ($opt,$addr,$val) = @_; $opt->{mem}{$addr} = $val; } sub get_mem_byte { my ($opt,$addr) = @_; defined($opt->{mem}{$addr}) ? $opt->{mem}{$addr} : $opt->{meminit}->get_byte($addr); } # Little-endian sub set_mem { my ($opt,$addr,$size,$val) = @_; printf "[0x%0.8x] <- 0x%0.8x [$size]\n",$addr,$val if $opt->{verbose}; for (my $byte=0; $byte<$size; $byte++) { my $byteval = $val & 0xff; $val >>= 8; set_mem_byte($opt,$addr+$byte,$byteval); } } sub get_mem { my ($opt,$addr,$size,$quiet) = @_; my $val = 0; for (my $byte=$size-1; $byte>=0; $byte--) { my $byteval = get_mem_byte($opt,$addr+$byte); $val <<= 8; $val |= $byteval; } printf "[0x%0.8x] -> 0x%0.8x [$size]\n",$addr,$val if $opt->{verbose}>1 && !$quiet; $val; } sub push_stack { my ($opt,$val,$size) = @_; $size /= 8; my $sp = $size==4 ? 'esp' : 'sp'; my $sp_val = get_reg($opt,$sp); $sp_val -= $size; set_reg($opt,$sp,$sp_val); my $top = calc_seg(get_reg($opt,'ss'), $sp_val); set_mem($opt,$top,$size,$val); } sub pop_stack { my ($opt,$size) = @_; $size /= 8; my $sp = $size==4 ? 'esp' : 'sp'; my $sp_val = get_reg($opt,$sp); my $top = calc_seg(get_reg($opt,'ss'), $sp_val); $sp_val += $size; set_reg($opt,$sp,$sp_val); get_mem($opt,$top,$size); } ################################################## # Registers ################################################## # ax is 16 bits. eax is 32 bits. al is low 8, ah is high 8 # standards: cx:counter, dx:i/o, si:DS data, di:ES data, sp:stack, bp:SS(stack) data my @REGS = qw(a b c d si di sp bp); # code, data, extra, extra2, extra3, stack my @SEGS = qw(cs ds es fs gs ss); my @FLAGS = qw(ip flags); my @CR = qw(cr0 cr2 cr3 cr4 gdtr ldtr idtr); # Also 'tr..' ?? sub init { my ($opt) = @_; # Init regs foreach my $reg ( @REGS, @SEGS, @FLAGS, @CR ) { $opt->{regs}{$reg}{val} = 0; } # Where should we put the segments?? (Default is segmented memory model) $opt->{regs}{cs}{val} = defined $opt->{start}{cs} ? $opt->{start}{cs} : 0x1000; $opt->{regs}{ds}{val} = defined $opt->{start}{ds} ? $opt->{start}{ds} : 0x2000; $opt->{regs}{ss}{val} = defined $opt->{start}{ss} ? $opt->{start}{ss} : 0x3000; $opt->{regs}{es}{val} = defined $opt->{start}{es} ? $opt->{start}{es} : 0x4000; $opt->{regs}{fs}{val} = defined $opt->{start}{fs} ? $opt->{start}{fs} : 0x5000; $opt->{regs}{gs}{val} = defined $opt->{start}{gs} ? $opt->{start}{gs} : 0x6000; # Arbitrary stack pointer $opt->{regs}{sp}{val} = 0x3fff; # Where do we start? set_ip($opt,$opt->{start}{ip}); # Flags start at 0x2 $opt->{regs}{flags}{val} = 0x2; } # ax is 16 bits. eax is 32 bits. al is low 8, ah is high 8 sub which_reg { my ($opt,$reg_str,$given_size) = @_; my $reg = $reg_str; my $ext = ($reg =~ s/^e(..)/$1/) ? 1 : 0; my $hi = ($reg =~ s/^([abcd])h/$1/) ? 1 : 0; my $lo = ($reg =~ s/^([abcd])l/$1/) ? 1 : 0; $reg =~ s/^([abcd])x/$1/; fatal("Register [$reg_str] can't be extended, hi and lo") if $ext+$hi+$lo > 1; return ($opt->{regs}{$reg}?1:0) unless wantarray; # Sanity check size my $size = $ext ? 32 : ($hi||$lo) ? 8 : 16; fatal("Size [$size] doesn't match [$reg_str]") if $given_size && $size != $given_size; fatal("Unknown register [$reg_str]") unless $opt->{regs}{$reg}; ($reg,$size,$hi); } sub get_reg { my ($opt,$reg_str,$size) = @_; (my $reg,$size,my $hi) = which_reg($opt,$reg_str,$size); my $val = $opt->{regs}{$reg}{val}; $val>>=8 if $hi; $val&=size_mask($size) if $size; return $val unless $opt->{verbose}>1; printf "%12s -> 0x%0.8x [%d]","\%$reg_str",$val,$size/8 unless $opt->{quiet}>1; show_flags($opt) if $reg eq 'flags'; print "\n" unless $opt->{quiet}>1; $val; } sub set_reg { my ($opt,$reg_str,$val,$size) = @_; (my $reg,$size, my $hi) = which_reg($opt,$reg_str,$size); my $mask = size_mask($size); $mask<<=8 if $hi; $val<<=8 if $hi; $opt->{regs}{$reg}{val} &= ~$mask; $opt->{regs}{$reg}{val} |= $val&$mask; printf "%12s <- 0x%0.8x [%d]\n","\%$reg_str",$opt->{regs}{$reg}{val},$size/8 if $opt->{verbose}; } ################################################## # FLAGS ################################################## # # FLAG BITS # --------- # 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 # ID VIP VIF AC VM RF 0 NT IOPL OF DF IF TF SF ZF 0 AF 0 PF 1 CF # # S Indicates a Status Flag # C Indicates a Control Flag # X Indicates a System Flag # # X ID Flag (ID) # X Virtual Interrupt Pending (VIP) # X Virtual Interrupt Flag (VIF) # X Alignment Check (AC) # X Virtual-8086 Mode (VM) # X Resume Flag (RF) # X Nested Task (NT) # X I/O Privilege Level (IOPL) # X Overflow Flag (OF) # X Direction Flag (DF) # X Interrupt Enable Flag (IF) # X Trap Flag (TF) # S Sign Flag (SF) # S Zero Flag (ZF) # S Auxiliary Carry Flag (AF) # S Parity Flag (PF) # S Carry Flag (CF) sub show_flags { my ($opt) = @_; my ($of,$sf,$zf,$af,$pf,$cf) = get_flags($opt); print " of=$of sf=$sf zf=$zf af=$af pf=$pf cf=$cf"; } # Need to rewrite this sub set_flags { my ($opt,$res,$size,$add,$v1,$v2,$no_carry) = @_; my $mask = size_mask($size); # Carry is unsigned my $cf = 0; if ($no_carry) { (undef, undef, undef, undef, undef, $cf) = get_flags($opt); } elsif (defined $v1) { my $uv1 = $v1 & $mask; my $uv2 = ($add ? -$v2 : $v2) & $mask; $cf = $uv1<$uv2 ? 1 : 0; } my $pf = 1; my $low = $res & 0xff; while ($low) { $pf=1-$pf if $low&1; $low>>=1; } my $zf = $res ? 0 : 1; my $sf = bit($res,$size-1); # Negative overflow if doesn't fit in mask # Positive overflow if signs change my $of = 0; $of = 1 if $res != ($res&$mask); my $sv1 = bit($v1,$size-1); my $sv2 = bit($v2,$size-1); $sv2 = 1-$sv2 unless $add; $of = 1 if $sv1==$sv2 && $sv1!=$sf; my $af = 0; # Borrow - only for mult and whatnot? my $flags = ($of<<11) | ($sf<<7) | ($zf<<6) | ($af<<4) | ($pf<<2) | ($cf<<0); $flags |= 0x000202; # 'IF' and '1' bits set $opt->{regs}{flags}{val} = $flags; get_reg($opt,'eflags') if $opt->{verbose}>1; # For verbosity $of; } sub TMP_set_flags { my ($opt,$of,$sf,$zf,$pf,$cf) = @_; my $flags = ($of<<11) | ($sf<<7) | ($zf<<6) | ($pf<<2) | ($cf<<0); $flags |= 0x000202; # 'IF' and '1' bits set $opt->{regs}{flags}{val} = $flags; get_reg($opt,'eflags') if $opt->{verbose}>1; # For verbosity } sub set_flag { my ($opt,$flag,$val) = @_; my ($of, $sf, $zf, $af, $pf, $cf) = get_flags($opt); $of = $val if $flag eq 'of'; $sf = $val if $flag eq 'sf'; $zf = $val if $flag eq 'zf'; $af = $val if $flag eq 'af'; $pf = $val if $flag eq 'pf'; $cf = $val if $flag eq 'cf'; my $flags = ($of<<11) | ($sf<<7) | ($zf<<6) | ($af<<4) | ($pf<<2) | ($cf<<0); $flags |= 0x000202; # IF and '1' bits set $opt->{regs}{flags}{val} = $flags; } sub get_flags { my ($opt) = @_; my $flags = $opt->{regs}{flags}{val}; my $of = bit($flags,11); my $sf = bit($flags,7); my $zf = bit($flags,6); my $af = bit($flags,4); my $pf = bit($flags,2); my $cf = bit($flags,0); ($of, $sf, $zf, $af, $pf, $cf); } sub get_flag { my ($opt,$flag) = @_; my $flags = $opt->{regs}{flags}{val}; return bit($flags,11) if $flag eq 'of'; return bit($flags,7) if $flag eq 'sf'; return bit($flags,6) if $flag eq 'zf'; return bit($flags,4) if $flag eq 'af'; return bit($flags,2) if $flag eq 'pf'; return bit($flags,0) if $flag eq 'cf'; die("Unknown flag? get_flag($flag)\n"); } ################################################## # PC/IP ################################################## sub set_ip { my ($opt,$pos,$quiet) = @_; my $dis = $opt->{dis}; if ($opt->{break}{ip}{$pos}) { $opt->{break}{HIT} = 1; printf "\nBREAKPOINT [0x%0.8x]\n",$pos unless $opt->{quiet}; } printf "%12s <- %0.8x [32]\n","\%eip",$pos unless $quiet; $opt->{regs}{ip}{val} = $pos; $opt->{pc} = calc_seg($opt->{regs}{cs}{val},$pos); $dis->pos($opt->{pc}); } sub next_inst { my ($opt,$len) = @_; set_ip($opt,$opt->{regs}{ip}{val}+$len,1); } sub jump { my ($opt,$type) = @_; my ($of, $sf, $zf, $af, $pf, $cf) = get_flags($opt); return 1 if $type eq 'mp'; # jmp. Heh. return $of==1 if $type eq 'o'; return $of==0 if $type eq 'no'; return $sf==1 if $type eq 's'; return $sf==0 if $type eq 'ns'; return $zf==1 if $type eq 'e' || $type eq 'z'; return $zf==0 if $type eq 'ne' || $type eq 'nz'; return $cf==1 if $type eq 'b' || $type eq 'nae' || $type eq 'c'; return $cf==0 if $type eq 'nb' || $type eq 'ae' || $type eq 'nc'; return ($cf==1 || $zf==1) if $type eq 'be' || $type eq 'na'; return ($cf==0 && $zf==0) if $type eq 'a' || $type eq 'nbe'; return ($sf!=$of) if $type eq 'l' || $type eq 'nge'; return ($sf==$of) if $type eq 'ge' || $type eq 'nl'; return ($zf==1 || $sf!=$of) if $type eq 'le' || $type eq 'ng'; return ($zf==0 && $sf==$of) if $type eq 'g' || $type eq 'nle'; return $pf==1 if $type eq 'p'; return $pf==0 if $type eq 'np'; return get_reg($opt,'cx')==0 if $type eq 'cxz'; return get_reg($opt,'ecx')==0 if $type eq 'ecxz'; fatal("Unknown jump? [j$type]"); } ################################################## # OPERANDS ################################################## sub size_mask { my ($size) = @_; return 0xffffffff if $size==32; return 0x0000ffff if $size==16; return 0x000000ff if $size==8; 2**($size)-1; } sub bit { my ($val,$bit) = @_; ($val>>$bit)&1; } sub ddump { my ($what) = @_; print "\nDUMP:\n"; dump($what); exit; } sub calc_seg { my ($seg,$base) = @_; ($seg<<16) | ($base); } sub calc_addr { my ($opt,$operand) = @_; my $size = $operand->{size}; my $arg = $operand->{arg}; fatal("Unknown calc_addr type") if $#$arg; $arg = $arg->[0]; #fatal("calc_addr size changed [$size != $arg->{size}]?") if $size != $arg->{size}; fatal("expected calc_addr size of 32?") if $arg->{size}!=32; get_operand($opt,$arg); } sub get_operand { my ($opt,$operand) = @_; my $op = $operand->{op}; my $size = $operand->{size}; my $mask = size_mask($size); if ($op eq 'reg') { my $reg = $operand->{arg}[0]; my $val = get_reg($opt,$reg, $size); return $val; } if ($op eq 'lit') { my $val = $operand->{arg}[0]; #print "Ret lit: $val & $mask\n"; return $val & $mask; } if ($op eq '*') { my $val1 = get_operand($opt,$operand->{arg}[0]); my $val2 = get_operand($opt,$operand->{arg}[1]); $val1 = extend($val1,$operand->{arg}[0]{size}); $val2 = extend($val2,$operand->{arg}[0]{size}); #print "Ret *: $val1 * $val2 [$mask]\n"; return ($val1*$val2) & $mask; } if ($op eq '+') { my $val1 = get_operand($opt,$operand->{arg}[0]); my $val2 = get_operand($opt,$operand->{arg}[1]); $val1 = extend($val1,$operand->{arg}[0]{size}); $val2 = extend($val2,$operand->{arg}[0]{size}); use integer; # Signed math here #print "Ret +: $val1 + $val2 [$mask]\n"; return ($val1+$val2) & $mask; } if ($op eq 'seg') { my $seg = get_operand($opt,$operand->{arg}[0]); my $base = get_operand($opt,$operand->{arg}[1]); return calc_seg($seg,$base); } if ($op eq 'mem') { my $addr = calc_addr($opt,$operand); my $val = get_mem($opt,$addr,$size/8); #print "GET MEM [$size] $addr -> $val\n"; return $val & $mask; } fatal("Unknown operand $operand->{op}\n"); } sub set_operand { my ($opt,$operand,$val) = @_; my $size = $operand->{size}; my $mask = size_mask($size); if ($operand->{op} eq 'reg') { my $reg = $operand->{arg}[0]; return set_reg($opt,$reg,$val); } if ($operand->{op} eq 'mem') { my $addr = calc_addr($opt,$operand); #print "SET MEM [$size] $addr <- $val\n"; return set_mem($opt,$addr,$size/8,$val); } fatal("unknown set operand $operand->{op}\n"); } ################################################## # MATH / ALU ################################################## sub extend { my ($val,$size) = @_; return $val if $size==32; my $extend = bit($val,$size-1) ? -1 : 0; my $mask = size_mask($size); ($val&$mask) | ($extend&~$mask); } sub twos_comp { my ($val,$size) = @_; ((~$val)+1) & size_mask($size); } # Do some math # Signed: # 0x7fffffff 2147483647 # 0x7ffffffe 2147483646 # 0x00000001 1 # 0x00000000 0 # 0xffffffff -1 # 0xfffffffe -2 # 0x80000001 -2147483647 # 0x80000000 -2147483648 # # Unsigned: # 0xffffffff 4294967295 # 0x00000001 1 # 0x00000000 0 sub signed_math { my ($opt, $add, $size, $v1, $s1, $v2, $s2, $cin) = @_; use integer; # Crucial! # For testing #print "\n"; #print " movl \$$v1, \%eax\n"; #print " movl \$$v2, \%edx\n"; #print " ".($add?'add':'sub')." \%edx, \%eax\n"; #print "\n"; #printf "Add $add - %0.8x %0.8x - $v1 [$s1] & $v2 [$s2] \n",$v1,$v2; # Size-prepare the operands my $mask = size_mask($size); $v1 = extend($v1,$s1) & $mask; $v2 = extend($v2,$s2) & $mask; # Figure out result my $res = $add ? $v1 + $v2 : $v1 - $v2; $res &= $mask; # Figure out flags # SF my $sf = bit($res,$size-1); # ZF my $zf = $res ? 0 : 1; # PF my $pf = 1; my $low = $res & 0xff; while ($low) { $pf=1-$pf if $low&1; $low>>=1; } # OF: signed: passing the 0x80000000 barrier either direction # OF: unsigned: passing the 0x0/0xffffffff barrier either direction my $sign1 = bit($v1,$size-1); my $sign2 = bit($v2,$size-1); # Convert to add $sign2 = 1-$sign2 unless $add; my $of = ($sign1==$sign2 && $sign1!=$sf) ? 1 : 0; # CF: my $cf = 0; # sub: cf if crossing 0x0->0xffffffff ($v2 > $v1) # add: cf if crossing 0xffffffff->0x0 (total is bigger than 0xffffffff) if ($add) { my $t = $mask-$v2; { no integer; # We need an unsigned compare here $cf = 1 if ($t&$mask) < ($v1&$mask); } } else { $cf = 1 if $v2 > $v1; } # printf "RES: %0.8x - $res: of=$of sf=$sf zf=$zf pf=$pf cf=$cf\n",$res; ($res,$of,$sf,$zf,$pf,$cf); } # For testing #my %opt; #signed_math(\%opt, 0, 32, 4, 32, -5, 32, 0); #signed_math(\%opt, 1, 32, 4, 32, 5, 32, 0); #signed_math(\%opt, 0, 32, 100, 32, 103, 32, 1); #signed_math(\%opt, 1, 32, 100, 32, -103, 32, 0); #signed_math(\%opt, 0, 32, 103, 32, 100, 32, 0); #signed_math(\%opt, 1, 32, -103, 32, 100, 32, 0); #signed_math(\%opt, 1, 32, 0x80000011, 32, 0x80000010, 32, 1); #signed_math(\%opt, 1, 32, 0xffff8011, 32, 0x80000010, 32, 1); #signed_math(\%opt, 1, 32, 0x80000011, 32, 0x80000010, 32, 1); #signed_math(\%opt, 1, 32, 0x80000010, 32, 0x80000011, 32, 1); #signed_math(\%opt, 1, 32, 0x00000005, 32, 0xfffffffe, 32, 1); #signed_math(\%opt, 1, 32, 0xfffffffe, 32, 0x00000005, 32, 1); #signed_math(\%opt, 0, 32, 0x80000011, 32, 0x80000010, 32, 0); #signed_math(\%opt, 0, 32, 0x80000010, 32, 0x80000011, 32, 1); ################################################## # SIMULATE ################################################## # Simulate an instruction sub sim { my ($opt) = @_; $opt->{simmed}++; my ($pos,$op,$op_str) = get_inst($opt); printf "\n# 0x%08x\t%s\n", $pos, $op_str if $opt->{verbose}; my $code = $op->{op}; my @operands = $op->{arg} ? @{$op->{arg}} : (); ## testing #$code = 'add'; # ax = ax - dx #$opt->{regs}{flags}{val} = 0x00000000; #$opt->{regs}{a}{val} = 0x80000000; #$opt->{regs}{d}{val} = 0x0ffff0f3; #$opt->{regs}{a}{val} = -3; #$opt->{regs}{d}{val} = -4; # #print "\n"; #print " movl \$$opt->{regs}{a}{val}, %eax\n"; #print " movl \$$opt->{regs}{d}{val}, %edx\n"; #print " $code %edx, %eax\n"; #print "\n"; if ($code eq 'nop') { # Nothing, dig? ######################### # Arithmetic ######################### } elsif ($code eq 'add' || $code eq 'adc') { my $v1 = get_operand($opt,$operands[0]); my $s1 = $operands[0]->{size}; my $v2 = get_operand($opt,$operands[1]); my $s2 = $operands[1]->{size}; my ($res,$of,$sf,$zf,$pf,$cf) = signed_math($opt, 1, $s1, $v1, $s1, $v2, $s2); # Just do the carry-in as a separate add. It's simpler. if ($code eq 'adc') { my (undef, undef, undef, undef, undef, $cin) = get_flags($opt); if ($cin) { my ($res2,$of2,$sf2,$zf2,$pf2,$cf2) = signed_math($opt, 1, $s1, $res, $s1, $cin, $s1); ($res,$of,$sf,$zf,$pf,$cf) = ($res2,$of|$of2,$sf2,$zf2,$pf2,$cf|$cf2); } } TMP_set_flags($opt,$of,$sf,$zf,$pf,$cf); set_operand($opt,$operands[0],$res); } elsif ($code eq 'cmp') { my $val1 = get_operand($opt,$operands[0]); my $val2 = get_operand($opt,$operands[1]); my $size = $operands[0]->{size}; my $res = ($val2>$val1) ? ~($val2-$val1-1) : ($val1-$val2); set_flags($opt,$res,$size,0,$val1,$val2); } elsif ($code eq 'inc') { my $v1 = get_operand($opt,$operands[0]); my $s1 = $operands[0]->{size}; my ($res,$of,$sf,$zf,$pf,$cf) = signed_math($opt, 1, $s1, $v1, $s1, 1, $s1); # INC doesn't set carry flag TMP_set_flags($opt,$of,$sf,$zf,$pf, get_flag($opt,'cf')); set_operand($opt,$operands[0],$res); } elsif ($code eq 'sub' || $code eq 'sbb' || $code eq 'cmp') { my $v1 = get_operand($opt,$operands[0]); my $s1 = $operands[0]->{size}; my $v2 = get_operand($opt,$operands[1]); my $s2 = $operands[1]->{size}; my ($res,$of,$sf,$zf,$pf,$cf) = signed_math($opt, 0, $s1, $v1, $s1, $v2, $s2); # Just do the borrow-in as a separate sub. It's simpler. if ($code eq 'sbb') { my (undef, undef, undef, undef, undef, $bin) = get_flags($opt); if ($bin) { my ($res2,$of2,$sf2,$zf2,$pf2,$cf2) = signed_math($opt, 0, $s1, $res, $s1, $bin, $s1); ($res,$of,$sf,$zf,$pf,$cf) = ($res2,$of|$of2,$sf2,$zf2,$pf2,$cf|$cf2); } } TMP_set_flags($opt,$of,$sf,$zf,$pf,$cf); set_operand($opt,$operands[0],$res) unless $code eq 'cmp'; } elsif ($code eq 'dec') { my $v1 = get_operand($opt,$operands[0]); my $s1 = $operands[0]->{size}; my ($res,$of,$sf,$zf,$pf,$cf) = signed_math($opt, 0, $s1, $v1, $s1, 1, $s1); # DEC doesn't set carry flag TMP_set_flags($opt,$of,$sf,$zf,$pf, get_flag($opt,'cf')); set_operand($opt,$operands[0],$res); ######################### # BitOps / Logic ######################### } elsif ($code eq 'shl' || $code eq 'sal') { my $val = get_operand($opt,$operands[0]); my $cnt = get_operand($opt,$operands[1]) & 0x1f; # 386 only uses 5bit cnt my $size = $operands[0]->{size}; # Shouldn't this be based on size? Right now all shifts are 32 bits my $cf = bit($val,31); set_flag($opt,'cf',$cf); my $res = $val<<$cnt; my $of = $cf!=bit($val,31); set_flag($opt,'of',$of) if $cnt==1; set_operand($opt,$operands[0],$res); } elsif ($code eq 'shr' || $code eq 'sar') { my $val = get_operand($opt,$operands[0]); my $cnt = get_operand($opt,$operands[1]) & 0x1f; # 386 only uses 5bit cnt my $size = $operands[0]->{size}; my $of = bit($val,$size); # Only set if $cnt==1 my $res = $val>>$cnt; $res = extend($res,$size-$cnt) if $code eq 'sar'; set_flag($opt,'of',$of) if $cnt==1; set_operand($opt,$operands[0],$res); } elsif ($code eq 'and' || $code eq 'test') { my $val1 = get_operand($opt,$operands[0]); my $val2 = get_operand($opt,$operands[1]); my $size = $operands[0]->{size}; my $res = ($val1 & $val2) & size_mask($size); set_flags($opt,$res,$size); set_operand($opt,$operands[0],$res) unless $code eq 'test'; } elsif ($code eq 'xor') { my $val1 = get_operand($opt,$operands[0]); my $val2 = get_operand($opt,$operands[1]); my $size = $operands[0]->{size}; my $res = ($val1 ^ $val2) & size_mask($size); set_flags($opt,$res,$size); set_operand($opt,$operands[0],$res); } elsif ($code eq 'or') { my $val1 = get_operand($opt,$operands[0]); my $val2 = get_operand($opt,$operands[1]); my $size = $operands[0]->{size}; my $res = ($val1 | $val2) & size_mask($size); set_flags($opt,$res,$size); set_operand($opt,$operands[0],$res); } elsif ($code eq 'not') { my $val = get_operand($opt,$operands[0]); set_operand($opt,$operands[0],~$val); ######################### # Stack/Memory ######################### } elsif ($code eq 'push') { # 386 behavior was get_val before set_reg(sp) (for 'push sp') my $val = get_operand($opt,$operands[0]); # Arguably this should be based on the 'D' flag in the SS segment my $size = 32; push_stack($opt,$val,$size); } elsif ($code eq 'pop') { my $val = pop_stack($opt,32); set_operand($opt,$operands[0],$val); } elsif ($code eq 'pusha' || $code eq 'pushad') { my $size = $code eq 'pushad' ? 32 : 16; my $e = $size==32 ? 'e' : ''; my $sp = get_reg($opt,$e.'sp'); push_stack($opt,get_reg($opt,$e.'ax'),$size); push_stack($opt,get_reg($opt,$e.'cx'),$size); push_stack($opt,get_reg($opt,$e.'dx'),$size); push_stack($opt,get_reg($opt,$e.'bx'),$size); push_stack($opt,$sp); push_stack($opt,get_reg($opt,$e.'bp'),$size); push_stack($opt,get_reg($opt,$e.'si'),$size); push_stack($opt,get_reg($opt,$e.'di'),$size); } elsif ($code eq 'pushf' || $code eq 'pushfd') { my $size = $code eq 'pushfd' ? 32 : 16; my $e = $size==32 ? 'e' : ''; push_stack($opt, get_reg($opt,$e.'flags'), 32); # Doubleword } elsif ($code eq 'popf' || $code eq 'popfd') { my $size = $code eq 'popfd' ? 32 : 16; my $e = $size==32 ? 'e' : ''; set_reg($opt,$e.'flags', pop_stack($opt,32)); # Doubleword } elsif ($code eq 'popa' || $code eq 'popad') { my $size = $code eq 'popad' ? 32 : 16; my $e = $size==32 ? 'e' : ''; set_reg($opt,$e.'di', pop_stack($opt,$size)); set_reg($opt,$e.'si', pop_stack($opt,$size)); set_reg($opt,$e.'bp', pop_stack($opt,$size)); pop_stack($opt,$size); set_reg($opt,$e.'bx', pop_stack($opt,$size)); set_reg($opt,$e.'dx', pop_stack($opt,$size)); set_reg($opt,$e.'cx', pop_stack($opt,$size)); set_reg($opt,$e.'ax', pop_stack($opt,$size)); } elsif ($code eq 'lea') { my $addr = calc_addr($opt,$operands[1]); my $size = $operands[0]->{size}; $addr &= size_mask($size); set_operand($opt,$operands[0],$addr); } elsif ($code =~ /^mov(.*)/) { my $type = $1; # mov is move # movzx is move zero extend fatal("Unknown mov: [mov$type]") unless $type =~ /^|sx|zx$/; my $val = get_operand($opt,$operands[1]); my $size = $operands[1]->{size}; # mov needs to mask old data in? $val = extend($val,$size) if $type eq 'sx'; if ($type eq '' && $size!=32) { my $old = get_operand($opt,$operands[0]); my $mask = size_mask($size); $val = ($old&~$mask) | ($val&$mask); } set_operand($opt,$operands[0], $val); ######################### # Internals ######################### } elsif ($code eq 'cmc') { set_flag($opt, 'cf', 1-get_flag($opt,'cf')); } elsif ($code eq 'clc') { set_flag($opt, 'cf', 0); ######################### # Control flow ######################### } elsif ($code =~ /^j(.*)$/) { if (jump($opt,$1)) { my $dest = get_operand($opt,$operands[0]); set_ip($opt,$dest); return; # Skip the next_inst() } } elsif ($code eq 'enter') { my $bytes = get_operand($opt,$operands[0]); my $level = get_operand($opt,$operands[1]); $level %= 32; my $ebp = get_reg($opt,'ebp'); push_stack($opt,$ebp,32); my $fp = get_reg($opt,'esp'); if ($level>0) { for (my $i=1; $i<$level; $i++) { $ebp -= 4; push_stack($opt,$ebp,32); } push_stack($opt,$fp,32); } set_reg($opt,'ebp',$fp); set_reg($opt,'esp', get_reg($opt,'esp') - $bytes); } elsif ($code eq 'leave') { set_reg($opt,'esp',get_reg($opt,'ebp')); set_reg($opt,'ebp',pop_stack($opt,32)); } elsif ($code eq 'ret' || $code eq 'call') { print STDERR "[$PROGNAME] WARNING: '$code' not implemented.\n"; $opt->{ret}=1 if $code eq 'ret'; ######################### # Unknown? ######################### } else { print "[$PROGNAME] UNKNOWN OPCODE! [$code]\n"; exit; } # Advance to the next instruction (skipped on jump) next_inst($opt,$op->{len}); } ################################################## # Command loop ################################################## sub get_inst { my ($opt,$addr) = @_; my $dis = $opt->{dis}; $addr = defined $addr ? $addr : calc_seg($opt->{regs}{cs}{val},$opt->{regs}{ip}{val}); $dis->pos($addr); my $op_str = $dis->disasm(); my $op = $dis->op(); my $pos = $dis->op_start(); # Sanity printf STDERR "Jump to nowhere: 0x%0.8x [try ip=0x40, cs=0?]\n",$pos unless defined $op_str; ($pos,$op,$op_str); } sub prompt { my ($opt,$num) = @_; my ($pos,$op,$op_str) = get_inst($opt); # Prompt printf STDERR "\n$PROGNAME: [0x%0.8x] $op_str : $num/$opt->{simmed}> ",$pos; } sub source { my ($opt,$file) = @_; fatal("Can't currently handle nested source commands, sorry") if $opt->{source}; return print STDERR "Warning: Can't read sourcefile [$file]\n" unless open(SOURCE,"<$file"); $opt->{source} = $file; } sub getcmd { my ($opt, $num) = @_; if ($opt->{nextcmd}) { $_ = delete $opt->{nextcmd}; print "$_\n"; return 1; } if ($opt->{source}) { $_ = ; s/#.*//; # Ignore comments in source return getcmd($opt, $num) unless !defined $_ || /\S/; return 1 if defined $_; return print if defined $_; close SOURCE; delete $opt->{source}; } return 0 if $opt->{batch}; prompt($opt,$num) unless $opt->{quiet}; $_ = <>; return 1 if defined $_; return 0; } sub cmdloop { my ($opt) = @_; my @history; my $opt_num = '(?:|\s+(\d+))'; while (getcmd($opt,scalar @history)) { chomp; # next unless /\S/; s/^\s*//; s/\s*$//; my $cmd = $_ || $history[-1]; next unless $cmd; push(@history,$cmd); last if $cmd =~ /^(q|quit|exit)$/; if ($cmd =~ /^(h(elp)?|\?)$/i) { print < Source (or '.') a file Running: s(tep) single-step assembly instructions S(tep) step instructions until a return c(ontinue) continue until breakpoint b(reak) set a breakpoint History: history Show last part of history ! Repeat history command !- Repeat -num commands back ! Repeat last matching command Values: p(rint) Show register value p(rint) regs Show register dump p(rint) Show memory dump d(is) [mem] Disassembly dump = Set a register value = Set memory value = [] Set memory value (by size) Most commands can be followed by a number to repeat. HELP next; } if ($cmd =~ /^p(rint)?\s+(\S.*)$/i) { my $what = $2; my $save = $opt->{verbose}; $opt->{verbose} = 2; if (lc($what) eq 'regs') { # For TESTING: # map get_reg($opt,$_), qw(eax ebx ecx edx esi edi ebp eflags); map get_reg($opt,$_), qw(eax ebx ecx edx esi edi esp ebp eip eflags); if ($what ne 'regs') { map get_reg($opt,$_), qw(cs ds es fs gs ss); map get_reg($opt,$_), qw(ecr0 ecr2 ecr3 ecr4 gdtr ldtr idtr); } } elsif (which_reg($opt,$what)) { get_reg($opt,$what); } else { $what = oct($what) if $what =~ /^0/; if ($what =~ /\D/) { print STDERR "Error: Unknown print: $what\n"; next; } foreach ( 0..3 ) { printf "0x%0.8x: ",$what; foreach ( 0..3 ) { printf " %0.8x", get_mem($opt,$what,4,1); $what+=4; } print "\n"; } } $opt->{verbose} = $save; next; } if ($cmd =~ /^d(is)?(\s+(\S.*))?$/i) { my $addr = $2 ? $3 : calc_seg($opt->{regs}{cs}{val},$opt->{regs}{ip}{val}); $addr = oct($addr) if $addr =~ /^0/; if ($addr =~ /\D/) { print STDERR "Error: Unknown print: $addr\n"; next; } foreach ( 0..10 ) { my ($pos,$op,$op_str) = get_inst($opt,$addr); printf "0x%0.8x: $op_str\n",$addr; $addr += $op->{len}; } next; } if ($cmd =~ /^(\S+)\s*=\s*(\S+)(\s+\[(\d+)\])?$/i) { my ($what,$val,$size) = ($1,$2,$4); $val = oct($val) if $val =~ /^0/; if (which_reg($opt,$what)) { print STDERR "Size ignored for register settings\n" if $size; set_reg($opt,$what,$val); next; } $what = oct($what) if $what =~ /^0/; unless ($what =~ /\D/) { set_mem($opt,$what,$size||4,$val); next; } print STDERR "Error: Unknown set: $what = $val\n"; next; } if ($cmd =~ /^(source|\.)\s+(.+)$/i) { my $save = $opt->{quiet}; $opt->{quiet} = 1; source($opt,$2); $opt->{quiet} = $save; next; } if ($cmd =~ /^b(reak)?\s+(.+)$/i) { my $what = $2; $what = oct($what) if $what =~ /^0/; $opt->{break}{ip}{$what} = 1; printf "Set breakpoint at 0x%0.8x\n",$what; next; } if ($cmd =~ /^bc\s+(.+)$/i) { my $what = $2; $what = oct($what) if $what =~ /^0/; print $opt->{break}{ip}{$what} ? "Cleared" : "No"; printf " breakpoint at 0x%0.8x\n",$what; $opt->{break}{mem}{$what} = 0; next; } if ($cmd =~ /^s(tep)?$opt_num$/) { my $num = $2 || 1; foreach (1..$num) { sim($opt); } next; } if ($cmd =~ /^S(tep)?$opt_num$/) { my $num = $2 || 1; foreach (1..$num) { $opt->{ret} = 0; while (!$opt->{ret}) { sim($opt); } } next; } if ($cmd =~ /^c(ontinue)?$opt_num$/i) { my $num = $2 || 1; foreach (1..$num) { $opt->{break}{HIT} = 0; while (!$opt->{break}{HIT}) { sim($opt); } } next; } # History: Handle !num, !-num and !str if ($cmd =~ /^history$/) { for(my $i=0; $i<=$#history; $i++) { print " $i $history[$i]\n"; } next; } if ($cmd =~ /^!(-)?(\d+)$/) { my ($back,$num) = ($1,$2); $num = $#history-$num if $back; if ($num>=0 && $num<=$#history) { pop @history; $opt->{nextcmd} = $history[$num]; } else { print STDERR "$num: Event not found\n"; } next; } if ($cmd =~ /^!(.+)$/) { my $str = $1; for(my $i=$#history-1; $i>=0; $i--) { if ($history[$i] =~ /^$str/) { $opt->{nextcmd} = $history[$i]; last; } } print STDERR "$str: Event not found\n" unless $opt->{nextcmd}; next; } print STDERR "Error: Unknown command: $cmd\n"; } } ################################################## # Main code ################################################## sub main { my $opt = parse_args(); open(FILE,"<$opt->{file}") || usage("Can't read: $opt->{file}"); my $data = join('',); close FILE; $opt->{meminit} = Disassemble::X86::MemRegion->new( mem => $data ); $opt->{dis} = Disassemble::X86->new(format => "Text", text => $data); init($opt); cmdloop($opt); } main();