mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
314 lines
8.2 KiB
Go
314 lines
8.2 KiB
Go
package ssh
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh/knownhosts"
|
|
)
|
|
|
|
var (
|
|
//go:embed data/debugserver.plist
|
|
entitlementsData []byte
|
|
//go:embed data/com.apple.system.logging.plist
|
|
loggingPlistData []byte
|
|
//go:embed data/com.apple.CrashReporter.plist
|
|
symbolicationPlistData []byte
|
|
)
|
|
|
|
func hostKeyCallback(path string) ssh.HostKeyCallback {
|
|
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
kh, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:gomnd
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open known_hosts: %w", err)
|
|
}
|
|
defer func() { _ = kh.Close() }()
|
|
|
|
callback, err := knownhosts.New(kh.Name())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check known_hosts: %w", err)
|
|
}
|
|
|
|
if err := callback(hostname, remote, key); err != nil {
|
|
var kerr *knownhosts.KeyError
|
|
if errors.As(err, &kerr) {
|
|
if len(kerr.Want) > 0 {
|
|
return fmt.Errorf("possible man-in-the-middle attack: %w", err)
|
|
}
|
|
// if want is empty, it means the host was not in the known_hosts file, so lets add it there.
|
|
fmt.Fprintln(kh, knownhosts.Line([]string{hostname}, key))
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to check known_hosts: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Config is the configuration for an SSH connection
|
|
type Config struct {
|
|
Host string
|
|
Port string
|
|
User string
|
|
Pass string
|
|
Key string
|
|
Insecure bool
|
|
}
|
|
|
|
// SSH is an ssh object
|
|
type SSH struct {
|
|
client *ssh.Client
|
|
conf *Config
|
|
}
|
|
|
|
// NewSSH creates a new SSH connection
|
|
func NewSSH(conf *Config) (*SSH, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user home directory: %w", err)
|
|
}
|
|
knownhostsPath := filepath.Join(home, ".ssh", "known_hosts")
|
|
|
|
var signer ssh.Signer
|
|
if len(conf.Key) > 0 {
|
|
key, err := os.ReadFile(conf.Key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read private key: %w", err)
|
|
}
|
|
signer, err = ssh.ParsePrivateKey(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
|
}
|
|
}
|
|
|
|
var sshConfig *ssh.ClientConfig
|
|
if conf.Insecure {
|
|
sshConfig = &ssh.ClientConfig{
|
|
User: "root",
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.Password(conf.Pass),
|
|
},
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
}
|
|
} else {
|
|
sshConfig = &ssh.ClientConfig{
|
|
User: "root",
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.Password(conf.Pass),
|
|
},
|
|
HostKeyCallback: hostKeyCallback(knownhostsPath),
|
|
}
|
|
}
|
|
if signer != nil {
|
|
sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
|
|
}
|
|
|
|
client, err := ssh.Dial("tcp", conf.Host+":"+conf.Port, sshConfig)
|
|
if err != nil {
|
|
log.Fatalf("failed to dial: %s", err)
|
|
}
|
|
|
|
return &SSH{
|
|
client: client,
|
|
conf: conf,
|
|
}, nil
|
|
}
|
|
|
|
// Close closes the SSH connection
|
|
func (s *SSH) Close() error {
|
|
return s.client.Close()
|
|
}
|
|
|
|
// FileExists checks if a file exists on the remote device
|
|
func (s *SSH) FileExists(path string) bool {
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
log.Fatalf("failed to create session: %s", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
return session.Run(fmt.Sprintf("test -f %s", path)) == nil
|
|
}
|
|
|
|
// CopyToDevice copies a file to the remote device
|
|
func (s *SSH) CopyToDevice(src, dst string) error {
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
log.Fatalf("failed to create session: %s", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
f, err := os.Open(src)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
go func() {
|
|
w, _ := session.StdinPipe()
|
|
defer w.Close()
|
|
count, err := io.Copy(w, f)
|
|
if err != nil {
|
|
log.Fatalf("failed to copy %s to device: %v", src, err)
|
|
}
|
|
if count == 0 {
|
|
log.Fatalf("%d bytes copied to device", count)
|
|
}
|
|
}()
|
|
|
|
if err := session.Start(fmt.Sprintf("cat > %s", dst)); err != nil {
|
|
return fmt.Errorf("failed to copy %s to %s on device: %w", src, dst, err)
|
|
}
|
|
|
|
if err := session.Wait(); err != nil {
|
|
return fmt.Errorf("failed to copy %s to device: %w", src, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyFromDevice copies a file from the remote device
|
|
func (s *SSH) CopyFromDevice(src, dst string) error {
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
log.Fatalf("failed to create session: %s", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
f, err := os.Create(dst)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create file: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
go func() {
|
|
w, _ := session.StdoutPipe()
|
|
count, err := io.Copy(f, w)
|
|
if err != nil {
|
|
log.Fatalf("failed to copy %s from device: %v", src, err)
|
|
}
|
|
if count == 0 {
|
|
log.Fatalf("%d bytes copied to device", count)
|
|
}
|
|
}()
|
|
|
|
if err := session.Start(fmt.Sprintf("cat %s", src)); err != nil {
|
|
return fmt.Errorf("failed to copy %s from device to %s: %w", src, dst, err)
|
|
}
|
|
|
|
if err := session.Wait(); err != nil {
|
|
return fmt.Errorf("failed to copy %s from device: %w", src, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RunCommand runs a command on the remote device
|
|
func (s *SSH) RunCommand(cmd string) error {
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
log.Fatalf("failed to create session: %s", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
err = session.Run(cmd)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to run command: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RunCommandWithOutput runs a command on the remote device and returns the output
|
|
func (s *SSH) RunCommandWithOutput(cmd string) (string, error) {
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
log.Fatalf("failed to create session: %s", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
output, err := session.Output(cmd)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to run command: %w", err)
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|
|
|
|
// ResignDebugserver resigns debugserver with more powerful entitlements
|
|
func ResignDebugserver(dspath string) error {
|
|
entitlements, err := os.CreateTemp("", "entitlements.plist")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create tmp entitlements file: %w", err)
|
|
}
|
|
defer os.Remove(entitlements.Name()) // clean up
|
|
|
|
if _, err := entitlements.Write(entitlementsData); err != nil {
|
|
return fmt.Errorf("failed to write entitlements.plist data to tmp file: %w", err)
|
|
}
|
|
if err := entitlements.Close(); err != nil {
|
|
return fmt.Errorf("failed to close tmp entitlements.plist file: %w", err)
|
|
}
|
|
|
|
if err := utils.CodeSignWithEntitlements(dspath, entitlements.Name(), "-"); err != nil {
|
|
return fmt.Errorf("failed to codesign debugserver with entitlements: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnablePrivateLogData enables private data in logs
|
|
// CREDIT - https://github.com/EthanArbuckle/unredact-private-os_logs
|
|
func (s *SSH) EnablePrivateLogData() error {
|
|
logging, err := os.CreateTemp("", "com.apple.system.logging.plist")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create tmp entitlements file: %w", err)
|
|
}
|
|
defer os.Remove(logging.Name()) // clean up
|
|
|
|
if _, err := logging.Write(loggingPlistData); err != nil {
|
|
return fmt.Errorf("failed to write com.apple.system.logging.plist data to tmp file: %w", err)
|
|
}
|
|
if err := logging.Close(); err != nil {
|
|
return fmt.Errorf("failed to close tmp com.apple.system.logging.plist file: %w", err)
|
|
}
|
|
|
|
return s.CopyToDevice(logging.Name(), "/Library/Preferences/Logging/com.apple.system.logging.plist")
|
|
}
|
|
|
|
// EnableSymbolication enables symbolication of mobile crash logs
|
|
// CREDIT - https://github.com/dlevi309/ios-scripts
|
|
func (s *SSH) EnableSymbolication() error {
|
|
crashReporter, err := os.CreateTemp("", "com.apple.CrashReporter.plist")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create tmp 'com.apple.CrashReporter.plist' file: %w", err)
|
|
}
|
|
defer os.Remove(crashReporter.Name()) // clean up
|
|
|
|
if _, err := crashReporter.Write(symbolicationPlistData); err != nil {
|
|
return fmt.Errorf("failed to write 'com.apple.CrashReporter.plist' data to tmp file: %w", err)
|
|
}
|
|
if err := crashReporter.Close(); err != nil {
|
|
return fmt.Errorf("failed to close tmp 'com.apple.CrashReporter.plist' file: %w", err)
|
|
}
|
|
return s.CopyToDevice(crashReporter.Name(), "/var/root/Library/Preferences/com.apple.CrashReporter.plist")
|
|
}
|
|
|
|
func (s *SSH) GetShshBlobs() ([]byte, error) {
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create ssh session: %w", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
return session.Output("cat /dev/rdisk1 | dd bs=256 count=$((0x4000))")
|
|
}
|