Skip to content

Commit e9556d5

Browse files
committed
version v0.30.0
1 parent 11636e1 commit e9556d5

File tree

9 files changed

+275
-12
lines changed

9 files changed

+275
-12
lines changed

README.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
# FACT
2-
Forensic Artifacts Collecting Toolkit.
1+
# Forensic Artifacts Collecting Toolkit
2+
3+
A basic shell pipeline for extracting forensic artifacts from disk images. Relevant artifacts will be processed and provided in [ECS](https://www.elastic.co/guide/en/ecs/current/index.html) format for ingestion with [Logstash](https://www.elastic.co/de/logstash).
34

45
```sh
5-
# fmount image.dd | ffind | flog
6+
# fmount image.dd | ffind | flog -D logstash
67
```
78

89
## Tools
@@ -99,14 +100,12 @@ Required system commands:
99100

100101
> Use `make tools` to install [Eric Zimmerman's Tools](https://ericzimmerman.github.io/#!index.md).
101102
102-
#### Roadmap
103-
- [ ] [System Active Directory](https://forensics.wiki/active_directory/)
104-
- [ ] [System Prefetch Files](https://forensics.wiki/prefetch/)
105-
- [x] [System Event Logs](https://forensics.wiki/windows_event_log_%28evt%29/)
106-
- [ ] [System AmCache](https://forensics.wiki/amcache/)
107-
- [x] [User JumpLists](https://forensics.wiki/jump_lists/)
108-
- [x] [User ShellBags](https://forensics.wiki/shell_item/)
109-
- [ ] [User Browser Histories](https://forensics.wiki/google_chrome/)
103+
Supported artifacts for Windows 7+ systems:
104+
105+
- [System Event Logs](https://forensics.wiki/windows_event_log_%28evt%29/)
106+
- [User JumpLists](https://forensics.wiki/jump_lists/)
107+
- [User ShellBags](https://forensics.wiki/shell_item/)
108+
- [User Browser Histories](https://forensics.wiki/google_chrome/)
110109

111110
## License
112111
Released under the [MIT License](LICENSE).

go.mod

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,21 @@ module github.com/cuhsat/fact
33
go 1.22
44

55
require (
6+
github.com/dustin/go-humanize v1.0.1 // indirect
67
github.com/go-ole/go-ole v1.3.0 // indirect
8+
github.com/google/uuid v1.6.0 // indirect
9+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
10+
github.com/mattn/go-isatty v0.0.20 // indirect
711
github.com/mxk/go-vss v1.2.0 // indirect
12+
github.com/ncruces/go-strftime v0.1.9 // indirect
13+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
814
golang.org/x/sync v0.7.0 // indirect
9-
golang.org/x/sys v0.15.0 // indirect
15+
golang.org/x/sys v0.19.0 // indirect
16+
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
17+
modernc.org/libc v1.52.1 // indirect
18+
modernc.org/mathutil v1.6.0 // indirect
19+
modernc.org/memory v1.8.0 // indirect
20+
modernc.org/sqlite v1.30.1 // indirect
21+
modernc.org/strutil v1.2.0 // indirect
22+
modernc.org/token v1.1.0 // indirect
1023
)

go.sum

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
1+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
2+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
13
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
24
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
5+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
6+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
7+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
8+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
9+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
10+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
311
github.com/mxk/go-vss v1.2.0 h1:JpdOPc/P6B3XyRoddn0iMiG/ADBi3AuEsv8RlTb+JeE=
412
github.com/mxk/go-vss v1.2.0/go.mod h1:ZQ4yFxCG54vqPnCd+p2IxAe5jwZdz56wSjbwzBXiFd8=
13+
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
14+
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
15+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
16+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
517
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
618
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
719
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
20+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
821
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
922
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
23+
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
24+
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
25+
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
26+
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
27+
modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
28+
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
29+
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
30+
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
31+
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
32+
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
33+
modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
34+
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
35+
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
36+
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
37+
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
38+
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

internal/flog/sqlite.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SQLite functions.
2+
package flog
3+
4+
import (
5+
"database/sql"
6+
"path/filepath"
7+
"strings"
8+
9+
_ "modernc.org/sqlite"
10+
)
11+
12+
type Url struct {
13+
Title string
14+
Url string
15+
Time int64
16+
}
17+
18+
func History(src string) (urls []Url, err error) {
19+
var query string
20+
21+
switch strings.ToLower(filepath.Base(src)) {
22+
case "history":
23+
query = `
24+
SELECT u.url, u.title, (t.visit_time-11644473600000000)
25+
FROM urls AS u, visits AS t
26+
WHERE u.id = t.url
27+
;`
28+
case "places.sqlite":
29+
query = `
30+
SELECT u.url, COALESCE(u.title, ''), t.visit_date
31+
FROM moz_places AS u, moz_historyvisits AS t
32+
WHERE u.id = t.place_id
33+
;`
34+
}
35+
36+
db, err := sql.Open("sqlite", src)
37+
38+
if err != nil {
39+
return
40+
}
41+
42+
defer db.Close()
43+
44+
rows, err := db.Query(query)
45+
46+
if err != nil {
47+
return
48+
}
49+
50+
defer rows.Close()
51+
52+
for rows.Next() {
53+
u := Url{}
54+
55+
err = rows.Scan(&u.Url, &u.Title, &u.Time)
56+
57+
if err != nil {
58+
return
59+
}
60+
61+
urls = append(urls, u)
62+
}
63+
64+
err = rows.Err()
65+
66+
return
67+
}

internal/testdata/windows/user.zip

17.9 KB
Binary file not shown.

pkg/ecs/history.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// ECS history mapping functions.
2+
package ecs
3+
4+
import (
5+
"fmt"
6+
"net"
7+
"net/url"
8+
"strconv"
9+
"time"
10+
11+
"github.com/cuhsat/fact/internal/flog"
12+
)
13+
14+
func MapHistory(fu *flog.Url, src string) (log *Log, err error) {
15+
log = NewLog(fmt.Sprint(fu), src, &Base{
16+
Timestamp: time.Unix(fu.Time/1000000, 0).UTC(),
17+
Message: fu.Url,
18+
Tags: "History",
19+
Labels: map[string]interface{}{
20+
"Title": fu.Title,
21+
},
22+
})
23+
24+
u, err := url.Parse(fu.Url)
25+
26+
if err != nil {
27+
return log, nil
28+
}
29+
30+
log.Url = &Url{
31+
Original: fu.Url,
32+
Full: u.String(),
33+
Scheme: u.Scheme,
34+
Domain: u.Host,
35+
Path: u.Path,
36+
Query: u.RawQuery,
37+
Fragment: u.Fragment,
38+
Username: u.User.Username(),
39+
}
40+
41+
log.Url.Password, _ = u.User.Password()
42+
43+
_, p, err1 := net.SplitHostPort(u.Host)
44+
port, err2 := strconv.ParseInt(p, 10, 64)
45+
46+
if err1 == nil || err2 == nil {
47+
log.Url.Port = port
48+
}
49+
50+
return
51+
}

pkg/ecs/spec.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Log struct {
2222
Agent *Agent `json:"agent"`
2323
Event *Evt `json:"event"`
2424
File *File `json:"file"`
25+
Url *Url `json:"url,omitempty"`
2526
Host *Host `json:"host,omitempty"`
2627
User *User `json:"user,omitempty"`
2728
Process *Process `json:"process,omitempty"`
@@ -66,6 +67,19 @@ type File struct {
6667
Path string `json:"path,omitempty"`
6768
}
6869

70+
type Url struct {
71+
Original string `json:"original,omitempty"`
72+
Full string `json:"full,omitempty"`
73+
Scheme string `json:"scheme,omitempty"`
74+
Domain string `json:"domain,omitempty"`
75+
Port int64 `json:"port,omitempty"`
76+
Path string `json:"path,omitempty"`
77+
Query string `json:"query,omitempty"`
78+
Fragment string `json:"fragment,omitempty"`
79+
Username string `json:"username,omitempty"`
80+
Password string `json:"password,omitempty"`
81+
}
82+
6983
type Host struct {
7084
Hostname string `json:"hostname,omitempty"`
7185
MAC string `json:"mac,omitempty"`

pkg/flog/flog.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ func Log(files []string, dir string, jp bool) error {
2424
"usrclass.dat",
2525
}
2626

27+
usrHistory := []string{
28+
"history",
29+
"places.sqlite",
30+
}
31+
2732
g := new(errgroup.Group)
2833

2934
for _, f := range files {
@@ -38,6 +43,8 @@ func Log(files []string, dir string, jp bool) error {
3843
fn = LogJumpList
3944
} else if slices.Contains(usrHives, name) {
4045
fn = LogShellBag
46+
} else if slices.Contains(usrHistory, name) {
47+
fn = LogHistory
4148
} else {
4249
continue
4350
}
@@ -167,6 +174,40 @@ func LogShellBag(src, dir string, jp bool) (logs []string, err error) {
167174
return logs, nil
168175
}
169176

177+
func LogHistory(src, dir string, jp bool) (logs []string, err error) {
178+
ll, err := flog.History(src)
179+
180+
if err != nil {
181+
return
182+
}
183+
184+
if err = os.MkdirAll(dir, sys.MODE_DIR); err != nil {
185+
return
186+
}
187+
188+
for _, l := range ll {
189+
dst := filepath.Join(dir, fmt.Sprintf("%s.json", ecs.Hash(fmt.Sprint(l))))
190+
191+
m, err := ecs.MapHistory(&l, src)
192+
193+
if err != nil {
194+
sys.Error(err)
195+
continue
196+
}
197+
198+
log, err := write(m, dst, jp)
199+
200+
if err != nil {
201+
sys.Error(err)
202+
continue
203+
}
204+
205+
logs = append(logs, log)
206+
}
207+
208+
return logs, nil
209+
}
210+
170211
func StripHash(in []string) (out []string) {
171212
if len(in) == 0 {
172213
return in

pkg/flog/flog_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,55 @@ func TestLogShellbag(t *testing.T) {
157157
}
158158
}
159159

160+
func TestLogHistory(t *testing.T) {
161+
cases := []struct {
162+
name, data, file string
163+
}{
164+
{
165+
name: "Test log history",
166+
data: test.Testdata("windows", "user.zip"),
167+
file: "History",
168+
},
169+
{
170+
name: "Test log places.sqlite",
171+
data: test.Testdata("windows", "user.zip"),
172+
file: "places.sqlite",
173+
},
174+
}
175+
176+
for _, tt := range cases {
177+
tmp, _ := os.MkdirTemp(os.TempDir(), "log")
178+
179+
err := zip.Unzip(tt.data, tmp)
180+
181+
if err != nil {
182+
t.Fatal(err)
183+
}
184+
185+
t.Run(tt.name, func(t *testing.T) {
186+
l, err := LogHistory(filepath.Join(tmp, tt.file), tmp, true)
187+
188+
if err != nil {
189+
t.Fatal(err)
190+
}
191+
192+
if len(l) == 0 {
193+
t.Fatal("file count zero")
194+
}
195+
196+
b, err := os.ReadFile(l[0])
197+
198+
if err != nil {
199+
t.Fatal(err)
200+
}
201+
202+
if !json.Valid(b) {
203+
t.Fatal("invalid json")
204+
}
205+
})
206+
}
207+
}
208+
160209
func TestStripHash(t *testing.T) {
161210
cases := []struct {
162211
name, file, hash string

0 commit comments

Comments
 (0)