parseLsOutput parses the text output of dir/ls into a LsResponse.
(output, osName, path string)
| 226 | |
| 227 | // parseLsOutput parses the text output of dir/ls into a LsResponse. |
| 228 | func parseLsOutput(output, osName, path string) *implantpb.LsResponse { |
| 229 | resp := &implantpb.LsResponse{ |
| 230 | Path: path, |
| 231 | Exists: true, |
| 232 | } |
| 233 | lines := strings.Split(output, "\n") |
| 234 | |
| 235 | if isWindows(osName) { |
| 236 | // Windows: dir /a "path" |
| 237 | // 2026/03/12 22:00 <DIR> subdir |
| 238 | // 2026/03/12 21:59 1,234 file.txt |
| 239 | for _, line := range lines { |
| 240 | line = strings.TrimSpace(line) |
| 241 | if line == "" { |
| 242 | continue |
| 243 | } |
| 244 | // Skip header/footer lines |
| 245 | if strings.HasPrefix(line, "Volume ") || strings.HasPrefix(line, "Directory of ") || |
| 246 | strings.Contains(line, " File(s)") || strings.Contains(line, " Dir(s)") { |
| 247 | continue |
| 248 | } |
| 249 | // Try to parse date-prefixed lines |
| 250 | // Format: YYYY/MM/DD HH:MM <DIR> name |
| 251 | // YYYY/MM/DD HH:MM 1,234,567 name |
| 252 | if len(line) < 20 { |
| 253 | continue |
| 254 | } |
| 255 | // Check if line starts with a date pattern |
| 256 | if !(line[4] == '/' || line[4] == '-' || line[2] == '/' || line[2] == '-') { |
| 257 | continue |
| 258 | } |
| 259 | |
| 260 | isDir := strings.Contains(line, "<DIR>") |
| 261 | fi := &implantpb.FileInfo{ |
| 262 | IsDir: isDir, |
| 263 | } |
| 264 | |
| 265 | if isDir { |
| 266 | // Find <DIR> and extract name after it |
| 267 | idx := strings.Index(line, "<DIR>") |
| 268 | if idx >= 0 { |
| 269 | fi.Name = strings.TrimSpace(line[idx+5:]) |
| 270 | } |
| 271 | } else { |
| 272 | // Find the size and name: everything after the date+time, before the name |
| 273 | // Split by multiple spaces to find size and name |
| 274 | parts := strings.Fields(line) |
| 275 | if len(parts) >= 4 { |
| 276 | // Last part is the filename, second-to-last is the size |
| 277 | fi.Name = parts[len(parts)-1] |
| 278 | sizeStr := strings.ReplaceAll(parts[len(parts)-2], ",", "") |
| 279 | sizeStr = strings.ReplaceAll(sizeStr, ".", "") |
| 280 | size, _ := strconv.ParseUint(sizeStr, 10, 64) |
| 281 | fi.Size = size |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if fi.Name == "." || fi.Name == ".." || fi.Name == "" { |