├── README.md └── recorder.go /README.md: -------------------------------------------------------------------------------- 1 | # goRecorder 2 | During pentesting I often miss screenshots of events for reports due to the quick pace of testing and a lack of foreknowledge about what will be important. To remedy that problem (and also to teach myself go) I built a command line tool that implements the "clip that" functionality of gaming consoles to allow me to save the last minute of screen activity as images to later view. 3 | 4 | By default the tool stores an image a second for each screen in a 60 second sliding window, which can both be changed. Once the tool is running you can type F1 with the tool window in focus to save the current buffer to disk for later viewing. If you step away from testing, or don't want the screen recorded, type F2 to pause collection and type F2 again to resume. 5 | To exit, simply type ESC. 6 | -------------------------------------------------------------------------------- /recorder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/kbinani/screenshot" 7 | term "github.com/nsf/termbox-go" 8 | "image" 9 | "image/png" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type capture struct { 17 | screenshots []*image.RGBA 18 | timestamp time.Time 19 | } 20 | 21 | func reset() { 22 | term.Sync() 23 | } 24 | 25 | func isNumeric(s string) bool { 26 | _, err := strconv.ParseFloat(s, 64) 27 | return err == nil 28 | } 29 | 30 | func splitScreens(ids string) []int { 31 | idList := strings.Split(ids, ",") 32 | idIntList := []int{} 33 | for _, i := range idList { 34 | if i == "" || !isNumeric(i) { 35 | continue 36 | } 37 | j, err := strconv.Atoi(i) 38 | if err != nil { 39 | panic(err) 40 | } 41 | if j < 0 || j > screenshot.NumActiveDisplays() { 42 | continue 43 | } 44 | idIntList = append(idIntList, j) 45 | } 46 | return idIntList 47 | } 48 | 49 | func getScreenInfo() { 50 | screensInfo := "" 51 | n := screenshot.NumActiveDisplays() 52 | for i := 0; i < n; i++ { 53 | bounds := screenshot.GetDisplayBounds(i) 54 | screensInfo += fmt.Sprintf("%d: %d by %d Monitor\n", i, bounds.Dx(), bounds.Dy()) 55 | } 56 | fmt.Println(screensInfo) 57 | } 58 | 59 | func main() { 60 | 61 | screenPtr := flag.String("screenIds", "", "list of screen id to use, e.g. 0,1,n. Use -listScreens to see available ids") 62 | pollingPtr := flag.Int("pollingInt", 1, "How many seconds between screen grabs") 63 | bufferSizePtr := flag.Int("bufferSize", 60, "How many pictures to store in memory per screen") 64 | listScreensPtr := flag.Bool("listScreens", false, "List Screen Info") 65 | flag.Parse() 66 | if *listScreensPtr { 67 | getScreenInfo() 68 | } else { 69 | captures := make([]capture, 0) 70 | numberOfScreens := splitScreens(*screenPtr) 71 | if len(numberOfScreens) == 0 { 72 | fmt.Println("you must provide at least 1 screen using --screenIds for capture to work properly. " + 73 | "See --listScreens for available ids") 74 | } else { 75 | ticker := time.NewTicker(time.Duration(*pollingPtr) * time.Second) 76 | defer ticker.Stop() 77 | finished := make(chan bool) 78 | pause := false 79 | output := false 80 | err := term.Init() 81 | if err != nil { 82 | panic(err) 83 | } 84 | defer term.Close() 85 | go func() { 86 | for { 87 | switch ev := term.PollEvent(); ev.Type { 88 | case term.EventKey: 89 | switch ev.Key { 90 | case term.KeyEsc: 91 | reset() 92 | fmt.Println("Exiting") 93 | finished <- true 94 | return 95 | case term.KeyF1: 96 | reset() 97 | fmt.Println("Compiling Images") 98 | output = true 99 | case term.KeyF2: 100 | reset() 101 | if pause { 102 | pause = false 103 | fmt.Println("Resuming") 104 | } else { 105 | pause = true 106 | fmt.Println("Pausing") 107 | } 108 | default: 109 | reset() 110 | } 111 | } 112 | } 113 | }() 114 | for { 115 | if output { 116 | for _, c := range captures { 117 | for index, pic := range c.screenshots { 118 | fileName := fmt.Sprintf("%d_%d-%02d-%02dT%02d.%02d.%02d.png", numberOfScreens[index], 119 | c.timestamp.Year(), c.timestamp.Month(), c.timestamp.Day(), 120 | c.timestamp.Hour(), c.timestamp.Minute(), c.timestamp.Second()) 121 | file, err := os.Create(fileName) 122 | if err != nil { 123 | panic(err) 124 | } 125 | png.Encode(file, pic) 126 | file.Close() 127 | } 128 | } 129 | output = false 130 | } else if !pause { 131 | select { 132 | case <-finished: 133 | ticker.Stop() 134 | return 135 | case t := <-ticker.C: 136 | fmt.Println("Current time: ", t.UTC().Format(time.UnixDate)) 137 | var screenPics []*image.RGBA 138 | 139 | for i := 0; i < len(numberOfScreens); i++ { 140 | bounds := screenshot.GetDisplayBounds(numberOfScreens[i]) 141 | 142 | img, err := screenshot.CaptureRect(bounds) 143 | if err != nil { 144 | panic(err) 145 | } 146 | screenPics = append(screenPics, img) 147 | } 148 | if len(captures) == *bufferSizePtr { 149 | captures = captures[1:] 150 | } 151 | c := capture{screenshots: screenPics, timestamp: t} 152 | captures = append(captures, c) 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | --------------------------------------------------------------------------------