├── .github └── main.png ├── recordandtrace ├── vncrec.go ├── qemu.go ├── job.go ├── disk.go ├── gdb.go └── main.go ├── README.md ├── jobserver ├── makedb.go └── main.go ├── int20h-gdb-trace └── main.go ├── remotegdb └── net.go ├── webui └── main.go └── benx86 └── main.go /.github/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjojo/dive-into-dos/HEAD/.github/main.png -------------------------------------------------------------------------------- /recordandtrace/vncrec.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "os/exec" 7 | ) 8 | 9 | type vncRecorder struct { 10 | p *exec.Cmd 11 | filename string 12 | } 13 | 14 | func startVNCRecording(vncport int) *vncRecorder { 15 | r := vncRecorder{} 16 | r.filename = fmt.Sprintf("%s.flv", randString(10)) 17 | r.p = exec.Command("flvrec.py", 18 | "-o", r.filename, fmt.Sprintf("localhost:%d", vncport-9000)) 19 | go r.p.Start() 20 | // if err != nil { 21 | // log.Fatalf("Unable to start VNC recorder %s", err.Error()) 22 | // } 23 | return &r 24 | } 25 | 26 | func (v *vncRecorder) stop() string { 27 | v.p.Process.Kill() 28 | v.p.Wait() 29 | return v.filename 30 | } 31 | 32 | func randString(n int) string { 33 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 34 | var bytes = make([]byte, n) 35 | rand.Read(bytes) 36 | for i, b := range bytes { 37 | bytes[i] = alphanum[b%byte(len(alphanum))] 38 | } 39 | return string(bytes) 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Deep Dive into the world of MS-DOS viruses 2 | === 3 | 4 | ![title](./.github/main.png) 5 | 6 | These are the tools that I wrote as a part of my talk, These are not fully intended to be used, however you can happily take bits from them if you need them as long as you respect the licence. 7 | 8 | It's worth mentioning that none of this code should be considered that great, it's mostly been written in a rush to get the talk done. 9 | 10 | ### WebUI 11 | 12 | This it the UI to view and inspect the database. You can find a prerendered version of it all here: https://dosv.benjojo.co.uk 13 | 14 | ### BenX86 15 | 16 | I don't know why you would want this, but the program is designed to interact with webui to pull code down, and then submit new subtasks to be completed, it does this by using a very bad x86 emulator to find date/time variations in the code path. 17 | 18 | ### Jobserver 19 | 20 | this is the core of the system, this allows many programs and servers to work on tasks, This also runs the SQLLite3 database. 21 | 22 | ### RecordandTrace 23 | 24 | this is the program that deals with testing a sample, gets jobs from jobserver and submits to it after it's done. 25 | 26 | ### int20h-gdb-trace 27 | 28 | Simple version of the tracer. 29 | 30 | 31 | 32 | ## Requirements 33 | 34 | Capstone: 35 | 36 | `apt install libcapstone-dev` 37 | 38 | flv2rec: 39 | 40 | `apt install python-pip` 41 | `pip install flv2rec` 42 | 43 | QEMU: 44 | 45 | `apt install qemu` 46 | 47 | **WARNING, I can't confirm if this works on any other version of qemu other than Debian 9's, I know this sounds nuts, but trust me on this one** 48 | 49 | Fatboy: 50 | 51 | Compile it from `https://github.com/John-K/fatboy` and install it to /usr/bin/ 52 | 53 | -------------------------------------------------------------------------------- /recordandtrace/qemu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | "os/exec" 9 | "time" 10 | ) 11 | 12 | func startQEMU(hda, fdd string) (VNCPort int, GDBSocket string, monitor string, qemu *exec.Cmd, err error) { 13 | VNCPort = freePort() 14 | GDBSocket = fmt.Sprintf("./gdbsocket-%d.socket", time.Now().UnixNano()) 15 | monitor = fmt.Sprintf("./monitor-%d.socket", time.Now().UnixNano()) 16 | 17 | qemuS := exec.Command("/usr/bin/qemu-system-x86_64", "-hda", hda, 18 | "-s", "-m", "1", "-fda", fdd, 19 | "-vnc", fmt.Sprintf(":%d", VNCPort-9000), 20 | "-chardev", fmt.Sprintf("socket,id=gdbs,path=%s,server,nowait", GDBSocket), 21 | "-gdb", "chardev:gdbs", 22 | "-chardev", fmt.Sprintf("socket,id=monitor,path=%s,server,nowait", monitor), 23 | "-monitor", "chardev:monitor") 24 | 25 | qemuS.Stderr = os.Stderr 26 | go qemuS.Run() 27 | return VNCPort, GDBSocket, monitor, qemuS, err 28 | } 29 | 30 | func freePort() int { 31 | for { 32 | addr, _ := net.ResolveTCPAddr("tcp", "localhost:0") 33 | l, _ := net.ListenTCP("tcp", addr) 34 | defer l.Close() 35 | if l.Addr().(*net.TCPAddr).Port < 9005 { 36 | continue 37 | } 38 | 39 | return l.Addr().(*net.TCPAddr).Port 40 | } 41 | 42 | } 43 | 44 | func qemuSendKeys(key string, socket string) { 45 | c, err := net.Dial("unix", socket) 46 | 47 | if err != nil { 48 | log.Fatalf("Unable to send key, becuase socket does not work %s", err.Error()) 49 | } 50 | 51 | dummy := make([]byte, 1000) 52 | // n, _ := c.Read(dummy) 53 | c.Read(dummy) 54 | // fmt.Print("A" + string(dummy[:n])) 55 | c.Write([]byte("\n")) 56 | dummy = make([]byte, 1000) 57 | // n, _ = c.Read(dummy) 58 | c.Read(dummy) 59 | 60 | // fmt.Print("B" + string(dummy[:n])) 61 | 62 | time.Sleep(time.Millisecond * 100) 63 | 64 | c.Write([]byte(fmt.Sprintf("\nsendkey %s\n", key))) 65 | dummy = make([]byte, 1000) 66 | // fmt.Print("C" + string(dummy[:n])) 67 | // n, _ = c.Read(dummy) 68 | c.Read(dummy) 69 | 70 | time.Sleep(time.Millisecond * 50) 71 | 72 | c.Close() 73 | } 74 | -------------------------------------------------------------------------------- /recordandtrace/job.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | /* 14 | { 15 | "ID": "abc123", 16 | "JobName": "Virus.DOS.Techo", 17 | "Binary": { 18 | "Type": "MZ", 19 | "Data": "abcd" 20 | } 21 | } 22 | */ 23 | 24 | type JobData struct { 25 | Binary struct { 26 | Data string `json:"Data"` 27 | Type string `json:"Type"` 28 | } `json:"Binary"` 29 | ID int `json:"ID"` 30 | JobName string `json:"JobName"` 31 | HasSideJob bool 32 | SideJob SideJob `json:"SideJob"` 33 | } 34 | 35 | type SideJob struct { 36 | DateBased bool 37 | Day, Month, Year, Hour, Min, Second int 38 | TimeBased bool 39 | OriginalID int 40 | SideJobID int 41 | } 42 | 43 | type JobFeedback struct { 44 | DoneAt time.Time 45 | FLV []byte 46 | FloppyDisk []byte 47 | Syscalls []dosSyscall 48 | RequestedJob JobData 49 | } 50 | 51 | var ( 52 | jobserver = flag.String("jobserver", "", "Job server IP to fetch a job from") 53 | ) 54 | 55 | func getJob(sidejob bool) (j *JobData, err error) { 56 | jj := JobData{} 57 | var jobdata []byte 58 | if *jobserver != "" { 59 | 60 | url := fmt.Sprintf("%s/newjob", *jobserver) 61 | 62 | if sidejob { 63 | url = fmt.Sprintf("%s/getsidejob", *jobserver) 64 | } 65 | 66 | resp, err := 67 | http.Get(url) 68 | 69 | if err != nil { 70 | return j, err 71 | } 72 | 73 | jobdata, err = ioutil.ReadAll(resp.Body) 74 | if err != nil { 75 | return j, err 76 | } 77 | } else { 78 | jobdata, err = ioutil.ReadFile("./job.json") 79 | if err != nil { 80 | return j, err 81 | } 82 | } 83 | 84 | err = json.Unmarshal(jobdata, &jj) 85 | fmt.Printf("[+] Got job: %s\n", jj.JobName) 86 | return &jj, err 87 | } 88 | 89 | func SubmitJob(flvpath string, fddimage []byte, syscalls []dosSyscall, OJob JobData) error { 90 | JF := JobFeedback{ 91 | DoneAt: time.Now(), 92 | } 93 | 94 | JF.FloppyDisk = fddimage 95 | 96 | JF.Syscalls = syscalls 97 | 98 | b, _ := ioutil.ReadFile(flvpath) 99 | JF.FLV = b 100 | JF.RequestedJob = OJob 101 | 102 | jsonbytes, _ := json.Marshal(JF) 103 | 104 | if *jobserver != "" { 105 | buf := bytes.NewReader(jsonbytes) 106 | url := fmt.Sprintf("%s/jobdone", *jobserver) 107 | 108 | if OJob.HasSideJob { 109 | url = fmt.Sprintf("%s/sidejobdone", *jobserver) 110 | } 111 | _, err := http.Post(url, "application/json", buf) 112 | return err 113 | 114 | } else { 115 | fmt.Print(string(jsonbytes)) 116 | } 117 | 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /recordandtrace/disk.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "crypto/md5" 7 | "encoding/base64" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "os/exec" 14 | "time" 15 | ) 16 | 17 | func makeDisk(malwarebase64, filename string) (hda, fdd string) { 18 | // First, Copy the base HDD image over to make a backup 19 | hda = fmt.Sprintf("./temphdd-%d.hda", time.Now().UnixNano()) 20 | hdaFD, err := os.Create(hda) 21 | if err != nil { 22 | log.Fatalf("Failed to make init HDD | Create file %s", err) 23 | } 24 | 25 | basehdaFD, err := os.Open("./base.hda") 26 | if err != nil { 27 | log.Fatalf("Failed to make init HDD | Opening base file %s", err) 28 | } 29 | 30 | _, err = io.Copy(hdaFD, basehdaFD) 31 | if err != nil { 32 | log.Fatalf("Failed to make init HDD | copying file %s", err) 33 | } 34 | hdaFD.Close() 35 | basehdaFD.Close() 36 | 37 | // Now the Floppy disk 38 | floppyname := fmt.Sprintf("./tempfdd-%d.fdd", time.Now().UnixNano()) 39 | fdd = floppyname 40 | fddFD, err := os.Create(floppyname) 41 | if err != nil { 42 | log.Fatalf("Failed to make init HDD | Create file %s", err) 43 | } 44 | 45 | basefddFD, err := os.Open("./base.fdd") 46 | if err != nil { 47 | log.Fatalf("Failed to make init HDD | Opening base file %s", err) 48 | } 49 | 50 | _, err = io.Copy(fddFD, basefddFD) 51 | if err != nil { 52 | log.Fatalf("Failed to make init HDD | copying file %s", err) 53 | } 54 | fddFD.Close() 55 | basefddFD.Close() 56 | 57 | // Write the malware to a temp file 58 | 59 | data, err := base64.StdEncoding.DecodeString(malwarebase64) 60 | if err != nil { 61 | log.Fatalf("Failed to decode malware base64 %s", err) 62 | } 63 | 64 | err = ioutil.WriteFile(filename, data, 0660) 65 | if err != nil { 66 | log.Fatalf("failed to write temp file %s", err) 67 | } 68 | 69 | // Now we need to put our sample on the floppy disk! 70 | 71 | fatboycmd := exec.Command("fatboy", floppyname, "add", filename) 72 | fatboycmd.Start() 73 | fatboycmd.Wait() 74 | 75 | // We should have the images in place now. 76 | os.Remove(filename) 77 | return hda, fdd 78 | } 79 | 80 | func areDisksDifferent(cleanpath, testpath string) bool { 81 | b1, _ := ioutil.ReadFile(cleanpath) 82 | b2, _ := ioutil.ReadFile(testpath) 83 | 84 | h1 := md5.Sum(b1) 85 | h2 := md5.Sum(b2) 86 | 87 | for i := 0; i < 15; i++ { 88 | if h1[i] != h2[i] { 89 | return true 90 | } 91 | } 92 | return false 93 | } 94 | 95 | func compressDisk(path string) []byte { 96 | b1, _ := ioutil.ReadFile(path) 97 | o := make([]byte, 0) 98 | buf := bytes.NewBuffer(o) 99 | gzr, _ := gzip.NewWriterLevel(buf, gzip.BestCompression) 100 | gzr.Write(b1) 101 | return buf.Bytes() 102 | } 103 | -------------------------------------------------------------------------------- /jobserver/makedb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "fmt" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | 12 | "crawshaw.io/sqlite" 13 | ) 14 | 15 | func makeDB() { 16 | pool, err := sqlite.Open("file:data.db", 0, 10) 17 | if err != nil { 18 | log.Fatalf("unable make database! %s", err.Error()) 19 | } 20 | 21 | // hmm, now we open the tar file. 22 | fd, err := os.Open("data.tar") 23 | if err != nil { 24 | log.Fatalf("unable open tar! %s", err.Error()) 25 | } 26 | 27 | rdtar := tar.NewReader(fd) 28 | 29 | conn := pool.Get(nil) 30 | if conn == nil { 31 | return 32 | } 33 | 34 | stmt, _, err := conn.PrepareTransient(`CREATE TABLE samples ( 35 | sample_id integer PRIMARY KEY AUTOINCREMENT, 36 | filename text NOT NULL, 37 | filetype text NOT NULL, 38 | samplebinary BLOB NOT NULL, 39 | evaluated integer, 40 | givenout integer, 41 | flv BLOB, 42 | floppydisk BLOB, 43 | syscalls BLOB 44 | );`) 45 | if err != nil { 46 | log.Fatalf("Table setup error 1 %s", err.Error()) 47 | } 48 | 49 | _, err = stmt.Step() 50 | if err != nil { 51 | log.Fatalf("Table setup error 2 %s", err.Error()) 52 | } 53 | 54 | if err := stmt.Finalize(); err != nil { 55 | log.Fatalf("Table setup error 3 %s", err.Error()) 56 | } 57 | conn.Close() 58 | conn = pool.Get(nil) 59 | if conn == nil { 60 | return 61 | } 62 | 63 | rdtar.Next() 64 | for { 65 | stmt, err = conn.Prepare( 66 | "INSERT INTO samples (filename, filetype, samplebinary) VALUES ($filename, $filetype, $sample);") 67 | 68 | if err != nil { 69 | log.Fatalf("Insert setup error %s", err.Error()) 70 | } 71 | 72 | header, err := rdtar.Next() 73 | if err != nil { 74 | log.Printf("done? %s", err.Error()) 75 | break 76 | } 77 | samplebin := make([]byte, 500*1024) 78 | n, _ := rdtar.Read(samplebin) 79 | 80 | stmt.SetBytes("$sample", samplebin[:n]) 81 | stmt.SetText("$filename", header.Name) 82 | stmt.SetText("$filetype", getMagic(samplebin[:n])) 83 | _, err = stmt.Step() 84 | if err != nil { 85 | log.Printf("Insert error %s", err.Error()) 86 | } 87 | stmt.Finalize() 88 | fmt.Print(".") 89 | } 90 | conn.Close() 91 | pool.Close() 92 | // defer pool.Put(conn) 93 | } 94 | 95 | func getMagic(data []byte) string { 96 | filecmd := exec.Command("/usr/bin/file", "-") 97 | 98 | stdi, _ := filecmd.StdinPipe() 99 | 100 | if filecmd.Stdout != nil { 101 | return "" 102 | } 103 | if filecmd.Stderr != nil { 104 | return "" 105 | } 106 | var b bytes.Buffer 107 | filecmd.Stdout = &b 108 | filecmd.Stderr = &b 109 | filecmd.Start() 110 | 111 | stdi.Write(data) 112 | stdi.Close() 113 | filecmd.Wait() 114 | 115 | output := b.Bytes() 116 | os := string(output) 117 | if strings.Contains(os, "/dev/stdin:") { 118 | return strings.Split(os, ":")[1] 119 | } 120 | return "" 121 | } 122 | 123 | // filename text NOT NULL, 124 | // filetype text NOT NULL, 125 | // samplebinary BLOB NOT NULL, 126 | -------------------------------------------------------------------------------- /recordandtrace/gdb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "sync" 10 | "time" 11 | 12 | gdb "github.com/benjojo/dive-into-dos/remotegdb" 13 | ) 14 | 15 | type dosSyscall struct { 16 | Time time.Time 17 | Opcode uint8 18 | Registers gdb.X86Registers 19 | DS, PostCode []byte 20 | PostCodeLocation int 21 | Marker int 22 | } 23 | 24 | type gdbTrace struct { 25 | Actions []dosSyscall 26 | stop bool 27 | g *gdb.GDBConnection 28 | l sync.Mutex 29 | Hack net.Conn 30 | } 31 | 32 | // F8 40 19 00 33 | var magicBreakpoint = 0x4289 // old 34 | // var magicBreakpoint = 0x1940f8 35 | 36 | func startTraceMSDOS(gdbsocket string) *gdbTrace { 37 | c, err := net.Dial("unix", gdbsocket) 38 | if err != nil { 39 | log.Fatal("Unabel to dial GDB socket") 40 | } 41 | 42 | gcon := gdb.NewConnection(c) 43 | gcon.SetBreakpoint(magicBreakpoint, true) 44 | 45 | syscallList := make([]dosSyscall, 0) 46 | 47 | gobj := &gdbTrace{ 48 | g: gcon, 49 | Actions: syscallList, 50 | Hack: c, 51 | } 52 | go gobj.backgroundTrace() 53 | return gobj 54 | } 55 | 56 | func (g *gdbTrace) Stop() { 57 | g.stop = true 58 | } 59 | 60 | func (g *gdbTrace) didCommandFinish() bool { 61 | g.l.Lock() 62 | defer g.l.Unlock() 63 | 64 | len := len(g.Actions) 65 | if len == 0 { 66 | return false 67 | } 68 | 69 | // check if has suspect syscalls that normally happen when waiting for DOS input 70 | if (g.Actions[len-1].Opcode == 10) && (g.Actions[len-2].Opcode == 93) { 71 | return true 72 | } 73 | 74 | return false 75 | } 76 | 77 | func (g *gdbTrace) SetMarker() { 78 | g.l.Lock() 79 | defer g.l.Unlock() 80 | 81 | g.Actions = append(g.Actions, dosSyscall{Time: time.Now(), Marker: 1}) 82 | } 83 | 84 | func (g *gdbTrace) backgroundTrace() { 85 | g.g.Continue() 86 | // magicBreakpoint 87 | g.g.UnsetBreakpoint(magicBreakpoint, true) 88 | g.g.Step() 89 | R, err := g.g.GetRegisters() 90 | if err != nil && !g.stop { 91 | log.Fatalf("failed to register %s", err.Error()) 92 | } 93 | 94 | // fmt.Printf("AH: %02x AKA: %s\n", R.Al, opCodes[fmt.Sprintf("%02x", R.Al)]) 95 | g.l.Lock() 96 | g.Actions = append(g.Actions, dosSyscall{Time: time.Now(), Opcode: R.Al, Registers: R}) 97 | g.l.Unlock() 98 | 99 | for { 100 | g.g.SetBreakpoint(magicBreakpoint, true) 101 | g.g.Continue() 102 | g.g.UnsetBreakpoint(magicBreakpoint, true) 103 | g.g.Step() 104 | R, err := g.g.GetRegisters() 105 | if err != nil { 106 | if !g.stop { 107 | log.Fatalf("failed to register %s", err.Error()) 108 | } else { 109 | log.Printf("Tracing disabled") 110 | return 111 | } 112 | } 113 | fmt.Print(".") 114 | 115 | DsPTR := int(R.Ds*16) + int(R.Dx) 116 | 117 | // if R.Al == 9 { 118 | // fmt.Printf("DEBUG: DsPTR location is %x\n", DsPTR) 119 | // fmt.Printf("DEBUG: DSS: %x DSS2: %x DS: %x EDX: %x DX: %x ", R.Dss, R.Dss2, R.Ds, R.Edx, R.Dx) 120 | 121 | // fmt.Printf("\n\n%#v\n", R) 122 | // } 123 | 124 | DSA, err := g.g.GetMemory(DsPTR, 100) 125 | if err != nil { 126 | log.Printf("Failed to get DS:DX for debug capture: %s", err.Error()) 127 | } 128 | 129 | // We need to get the code ran after the syscall, the two values we need to get these 130 | // have been placed on the stack, so we need SS:SP 131 | StackPTR := int(uint32(R.Ss)*16) + int(R.Esp) 132 | Stack, err := g.g.GetMemory(StackPTR, 10) 133 | if err != nil { 134 | log.Printf("Failed to get SS:SP for debug capture: %s", err.Error()) 135 | } 136 | 137 | // fmt.Printf("SS: %x ESP: %x ", R.Ss, R.Esp) 138 | // fmt.Printf("STACK PTR: %x\n", StackPTR) 139 | // fmt.Printf("STACK: %x\n", Stack) 140 | 141 | // Stack look here like: 142 | // 07 01 94 10 143 | // When we want it to look like: 144 | // 01 07 10 94 145 | 146 | // Meaning: 147 | // AA BB CC DD 148 | // To 149 | // BB AA DD CC 150 | 151 | // This is because we need to construct a PTR again for the code, so like: 152 | 153 | // 1094:0107 = Code Address Return 154 | 155 | // Major := binary.LittleEndian.Uint16([]byte{Stack[3], Stack[2]}) 156 | Major := binary.LittleEndian.Uint16([]byte{Stack[2], Stack[3]}) 157 | Minor := binary.LittleEndian.Uint16([]byte{Stack[0], Stack[1]}) 158 | 159 | CodePTR := int(int32(Major)*16) + int(Minor) 160 | ReturnCode, err := g.g.GetMemory(CodePTR, 50) 161 | if err != nil { 162 | log.Printf("Failed to get return code for debug capture: %s", err.Error()) 163 | } 164 | // fmt.Printf("Urgh CS: %x EIP: %x\n", Major, Minor) 165 | 166 | if R.Al == 42 { 167 | // fmt.Printf("RCode: %x\n", ReturnCode) 168 | 169 | // go FuckItProxy(g.Hack) 170 | 171 | // g.l.Lock() 172 | // log.Printf("Launching the fuck it proxy!") 173 | // time.Sleep(time.Hour) 174 | 175 | } 176 | 177 | g.l.Lock() 178 | g.Actions = append(g.Actions, dosSyscall{Time: time.Now(), Opcode: R.Al, Registers: R, DS: DSA, PostCode: ReturnCode, PostCodeLocation: CodePTR}) 179 | g.l.Unlock() 180 | } 181 | } 182 | 183 | func FuckItProxy(lol net.Conn) { 184 | l, err := net.Listen("tcp4", "localhost:1234") 185 | if err != nil { 186 | log.Printf("FuckItProxy was not able to run\n\n\n\n\n%s\n\n", err) 187 | return 188 | } 189 | 190 | conn, _ := l.Accept() 191 | 192 | go func() { 193 | io.Copy(conn, lol) 194 | }() 195 | io.Copy(lol, conn) 196 | 197 | } 198 | -------------------------------------------------------------------------------- /int20h-gdb-trace/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | gdb "github.com/benjojo/dive-into-dos/remotegdb" 9 | ) 10 | 11 | func main() { 12 | fmt.Print(".") 13 | 14 | opCodes := make(map[string]string) 15 | 16 | opCodes["00"] = "Program terminate" 17 | opCodes["01"] = "Keyboard input with echo" 18 | opCodes["02"] = "Display output" 19 | opCodes["03"] = "Wait for auxiliary device input" 20 | opCodes["04"] = "Auxiliary output" 21 | opCodes["05"] = "Printer output" 22 | opCodes["06"] = "Direct console I/O" 23 | opCodes["07"] = "Wait for direct console input without echo" 24 | opCodes["08"] = "Wait for console input without echo" 25 | opCodes["09"] = "Print string" 26 | opCodes["0a"] = "Buffered keyboard input" 27 | opCodes["0b"] = "Check standard input status" 28 | opCodes["0c"] = "Clear keyboard buffer, invoke keyboard function" 29 | opCodes["0d"] = "Disk reset" 30 | opCodes["0e"] = "Select disk" 31 | opCodes["0f"] = "Open file using FCB" 32 | opCodes["10"] = "Close file using FCB" 33 | opCodes["11"] = "Search for first entry using FCB" 34 | opCodes["12"] = "Search for next entry using FCB" 35 | opCodes["13"] = "Delete file using FCB" 36 | opCodes["14"] = "Sequential read using FCB" 37 | opCodes["15"] = "Sequential write using FCB" 38 | opCodes["16"] = "Create a file using FCB" 39 | opCodes["17"] = "Rename file using FCB" 40 | opCodes["18"] = "DOS dummy function (CP/M) (not used/listed)" 41 | opCodes["19"] = "Get current default drive" 42 | opCodes["1a"] = "Set disk transfer address" 43 | opCodes["1b"] = "Get allocation table information" 44 | opCodes["1c"] = "Get allocation table info for specific device" 45 | opCodes["1d"] = "DOS dummy function (CP/M) (not used/listed)" 46 | opCodes["1e"] = "DOS dummy function (CP/M) (not used/listed)" 47 | opCodes["1f"] = "Get pointer to default drive parameter table (undocumented)" 48 | opCodes["20"] = "DOS dummy function (CP/M) (not used/listed)" 49 | opCodes["21"] = "Random read using FCB" 50 | opCodes["22"] = "Random write using FCB" 51 | opCodes["23"] = "Get file size using FCB" 52 | opCodes["24"] = "Set relative record field for FCB" 53 | opCodes["25"] = "Set interrupt vector" 54 | opCodes["26"] = "Create new program segment" 55 | opCodes["27"] = "Random block read using FCB" 56 | opCodes["28"] = "Random block write using FCB" 57 | opCodes["29"] = "Parse filename for FCB" 58 | opCodes["2a"] = "Get date" 59 | opCodes["2b"] = "Set date" 60 | opCodes["2c"] = "Get time" 61 | opCodes["2d"] = "Set time" 62 | opCodes["2e"] = "Set/reset verify switch" 63 | opCodes["2f"] = "Get disk transfer address" 64 | opCodes["30"] = "Get DOS version number" 65 | opCodes["31"] = "Terminate process and remain resident" 66 | opCodes["32"] = "Get pointer to drive parameter table (undocumented)" 67 | opCodes["33"] = "Get/set Ctrl-Break check state & get boot drive" 68 | opCodes["34"] = "Get address to DOS critical flag (undocumented)" 69 | opCodes["35"] = "Get vector" 70 | opCodes["36"] = "Get disk free space" 71 | opCodes["37"] = "Get/set switch character (undocumented)" 72 | opCodes["38"] = "Get/set country dependent information" 73 | opCodes["39"] = "Create subdirectory (mkdir)" 74 | opCodes["3a"] = "Remove subdirectory (rmdir)" 75 | opCodes["3b"] = "Change current subdirectory (chdir)" 76 | opCodes["3c"] = "Create file using handle" 77 | opCodes["3d"] = "Open file using handle" 78 | opCodes["3e"] = "Close file using handle" 79 | opCodes["3f"] = "Read file or device using handle" 80 | opCodes["40"] = "Write file or device using handle" 81 | opCodes["41"] = "Delete file" 82 | opCodes["42"] = "Move file pointer using handle" 83 | opCodes["43"] = "Change file mode" 84 | opCodes["44"] = "I/O control for devices (IOCTL)" 85 | opCodes["45"] = "Duplicate file handle" 86 | opCodes["46"] = "Force duplicate file handle" 87 | opCodes["47"] = "Get current directory" 88 | opCodes["48"] = "Allocate memory blocks" 89 | opCodes["49"] = "Free allocated memory blocks" 90 | opCodes["4a"] = "Modify allocated memory blocks" 91 | opCodes["4b"] = "EXEC load and execute program (func 1 undocumented)" 92 | opCodes["4c"] = "Terminate process with return code" 93 | opCodes["4d"] = "Get return code of a sub-process" 94 | opCodes["4e"] = "Find first matching file" 95 | opCodes["4f"] = "Find next matching file" 96 | opCodes["50"] = "Set current process id (undocumented)" 97 | opCodes["51"] = "Get current process id (undocumented)" 98 | opCodes["52"] = "Get pointer to DOS \"INVARS\" (undocumented)" 99 | opCodes["53"] = "Generate drive parameter table (undocumented)" 100 | opCodes["54"] = "Get verify setting" 101 | opCodes["55"] = "Create PSP (undocumented)" 102 | opCodes["56"] = "Rename file" 103 | opCodes["57"] = "Get/set file date and time using handle" 104 | opCodes["58"] = "Get/set memory allocation strategy (3.x+, undocumented)" 105 | opCodes["59"] = "Get extended error information (3.x+)" 106 | opCodes["5a"] = "Create temporary file (3.x+)" 107 | opCodes["5b"] = "Create new file (3.x+)" 108 | opCodes["5c"] = "Lock/unlock file access (3.x+)" 109 | opCodes["5d"] = "Critical error information (undocumented 3.x+)" 110 | opCodes["5e"] = "Network services (3.1+)" 111 | opCodes["5f"] = "Network redirection (3.1+)" 112 | opCodes["60"] = "Get fully qualified file name (undocumented 3.x+)" 113 | opCodes["62"] = "Get address of program segment prefix (3.x+)" 114 | opCodes["63"] = "Get system lead byte table (MSDOS 2.25 only)" 115 | opCodes["64"] = "Set device driver look ahead (undocumented 3.3+)" 116 | opCodes["65"] = "Get extended country information (3.3+)" 117 | opCodes["66"] = "Get/set global code page (3.3+)" 118 | opCodes["67"] = "Set handle count (3.3+)" 119 | opCodes["68"] = "Flush buffer (3.3+)" 120 | opCodes["69"] = "Get/set disk serial number (undocumented DOS 4.0+)" 121 | opCodes["6a"] = "DOS reserved (DOS 4.0+)" 122 | opCodes["6b"] = "DOS reserved" 123 | opCodes["6c"] = "Extended open/create (4.x+)" 124 | opCodes["F8"] = "Set OEM INT 21 handler (functions F9-FF) (undocumented)" 125 | 126 | c, err := net.Dial("tcp", "localhost:1234") 127 | if err != nil { 128 | log.Fatalf("failed to dial %s", err.Error()) 129 | } 130 | 131 | g := gdb.NewConnection(c) 132 | for { 133 | g.SetBreakpoint(0x4289, true) 134 | g.Continue() 135 | g.UnsetBreakpoint(0x4289, true) 136 | g.Step() 137 | R, err := g.GetRegisters() 138 | if err != nil { 139 | log.Fatalf("failed to register %s", err.Error()) 140 | } 141 | 142 | fmt.Printf("AH: %02x AKA: %s\n", R.Al, opCodes[fmt.Sprintf("%02x", R.Al)]) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /recordandtrace/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | sidejobEnabled := flag.Bool("sidejobs", false, "Should I look for side jobs instead") 13 | 14 | flag.Parse() 15 | fmt.Print("Loading data...\n") 16 | 17 | // Steps: 18 | // [+] Obtain Manifest 19 | job, err := getJob(*sidejobEnabled) 20 | if err != nil { 21 | log.Fatalf("Unable to fetch job, %s", err.Error()) 22 | } 23 | log.Print("[+] Obtained Manifest") 24 | 25 | // [+] Make floppy disk image + Hard Disk 26 | filename := "test.com" 27 | if job.Binary.Type == "MZ" { 28 | filename = "test.exe" 29 | } 30 | 31 | hddimage, fddimage := makeDisk(job.Binary.Data, filename) 32 | log.Print("[+] Make floppy disk image + Hard Disk") 33 | 34 | // [+] Start QEMU in Stopped GDB mode and monitor mode set to stdin 35 | vncport, gdbsocketpath, monitorsocketpath, qemuProcess, err := startQEMU(hddimage, fddimage) 36 | if err != nil { 37 | log.Fatalf("Unable to start qemu ! %q", err.Error()) 38 | } 39 | log.Print("[+] Start QEMU in Stopped GDB mode + Monitor + VNC") 40 | 41 | // [+] Check boot every second for complete 42 | time.Sleep(time.Second * 6) 43 | 44 | // [+] Send keys to change drive, type in exec name 45 | log.Print("[+] Send keys to change drive, type in exec name") 46 | 47 | if *sidejobEnabled { 48 | log.Print("[+] Typing in date") 49 | 50 | if job.SideJob.DateBased { 51 | qemuSendKeys("d", monitorsocketpath) 52 | qemuSendKeys("a", monitorsocketpath) 53 | qemuSendKeys("t", monitorsocketpath) 54 | qemuSendKeys("e", monitorsocketpath) 55 | qemuSendKeys("kp_enter", monitorsocketpath) 56 | time.Sleep(time.Millisecond * 200) 57 | 58 | datestr := fmt.Sprintf("%02d-%02d-%d", job.SideJob.Month, job.SideJob.Day, job.SideJob.Year) 59 | 60 | for _, v := range []byte(datestr) { 61 | switch v { 62 | case '0': 63 | qemuSendKeys("0", monitorsocketpath) 64 | case '1': 65 | qemuSendKeys("1", monitorsocketpath) 66 | case '2': 67 | qemuSendKeys("2", monitorsocketpath) 68 | case '3': 69 | qemuSendKeys("3", monitorsocketpath) 70 | case '4': 71 | qemuSendKeys("4", monitorsocketpath) 72 | case '5': 73 | qemuSendKeys("5", monitorsocketpath) 74 | case '6': 75 | qemuSendKeys("6", monitorsocketpath) 76 | case '7': 77 | qemuSendKeys("7", monitorsocketpath) 78 | case '8': 79 | qemuSendKeys("8", monitorsocketpath) 80 | case '9': 81 | qemuSendKeys("9", monitorsocketpath) 82 | case '-': 83 | qemuSendKeys("minus", monitorsocketpath) 84 | } 85 | } 86 | 87 | qemuSendKeys("kp_enter", monitorsocketpath) 88 | time.Sleep(time.Millisecond * 200) 89 | 90 | } 91 | 92 | // Can't and won't always be correct, but we can try 93 | if job.SideJob.TimeBased { 94 | qemuSendKeys("t", monitorsocketpath) 95 | qemuSendKeys("i", monitorsocketpath) 96 | qemuSendKeys("m", monitorsocketpath) 97 | qemuSendKeys("e", monitorsocketpath) 98 | qemuSendKeys("kp_enter", monitorsocketpath) 99 | time.Sleep(time.Millisecond * 200) 100 | 101 | timestr := fmt.Sprintf("%02d:%02d:%02d.0", job.SideJob.Hour, job.SideJob.Min, job.SideJob.Second) 102 | 103 | for _, v := range []byte(timestr) { 104 | switch v { 105 | case '0': 106 | qemuSendKeys("0", monitorsocketpath) 107 | case '1': 108 | qemuSendKeys("1", monitorsocketpath) 109 | case '2': 110 | qemuSendKeys("2", monitorsocketpath) 111 | case '3': 112 | qemuSendKeys("3", monitorsocketpath) 113 | case '4': 114 | qemuSendKeys("4", monitorsocketpath) 115 | case '5': 116 | qemuSendKeys("5", monitorsocketpath) 117 | case '6': 118 | qemuSendKeys("6", monitorsocketpath) 119 | case '7': 120 | qemuSendKeys("7", monitorsocketpath) 121 | case '8': 122 | qemuSendKeys("8", monitorsocketpath) 123 | case '9': 124 | qemuSendKeys("9", monitorsocketpath) 125 | case ':': 126 | qemuSendKeys("shift-semicolon", monitorsocketpath) 127 | case '.': 128 | qemuSendKeys("dot", monitorsocketpath) 129 | } 130 | } 131 | 132 | qemuSendKeys("kp_enter", monitorsocketpath) 133 | time.Sleep(time.Millisecond * 200) 134 | log.Print("[+] Typing in binary path") 135 | 136 | } 137 | } 138 | 139 | qemuSendKeys("a", monitorsocketpath) 140 | qemuSendKeys("shift-semicolon", monitorsocketpath) 141 | qemuSendKeys("kp_enter", monitorsocketpath) 142 | time.Sleep(time.Millisecond * 200) 143 | qemuSendKeys("t", monitorsocketpath) 144 | qemuSendKeys("e", monitorsocketpath) 145 | qemuSendKeys("s", monitorsocketpath) 146 | qemuSendKeys("t", monitorsocketpath) 147 | qemuSendKeys("dot", monitorsocketpath) 148 | if job.Binary.Type == "MZ" { 149 | qemuSendKeys("e", monitorsocketpath) 150 | qemuSendKeys("x", monitorsocketpath) 151 | qemuSendKeys("e", monitorsocketpath) 152 | } else { 153 | qemuSendKeys("c", monitorsocketpath) 154 | qemuSendKeys("o", monitorsocketpath) 155 | qemuSendKeys("m", monitorsocketpath) 156 | } 157 | // qemuSendKeys("p", monitorsocketpath) 158 | // qemuSendKeys("a", monitorsocketpath) 159 | // qemuSendKeys("h", monitorsocketpath) 160 | // qemuSendKeys("dot", monitorsocketpath) 161 | // qemuSendKeys("c", monitorsocketpath) 162 | // qemuSendKeys("o", monitorsocketpath) 163 | // qemuSendKeys("m", monitorsocketpath) 164 | 165 | // [+] Install trace handle breakpoint 166 | log.Print("[+] Install trace handle breakpoint") 167 | 168 | gdbtracer := startTraceMSDOS(gdbsocketpath) 169 | 170 | // [+] Start VNC recorder 171 | log.Print("[+] Start VNC recorder") 172 | 173 | vncr := startVNCRecording(vncport) 174 | // [+] Send enter 175 | log.Print("[+] Send enter") 176 | 177 | qemuSendKeys("kp_enter", monitorsocketpath) 178 | 179 | // [+] Wait 15 seconds 180 | log.Print("[+] Wait 15 seconds") 181 | 182 | time.Sleep(time.Second * 15) 183 | 184 | if gdbtracer.didCommandFinish() { 185 | log.Print("[+] Marking and then trying to run a few short commands to test infections") 186 | 187 | gdbtracer.SetMarker() 188 | qemuSendKeys("p", monitorsocketpath) 189 | qemuSendKeys("r", monitorsocketpath) 190 | qemuSendKeys("i", monitorsocketpath) 191 | qemuSendKeys("n", monitorsocketpath) 192 | qemuSendKeys("t", monitorsocketpath) 193 | qemuSendKeys("dot", monitorsocketpath) 194 | qemuSendKeys("c", monitorsocketpath) 195 | qemuSendKeys("o", monitorsocketpath) 196 | qemuSendKeys("m", monitorsocketpath) 197 | gdbtracer.SetMarker() 198 | qemuSendKeys("kp_enter", monitorsocketpath) 199 | 200 | log.Print("[+] Send enter") 201 | time.Sleep(time.Second * 2) 202 | } 203 | 204 | // [+] Stop recorder 205 | log.Print("[+] Stop recorder") 206 | 207 | vncrecFN := vncr.stop() 208 | syscalls := gdbtracer.Actions 209 | 210 | // [+] Stop QEMU 211 | log.Print("[+] Stop QEMU") 212 | 213 | gdbtracer.SetMarker() // TEMP: REMOVE ASAP 214 | gdbtracer.Stop() 215 | qemuProcess.Process.Kill() 216 | qemuProcess.Wait() 217 | 218 | // [+] Check floppy for changes 219 | log.Print("[+] Check floppy for changes") 220 | var diskimg []byte 221 | if areDisksDifferent(fddimage, "./base.fdd") { 222 | diskimg = compressDisk(fddimage) 223 | } 224 | 225 | // [+] Send (Recording + Diff binarys + GDB Trace log) 226 | log.Print("[+] Send (Recording + Diff binarys + GDB Trace log)") 227 | err = SubmitJob(vncrecFN, diskimg, syscalls, *job) 228 | if err != nil { 229 | log.Printf("[-] Failed to send, %s", err.Error()) 230 | } 231 | 232 | os.Remove(monitorsocketpath) 233 | os.Remove(gdbsocketpath) 234 | os.Remove(hddimage) 235 | os.Remove(fddimage) 236 | os.Remove(vncrecFN) 237 | 238 | // log.Printf("VNC: %s \n syscalls: %+v", vncrecFN, syscalls) 239 | } 240 | -------------------------------------------------------------------------------- /remotegdb/net.go: -------------------------------------------------------------------------------- 1 | package gdb 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "time" 9 | ) 10 | 11 | type GDBConnection struct { 12 | c io.ReadWriter 13 | askedVcont bool 14 | } 15 | 16 | func NewConnection(C io.ReadWriter) *GDBConnection { 17 | C.Write([]byte("+")) 18 | 19 | g := &GDBConnection{ 20 | c: C, 21 | } 22 | 23 | g.checksumAndSend("qSupported:xmlRegisters=i386,arm,mips") 24 | 25 | time.Sleep(time.Millisecond * 500) 26 | throwaway := make([]byte, 100000) 27 | g.c.Read(throwaway) 28 | 29 | g.checksumAndSend("Hg0") 30 | time.Sleep(time.Millisecond * 500) 31 | throwaway = make([]byte, 100000) 32 | g.c.Read(throwaway) 33 | 34 | return g 35 | } 36 | 37 | type X86Registers struct { 38 | Eax uint32 39 | Ecx uint32 40 | Edx uint32 41 | Ebx uint32 42 | Esp uint32 43 | Ebp uint32 44 | Esi uint32 45 | Edi uint32 46 | Eip uint32 47 | Eflags uint32 48 | Cs uint32 49 | Ss uint16 50 | Ds uint32 51 | Dss uint16 52 | Dss2 uint16 53 | Dss3 uint16 54 | Es uint32 55 | Ess uint16 56 | Fs uint32 57 | Gs uint32 58 | Ax uint16 59 | Dx uint16 60 | Bx uint16 61 | Ah uint8 62 | Al uint8 63 | } 64 | 65 | func (g *GDBConnection) GetRegisters() (o X86Registers, err error) { 66 | g.checksumAndSend("g") // https://www.reddit.com/r/ggggg/ 67 | rout, err := g.readAndChecksum() 68 | if err != nil { 69 | return o, err // ERR 70 | } 71 | 72 | // 00840000 73 | // f90000007f00000000000000900a0000000000000d010000fb010000d24000004600000019000000160100001601000016010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000 74 | 75 | hexs, err := hex.DecodeString(rout[:len(rout)-1]) 76 | if err != nil { 77 | return o, err 78 | } 79 | 80 | o.Eax = binary.LittleEndian.Uint32(hexs[0 : 0+4]) 81 | o.Ax = binary.LittleEndian.Uint16(hexs[0 : 0+2]) 82 | o.Ah = uint8(hexs[0]) 83 | o.Al = uint8(hexs[1]) 84 | 85 | o.Ecx = binary.LittleEndian.Uint32(hexs[4 : 4+4]) 86 | o.Edx = binary.LittleEndian.Uint32(hexs[8 : 8+4]) 87 | o.Dx = binary.LittleEndian.Uint16(hexs[8 : 8+2]) 88 | 89 | o.Ebx = binary.LittleEndian.Uint32(hexs[12 : 12+4]) 90 | o.Bx = binary.LittleEndian.Uint16(hexs[12 : 12+2]) 91 | 92 | // o.Esp = binary.LittleEndian.Uint32(hexs[16 : 16+4]) 93 | // comes in like 94 | // 00009412 95 | // should look like 96 | // 0000:1294 97 | // 11223344 98 | // 11224433 99 | ESPstart := 16 100 | SPR := make([]byte, 4) 101 | SPR[1], SPR[0], SPR[3], SPR[2] = hexs[ESPstart+1], hexs[ESPstart], hexs[ESPstart+3], hexs[ESPstart+2] 102 | o.Esp = binary.LittleEndian.Uint32(SPR) 103 | // fmt.Printf("IS THIS WHAT YOU WANT? SPR = %x AND ESP = %x\n", SPR, o.Esp) 104 | 105 | o.Ebp = binary.LittleEndian.Uint32(hexs[20 : 20+4]) 106 | o.Esi = binary.LittleEndian.Uint32(hexs[24 : 24+4]) 107 | o.Edi = binary.LittleEndian.Uint32(hexs[28 : 28+4]) 108 | o.Eip = binary.LittleEndian.Uint32(hexs[32 : 32+4]) 109 | o.Eflags = binary.LittleEndian.Uint32(hexs[36 : 36+4]) 110 | // o.Cs = binary.LittleEndian.Uint32(hexs[40 : 40+4]) 111 | // o.Ss = binary.LittleEndian.Uint32(hexs[44 : 44+4]) 112 | // o.Ds = binary.LittleEndian.Uint32(hexs[48 : 48+4]) 113 | 114 | // comes in like 115 | // 00009412 116 | // should look like 117 | // 0000:1294 118 | // 11223344 119 | // 11224433 120 | 121 | CSR := make([]byte, 4) 122 | CSR[1], CSR[0], CSR[3], CSR[2] = hexs[45], hexs[44], hexs[42], hexs[43] 123 | 124 | SSR := make([]byte, 2) 125 | SSR[1], SSR[0] = hexs[45], hexs[44] 126 | 127 | DSR := make([]byte, 4) 128 | DSR[1], DSR[0], DSR[3], DSR[2] = hexs[53], hexs[52], hexs[50], hexs[51] 129 | 130 | o.Cs = binary.LittleEndian.Uint32(CSR[:]) 131 | o.Ss = binary.LittleEndian.Uint16(SSR[:]) 132 | o.Ds = binary.LittleEndian.Uint32(DSR[:]) 133 | 134 | fmt.Printf("\nRAW REGS: %x\n\n", hexs) 135 | // fmt.Printf("HAHAH %x OR! %x\n", CSR) 136 | o.Dss = binary.LittleEndian.Uint16(hexs[48 : 48+2]) 137 | o.Dss2 = binary.LittleEndian.Uint16(hexs[50 : 50+2]) 138 | o.Es = binary.LittleEndian.Uint32(hexs[52 : 52+4]) 139 | o.Ess = binary.LittleEndian.Uint16(hexs[52 : 52+2]) 140 | o.Fs = binary.LittleEndian.Uint32(hexs[56 : 56+4]) 141 | o.Gs = binary.LittleEndian.Uint32(hexs[60 : 60+4]) 142 | 143 | return o, err 144 | } 145 | 146 | func (g *GDBConnection) Continue() { 147 | if !g.askedVcont { 148 | // fuck it, I'm going to assume it works 149 | g.checksumAndSend("vCont?") 150 | time.Sleep(time.Millisecond * 50) 151 | g.readAndChecksum() 152 | g.askedVcont = true 153 | } 154 | g.checksumAndSend("vCont;c") 155 | g.readAndChecksum() 156 | } 157 | 158 | func (g *GDBConnection) Step() { 159 | g.checksumAndSend("vCont;s") 160 | g.readAndChecksum() 161 | } 162 | 163 | func (g *GDBConnection) SetBreakpoint(address int, hardwareBreakpoint bool) error { 164 | hardwarebit := 1 165 | if hardwareBreakpoint != true { 166 | hardwarebit = 0 167 | } 168 | 169 | err := g.checksumAndSend(fmt.Sprintf("Z%d,%04x,1", hardwarebit, address)) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | _, err = g.readAndChecksum() 175 | if err != nil { 176 | return err 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func (g *GDBConnection) GetMemory(address int, length int) (ram []byte, err error) { 183 | 184 | err = g.checksumAndSend(fmt.Sprintf("m%x,%d", address, length)) 185 | if err != nil { 186 | return ram, err 187 | } 188 | 189 | rams, err := g.readAndChecksum() 190 | if err != nil { 191 | return ram, err 192 | } 193 | 194 | if len(rams)%2 == 1 { 195 | rams = rams + "0" 196 | } 197 | 198 | ram, err = hex.DecodeString(rams) 199 | 200 | return ram, err 201 | } 202 | 203 | func (g *GDBConnection) UnsetBreakpoint(address int, hardwareBreakpoint bool) error { 204 | hardwarebit := 1 205 | if hardwareBreakpoint != true { 206 | hardwarebit = 0 207 | } 208 | 209 | err := g.checksumAndSend(fmt.Sprintf("z%d,%04x,1", hardwarebit, address)) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | _, err = g.readAndChecksum() 215 | if err != nil { 216 | return err 217 | } 218 | 219 | return nil 220 | } 221 | 222 | var EremoteGDBerror = fmt.Errorf("The remove GDB stub was unable to decode message") 223 | 224 | func (g *GDBConnection) checksumAndSend(msg string) error { 225 | cs := byte(0) 226 | for _, v := range []byte(msg) { 227 | cs += v 228 | } 229 | 230 | message := fmt.Sprintf("$%s#%02x", msg, cs) 231 | 232 | n, err := g.c.Write([]byte(message)) 233 | if err != nil { 234 | return err 235 | } 236 | 237 | if n != len(message) { 238 | // Uh. fuck? 239 | panic("Window too full, not bothered to write this code yet.") 240 | } 241 | 242 | o := make([]byte, 1) 243 | _, err = g.c.Read(o) 244 | if err != nil { 245 | return err 246 | } 247 | 248 | if o[0] == byte('+') { 249 | return nil 250 | } 251 | return EremoteGDBerror 252 | } 253 | 254 | var EbadGDBresponce = fmt.Errorf("GDB Stub gave back an abnormal response") 255 | 256 | func (g *GDBConnection) readAndChecksum() (string, error) { 257 | jumbobuffer := make([]byte, 0) 258 | 259 | mini := make([]byte, 1) 260 | g.c.Read(mini) 261 | 262 | if mini[0] == '+' { 263 | g.c.Read(mini) 264 | } 265 | 266 | if mini[0] != '$' { 267 | fmt.Printf("???????? %s ?????????/\n", string(mini)) 268 | return string(mini), EbadGDBresponce 269 | } 270 | 271 | for { 272 | mini := make([]byte, 128000) 273 | n, err := g.c.Read(mini) 274 | if err != nil { 275 | return "", err 276 | } 277 | 278 | hashash := false 279 | hashpos := 0 280 | for k, v := range mini[:n] { 281 | if v == '#' { 282 | hashash = true 283 | hashpos = k 284 | } 285 | } 286 | 287 | if hashash { 288 | if hashpos == 0 { 289 | break 290 | } 291 | jumbobuffer = append(jumbobuffer, mini[:hashpos-1]...) 292 | break 293 | } 294 | jumbobuffer = append(jumbobuffer, mini...) 295 | } 296 | 297 | g.c.Write([]byte("+")) 298 | return string(jumbobuffer), nil 299 | } 300 | -------------------------------------------------------------------------------- /jobserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "flag" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "crawshaw.io/sqlite" 15 | gdb "github.com/benjojo/dive-into-dos/remotegdb" 16 | ) 17 | 18 | var ( 19 | gendb = flag.Bool("generatedb", false, "take a tar and produce a init database") 20 | dbpool *sqlite.Pool 21 | ) 22 | 23 | func main() { 24 | flag.Parse() 25 | 26 | if *gendb { 27 | makeDB() 28 | return 29 | } 30 | 31 | var err error 32 | dbpool, err = sqlite.Open("file:data.db", 0, 10) 33 | if err != nil { 34 | log.Fatalf("Unable to open SQLliteDB %s", err) 35 | } 36 | 37 | http.HandleFunc("/newjob", handleNewJob) 38 | http.HandleFunc("/jobdone", handleJobDone) 39 | http.HandleFunc("/addsidejob", handleSideJob) 40 | http.HandleFunc("/getsidejob", fetchSideJob) 41 | http.HandleFunc("/sidejobdone", logSideJob) 42 | http.ListenAndServe(":9998", http.DefaultServeMux) 43 | } 44 | 45 | type SideJob struct { 46 | DateBased bool 47 | Day, Month, Year, Hour, Min, Second int 48 | TimeBased bool 49 | OriginalID int 50 | SideJobID int 51 | } 52 | 53 | type JobData struct { 54 | Binary struct { 55 | Data string `json:"Data"` 56 | Type string `json:"Type"` 57 | } `json:"Binary"` 58 | ID int `json:"ID"` 59 | JobName string `json:"JobName"` 60 | HasSideJob bool 61 | SideJob SideJob `json:"SideJob"` 62 | } 63 | 64 | func fetchSideJob(rw http.ResponseWriter, req *http.Request) { 65 | conn := dbpool.Get(req.Context().Done()) 66 | if conn == nil { 67 | log.Printf("nil connection") 68 | return 69 | } 70 | defer dbpool.Put(conn) 71 | 72 | JD := JobData{} 73 | 74 | stmt, err := conn.Prepare("SELECT subtask_id,sample_id,state FROM subtasks WHERE evaluated = 0 LIMIT 1") 75 | if err != nil { 76 | Error(rw, err) 77 | return 78 | } 79 | uniqLock.Lock() 80 | defer uniqLock.Unlock() 81 | SID := int64(0) 82 | for { 83 | hasRow, err := stmt.Step() 84 | if err != nil { 85 | Error(rw, err) 86 | return 87 | } else if !hasRow { 88 | log.Print("uh") 89 | break 90 | } 91 | 92 | JD.HasSideJob = true 93 | 94 | subState := stmt.GetText("state") 95 | SJ := SideJob{} 96 | json.Unmarshal([]byte(subState), &SJ) 97 | JD.SideJob = SJ 98 | JD.SideJob.SideJobID = int(stmt.GetInt64("subtask_id")) 99 | JD.SideJob.OriginalID = int(stmt.GetInt64("sample_id")) 100 | 101 | } 102 | 103 | stmt2, err := conn.Prepare("SELECT sample_id,filename,filetype,samplebinary FROM samples WHERE sample_id = $SID LIMIT 1") 104 | if err != nil { 105 | Error(rw, err) 106 | return 107 | } 108 | 109 | stmt2.SetInt64("$SID", int64(JD.SideJob.OriginalID)) 110 | 111 | for { 112 | hasRow, err := stmt2.Step() 113 | if err != nil { 114 | Error(rw, err) 115 | return 116 | } else if !hasRow { 117 | log.Print("uh") 118 | break 119 | } 120 | SID = stmt2.GetInt64("sample_id") 121 | JD.ID = int(SID) 122 | JD.JobName = stmt2.GetText("filename") 123 | Ftype := stmt2.GetText("filetype") 124 | if strings.Contains(Ftype, "MS-DOS executable") { 125 | JD.Binary.Type = "MZ" 126 | } else { 127 | JD.Binary.Type = "COM" 128 | } 129 | 130 | sample := make([]byte, 512*1024) 131 | 132 | n := stmt2.GetBytes("samplebinary", sample) 133 | 134 | JD.Binary.Data = base64.StdEncoding.EncodeToString(sample[:n]) 135 | break 136 | } 137 | stmt2.Finalize() 138 | 139 | stmt2, err = conn.Prepare("UPDATE subtasks SET evaluated = 1 WHERE subtask_id = $sid") 140 | if err != nil { 141 | Error(rw, err) 142 | return 143 | } 144 | stmt2.SetInt64("$sid", int64(JD.SideJob.SideJobID)) 145 | _, err = stmt2.Step() 146 | if err != nil { 147 | Error(rw, err) 148 | return 149 | } 150 | err = stmt2.Finalize() 151 | if err != nil { 152 | Error(rw, err) 153 | return 154 | } 155 | 156 | b, _ := json.Marshal(JD) 157 | rw.Write(b) 158 | 159 | return 160 | } 161 | 162 | func handleSideJob(rw http.ResponseWriter, req *http.Request) { 163 | data, err := ioutil.ReadAll(req.Body) 164 | if err != nil { 165 | Error(rw, err) 166 | return 167 | } 168 | 169 | SJ := SideJob{} 170 | err = json.Unmarshal(data, &SJ) 171 | if err != nil { 172 | Error(rw, err) 173 | return 174 | } 175 | 176 | if SJ.OriginalID == 0 { 177 | log.Printf("Invalid data, 0121") 178 | return 179 | } 180 | 181 | // ok we have the data, so let's store the sub-task 182 | 183 | conn := dbpool.Get(req.Context().Done()) 184 | if conn == nil { 185 | log.Printf("nil connection") 186 | return 187 | } 188 | defer dbpool.Put(conn) 189 | 190 | /* 191 | CREATE TABLE subtasks ( 192 | subtask_id integer PRIMARY KEY AUTOINCREMENT, 193 | sample_id integer, 194 | evaluated integer, 195 | flv BLOB, 196 | floppydisk BLOB, 197 | state BLOB, 198 | syscalls BLOB 199 | ); 200 | 201 | */ 202 | stmt, err := conn.Prepare("INSERT INTO subtasks (sample_id,state,evaluated) VALUES ($sid, $state, 0);") 203 | if err != nil { 204 | Error(rw, err) 205 | return 206 | } 207 | stmt.SetInt64("$sid", int64(SJ.OriginalID)) 208 | stmt.SetBytes("$state", data) 209 | 210 | _, err = stmt.Step() 211 | if err != nil { 212 | Error(rw, err) 213 | return 214 | } 215 | err = stmt.Finalize() 216 | if err != nil { 217 | Error(rw, err) 218 | return 219 | } 220 | 221 | return 222 | } 223 | 224 | var uniqLock sync.Mutex 225 | 226 | func handleNewJob(rw http.ResponseWriter, req *http.Request) { 227 | conn := dbpool.Get(req.Context().Done()) 228 | if conn == nil { 229 | log.Printf("nil connection") 230 | return 231 | } 232 | defer dbpool.Put(conn) 233 | 234 | JD := JobData{} 235 | 236 | stmt, err := conn.Prepare("SELECT sample_id,filename,filetype,samplebinary FROM samples WHERE evaluated = 0 LIMIT 1") 237 | if err != nil { 238 | Error(rw, err) 239 | return 240 | } 241 | uniqLock.Lock() 242 | defer uniqLock.Unlock() 243 | 244 | SID := int64(0) 245 | for { 246 | hasRow, err := stmt.Step() 247 | if err != nil { 248 | Error(rw, err) 249 | return 250 | } else if !hasRow { 251 | log.Print("uh") 252 | break 253 | } 254 | SID = stmt.GetInt64("sample_id") 255 | JD.ID = int(SID) 256 | JD.JobName = stmt.GetText("filename") 257 | Ftype := stmt.GetText("filetype") 258 | if strings.Contains(Ftype, "MS-DOS executable") { 259 | JD.Binary.Type = "MZ" 260 | } else { 261 | JD.Binary.Type = "COM" 262 | } 263 | 264 | sample := make([]byte, 512*1024) 265 | 266 | n := stmt.GetBytes("samplebinary", sample) 267 | 268 | JD.Binary.Data = base64.StdEncoding.EncodeToString(sample[:n]) 269 | break 270 | } 271 | stmt.Finalize() 272 | 273 | stmt, err = conn.Prepare("UPDATE samples SET evaluated = 1 WHERE sample_id = $sid") 274 | if err != nil { 275 | Error(rw, err) 276 | return 277 | } 278 | stmt.SetInt64("$sid", SID) 279 | _, err = stmt.Step() 280 | if err != nil { 281 | Error(rw, err) 282 | return 283 | } 284 | err = stmt.Finalize() 285 | if err != nil { 286 | Error(rw, err) 287 | return 288 | } 289 | 290 | b, _ := json.Marshal(JD) 291 | rw.Write(b) 292 | 293 | } 294 | 295 | func Error(rw http.ResponseWriter, err error) { 296 | log.Printf("ERR: %s ", err.Error()) 297 | http.Error(rw, err.Error(), http.StatusInternalServerError) 298 | } 299 | 300 | type dosSyscall struct { 301 | Time time.Time 302 | Opcode uint8 303 | Registers gdb.X86Registers 304 | DS, PostCode []byte 305 | Marker int 306 | PostCodeLocation int 307 | } 308 | 309 | type JobFeedback struct { 310 | DoneAt time.Time 311 | FLV []byte 312 | FloppyDisk []byte 313 | Syscalls []dosSyscall 314 | RequestedJob JobData 315 | } 316 | 317 | func logSideJob(rw http.ResponseWriter, req *http.Request) { 318 | data, err := ioutil.ReadAll(req.Body) 319 | if err != nil { 320 | Error(rw, err) 321 | return 322 | } 323 | 324 | JF := JobFeedback{} 325 | 326 | log.Printf("wtf %s", string(data)) 327 | 328 | err = json.Unmarshal(data, &JF) 329 | if err != nil { 330 | Error(rw, err) 331 | return 332 | } 333 | 334 | conn := dbpool.Get(req.Context().Done()) 335 | if conn == nil { 336 | log.Printf("nil connection") 337 | return 338 | } 339 | defer dbpool.Put(conn) 340 | 341 | /* 342 | CREATE TABLE subtasks ( 343 | subtask_id integer PRIMARY KEY AUTOINCREMENT, 344 | sample_id integer, 345 | evaluated integer, 346 | flv BLOB, 347 | floppydisk BLOB, 348 | state BLOB, 349 | syscalls BLOB 350 | ); 351 | 352 | */ 353 | 354 | stmt, err := conn.Prepare("UPDATE subtasks SET flv = $flv, floppydisk = $floppy, syscalls = $syscalls, evaluated = 2 WHERE subtask_id = $sid") 355 | if err != nil { 356 | Error(rw, err) 357 | return 358 | } 359 | stmt.SetInt64("$sid", int64(JF.RequestedJob.SideJob.SideJobID)) 360 | 361 | stmt.SetBytes("$flv", JF.FLV) 362 | stmt.SetBytes("$floppy", JF.FloppyDisk) 363 | JFSys, _ := json.Marshal(JF.Syscalls) 364 | stmt.SetBytes("$syscalls", JFSys) 365 | _, err = stmt.Step() 366 | if err != nil { 367 | Error(rw, err) 368 | return 369 | } 370 | err = stmt.Finalize() 371 | if err != nil { 372 | Error(rw, err) 373 | return 374 | } 375 | 376 | } 377 | 378 | func handleJobDone(rw http.ResponseWriter, req *http.Request) { 379 | data, err := ioutil.ReadAll(req.Body) 380 | if err != nil { 381 | Error(rw, err) 382 | return 383 | } 384 | 385 | JF := JobFeedback{} 386 | 387 | log.Printf("wtf %s", string(data)) 388 | 389 | err = json.Unmarshal(data, &JF) 390 | if err != nil { 391 | Error(rw, err) 392 | return 393 | } 394 | 395 | conn := dbpool.Get(req.Context().Done()) 396 | if conn == nil { 397 | log.Printf("nil connection") 398 | return 399 | } 400 | defer dbpool.Put(conn) 401 | 402 | stmt, err := conn.Prepare("UPDATE samples SET flv = $flv, floppydisk = $floppy, syscalls = $syscalls, evaluated = 2 WHERE sample_id = $sid") 403 | if err != nil { 404 | Error(rw, err) 405 | return 406 | } 407 | stmt.SetInt64("$sid", int64(JF.RequestedJob.ID)) 408 | 409 | stmt.SetBytes("$flv", JF.FLV) 410 | stmt.SetBytes("$floppy", JF.FloppyDisk) 411 | JFSys, _ := json.Marshal(JF.Syscalls) 412 | stmt.SetBytes("$syscalls", JFSys) 413 | _, err = stmt.Step() 414 | if err != nil { 415 | Error(rw, err) 416 | return 417 | } 418 | err = stmt.Finalize() 419 | if err != nil { 420 | Error(rw, err) 421 | return 422 | } 423 | 424 | } 425 | -------------------------------------------------------------------------------- /webui/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "os/exec" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | 18 | "crawshaw.io/sqlite" 19 | gdb "github.com/benjojo/dive-into-dos/remotegdb" 20 | "github.com/bnagy/gapstone" 21 | ) 22 | 23 | func main() { 24 | 25 | var err error 26 | dbpool, err = sqlite.Open("file:data.db", 0, 10) 27 | if err != nil { 28 | log.Fatalf("Unable to open SQLliteDB %s", err) 29 | } 30 | 31 | http.HandleFunc("/", listAll) 32 | http.HandleFunc("/time", listAllTime) 33 | http.HandleFunc("/favicon.ico", nothing) 34 | http.HandleFunc("/robots.txt", nothing) 35 | http.HandleFunc("/sample", sampleview) 36 | http.HandleFunc("/gif", getGIF) 37 | 38 | log.Fatalf("Failed to listen %s", http.ListenAndServe(":8888", nil).Error()) 39 | } 40 | 41 | func nothing(rw http.ResponseWriter, req *http.Request) { 42 | 43 | } 44 | 45 | type syscallCapture struct { 46 | Opcode int `json:"Opcode"` 47 | Time string `json:"Time"` 48 | Registers gdb.X86Registers 49 | DS, PostCode []byte 50 | Marker int 51 | PostCodeLocation int 52 | } 53 | 54 | var giflock sync.Mutex 55 | 56 | func getGIF(rw http.ResponseWriter, req *http.Request) { 57 | /* 58 | ffmpeg -i $1 -vf "fps=15,palettegen" -y /tmp/palette.png 59 | ffmpeg -i $1 -i /tmp/palette.png -lavfi "fps=15 [x]; [x][1:v] paletteuse" - 60 | */ 61 | 62 | id := req.URL.Query().Get("id") 63 | 64 | iid, _ := strconv.ParseInt(id, 10, 64) 65 | subiid := 0 66 | if iid == 0 { 67 | subid := req.URL.Query().Get("subid") 68 | 69 | siid, _ := strconv.ParseInt(subid, 10, 64) 70 | if siid != 0 { 71 | subiid = int(siid) 72 | } else { 73 | return 74 | } 75 | } 76 | 77 | conn := dbpool.Get(req.Context().Done()) 78 | defer dbpool.Put(conn) 79 | 80 | query := "SELECT flv FROM samples WHERE sample_id = $sid LIMIT 1" 81 | 82 | if subiid != 0 { 83 | query = "SELECT flv FROM subtasks WHERE subtask_id = $sid LIMIT 1" 84 | } 85 | 86 | giflock.Lock() 87 | defer giflock.Unlock() 88 | 89 | stmt, err := conn.Prepare(query) 90 | if err != nil { 91 | Error(rw, err) 92 | return 93 | } 94 | 95 | if subiid != 0 { 96 | stmt.SetInt64("$sid", int64(subiid)) 97 | } else { 98 | stmt.SetInt64("$sid", iid) 99 | } 100 | flvbytes := make([]byte, 10*10e6) 101 | 102 | for { 103 | hasRow, err := stmt.Step() 104 | if err != nil { 105 | Error(rw, err) 106 | return 107 | } else if !hasRow { 108 | log.Print("uh") 109 | break 110 | } 111 | break 112 | } 113 | n := stmt.GetBytes("flv", flvbytes) 114 | ioutil.WriteFile("/tmp/lol.flv", flvbytes[:n], 0777) 115 | stmt.Finalize() 116 | 117 | pg := exec.Command("ffmpeg", "-i", "/tmp/lol.flv", "-vf", "fps=15,palettegen", "-y", "/tmp/palette.png") 118 | pg.Stderr = os.Stderr 119 | pg.Stdout = os.Stdout 120 | pg.Run() 121 | pg.Wait() 122 | 123 | // Yeah I don't actually care about the output here 124 | pg2 := exec.Command("ffmpeg", "-i", "/tmp/lol.flv", "-i", "/tmp/palette.png", "-lavfi", "fps=15 [x]; [x][1:v] paletteuse", "-f", "gif", "-") 125 | pg2.Stderr = os.Stderr 126 | stdout, _ := pg2.StdoutPipe() 127 | pg2.Start() 128 | rw.Header().Set("Content-Type", "image/gif") 129 | rw.WriteHeader(200) 130 | io.Copy(rw, stdout) 131 | pg2.Wait() 132 | 133 | } 134 | 135 | func cleanUpSyscalls(in []syscallCapture) []syscallCapture { 136 | if len(in) < 10 { 137 | return in 138 | } 139 | 140 | if in[0].Opcode == 64 && 141 | in[1].Opcode == 41 && 142 | in[2].Opcode == 41 && 143 | in[3].Opcode == 41 && 144 | in[4].Opcode == 26 && 145 | in[5].Opcode == 71 && 146 | in[6].Opcode == 78 && 147 | in[7].Opcode == 71 && 148 | in[8].Opcode == 73 && 149 | in[9].Opcode == 75 { 150 | in = in[10:] 151 | } 152 | 153 | for k, v := range in { 154 | if v.Opcode == 72 { 155 | 156 | if len(in)-k < 8 { 157 | // Checking if we have enough syscalls ahead of us 158 | // for this check to work. 159 | return in 160 | } 161 | 162 | if in[k+1].Opcode == 72 && 163 | in[k+2].Opcode == 37 && 164 | in[k+3].Opcode == 37 && 165 | in[k+4].Opcode == 37 && 166 | in[k+5].Opcode == 62 && 167 | in[k+6].Opcode == 62 { 168 | return in[:k-1] 169 | } 170 | } 171 | } 172 | 173 | return in 174 | } 175 | 176 | type sampleFeatures struct { 177 | Finishes, LooksAtTime, WritesToFiles bool 178 | } 179 | 180 | func getFlagsFromSample(syscalls string) (f sampleFeatures, clean []syscallCapture) { 181 | var SysCalls []syscallCapture 182 | 183 | json.Unmarshal([]byte(syscalls), &SysCalls) 184 | CleanSySCalls := cleanUpSyscalls(SysCalls) 185 | 186 | len := len(SysCalls) 187 | if len == 0 { 188 | return f, CleanSySCalls 189 | } 190 | 191 | // check if has suspect syscalls that normally happen when waiting for DOS input 192 | if (SysCalls[len-1].Opcode == 10) && (SysCalls[len-2].Opcode == 93) { 193 | f.Finishes = true 194 | } 195 | 196 | for _, v := range CleanSySCalls { 197 | if v.Opcode == 44 { 198 | f.LooksAtTime = true 199 | } 200 | 201 | if v.Opcode == 42 { 202 | f.LooksAtTime = true 203 | } 204 | 205 | if v.Opcode == 65 || // Unlink 206 | v.Opcode == 0x17 || // Rename 207 | v.Opcode == 0x40 || // Write file or device 208 | v.Opcode == 0x41 || // Unlink 2 209 | v.Opcode == 0x13 || // Unlink 210 | v.Opcode == 0x5A || // Create unique file 211 | v.Opcode == 0x56 { // Rename 2 212 | f.WritesToFiles = true 213 | } 214 | 215 | } 216 | 217 | return f, CleanSySCalls 218 | } 219 | 220 | func sampleview(rw http.ResponseWriter, req *http.Request) { 221 | id := req.URL.Query().Get("id") 222 | 223 | iid, _ := strconv.ParseInt(id, 10, 64) 224 | if iid == 0 { 225 | return 226 | } 227 | 228 | conn := dbpool.Get(req.Context().Done()) 229 | defer dbpool.Put(conn) 230 | 231 | stmt, err := conn.Prepare("SELECT filename,syscalls FROM samples WHERE sample_id = $sid LIMIT 1") 232 | if err != nil { 233 | Error(rw, err) 234 | return 235 | } 236 | 237 | stmt.SetInt64("$sid", iid) 238 | var filename, syscalls string 239 | 240 | for { 241 | hasRow, err := stmt.Step() 242 | if err != nil { 243 | Error(rw, err) 244 | return 245 | } else if !hasRow { 246 | log.Print("uh") 247 | break 248 | } 249 | 250 | filename = stmt.GetText("filename") 251 | syscalls = stmt.GetText("syscalls") 252 | break 253 | } 254 | stmt.Finalize() 255 | 256 | var SysCalls []syscallCapture 257 | 258 | json.Unmarshal([]byte(syscalls), &SysCalls) 259 | TSC := SysCalls 260 | SysCalls = cleanUpSyscalls(SysCalls) 261 | 262 | if req.URL.Query().Get("raw") != "" { 263 | log.Printf("Weeeeeeeeeeeeee") 264 | b, err := json.Marshal(struct { 265 | Filename string 266 | SC []syscallCapture 267 | }{ 268 | Filename: filename, 269 | SC: TSC, 270 | }) 271 | 272 | if err != nil { 273 | http.Error(rw, err.Error(), 500) 274 | return 275 | } 276 | rw.WriteHeader(200) 277 | rw.Write(b) 278 | return 279 | } 280 | 281 | // Now we render! 282 | 283 | rw.Write([]byte(` 284 | 285 | 286 | 297 | 298 | 299 | 300 |

Sample viewer

`)) 301 | 302 | rw.Write([]byte(fmt.Sprintf("

%s

\n

.

GIF

", filename))) 303 | 304 | rw.Write([]byte(fmt.Sprintf("\"\n

Syscalls:

", iid))) 305 | 306 | rw.Write([]byte(` 307 | 308 | 309 | 310 | 311 | `)) 312 | 313 | for _, v := range SysCalls { 314 | rw.Write([]byte(fmt.Sprintf(` 315 | `, 316 | v.Time, v.Opcode, v.PostCodeLocation, getSyscallName(v.Opcode), AnnotateSyscall(v.Opcode, v.Registers, v)))) 317 | } 318 | 319 | rw.Write([]byte(`
TimeSyscall OpSyscall Name
%s %d PC: %x | %s %s
`)) 320 | 321 | rw.Write([]byte(``)) 322 | 323 | // Now lookup the sub-tasks 324 | 325 | stmt2, err := conn.Prepare("SELECT subtask_id,state,syscalls FROM subtasks WHERE sample_id = $sid AND evaluated = 2") 326 | if err != nil { 327 | Error(rw, err) 328 | return 329 | } 330 | 331 | stmt2.SetInt64("$sid", iid) 332 | 333 | for { 334 | var subtaskID int64 335 | var state, syscallblob string 336 | 337 | hasRow, err := stmt2.Step() 338 | if err != nil { 339 | Error(rw, err) 340 | break 341 | } else if !hasRow { 342 | log.Print("uh") 343 | break 344 | } 345 | 346 | state = stmt2.GetText("state") 347 | syscallblob = stmt2.GetText("syscalls") 348 | subtaskID = stmt2.GetInt64("subtask_id") 349 | 350 | rw.Write([]byte(fmt.Sprintf("

%s

\n

.

GIF

", state))) 351 | 352 | rw.Write([]byte(fmt.Sprintf("\n

Syscalls:

", subtaskID))) 353 | 354 | var SysCalls []syscallCapture 355 | 356 | json.Unmarshal([]byte(syscallblob), &SysCalls) 357 | // TSC := SysCalls 358 | SysCalls = cleanUpSyscalls(SysCalls) 359 | 360 | rw.Write([]byte(` 361 | 362 | 363 | 364 | 365 | `)) 366 | 367 | for _, v := range SysCalls { 368 | rw.Write([]byte(fmt.Sprintf(` 369 | `, 370 | v.Time, v.Opcode, v.PostCodeLocation, getSyscallName(v.Opcode), AnnotateSyscall(v.Opcode, v.Registers, v)))) 371 | } 372 | 373 | rw.Write([]byte(`
TimeSyscall OpSyscall Name
%s %d PC: %x | %s %s
`)) 374 | 375 | rw.Write([]byte(``)) 376 | 377 | } 378 | stmt2.Finalize() 379 | 380 | rw.Write([]byte(` 381 | 382 | `)) 383 | } 384 | 385 | var dbpool *sqlite.Pool 386 | 387 | func getSyscallName(in int) string { 388 | switch in { 389 | case 0x00: 390 | return "Program terminate" 391 | case 0x01: 392 | return "Character input" 393 | case 0x02: 394 | return "Character output" 395 | case 0x03: 396 | return "Auxiliary input" 397 | case 0x04: 398 | return "Auxiliary output" 399 | case 0x05: 400 | return "Printer output" 401 | case 0x06: 402 | return "Direct console I/O" 403 | case 0x07: 404 | return "Direct console input without echo" 405 | case 0x08: 406 | return "Console input without echo" 407 | case 0x09: 408 | return "Display string" 409 | case 0x0A: 410 | return "Buffered keyboard input" 411 | case 0x0B: 412 | return "Get input status" 413 | case 0x0C: 414 | return "Flush input buffer and input" 415 | case 0x0D: 416 | return "Disk reset" 417 | case 0x0E: 418 | return "Set default drive" 419 | case 0x0F: 420 | return "Open file" 421 | case 0x10: 422 | return "Close file" 423 | case 0x11: 424 | return "Find first file" 425 | case 0x12: 426 | return "Find next file" 427 | case 0x13: 428 | return "Delete file" 429 | case 0x14: 430 | return "Sequential read" 431 | case 0x15: 432 | return "Sequential write" 433 | case 0x16: 434 | return "Create or truncate file" 435 | case 0x17: 436 | return "Rename file" 437 | case 0x18: 438 | return "Reserved" 439 | case 0x19: 440 | return "Get default drive" 441 | case 0x1A: 442 | return "Set disk transfer address" 443 | case 0x1B: 444 | return "Get allocation info for default drive" 445 | case 0x1C: 446 | return "Get allocation info for specified drive" 447 | case 0x1D: 448 | return "Reserved" 449 | case 0x1E: 450 | return "Reserved" 451 | case 0x1F: 452 | return "Get disk parameter block for default drive" 453 | case 0x20: 454 | return "Reserved" 455 | case 0x21: 456 | return "Random read" 457 | case 0x22: 458 | return "Random write" 459 | case 0x23: 460 | return "Get file size in records" 461 | case 0x24: 462 | return "Set random record number" 463 | case 0x25: 464 | return "Set interrupt vector" 465 | case 0x26: 466 | return "Create PSP" 467 | case 0x27: 468 | return "Random block read" 469 | case 0x28: 470 | return "Random block write" 471 | case 0x29: 472 | return "Parse filename" 473 | case 0x2A: 474 | return "Get date" 475 | case 0x2B: 476 | return "Set date" 477 | case 0x2C: 478 | return "Get time" 479 | case 0x2D: 480 | return "Set time" 481 | case 0x2E: 482 | return "Set verify flag" 483 | case 0x2F: 484 | return "Get disk transfer address" 485 | case 0x30: 486 | return "Get DOS version" 487 | case 0x31: 488 | return "Terminate and stay resident" 489 | case 0x32: 490 | return "Get disk parameter block for specified drive" 491 | case 0x33: 492 | return "Get or set Ctrl-Break" 493 | case 0x34: 494 | return "Get InDOS flag pointer" 495 | case 0x35: 496 | return "Get interrupt vector" 497 | case 0x36: 498 | return "Get free disk space" 499 | case 0x37: 500 | return "Get or set switch character" 501 | case 0x38: 502 | return "Get or set country info" 503 | case 0x39: 504 | return "Create subdirectory" 505 | case 0x3A: 506 | return "Remove subdirectory" 507 | case 0x3B: 508 | return "Change current directory" 509 | case 0x3C: 510 | return "Create or truncate file" 511 | case 0x3D: 512 | return "Open file" 513 | case 0x3E: 514 | return "Close file" 515 | case 0x3F: 516 | return "Read file or device" 517 | case 0x40: 518 | return "Write file or device" 519 | case 0x41: 520 | return "Delete file" 521 | case 0x42: 522 | return "Move file pointer" 523 | case 0x43: 524 | return "Get or set file attributes" 525 | case 0x44: 526 | return "I/O control for devices" 527 | case 0x45: 528 | return "Duplicate handle" 529 | case 0x46: 530 | return "Redirect handle" 531 | case 0x47: 532 | return "Get current directory" 533 | case 0x48: 534 | return "Allocate memory" 535 | case 0x49: 536 | return "Release memory" 537 | case 0x4A: 538 | return "Reallocate memory" 539 | case 0x4B: 540 | return "Execute program" 541 | case 0x4C: 542 | return "Terminate with return code" 543 | case 0x4D: 544 | return "Get program return code" 545 | case 0x4E: 546 | return "Find first file" 547 | case 0x4F: 548 | return "Find next file" 549 | case 0x50: 550 | return "Set current PSP" 551 | case 0x51: 552 | return "Get current PSP" 553 | case 0x52: 554 | return "Get DOS internal pointers (SYSVARS)" 555 | case 0x53: 556 | return "Create disk parameter block" 557 | case 0x54: 558 | return "Get verify flag" 559 | case 0x55: 560 | return "Create program PSP" 561 | case 0x56: 562 | return "Rename file" 563 | case 0x57: 564 | return "Get or set file date and time" 565 | case 0x58: 566 | return "case 0xGet or set allocation strateg:" 567 | case 0x59: 568 | return "Get extended error info" 569 | case 0x5A: 570 | return "Create unique file" 571 | case 0x5B: 572 | return "Create new file" 573 | case 0x5C: 574 | return "Lock or unlock file" 575 | case 0x5D: 576 | return "File sharing functions" 577 | case 0x5E: 578 | return "Network functions" 579 | case 0x5F: 580 | return "Network redirection functions" 581 | case 0x60: 582 | return "Qualify filename" 583 | case 0x61: 584 | return "Reserved" 585 | case 0x62: 586 | return "Get current PSP" 587 | case 0x63: 588 | return "Get DBCS lead byte table pointer" 589 | case 0x64: 590 | return "Set wait for external event flag" 591 | case 0x65: 592 | return "Get extended country info" 593 | case 0x66: 594 | return "Get or set code page" 595 | case 0x67: 596 | return "Set handle count" 597 | case 0x68: 598 | return "Commit file" 599 | case 0x69: 600 | return "Get or set media id" 601 | case 0x6A: 602 | return "Commit file" 603 | case 0x6B: 604 | return "Reserved" 605 | case 0x6C: 606 | return "Extended open/create file" 607 | } 608 | 609 | return "UNKNOWN!" 610 | } 611 | 612 | func int16ToHex(in uint16) string { 613 | bs := make([]byte, 2) 614 | binary.LittleEndian.PutUint16(bs, in) 615 | return hex.EncodeToString(bs) 616 | } 617 | 618 | func AnnotateSyscall(Ah int, R gdb.X86Registers, full syscallCapture) string { 619 | switch Ah { 620 | case 0x02: 621 | return fmt.Sprintf("(Char = '%s')", int16ToHex(R.Dx)[:2]) 622 | case 0x09: 623 | i := strings.IndexByte(string(full.DS), '$') 624 | if i == -1 { 625 | return "(Could not find end pointer)" 626 | } 627 | return fmt.Sprintf("(String= '%s')", string(full.DS[:i])) 628 | case 0x0E: 629 | // return "Set default drive" 630 | b, _ := hex.DecodeString(int16ToHex(R.Dx)[:2]) 631 | return fmt.Sprintf("(Drive = '%s')", string('A'+b[0])) 632 | case 0x0F: 633 | return fmt.Sprintf("(Filename = '%s')", string(full.DS)) 634 | case 0x3D: // Open File using Handle 635 | i := strings.IndexByte(string(full.DS), 0x00) 636 | if i == -1 { 637 | return "" 638 | } 639 | // return fmt.Sprintf("(Filename = '%s') / RC
%s", string(full.DS[:i]), dumpReturnCode(full)) 640 | return fmt.Sprintf("(Filename = '%s')", string(full.DS[:i])) 641 | case 0x3F: // Read using Handle 642 | return fmt.Sprintf("(Read %d bytes on handle %d)", R.Ecx, R.Bx) 643 | case 0x40: // Write using handle 644 | return fmt.Sprintf("(Write %d bytes on handle %d)", R.Ecx, R.Bx) 645 | case 0x41: // Delete using Handle 646 | i := strings.IndexByte(string(full.DS), 0x00) 647 | if i == -1 { 648 | return "" 649 | } 650 | return fmt.Sprintf("(Filename = '%s')", string(full.DS[:i])) 651 | case 0x44: // Get/Set File Attributes 652 | i := strings.IndexByte(string(full.DS), 0x00) 653 | if i == -1 { 654 | return "" 655 | } 656 | if R.Al == 00 { 657 | return fmt.Sprintf("(Get for = '%s')", string(full.DS[:i])) 658 | } 659 | return fmt.Sprintf("(Set for = '%s')", string(full.DS[:i])) 660 | 661 | case 0x25: 662 | return fmt.Sprintf("(Interrupt = '%d' AKA '%s')", R.Ah, getSyscallName(int(R.Ah))) 663 | // return "Set Interrupt Vector" 664 | case 0x31: 665 | return fmt.Sprintf("(Return code = '%d' | Memory size = '%d')", R.Ah, R.Dx) 666 | // return "Terminate and stay resident" 667 | case 0x35: 668 | return fmt.Sprintf("(Interrupt = '%d' AKA '%s')", R.Ah, getSyscallName(int(R.Ah))) 669 | // return "Get Interrupt Vector" 670 | case 0x4C: 671 | return fmt.Sprintf("(Return code = '%d')", R.Ah) 672 | // return "Terminate with return code" 673 | case 0x2A: // Get Date 674 | return dumpReturnCode(full) 675 | case 0x2C: // Get Time 676 | return dumpReturnCode(full) 677 | } 678 | return "" 679 | } 680 | 681 | func dumpReturnCode(all syscallCapture) string { 682 | log.Printf("wow ! \n\n%#v\n", all) 683 | 684 | buffer := "(DISAM FAILED)" 685 | if len(all.PostCode) == 0 { 686 | return buffer 687 | } 688 | 689 | engine, err := gapstone.New( 690 | gapstone.CS_ARCH_X86, 691 | // gapstone.CS_MODE_32, 692 | gapstone.CS_MODE_16, 693 | ) 694 | 695 | if err == nil { 696 | 697 | defer engine.Close() 698 | buffer = "" 699 | maj, min := engine.Version() 700 | log.Printf("Hello one! Version: %v.%v\n", maj, min) 701 | log.Printf("Code: %x", all.PostCode) 702 | 703 | insns, err := engine.Disasm( 704 | []byte(all.PostCode), // code buffer 705 | uint64(all.PostCodeLocation), // starting address 706 | 20, // insns to disassemble, 0 for all 707 | ) 708 | 709 | if err == nil { 710 | log.Printf("Disasm:\n") 711 | for _, insn := range insns { 712 | log.Printf("0x%x:\t%s\t\t%s\n", insn.Address, insn.Mnemonic, insn.OpStr) 713 | buffer += fmt.Sprintf("0x%x:\t%s\t\t%s\n
", insn.Address, insn.Mnemonic, insn.OpStr) 714 | } 715 | return buffer 716 | } else { 717 | log.Printf("FUCK \n\n\n\n FUCK FUUUUUUUUUUUUUUCK~ \n\n\n %s \n\n\n", err.Error()) 718 | } 719 | } 720 | return buffer 721 | } 722 | 723 | func listAllTime(rw http.ResponseWriter, req *http.Request) { 724 | conn := dbpool.Get(req.Context().Done()) 725 | defer dbpool.Put(conn) 726 | 727 | stmt, err := conn.Prepare("SELECT DISTINCT(sample_id) as A FROM subtasks WHERE evaluated = 2;") // ORDER BY filename DESC 728 | if err != nil { 729 | Error(rw, err) 730 | return 731 | } 732 | defer stmt.Finalize() 733 | 734 | rw.Write([]byte(` 735 | 736 | 737 | 748 | 749 | 750 | 751 |

Malware listing

752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | `)) 762 | 763 | for { 764 | hasRow, err := stmt.Step() 765 | if err != nil { 766 | Error(rw, err) 767 | return 768 | } else if !hasRow { 769 | log.Print("uh") 770 | break 771 | } 772 | 773 | var jobid int64 774 | // var sampleName string 775 | 776 | jobid = stmt.GetInt64("A") 777 | 778 | stmt2, err := conn.Prepare("SELECT syscalls FROM subtasks WHERE sample_id = $SID;") // ORDER BY filename DESC 779 | if err != nil { 780 | Error(rw, err) 781 | return 782 | } 783 | 784 | stmt2.SetInt64("$SID", jobid) 785 | syscallsamples := make([][]byte, 0) 786 | 787 | for { 788 | rr, err := stmt2.Step() 789 | if !rr || err != nil { 790 | break 791 | } 792 | 793 | syscallblob := make([]byte, 1e6*15) 794 | n := stmt2.GetBytes("syscalls", syscallblob) 795 | // syscallblob 796 | 797 | syscallsamples = append(syscallsamples, []byte(syscallblob[:n])) 798 | } 799 | stmt2.Finalize() 800 | stmt2, err = conn.Prepare("SELECT filename,syscalls FROM samples WHERE sample_id = $SID;") // ORDER BY filename DESC 801 | if err != nil { 802 | Error(rw, err) 803 | return 804 | } 805 | 806 | stmt2.SetInt64("$SID", jobid) 807 | filename := "" 808 | for { 809 | rr, err := stmt2.Step() 810 | if !rr || err != nil { 811 | break 812 | } 813 | 814 | syscallblob := stmt2.GetText("syscalls") 815 | filename = stmt2.GetText("filename") 816 | // syscallblob 817 | // fmt.Printf("\n%v\n", syscallblob) 818 | 819 | syscallsamples = append(syscallsamples, []byte(syscallblob)) 820 | } 821 | stmt2.Finalize() 822 | 823 | // 824 | // We have all the syscall blobs now in syscallsamples 825 | // 826 | 827 | // 828 | // 829 | // 830 | // 831 | // 832 | // 833 | 834 | syscalllen := make(map[int]int) 835 | removesfiles := 0 836 | hangssystem := 0 837 | 838 | for _, v := range syscallsamples { 839 | var SysCalls []syscallCapture 840 | 841 | json.Unmarshal([]byte(v), &SysCalls) 842 | CleanSySCalls := cleanUpSyscalls(SysCalls) 843 | 844 | rfiles := false 845 | 846 | syscalllen[len(SysCalls)]++ 847 | 848 | for _, scall := range CleanSySCalls { 849 | if scall.Opcode == 0x41 { 850 | rfiles = true 851 | } 852 | 853 | if scall.Opcode == 0x13 { 854 | rfiles = true 855 | } 856 | } 857 | 858 | if rfiles { 859 | removesfiles++ 860 | } 861 | 862 | if len(SysCalls) > 2 && 863 | SysCalls[len(SysCalls)-2].Opcode == 93 && SysCalls[len(SysCalls)-1].Opcode == 10 { 864 | 865 | } else { 866 | hangssystem++ 867 | } 868 | } 869 | 870 | rw.Write([]byte(fmt.Sprintf(` 871 | 872 | 873 | 874 | 875 | 876 | `, jobid, filename, 877 | len(syscalllen) != 1, len(syscalllen) != 1, 878 | removesfiles != len(syscallsamples) && removesfiles != 0, removesfiles != len(syscallsamples) && removesfiles != 0, 879 | hangssystem != len(syscallsamples), hangssystem != len(syscallsamples), 880 | jobid))) 881 | 882 | fmt.Printf("Sample: %d,\n\n Sampleset %d, \n Samples that remove files %d\nSamples that hang %d\nLendist: %v\n\n----\n", jobid, len(syscallsamples), removesfiles, hangssystem, syscalllen) 883 | 884 | } 885 | 886 | rw.Write([]byte(` 887 |
IDFilenameDiff syscallsDiff Removes FilesDiff Hangs systemLink
IDFilenameDiff syscallsDiff Removes FilesDiff Hangs systemLink
%d%s%v%v%vLink
888 | 889 | 890 | `)) 891 | } 892 | 893 | func listAll(rw http.ResponseWriter, req *http.Request) { 894 | conn := dbpool.Get(req.Context().Done()) 895 | defer dbpool.Put(conn) 896 | 897 | stmt, err := conn.Prepare("SELECT sample_id,filename,syscalls FROM samples WHERE evaluated = 2") // ORDER BY filename DESC 898 | if err != nil { 899 | Error(rw, err) 900 | return 901 | } 902 | defer stmt.Finalize() 903 | 904 | rw.Write([]byte(` 905 | 906 | 907 | 918 | 919 | 920 | 921 |

Malware listing

922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | `)) 933 | 934 | for { 935 | hasRow, err := stmt.Step() 936 | if err != nil { 937 | Error(rw, err) 938 | return 939 | } else if !hasRow { 940 | log.Print("uh") 941 | break 942 | } 943 | 944 | var jobid int64 945 | var sampleName string 946 | 947 | jobid = stmt.GetInt64("sample_id") 948 | sampleName = stmt.GetText("filename") 949 | samples := stmt.GetText("syscalls") 950 | sFeatures, fCalls := getFlagsFromSample(samples) 951 | 952 | rw.Write([]byte(fmt.Sprintf(` 953 | 954 | 955 | 956 | 957 | 958 | 959 | `, jobid, sampleName, 960 | sFeatures.Finishes, sFeatures.Finishes, 961 | sFeatures.LooksAtTime, sFeatures.LooksAtTime, 962 | sFeatures.WritesToFiles, sFeatures.WritesToFiles, 963 | len(fCalls) != 0, len(fCalls) != 0, 964 | jobid))) 965 | 966 | } 967 | 968 | rw.Write([]byte(` 969 |
IDFilenameProgram FinishesChecks TimeModifies FilesDoes somethingLink
%d%s%v%v%v%vLink
970 | 971 | 972 | `)) 973 | } 974 | 975 | func Error(rw http.ResponseWriter, err error) { 976 | log.Printf("ERR: %s ", err.Error()) 977 | http.Error(rw, err.Error(), http.StatusInternalServerError) 978 | } 979 | -------------------------------------------------------------------------------- /benx86/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "net/url" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | gdb "github.com/benjojo/dive-into-dos/remotegdb" 18 | "github.com/bnagy/gapstone" 19 | ) 20 | 21 | type Webdata struct { 22 | Filename string 23 | SC []syscallCapture 24 | } 25 | 26 | type syscallCapture struct { 27 | Opcode int `json:"Opcode"` 28 | Time string `json:"Time"` 29 | Registers gdb.X86Registers 30 | DS, PostCode []byte 31 | Marker int 32 | PostCodeLocation int 33 | } 34 | 35 | func main() { 36 | urladdr := flag.String("url", "", "the target sample URL") 37 | flag.Parse() 38 | log.SetFlags(log.Lshortfile | log.Ltime) 39 | 40 | if *urladdr == "" { 41 | flag.Usage() 42 | return 43 | } 44 | 45 | resp, err := http.Get(*urladdr) 46 | if err != nil { 47 | log.Fatalf("Unable to fetch job, %s", err.Error()) 48 | } 49 | 50 | b, _ := ioutil.ReadAll(resp.Body) 51 | WD := Webdata{} 52 | err = json.Unmarshal(b, &WD) 53 | if err != nil { 54 | log.Fatalf("Unable to decode job %s", err.Error()) 55 | } 56 | 57 | utimes := make([]time.Time, 0) 58 | udates := make([]time.Time, 0) 59 | donepc := make(map[int]bool) 60 | 61 | for _, call := range WD.SC { 62 | if call.Opcode == 0x2A { 63 | if donepc[call.PostCodeLocation] { 64 | continue 65 | } 66 | donepc[call.PostCodeLocation] = true 67 | 68 | paths := SolveForDate(call) 69 | if len(paths) == 0 || len(paths) == 1 { 70 | continue 71 | } 72 | // idk handle it 73 | 74 | for _, v := range paths { 75 | udates = append(udates, v[0]) 76 | } 77 | } 78 | 79 | if call.Opcode == 0x2C { 80 | if donepc[call.PostCodeLocation] { 81 | continue 82 | } 83 | donepc[call.PostCodeLocation] = true 84 | 85 | paths := SolveForTime(call) 86 | if len(paths) == 0 || len(paths) == 1 { 87 | continue 88 | } 89 | 90 | for _, v := range paths { 91 | utimes = append(utimes, v[0]) 92 | } 93 | 94 | } 95 | } 96 | 97 | // now we need to calculate tests 98 | 99 | jobs := make([]SideJob, 0) 100 | 101 | bits, _ := url.Parse(*urladdr) 102 | idn, _ := strconv.ParseInt(bits.Query().Get("id"), 10, 64) 103 | 104 | if len(utimes) == 0 && len(udates) != 0 { 105 | // Compile for dates 106 | for _, v := range udates { 107 | SJ := SideJob{} 108 | SJ.DateBased = true 109 | SJ.TimeBased = false 110 | 111 | SJ.Day = v.Day() 112 | SJ.Year = v.Year() 113 | SJ.Month = int(v.Month()) 114 | SJ.OriginalID = int(idn) 115 | 116 | jobs = append(jobs, SJ) 117 | } 118 | 119 | } else if len(udates) == 0 && len(utimes) != 0 { 120 | // Compile for time 121 | 122 | for _, v := range utimes { 123 | SJ := SideJob{} 124 | SJ.DateBased = false 125 | SJ.TimeBased = true 126 | 127 | SJ.Hour = v.Hour() 128 | SJ.Min = v.Minute() 129 | SJ.Second = v.Second() 130 | SJ.OriginalID = int(idn) 131 | 132 | jobs = append(jobs, SJ) 133 | } 134 | } else if len(udates) == 0 && len(utimes) == 0 { 135 | // None 136 | } else { 137 | // Both 138 | 139 | for _, timeS := range utimes { 140 | for _, dateS := range utimes { 141 | 142 | SJ := SideJob{} 143 | SJ.DateBased = true 144 | SJ.TimeBased = true 145 | 146 | SJ.Day = dateS.Day() 147 | SJ.Year = dateS.Year() 148 | SJ.Month = int(dateS.Month()) 149 | 150 | SJ.Hour = timeS.Hour() 151 | SJ.Min = timeS.Minute() 152 | SJ.Second = timeS.Second() 153 | SJ.OriginalID = int(idn) 154 | 155 | jobs = append(jobs, SJ) 156 | } 157 | } 158 | } 159 | 160 | log.Printf("Puzzle solver results:") 161 | for _, v := range jobs { 162 | log.Printf("%#v\n", v) 163 | 164 | b, _ := json.Marshal(v) 165 | 166 | req, _ := http.Post("http://localhost:9998/addsidejob", "application/json", bytes.NewReader(b)) 167 | req.Body.Close() 168 | } 169 | 170 | } 171 | 172 | type SideJob struct { 173 | DateBased bool 174 | Day, Month, Year, Hour, Min, Second int 175 | TimeBased bool 176 | OriginalID int 177 | SideJobID int 178 | } 179 | 180 | func SolveForTime(in syscallCapture) map[string][]time.Time { 181 | // This will emulate x86 until a different solution is found. 182 | 183 | engine, err := gapstone.New( 184 | gapstone.CS_ARCH_X86, 185 | // gapstone.CS_MODE_32, 186 | gapstone.CS_MODE_16, 187 | ) 188 | 189 | defer engine.Close() 190 | maj, min := engine.Version() 191 | log.Printf("Hello Capstone! Version: %v.%v\n", maj, min) 192 | log.Printf("Code: %x", in.PostCode) 193 | 194 | insns, err := engine.Disasm( 195 | []byte(in.PostCode), // code buffer 196 | uint64(in.PostCodeLocation), // starting address 197 | 0, // insns to disassemble, 0 for all 198 | ) 199 | 200 | if err != nil { 201 | log.Fatalf("unable to disasm: %s", err.Error()) 202 | } 203 | 204 | log.Printf("Disasm:\n") 205 | for _, insn := range insns { 206 | log.Printf("0x%x:\t%s\t\t%s\n", insn.Address, insn.Mnemonic, insn.OpStr) 207 | // buffer += fmt.Sprintf("0x%x:\t%s\t\t%s\n
", insn.Address, insn.Mnemonic, insn.OpStr) 208 | } 209 | 210 | DHmonth := 1 // 1-12 211 | DLday := 1 // 1-31 212 | 213 | testingdate := time.Date(1980, time.Month(DHmonth), DLday, 0, 0, 0, 0, time.Local) 214 | 215 | dateToPath := make(map[string][]time.Time) 216 | 217 | for { 218 | Dhour, Dmins, Dseconds := testingdate.Hour(), testingdate.Minute(), testingdate.Second() 219 | // and run the test here 220 | 221 | injectRegisters := in.Registers 222 | injectRegisters = setRegister("ch", uint16(Dhour), injectRegisters) 223 | injectRegisters = setRegister("cl", uint16(Dmins), injectRegisters) 224 | injectRegisters = setRegister("dh", uint16(Dseconds), injectRegisters) 225 | injectRegisters = setRegister("dl", uint16(0), injectRegisters) 226 | 227 | path := runFunctionWithRegisters(insns, in.PostCodeLocation, injectRegisters) 228 | 229 | pstr := fmt.Sprintf("%v", path) 230 | timearray := dateToPath[pstr] 231 | if timearray == nil { 232 | timearray = make([]time.Time, 0) 233 | } 234 | timearray = append(timearray, testingdate) 235 | dateToPath[pstr] = timearray 236 | 237 | // Done? 238 | // set it again for the loop 239 | 240 | testingdate = testingdate.Add(time.Second) 241 | if testingdate.Day() != 1 { 242 | break 243 | } 244 | } 245 | 246 | log.Printf("Paths:") 247 | for k, v := range dateToPath { 248 | log.Printf("For the path: %s", k) 249 | if len(v) > 20 { 250 | log.Printf("\n\nDate: %v\n", v[:20]) 251 | } else { 252 | log.Printf("\n\nDate: %v\n", v) 253 | 254 | } 255 | } 256 | 257 | return dateToPath 258 | } 259 | 260 | func SolveForDate(in syscallCapture) map[string][]time.Time { 261 | // This will emulate x86 until a different solution is found. 262 | 263 | engine, err := gapstone.New( 264 | gapstone.CS_ARCH_X86, 265 | // gapstone.CS_MODE_32, 266 | gapstone.CS_MODE_16, 267 | ) 268 | 269 | defer engine.Close() 270 | maj, min := engine.Version() 271 | log.Printf("Hello Capstone! Version: %v.%v\n", maj, min) 272 | log.Printf("Code: %x", in.PostCode) 273 | 274 | insns, err := engine.Disasm( 275 | []byte(in.PostCode), // code buffer 276 | uint64(in.PostCodeLocation), // starting address 277 | 0, // insns to disassemble, 0 for all 278 | ) 279 | 280 | if err != nil { 281 | log.Fatalf("unable to disasm: %s", err.Error()) 282 | } 283 | 284 | log.Printf("Disasm:\n") 285 | for _, insn := range insns { 286 | log.Printf("0x%x:\t%s\t\t%s\n", insn.Address, insn.Mnemonic, insn.OpStr) 287 | // buffer += fmt.Sprintf("0x%x:\t%s\t\t%s\n
", insn.Address, insn.Mnemonic, insn.OpStr) 288 | } 289 | 290 | t, err := lazyScan(insns) 291 | if err == nil { 292 | log.Printf("Possible solution: %q", t) 293 | } else { 294 | log.Printf("failed") 295 | } 296 | 297 | DHmonth := 1 // 1-12 298 | DLday := 1 // 1-31 299 | 300 | testingdate := time.Date(1980, time.Month(DHmonth), DLday, 5, 5, 5, 5, time.Local) 301 | 302 | dateToPath := make(map[string][]time.Time) 303 | 304 | for { 305 | ALdayofweek := int(testingdate.Weekday()) 306 | Dyear, DHmonth, DLday := testingdate.Date() 307 | // and run the test here 308 | 309 | injectRegisters := in.Registers 310 | injectRegisters.Al = uint8(ALdayofweek) 311 | injectRegisters = setRegister("al", uint16(injectRegisters.Al), injectRegisters) 312 | // injectRegisters.Dx = binary.LittleEndian.Uint16([]byte{byte(DLday), byte(DHmonth)}) 313 | injectRegisters = setRegister("dl", uint16(byte(DLday)), injectRegisters) 314 | injectRegisters = setRegister("dh", uint16(byte(DHmonth)), injectRegisters) 315 | injectRegisters.Ecx = uint32(Dyear) 316 | injectRegisters = setRegister("cx", uint16(Dyear), injectRegisters) 317 | 318 | path := runFunctionWithRegisters(insns, in.PostCodeLocation, injectRegisters) 319 | 320 | pstr := fmt.Sprintf("%v", path) 321 | timearray := dateToPath[pstr] 322 | if timearray == nil { 323 | timearray = make([]time.Time, 0) 324 | } 325 | timearray = append(timearray, testingdate) 326 | dateToPath[pstr] = timearray 327 | 328 | // Done? 329 | // set it again for the loop 330 | 331 | Dyear, DHmonth, DLday = testingdate.AddDate(0, 0, 1).Date() 332 | testingdate = time.Date(Dyear, time.Month(DHmonth), DLday, 5, 5, 5, 5, time.Local) 333 | if Dyear == 2005 { 334 | break 335 | } 336 | } 337 | 338 | log.Printf("Paths:") 339 | for k, v := range dateToPath { 340 | log.Printf("For the path: %s", k) 341 | if len(v) > 20 { 342 | log.Printf("\n\nDate: %v\n", v[:20]) 343 | } else { 344 | log.Printf("\n\nDate: %v\n", v) 345 | 346 | } 347 | } 348 | 349 | return dateToPath 350 | } 351 | 352 | func lazyScan(opcodes []gapstone.Instruction) (t time.Time, err error) { 353 | log.Printf("YOOOOOOOOOOOOOOOOOOOOOOOOO") 354 | 355 | dateRegisters := map[string]bool{"al": true, "ax": true, "cx": true, "dh": true, "dl": true, "dx": true} 356 | for _, v := range opcodes { 357 | if v.Mnemonic == "cmp" { 358 | parts := strings.Split(v.OpStr, ",") 359 | for k, v := range parts { 360 | parts[k] = strings.Trim(v, " \t\n\r") 361 | } 362 | 363 | if !dateRegisters[parts[0]] { 364 | // It does not compare a date register 365 | continue 366 | } 367 | 368 | // If we are at this point then we are CMPing date registers. 369 | 370 | if parts[0] == "ax" { 371 | log.Printf("a CMP with a AX comparison for date, this is not traceable due to lack of context") 372 | return time.Time{}, nil 373 | } 374 | 375 | if parts[0] == "al" { 376 | // Day of week! 377 | day := 14 378 | for { 379 | tdate := time.Date(1995, time.Month(2), day, 5, 5, 5, 0, time.Local) 380 | targetDOW, err := parsePram(parts[1]) 381 | 382 | // simpleint, err := strconv.ParseInt(parts[1], 10, 64) 383 | // if err != nil { 384 | // // must be an addr 385 | // simpleint = int64(resolveAddress(parts[1])) 386 | if err != nil { 387 | log.Printf("failed to parse CMP, %s", parts[1]) 388 | break 389 | } 390 | // } 391 | 392 | if int(targetDOW) == int(tdate.Weekday()) { 393 | // Cool, that' works for me 394 | log.Printf("DOW trigger, %q", tdate.Weekday()) 395 | return tdate, nil 396 | } 397 | day++ 398 | if day == 30 { 399 | break 400 | } 401 | } 402 | log.Printf("failed to find date needed for AL") 403 | return time.Time{}, fmt.Errorf("Unable to find solution") 404 | } 405 | 406 | if parts[0] == "cx" { 407 | targetyear, err := parsePram(parts[1]) 408 | if err != nil { 409 | log.Printf("failed to parse CMP, %s", parts[1]) 410 | return time.Time{}, fmt.Errorf("Unable to find solution") 411 | } 412 | tdate := time.Date(int(targetyear), time.Month(2), 14, 5, 5, 5, 0, time.Local) 413 | log.Printf("Year trigger, %d", targetyear) 414 | return tdate, nil 415 | } 416 | 417 | if parts[0] == "dh" { 418 | month, err := parsePram(parts[1]) 419 | if err != nil { 420 | log.Printf("failed to parse CMP, %s", parts[1]) 421 | return time.Time{}, fmt.Errorf("Unable to find solution") 422 | } 423 | tdate := time.Date(1995, time.Month(month), 14, 5, 5, 5, 0, time.Local) 424 | log.Printf("Month trigger, %d", month) 425 | return tdate, nil 426 | } 427 | 428 | if parts[0] == "dl" { 429 | day, err := parsePram(parts[1]) 430 | if err != nil { 431 | log.Printf("failed to parse CMP, %s", parts[1]) 432 | return time.Time{}, fmt.Errorf("Unable to find solution") 433 | } 434 | tdate := time.Date(1995, 1, int(day), 5, 5, 5, 0, time.Local) 435 | log.Printf("Day trigger, %d", day) 436 | return tdate, nil 437 | } 438 | 439 | if parts[0] == "dx" { 440 | // Oh. Shit. 441 | 442 | // They are checking a combo of day and month in a single cmp. 443 | dx, err := parsePram(parts[1]) 444 | if err != nil { 445 | log.Printf("failed to parse CMP, %s", parts[1]) 446 | return time.Time{}, fmt.Errorf("Unable to find solution") 447 | } 448 | // log.Printf("YOOOOOOOOOOOOOOOOOOOOOOOOO") 449 | urgh := make([]byte, 2) 450 | binary.LittleEndian.PutUint16(urgh, uint16(dx)) 451 | log.Printf("DX check %#v %x", parts, urgh) 452 | 453 | tdate := time.Date(1995, time.Month(urgh[1]), int(urgh[0]), 5, 5, 5, 0, time.Local) 454 | log.Printf("Day and Month trigger, %d the %d", time.Month(urgh[1]), int(urgh[0])) 455 | 456 | return tdate, nil 457 | } 458 | 459 | } 460 | return time.Time{}, fmt.Errorf("Unable to find solution") 461 | // Fuck it lol 462 | } 463 | 464 | return time.Time{}, fmt.Errorf("Unable to find solution") 465 | } 466 | 467 | func parsePram(in string) (int, error) { 468 | simpleint, err := strconv.ParseInt(in, 10, 64) 469 | if err != nil { 470 | // must be an addr 471 | simpleint = int64(resolveAddress(in)) 472 | if simpleint == 999999999 { 473 | log.Printf("failed to parse pram, %s", in) 474 | return 0, fmt.Errorf("Failed to get anything reasonable") 475 | } 476 | return int(simpleint), nil 477 | } 478 | return int(simpleint), nil 479 | } 480 | 481 | var maxopcodes = 10000 482 | 483 | type eflags struct { 484 | CF, PF, AF, ZF, SF, TF, IF, DF, OF bool 485 | } 486 | 487 | // Runs a x86 snippet and logs where it is going until it reaches the end or the max op code limit 488 | func runFunctionWithRegisters(opcodes []gapstone.Instruction, startloc int, oreg gdb.X86Registers) []uint { 489 | execpath := make([]uint, 0) 490 | ptr := 0 491 | OpsRan := 0 492 | 493 | CPUFlags := eflags{} 494 | CMPOp1 := uint16(0) 495 | CMPOp2 := uint16(0) 496 | 497 | state := oreg 498 | for { 499 | OpsRan++ 500 | if OpsRan > maxopcodes { 501 | return execpath 502 | } 503 | 504 | execpath = append(execpath, opcodes[ptr].Address) // log path 505 | 506 | // clean up the parts of the opstr for later use 507 | parts := strings.Split(opcodes[ptr].OpStr, ",") 508 | for k, v := range parts { 509 | parts[k] = strings.Trim(v, " \t\n\r") 510 | } 511 | 512 | // log.Printf("0x%x:\t%s\t\t%s\n", opcodes[ptr].Address, opcodes[ptr].Mnemonic, opcodes[ptr].OpStr) 513 | 514 | for _, v := range parts { 515 | if strings.Contains(v, "ptr") { 516 | log.Printf("data access to a memory pointer happened, this can't be simulated, exiting.") 517 | return execpath // Unhappy path 518 | } 519 | } 520 | 521 | // Check if it's a conditional jump 522 | switch opcodes[ptr].Mnemonic { 523 | case "jz", "je": 524 | if (CMPOp1 - CMPOp2) == 0 { 525 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 526 | if nextptr != -1 { 527 | ptr = nextptr 528 | continue 529 | } else { 530 | return execpath 531 | } 532 | } 533 | ptr++ 534 | continue 535 | case "ja": 536 | if CMPOp1 > CMPOp2 { 537 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 538 | if nextptr != -1 { 539 | ptr = nextptr 540 | continue 541 | } else { 542 | return execpath 543 | } 544 | } 545 | ptr++ 546 | continue 547 | case "jne": 548 | // log.Printf("JNE does %x != %x", CMPOp1, CMPOp2) 549 | if CMPOp1 != CMPOp2 { 550 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 551 | if nextptr != -1 { 552 | ptr = nextptr 553 | continue 554 | } else { 555 | return execpath 556 | } 557 | } 558 | ptr++ 559 | continue 560 | case "jmp": 561 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 562 | if nextptr != -1 { 563 | ptr = nextptr 564 | continue 565 | } else { 566 | return execpath 567 | } 568 | case "jb": 569 | if CMPOp1 < CMPOp2 { 570 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 571 | if nextptr != -1 { 572 | ptr = nextptr 573 | continue 574 | } else { 575 | return execpath 576 | } 577 | } 578 | ptr++ 579 | continue 580 | case "jae": 581 | if CMPOp1 < CMPOp2 || CMPOp1 == CMPOp2 { 582 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 583 | if nextptr != -1 { 584 | ptr = nextptr 585 | continue 586 | } else { 587 | return execpath 588 | } 589 | } 590 | ptr++ 591 | continue 592 | case "jbe": 593 | if CMPOp1 > CMPOp2 || CMPOp1 == CMPOp2 { 594 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 595 | if nextptr != -1 { 596 | ptr = nextptr 597 | continue 598 | } else { 599 | return execpath 600 | } 601 | } 602 | ptr++ 603 | continue 604 | case "jl": 605 | if CMPOp1 < CMPOp2 { 606 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 607 | if nextptr != -1 { 608 | ptr = nextptr 609 | continue 610 | } else { 611 | return execpath 612 | } 613 | } 614 | ptr++ 615 | continue 616 | case "jge": 617 | if CMPOp1 > CMPOp2 || CMPOp1 == CMPOp2 { 618 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 619 | if nextptr != -1 { 620 | ptr = nextptr 621 | continue 622 | } else { 623 | return execpath 624 | } 625 | } 626 | ptr++ 627 | continue 628 | case "jg": 629 | if CMPOp1 > CMPOp2 { 630 | nextptr := FindAddressPtr(opcodes, resolveAddress(opcodes[ptr].OpStr)) 631 | if nextptr != -1 { 632 | ptr = nextptr 633 | continue 634 | } else { 635 | return execpath 636 | } 637 | } 638 | ptr++ 639 | continue 640 | case "loop": 641 | // Will almost never end well, end simulation 642 | return execpath 643 | case "lea": 644 | // Will almost never end well, end simulation 645 | return execpath 646 | case "ret", "call", "les", "out", "iret": 647 | // End the simulation, since not possible 648 | return execpath 649 | } 650 | 651 | // Check if it's maths 652 | switch opcodes[ptr].Mnemonic { 653 | case "cmp": 654 | // ok, so it is normally formualted like: 655 | // cmp cx, 0x7bc 656 | // or 657 | // cmp dx, cx 658 | 659 | // so we will split the ops in two and then I guess run some 660 | // test maths on them, CPU flags are going to be a pain but that's 661 | // just like in the world of x86! 662 | 663 | if len(parts) != 2 { 664 | log.Fatalf("there is a more than two pram CMP! WTF?! %#v", parts) 665 | } 666 | 667 | if isRegister(parts[0]) { 668 | CMPOp1 = resolveRegister(parts[0], state) 669 | } else if resolveAddress(parts[0]) != 999999999 { 670 | // It's an address! neat. 671 | CMPOp1 = uint16(resolveAddress(parts[0])) 672 | } else { 673 | log.Printf("Unsupported CMP %#v", parts) 674 | return execpath 675 | } 676 | 677 | if isRegister(parts[1]) { 678 | CMPOp2 = resolveRegister(parts[1], state) 679 | } else if resolveAddress(parts[1]) != 999999999 { 680 | // It's an address! neat. 681 | CMPOp2 = uint16(resolveAddress(parts[1])) 682 | } else { 683 | log.Printf("Unsupported CMP %#v", parts) 684 | return execpath 685 | } 686 | 687 | ptr++ 688 | continue 689 | case "sub": 690 | if len(parts) != 2 { 691 | log.Fatalf("there is a more than two pram SUB! WTF?! %#v", parts) 692 | } 693 | 694 | CMPOp1 = resolveRegister(parts[0], state) 695 | if isRegister(parts[1]) { 696 | CMPOp2 = resolveRegister(parts[1], state) 697 | } else { 698 | CMPOp2 = uint16(resolveAddress(parts[1])) 699 | } 700 | 701 | state = setRegister(parts[0], CMPOp1-CMPOp2, state) 702 | ptr++ 703 | continue 704 | case "inc": 705 | CMPOp1 = resolveRegister(parts[0], state) 706 | state = setRegister(parts[0], CMPOp1+1, state) 707 | ptr++ 708 | continue 709 | case "or": 710 | if len(parts) != 2 { 711 | log.Fatalf("there is a more than two pram ADD! WTF?! %#v", parts) 712 | } 713 | 714 | CMPOp1 = resolveRegister(parts[0], state) 715 | if isRegister(parts[1]) { 716 | CMPOp2 = resolveRegister(parts[1], state) 717 | } else { 718 | CMPOp2 = uint16(resolveAddress(parts[1])) 719 | } 720 | 721 | state = setRegister(parts[0], CMPOp1|CMPOp2, state) 722 | ptr++ 723 | continue 724 | case "xor": 725 | if len(parts) != 2 { 726 | log.Fatalf("there is a more than two pram ADD! WTF?! %#v", parts) 727 | } 728 | 729 | CMPOp1 = resolveRegister(parts[0], state) 730 | if isRegister(parts[1]) { 731 | CMPOp2 = resolveRegister(parts[1], state) 732 | } else { 733 | CMPOp2 = uint16(resolveAddress(parts[1])) 734 | } 735 | 736 | state = setRegister(parts[0], CMPOp1^CMPOp2, state) 737 | ptr++ 738 | continue 739 | case "add": 740 | if len(parts) != 2 { 741 | log.Fatalf("there is a more than two pram ADD! WTF?! %#v", parts) 742 | } 743 | 744 | CMPOp1 = resolveRegister(parts[0], state) 745 | if isRegister(parts[1]) { 746 | CMPOp2 = resolveRegister(parts[1], state) 747 | } else { 748 | CMPOp2 = uint16(resolveAddress(parts[1])) 749 | } 750 | 751 | state = setRegister(parts[0], CMPOp1+CMPOp2, state) 752 | ptr++ 753 | continue 754 | case "and": 755 | if len(parts) != 2 { 756 | log.Fatalf("there is a more than two pram ADD! WTF?! %#v", parts) 757 | } 758 | 759 | CMPOp1 = resolveRegister(parts[0], state) 760 | if isRegister(parts[1]) { 761 | CMPOp2 = resolveRegister(parts[1], state) 762 | } else { 763 | CMPOp2 = uint16(resolveAddress(parts[1])) 764 | } 765 | 766 | state = setRegister(parts[0], CMPOp1&CMPOp2, state) 767 | ptr++ 768 | continue 769 | 770 | case "dec": 771 | CMPOp1 = resolveRegister(parts[0], state) 772 | state = setRegister(parts[0], CMPOp1-1, state) 773 | ptr++ 774 | continue 775 | case "shl": 776 | // Fuck this lol 777 | if len(parts) != 2 { 778 | log.Fatalf("there is a more than two pram SHL! WTF?! %#v", parts) 779 | } 780 | 781 | CMPOp1 = resolveRegister(parts[0], state) 782 | if isRegister(parts[1]) { 783 | CMPOp2 = resolveRegister(parts[1], state) 784 | } else { 785 | CMPOp2 = uint16(resolveAddress(parts[1])) 786 | } 787 | 788 | if CMPOp2 == 1 { 789 | state = setRegister(parts[0], CMPOp1*2, state) 790 | } else { 791 | for i := uint16(0); i < CMPOp2; i++ { 792 | CMPOp1 = CMPOp1 * 2 793 | } 794 | state = setRegister(parts[0], CMPOp1, state) 795 | } 796 | ptr++ 797 | continue 798 | case "mul": 799 | if len(parts) != 1 { 800 | log.Fatalf("Invalid MUL! %#v", parts) 801 | } 802 | 803 | CMPOp1 = resolveRegister("al", state) 804 | if isRegister(parts[0]) { 805 | CMPOp2 = resolveRegister(parts[0], state) 806 | } else { 807 | CMPOp2 = uint16(resolveAddress(parts[0])) 808 | } 809 | 810 | state = setRegister("ax", CMPOp1*CMPOp2, state) 811 | ptr++ 812 | continue 813 | // case "not": 814 | // case "test": 815 | case "cld": 816 | CPUFlags.DF = false 817 | ptr++ 818 | continue 819 | 820 | // case "xchg": 821 | //https://www.felixcloutier.com/x86/XCHG.html 822 | 823 | case "nop", "cli", "sti": 824 | // Nice 825 | ptr++ 826 | continue 827 | } 828 | 829 | // // Stack 830 | // // Check if it's maths 831 | // switch opcodes[ptr].Mnemonic { 832 | // case "pop": 833 | // case "push": 834 | // case "pushf": 835 | // // Push Flags 836 | // } 837 | 838 | // Mov: 839 | if opcodes[ptr].Mnemonic == "mov" { 840 | if isRegister(parts[1]) { 841 | CMPOp2 = resolveRegister(parts[1], state) 842 | } else { 843 | CMPOp2 = uint16(resolveAddress(parts[1])) 844 | } 845 | 846 | state = setRegister(parts[0], CMPOp2, state) 847 | ptr++ 848 | continue 849 | } 850 | 851 | if opcodes[ptr].Mnemonic == "stosw" { 852 | // https://www.felixcloutier.com/x86/STOS:STOSB:STOSW:STOSD:STOSQ.html 853 | } 854 | 855 | //log.Printf("Bailing out since I've hit an unsupported opcode %s\t%s", opcodes[ptr].Mnemonic, opcodes[ptr].OpStr) 856 | break 857 | } 858 | 859 | return execpath 860 | } 861 | 862 | func setRegister(register string, value uint16, g gdb.X86Registers) gdb.X86Registers { 863 | if !isRegister(register) { 864 | log.Printf("!!! Register set with a invalid register name! %s", register) 865 | return g 866 | } 867 | 868 | switch register { 869 | case "ax": 870 | g.Eax = uint32(value) 871 | return g 872 | case "bx": 873 | g.Ebx = uint32(value) 874 | return g 875 | case "cx": 876 | g.Ecx = uint32(value) 877 | return g 878 | case "dx": 879 | g.Edx = uint32(value) 880 | return g 881 | 882 | case "ah": 883 | reg := make([]byte, 2) 884 | binary.LittleEndian.PutUint16(reg, uint16(g.Eax)) 885 | // AL:AH 886 | u16 := binary.LittleEndian.Uint16([]byte{reg[0], byte(value)}) 887 | g.Eax = uint32(u16) 888 | return g 889 | 890 | case "al": 891 | reg := make([]byte, 2) 892 | binary.LittleEndian.PutUint16(reg, uint16(g.Eax)) 893 | // AL:AH 894 | u16 := binary.LittleEndian.Uint16([]byte{byte(value), reg[1]}) 895 | g.Eax = uint32(u16) 896 | return g 897 | 898 | case "bh": 899 | reg := make([]byte, 2) 900 | binary.LittleEndian.PutUint16(reg, uint16(g.Ebx)) 901 | // AL:AH 902 | u16 := binary.LittleEndian.Uint16([]byte{reg[0], byte(value)}) 903 | g.Ebx = uint32(u16) 904 | return g 905 | case "bl": 906 | reg := make([]byte, 2) 907 | binary.LittleEndian.PutUint16(reg, uint16(g.Ebx)) 908 | // AL:AH 909 | u16 := binary.LittleEndian.Uint16([]byte{byte(value), reg[1]}) 910 | g.Ebx = uint32(u16) 911 | return g 912 | 913 | case "ch": 914 | reg := make([]byte, 2) 915 | binary.LittleEndian.PutUint16(reg, uint16(g.Ecx)) 916 | // AL:AH 917 | u16 := binary.LittleEndian.Uint16([]byte{reg[0], byte(value)}) 918 | g.Ecx = uint32(u16) 919 | return g 920 | case "cl": 921 | reg := make([]byte, 2) 922 | binary.LittleEndian.PutUint16(reg, uint16(g.Ecx)) 923 | // AL:AH 924 | u16 := binary.LittleEndian.Uint16([]byte{byte(value), reg[1]}) 925 | g.Ecx = uint32(u16) 926 | return g 927 | 928 | case "dh": 929 | reg := make([]byte, 2) 930 | binary.LittleEndian.PutUint16(reg, uint16(g.Edx)) 931 | // DL:DH 932 | u16 := binary.LittleEndian.Uint16([]byte{reg[0], byte(value)}) 933 | g.Edx = uint32(u16) 934 | return g 935 | 936 | case "dl": 937 | reg := make([]byte, 2) 938 | binary.LittleEndian.PutUint16(reg, uint16(g.Edx)) 939 | // DL:DH 940 | u16 := binary.LittleEndian.Uint16([]byte{byte(value), reg[1]}) 941 | g.Edx = uint32(u16) 942 | return g 943 | 944 | case "bp": 945 | g.Ebp = uint32(value) 946 | return g 947 | 948 | case "si": 949 | g.Esi = uint32(value) 950 | return g 951 | 952 | case "di": 953 | g.Edi = uint32(value) 954 | return g 955 | 956 | case "sp": 957 | g.Esp = uint32(value) 958 | return g 959 | 960 | } 961 | 962 | log.Fatalf("Boom, Unknown register, still got past filter") 963 | return g 964 | 965 | } 966 | 967 | func isRegister(in string) bool { 968 | if in == "ax" || in == "ah" || in == "al" || 969 | in == "bh" || in == "bl" || in == "bx" || 970 | in == "ch" || in == "cl" || in == "cx" || 971 | in == "dh" || in == "dl" || in == "dx" || 972 | in == "bp" || in == "si" || in == "di" || in == "sp" { 973 | return true 974 | } 975 | return false 976 | } 977 | 978 | func resolveRegister(register string, g gdb.X86Registers) uint16 { 979 | switch register { 980 | case "ax": 981 | return uint16(g.Eax) 982 | case "bx": 983 | return uint16(g.Ebx) 984 | case "cx": 985 | return uint16(g.Ecx) 986 | case "dx": 987 | return uint16(g.Edx) 988 | 989 | // lol you are now fucked 990 | 991 | case "ah": 992 | reg := make([]byte, 2) 993 | binary.LittleEndian.PutUint16(reg, uint16(g.Eax)) 994 | return uint16(reg[1]) 995 | 996 | case "al": 997 | reg := make([]byte, 2) 998 | binary.LittleEndian.PutUint16(reg, uint16(g.Eax)) 999 | return uint16(reg[0]) 1000 | 1001 | case "bh": 1002 | reg := make([]byte, 2) 1003 | binary.LittleEndian.PutUint16(reg, uint16(g.Ebx)) 1004 | return uint16(reg[1]) 1005 | 1006 | case "bl": 1007 | reg := make([]byte, 2) 1008 | binary.LittleEndian.PutUint16(reg, uint16(g.Ebx)) 1009 | return uint16(reg[0]) 1010 | 1011 | case "ch": 1012 | reg := make([]byte, 2) 1013 | binary.LittleEndian.PutUint16(reg, uint16(g.Ecx)) 1014 | return uint16(reg[1]) 1015 | 1016 | case "cl": 1017 | reg := make([]byte, 2) 1018 | binary.LittleEndian.PutUint16(reg, uint16(g.Ecx)) 1019 | return uint16(reg[0]) 1020 | 1021 | case "dh": 1022 | reg := make([]byte, 2) 1023 | binary.LittleEndian.PutUint16(reg, uint16(g.Edx)) 1024 | return uint16(reg[1]) 1025 | 1026 | case "dl": 1027 | reg := make([]byte, 2) 1028 | binary.LittleEndian.PutUint16(reg, uint16(g.Edx)) 1029 | return uint16(reg[0]) 1030 | 1031 | case "bp": 1032 | return uint16(g.Ebp) 1033 | 1034 | case "si": 1035 | return uint16(g.Esi) 1036 | 1037 | case "di": 1038 | return uint16(g.Edi) 1039 | 1040 | case "sp": 1041 | return uint16(g.Esp) 1042 | 1043 | } 1044 | log.Fatalf("UNKNOWN REGISTER %s", register) 1045 | return 0 1046 | } 1047 | 1048 | func resolveAddress(in string) uint { 1049 | if !strings.HasPrefix(in, "0x") { 1050 | i, err := strconv.ParseInt(in, 10, 64) 1051 | if err == nil { 1052 | return uint(i) 1053 | } 1054 | return 999999999 1055 | } 1056 | // log.Printf("Hello from 0x parsing land") 1057 | s := strings.Split(in, "x") 1058 | if len(s) != 2 { 1059 | return 999999999 1060 | } 1061 | 1062 | if len(s[1])%2 == 1 { 1063 | // we need to pad one 1064 | s[1] = "0" + s[1] 1065 | } 1066 | 1067 | i, err := strconv.ParseUint(s[1], 16, 20) 1068 | if err != nil { 1069 | return 999999999 1070 | } 1071 | 1072 | return uint(i) 1073 | } 1074 | 1075 | func FindAddressPtr(opcodes []gapstone.Instruction, target uint) int { 1076 | for k, v := range opcodes { 1077 | if v.Address == target { 1078 | return k 1079 | } 1080 | } 1081 | 1082 | return -1 1083 | } 1084 | --------------------------------------------------------------------------------