diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb94ebf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +testdata/ \ No newline at end of file diff --git a/go.mod b/go.mod index c3e8083..3ee96c2 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/kgoins/go-winacl go 1.16 -require golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 +require ( + github.com/stretchr/testify v1.7.0 + golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 +) diff --git a/go.sum b/go.sum index e731adb..7269144 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,13 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/models/acl.go b/models/acl.go deleted file mode 100644 index e78a389..0000000 --- a/models/acl.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -type ACL struct { - Header ACLHeader - Aces []ACE -} - -type ACLHeader struct { - Revision byte - Sbz1 byte - Size uint16 - AceCount uint16 - Sbz2 uint16 -} diff --git a/models/ntsecuritydescriptor.go b/models/ntsecuritydescriptor.go deleted file mode 100644 index 1165bca..0000000 --- a/models/ntsecuritydescriptor.go +++ /dev/null @@ -1,36 +0,0 @@ -package models - -import ( - "bytes" - "fmt" - "golang.org/x/sys/windows" -) - -type RawSecurityDescriptor []byte -type ParsedSecurityDescriptor struct { - Buf *bytes.Buffer - Descriptor NtSecurityDescriptor -} -type BufferedSecurityDescriptor *bytes.Buffer - -type NtSecurityDescriptor struct { - Header NtSecurityDescriptorHeader - DACL ACL - SACL ACL - Owner windows.SID - Group windows.SID -} - -type NtSecurityDescriptorHeader struct { - Revision byte - Sbz1 byte - Control uint16 - OffsetOwner uint32 - OffsetGroup uint32 - OffsetSacl uint32 - OffsetDacl uint32 -} - -func (s NtSecurityDescriptor) String() string { - return fmt.Sprintf("Parsed Security Descriptor:\n Offsets:\n Owner=%v Group=%v Sacl=%v Dacl=%v\n", s.Header.OffsetOwner, s.Header.OffsetGroup, s.Header.OffsetDacl, s.Header.OffsetSacl) -} diff --git a/models/sid.go b/models/sid.go deleted file mode 100644 index b08d11c..0000000 --- a/models/sid.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import ( - "fmt" - "strings" -) - -type SID struct { - Revision byte - NumAuthorities byte - Authority []byte - SubAuthorities []uint32 -} - -func (s SID) String() string { - var sb strings.Builder - - sb.WriteString(fmt.Sprintf("S-%v-%v", s.Revision, int(s.Authority[5]))) - for i := 0; i < int(s.NumAuthorities); i++ { - sb.WriteString(fmt.Sprintf("-%v", s.SubAuthorities[i])) - } - return sb.String() -} diff --git a/parsers/ace.go b/parsers/ace.go deleted file mode 100644 index 19cd4b5..0000000 --- a/parsers/ace.go +++ /dev/null @@ -1,66 +0,0 @@ -package parsers - -import ( - "bytes" - "encoding/binary" - "fmt" - - "github.com/kgoins/go-winacl/models" -) - -func ParseAce(buf *bytes.Buffer) models.ACE { - ace := models.ACE{} - - ace.Header = ReadACEHeader(buf) - binary.Read(buf, binary.LittleEndian, &ace.AccessMask) - switch ace.Header.Type { - case models.AceTypeAccessAllowed, models.AceTypeAccessDenied, models.AceTypeSystemAudit, models.AceTypeSystemAlarm, models.AceTypeAccessAllowedCallback, models.AceTypeAccessDeniedCallback, models.AceTypeSystemAuditCallback, models.AceTypeSystemAlarmCallback: - ace.ObjectAce = ReadBasicAce(buf, ace.Header.Size) - case models.AceTypeAccessAllowedObject, models.AceTypeAccessDeniedObject, models.AceTypeSystemAuditObject, models.AceTypeSystemAlarmObject, models.AceTypeAccessAllowedCallbackObject, models.AceTypeAccessDeniedCallbackObject, models.AceTypeSystemAuditCallbackObject, models.AceTypeSystemAlarmCallbackObject: - ace.ObjectAce = ReadAdvancedAce(buf, ace.Header.Size) - } - - return ace -} - -func ReadACEHeader(buf *bytes.Buffer) models.ACEHeader { - header := models.ACEHeader{} - binary.Read(buf, binary.LittleEndian, &header.Type) - binary.Read(buf, binary.LittleEndian, &header.Flags) - binary.Read(buf, binary.LittleEndian, &header.Size) - return header -} - -func ReadBasicAce(buf *bytes.Buffer, totalSize uint16) models.BasicAce { - oa := models.BasicAce{} - - if sid, err := ReadSID(buf, int(totalSize-8)); err != nil { - fmt.Printf("Error reading sid: %v\n", err) - } else { - oa.SecurityIdentifier = sid - } - return oa -} - -func ReadAdvancedAce(buf *bytes.Buffer, totalSize uint16) models.AdvancedAce { - oa := models.AdvancedAce{} - binary.Read(buf, binary.LittleEndian, &oa.Flags) - offset := 12 - if (oa.Flags & uint32(models.ACEInheritanceFlagsObjectTypePresent)) != 0 { - oa.ObjectType = ReadGUID(buf) - offset += 16 - } - - if (oa.Flags & uint32(models.ACEInheritanceFlagsInheritedObjectTypePresent)) != 0 { - oa.InheritedObjectType = ReadGUID(buf) - offset += 16 - } - - // Header+AccessMask is 16 bytes, other members are 36 bytes. - if sid, err := ReadSID(buf, int(totalSize)-offset); err != nil { - fmt.Printf("Error reading sid: %v\n", err) - } else { - oa.SecurityIdentifier = sid - } - return oa -} diff --git a/parsers/ntsecuritydescriptor.go b/parsers/ntsecuritydescriptor.go deleted file mode 100644 index f8c6929..0000000 --- a/parsers/ntsecuritydescriptor.go +++ /dev/null @@ -1,55 +0,0 @@ -package parsers - -import ( - "bytes" - "encoding/binary" - - "github.com/kgoins/go-winacl/models" -) - -func ParseNtSecurityDescriptor(ntSecurityDescriptorBytes []byte) (models.NtSecurityDescriptor, error) { - var buf = bytes.NewBuffer(ntSecurityDescriptorBytes) - ntsd := models.NtSecurityDescriptor{} - ntsd.Header = ReadNTSDHeader(buf) - ntsd.DACL = ReadACL(buf) - - return ntsd, nil -} - -func ReadNTSDHeader(buf *bytes.Buffer) models.NtSecurityDescriptorHeader { - var descriptor = models.NtSecurityDescriptorHeader{} - - binary.Read(buf, binary.LittleEndian, &descriptor.Revision) - binary.Read(buf, binary.LittleEndian, &descriptor.Sbz1) - binary.Read(buf, binary.LittleEndian, &descriptor.Control) - binary.Read(buf, binary.LittleEndian, &descriptor.OffsetOwner) - binary.Read(buf, binary.LittleEndian, &descriptor.OffsetGroup) - binary.Read(buf, binary.LittleEndian, &descriptor.OffsetSacl) - binary.Read(buf, binary.LittleEndian, &descriptor.OffsetDacl) - - return descriptor -} - -func ReadACLHeader(buf *bytes.Buffer) models.ACLHeader { - var header = models.ACLHeader{} - binary.Read(buf, binary.LittleEndian, &header.Revision) - binary.Read(buf, binary.LittleEndian, &header.Sbz1) - binary.Read(buf, binary.LittleEndian, &header.Size) - binary.Read(buf, binary.LittleEndian, &header.AceCount) - binary.Read(buf, binary.LittleEndian, &header.Sbz2) - - return header -} - -func ReadACL(buf *bytes.Buffer) models.ACL { - acl := models.ACL{} - acl.Header = ReadACLHeader(buf) - acl.Aces = make([]models.ACE, 0, acl.Header.AceCount) - - for i := 0; i < int(acl.Header.AceCount); i++ { - ace := ParseAce(buf) - acl.Aces = append(acl.Aces, ace) - } - - return acl -} diff --git a/models/ace.go b/pkg/ace.go similarity index 92% rename from models/ace.go rename to pkg/ace.go index d4005b8..df26204 100644 --- a/models/ace.go +++ b/pkg/ace.go @@ -1,8 +1,7 @@ -package models +package winacl import ( "fmt" - "golang.org/x/sys/windows" "strings" ) @@ -47,10 +46,12 @@ const ( ACEInheritanceFlagsInheritedObjectTypePresent = 0x02 ) +type ACEAccessMask uint32 + //Header + AccessMask is 16 bytes type ACE struct { Header ACEHeader - AccessMask windows.ACCESS_MASK + AccessMask ACEAccessMask ObjectAce ObjectAce } @@ -95,9 +96,9 @@ func (s BasicAce) GetPrincipal() SID { } type AdvancedAce struct { - Flags uint32 //4 bytes - ObjectType windows.GUID //16 bytes - InheritedObjectType windows.GUID + Flags uint32 //4 bytes + ObjectType GUID //16 bytes + InheritedObjectType GUID SecurityIdentifier SID } @@ -115,8 +116,7 @@ type SystemAuditAce BasicAce type SystemAlarmAce BasicAce // No idea what this actually is and it doesn't appear to be documented anywhere -type AccessAllowedCompoundAce struct { -} +type AccessAllowedCompoundAce struct{} type AccessAllowedObjectAce AdvancedAce type AccessDeniedObjectAce AdvancedAce diff --git a/pkg/acebuilder.go b/pkg/acebuilder.go new file mode 100644 index 0000000..619601c --- /dev/null +++ b/pkg/acebuilder.go @@ -0,0 +1,64 @@ +package winacl + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +func NewAce(buf *bytes.Buffer) ACE { + ace := ACE{} + + ace.Header = NewACEHeader(buf) + binary.Read(buf, binary.LittleEndian, &ace.AccessMask) + switch ace.Header.Type { + case AceTypeAccessAllowed, AceTypeAccessDenied, AceTypeSystemAudit, AceTypeSystemAlarm, AceTypeAccessAllowedCallback, AceTypeAccessDeniedCallback, AceTypeSystemAuditCallback, AceTypeSystemAlarmCallback: + ace.ObjectAce = NewBasicAce(buf, ace.Header.Size) + case AceTypeAccessAllowedObject, AceTypeAccessDeniedObject, AceTypeSystemAuditObject, AceTypeSystemAlarmObject, AceTypeAccessAllowedCallbackObject, AceTypeAccessDeniedCallbackObject, AceTypeSystemAuditCallbackObject, AceTypeSystemAlarmCallbackObject: + ace.ObjectAce = NewAdvancedAce(buf, ace.Header.Size) + } + + return ace +} + +func NewACEHeader(buf *bytes.Buffer) ACEHeader { + header := ACEHeader{} + binary.Read(buf, binary.LittleEndian, &header.Type) + binary.Read(buf, binary.LittleEndian, &header.Flags) + binary.Read(buf, binary.LittleEndian, &header.Size) + return header +} + +func NewBasicAce(buf *bytes.Buffer, totalSize uint16) BasicAce { + oa := BasicAce{} + + if sid, err := NewSID(buf, int(totalSize-8)); err != nil { + fmt.Printf("Error reading sid: %v\n", err) + } else { + oa.SecurityIdentifier = sid + } + return oa +} + +func NewAdvancedAce(buf *bytes.Buffer, totalSize uint16) AdvancedAce { + oa := AdvancedAce{} + binary.Read(buf, binary.LittleEndian, &oa.Flags) + offset := 12 + if (oa.Flags & uint32(ACEInheritanceFlagsObjectTypePresent)) != 0 { + oa.ObjectType = NewGUID(buf) + offset += 16 + } + + if (oa.Flags & uint32(ACEInheritanceFlagsInheritedObjectTypePresent)) != 0 { + oa.InheritedObjectType = NewGUID(buf) + offset += 16 + } + + // Header+AccessMask is 16 bytes, other members are 36 bytes. + if sid, err := NewSID(buf, int(totalSize)-offset); err != nil { + fmt.Printf("Error reading sid: %v\n", err) + } else { + oa.SecurityIdentifier = sid + } + return oa +} diff --git a/pkg/acl.go b/pkg/acl.go new file mode 100644 index 0000000..b1c1258 --- /dev/null +++ b/pkg/acl.go @@ -0,0 +1,43 @@ +package winacl + +import ( + "bytes" + "encoding/binary" +) + +type ACL struct { + Header ACLHeader + Aces []ACE +} + +type ACLHeader struct { + Revision byte + Sbz1 byte + Size uint16 + AceCount uint16 + Sbz2 uint16 +} + +func NewACLHeader(buf *bytes.Buffer) ACLHeader { + var header = ACLHeader{} + binary.Read(buf, binary.LittleEndian, &header.Revision) + binary.Read(buf, binary.LittleEndian, &header.Sbz1) + binary.Read(buf, binary.LittleEndian, &header.Size) + binary.Read(buf, binary.LittleEndian, &header.AceCount) + binary.Read(buf, binary.LittleEndian, &header.Sbz2) + + return header +} + +func NewACL(buf *bytes.Buffer) ACL { + acl := ACL{} + acl.Header = NewACLHeader(buf) + acl.Aces = make([]ACE, 0, acl.Header.AceCount) + + for i := 0; i < int(acl.Header.AceCount); i++ { + ace := NewAce(buf) + acl.Aces = append(acl.Aces, ace) + } + + return acl +} diff --git a/pkg/guid.go b/pkg/guid.go new file mode 100644 index 0000000..360f7ed --- /dev/null +++ b/pkg/guid.go @@ -0,0 +1,22 @@ +package winacl + +import ( + "bytes" + "encoding/binary" +) + +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +func NewGUID(buf *bytes.Buffer) GUID { + guid := GUID{} + binary.Read(buf, binary.LittleEndian, &guid.Data1) + binary.Read(buf, binary.LittleEndian, &guid.Data2) + binary.Read(buf, binary.LittleEndian, &guid.Data3) + binary.Read(buf, binary.LittleEndian, &guid.Data4) + return guid +} diff --git a/pkg/ntsd.go b/pkg/ntsd.go new file mode 100644 index 0000000..d27de0b --- /dev/null +++ b/pkg/ntsd.go @@ -0,0 +1,33 @@ +package winacl + +import ( + "bytes" + "fmt" +) + +type NtSecurityDescriptor struct { + Header NtSecurityDescriptorHeader + DACL ACL + SACL ACL + Owner SID + Group SID +} + +func (s NtSecurityDescriptor) String() string { + return fmt.Sprintf( + "Parsed Security Descriptor:\n Offsets:\n Owner=%v Group=%v Sacl=%v Dacl=%v\n", + s.Header.OffsetOwner, + s.Header.OffsetGroup, + s.Header.OffsetDacl, + s.Header.OffsetSacl, + ) +} + +func NewNtSecurityDescriptor(ntsdBytes []byte) (NtSecurityDescriptor, error) { + var buf = bytes.NewBuffer(ntsdBytes) + ntsd := NtSecurityDescriptor{} + ntsd.Header = NewNTSDHeader(buf) + ntsd.DACL = NewACL(buf) + + return ntsd, nil +} diff --git a/pkg/ntsd_test.go b/pkg/ntsd_test.go new file mode 100644 index 0000000..1a54f46 --- /dev/null +++ b/pkg/ntsd_test.go @@ -0,0 +1,35 @@ +package winacl_test + +import ( + "encoding/base64" + "io/ioutil" + "path/filepath" + "testing" + + winacl "github.com/kgoins/go-winacl/pkg" + "github.com/stretchr/testify/require" +) + +func getTestNtsdBytes() ([]byte, error) { + testFile := filepath.Join(getTestDataDir(), "ntsd.b64") + testBytes, err := ioutil.ReadFile(testFile) + if err != nil { + return nil, err + } + + return base64.StdEncoding.DecodeString(string(testBytes)) +} + +func TestBuildNTSD(t *testing.T) { + r := require.New(t) + + ntsdBytes, err := getTestNtsdBytes() + r.NoError(err) + + ntsd, err := winacl.NewNtSecurityDescriptor(ntsdBytes) + r.NoError(err) + + dacl := ntsd.DACL + r.NotNil(dacl) + r.Equal(dacl.Header.AceCount, len(dacl.Aces)) +} diff --git a/pkg/ntsdheader.go b/pkg/ntsdheader.go new file mode 100644 index 0000000..896fc46 --- /dev/null +++ b/pkg/ntsdheader.go @@ -0,0 +1,30 @@ +package winacl + +import ( + "bytes" + "encoding/binary" +) + +type NtSecurityDescriptorHeader struct { + Revision byte + Sbz1 byte + Control uint16 + OffsetOwner uint32 + OffsetGroup uint32 + OffsetSacl uint32 + OffsetDacl uint32 +} + +func NewNTSDHeader(buf *bytes.Buffer) NtSecurityDescriptorHeader { + var descriptor = NtSecurityDescriptorHeader{} + + binary.Read(buf, binary.LittleEndian, &descriptor.Revision) + binary.Read(buf, binary.LittleEndian, &descriptor.Sbz1) + binary.Read(buf, binary.LittleEndian, &descriptor.Control) + binary.Read(buf, binary.LittleEndian, &descriptor.OffsetOwner) + binary.Read(buf, binary.LittleEndian, &descriptor.OffsetGroup) + binary.Read(buf, binary.LittleEndian, &descriptor.OffsetSacl) + binary.Read(buf, binary.LittleEndian, &descriptor.OffsetDacl) + + return descriptor +} diff --git a/parsers/generic.go b/pkg/sid.go similarity index 60% rename from parsers/generic.go rename to pkg/sid.go index b428ca6..827a3d2 100644 --- a/parsers/generic.go +++ b/pkg/sid.go @@ -1,25 +1,38 @@ -package parsers +package winacl import ( "bytes" "encoding/binary" "errors" - - "github.com/kgoins/go-winacl/models" - "golang.org/x/sys/windows" + "fmt" + "strings" ) -func ReadGUID(buf *bytes.Buffer) windows.GUID { - guid := windows.GUID{} - binary.Read(buf, binary.LittleEndian, &guid.Data1) - binary.Read(buf, binary.LittleEndian, &guid.Data2) - binary.Read(buf, binary.LittleEndian, &guid.Data3) - binary.Read(buf, binary.LittleEndian, &guid.Data4) - return guid +type SID struct { + Revision byte + NumAuthorities byte + Authority []byte + SubAuthorities []uint32 +} + +func (s SID) String() string { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf( + "S-%v-%v", + s.Revision, + int(s.Authority[5]), + )) + + for i := 0; i < int(s.NumAuthorities); i++ { + sb.WriteString(fmt.Sprintf("-%v", s.SubAuthorities[i])) + } + + return sb.String() } -func ReadSID(buf *bytes.Buffer, sidLength int) (models.SID, error) { - sid := models.SID{} +func NewSID(buf *bytes.Buffer, sidLength int) (SID, error) { + sid := SID{} data := buf.Next(sidLength) if revision := data[0]; revision != 1 { @@ -43,5 +56,4 @@ func ReadSID(buf *bytes.Buffer, sidLength int) (models.SID, error) { return sid, nil } - } diff --git a/pkg/testutils_test.go b/pkg/testutils_test.go new file mode 100644 index 0000000..676adc4 --- /dev/null +++ b/pkg/testutils_test.go @@ -0,0 +1,5 @@ +package winacl_test + +func getTestDataDir() string { + return "../testdata" +}