Skip to content

Commit 308fd98

Browse files
authored
Merge pull request #192 from ByteTheCookies/dev
Enhance exploiter with multiprocessing and service-name validation
2 parents bf3de7f + aa14bc9 commit 308fd98

33 files changed

Lines changed: 516 additions & 343 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ This will generate `your_exploit_name.py` in `~/.cookiefarm/exploits/`.
140140

141141
5. Run your exploit:
142142
```bash
143-
ckc exploit run -e your_exploit_name.py -n CookieService -t 120 -T 40
143+
ckc exploit run -e your_exploit_name.py -n CookieService -t 120 -W 40
144144
```
145145
> [!NOTE]
146146
> For more usage examples, check out the [client documentation](https://cookiefarm.bytethecookies.org/docs/client/overview).

benchmarks/ckvsdf/tool/start_benchmark.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ echo "==> Starting CookieFarm Exploit..."
4949
# Example command to run CF exploit:
5050
just client-build-linux-prod
5151
../../../bin/ckc login -P password
52-
../../../bin/ckc exploit run -e benchmark -n CookieService -t 5 -T 10 &
52+
../../../bin/ckc exploit run -e benchmark -n CookieService -t 5 -W 10 &
5353
PIDS+=($!)
5454

5555
echo "==> Starting DestructiveFarm Exploit..."
@@ -75,7 +75,7 @@ echo "==> Benchmark is running. Waiting for $BENCHMARK_DUR seconds for completio
7575
wait $TIMELINE_PID
7676

7777
echo "==> Benchmark duration completed. Stopping exploits and monitoring..."
78-
pkill -f "../../../bin/ckc exploit run -e benchmark -n CookieService -t 5 -T 10"
78+
pkill -f "../../../bin/ckc exploit run -e benchmark -n CookieService -t 5 -W 10"
7979
pkill -f "python3 ./start_sploit.py benchmark.py -u http://localhost:5000 --attack-period 5"
8080
pkill -f "python3 /tmp/DestructiveFarm/client/benchmark.py"
8181

benchmarks/db_improve/notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ LAN
3535
LAN
3636

3737
- Build command: `just client-build-prod`
38-
- Command run: `./bin/ckc exploit run -e benchmark -n CookieService -t 5 -T 10`
38+
- Command run: `./bin/ckc exploit run -e benchmark -n CookieService -t 5 -W 10`
3939
- Run command: `time python3 riempire_db_daiii.py`
4040

4141
### Cks config used:

benchmarks/protocol_beef/notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ LAN
3434
(same machine)
3535

3636
- Build command: `just client-build-prod`
37-
- Command run: `./bin/ckc exploit run -e benchmark -n CookieService -t 5 -T 10`
37+
- Command run: `./bin/ckc exploit run -e benchmark -n CookieService -t 5 -W 10`
3838

3939
### Cks config used:
4040

cookiefarm/client/cmd/exploit.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var (
2525
teamID uint16 // Service name and team ID for the submit command
2626
detach bool // Run the exploit in the background (detached mode)
2727
submitValue bool // This flag is used to directly submit to the game server
28+
processMode bool // Using multiprocessing instead of multi-threading to run the exploit
2829
tickTime int // Interval in seconds between exploit executions
2930
threadCount int // Number of concurrent threads to run the exploit with
3031
pid int // PID of the exploit to stop
@@ -125,8 +126,9 @@ func buildExploitCmd() *cobra.Command {
125126
runCmd.Flags().StringVarP(&serviceName, "name", "n", "", "Service name to attack")
126127
runCmd.Flags().BoolVarP(&detach, "detach", "d", false, "Run the exploit in the background (detached mode)")
127128
runCmd.Flags().IntVarP(&tickTime, "tick", "t", 120, "Interval in seconds between exploit executions")
128-
runCmd.Flags().IntVarP(&threadCount, "thread", "T", 5, "Number of concurrent threads to run the exploit with")
129+
runCmd.Flags().IntVarP(&threadCount, "worker", "W", 10, "Number of concurrent worker to run the exploit with")
129130
runCmd.Flags().BoolVarP(&submitValue, "submit", "S", false, "Direct submit to the game server")
131+
runCmd.Flags().BoolVarP(&processMode, "process_mode", "P", false, "Using multiprocessing instead of multi-threading to run the exploit")
130132
runCmd.MarkFlagRequired("exploit")
131133
runCmd.MarkFlagRequired("port")
132134
runCmd.MarkFlagRequired("host")
@@ -135,7 +137,8 @@ func buildExploitCmd() *cobra.Command {
135137
testCmd.Flags().StringVarP(&serviceName, "name", "n", "", "Service name to attack")
136138
testCmd.Flags().BoolVarP(&detach, "detach", "d", false, "Run the exploit in the background (detached mode)")
137139
testCmd.Flags().IntVarP(&tickTime, "tick", "t", 120, "Interval in seconds between exploit executions")
138-
testCmd.Flags().IntVarP(&threadCount, "thread", "T", 5, "Number of concurrent threads to run the exploit with")
140+
testCmd.Flags().IntVarP(&threadCount, "worker", "W", 10, "Number of concurrent worker to run the exploit with")
141+
testCmd.Flags().BoolVarP(&processMode, "process_mode", "P", false, "Using multiprocessing instead of multi-threading to run the exploit")
139142
testCmd.MarkFlagRequired("exploit")
140143
testCmd.MarkFlagRequired("port")
141144
testCmd.MarkFlagRequired("host")
@@ -162,9 +165,10 @@ func test(cmd *cobra.Command, args []string) {
162165
exploitArgs := exploit.ExploitArgs{
163166
ServiceName: serviceName,
164167
TickTime: tickTime,
165-
ThreadCount: threadCount,
168+
WorkerCount: threadCount,
166169
Detach: detach,
167170
ExploitPath: exploitPath,
171+
ProcessMode: processMode,
168172
}
169173

170174
exploit.Setup(&exploitArgs, detach)
@@ -185,9 +189,10 @@ func run(cmd *cobra.Command, args []string) {
185189
exploitArgs := exploit.ExploitArgs{
186190
ServiceName: serviceName,
187191
TickTime: tickTime,
188-
ThreadCount: threadCount,
192+
WorkerCount: threadCount,
189193
Detach: detach,
190194
ExploitPath: exploitPath,
195+
ProcessMode: processMode,
191196
}
192197

193198
err := exploit.Setup(&exploitArgs, detach)

cookiefarm/client/internal/api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func Login(username, password string) error {
7777
return nil
7878
}
7979

80-
// @IMPORTANT: prefer websockets
80+
// @IMPORTANT: prefer ckp
8181
func SubmitBatchDirect(flags []database.Flag) error {
8282
payload, err := json.Marshal(models.SubmitFlagsRequest{
8383
Flags: flags,

cookiefarm/client/internal/ckp/submitter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func Start(flagsChan <-chan database.Flag) {
1616

1717
conn, err := NewClient(addr)
1818
if err != nil {
19-
logger.Log.Fatal().Err(err).Msg("Error connecting to WebSocket")
19+
logger.Log.Fatal().Err(err).Msg("Error connecting to ckp")
2020
}
2121

2222
defer conn.Close()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package exploit
2+
3+
// Go code from https://github.com/agnivade/levenshtein/blob/master/levenshtein.go
4+
// Package levenshtein is a Go implementation to calculate Levenshtein Distance.
5+
//
6+
// Implementation taken from
7+
// https://gist.github.com/andrei-m/982927#gistcomment-1931258
8+
9+
import "unicode/utf8"
10+
11+
// minLengthThreshold is the length of the string beyond which
12+
// an allocation will be made. Strings smaller than this will be
13+
// zero alloc.
14+
const minLengthThreshold = 32
15+
16+
// ComputeDistance computes the levenshtein distance between the two
17+
// strings passed as an argument. The return value is the levenshtein distance
18+
//
19+
// Works on runes (Unicode code points) but does not normalize
20+
// the input strings. See https://blog.golang.org/normalization
21+
// and the golang.org/x/text/unicode/norm package.
22+
func ComputeDistance(a, b string) int {
23+
if len(a) == 0 {
24+
return utf8.RuneCountInString(b)
25+
}
26+
27+
if len(b) == 0 {
28+
return utf8.RuneCountInString(a)
29+
}
30+
31+
if a == b {
32+
return 0
33+
}
34+
35+
// We need to convert to []rune if the strings are non-ASCII.
36+
// This could be avoided by using utf8.RuneCountInString
37+
// and then doing some juggling with rune indices,
38+
// but leads to far more bounds checks. It is a reasonable trade-off.
39+
s1 := []rune(a)
40+
s2 := []rune(b)
41+
42+
// swap to save some memory O(min(a,b)) instead of O(a)
43+
if len(s1) > len(s2) {
44+
s1, s2 = s2, s1
45+
}
46+
47+
// remove trailing identical runes.
48+
s1, s2 = trimLongestCommonSuffix(s1, s2)
49+
50+
// Remove leading identical runes.
51+
s1, s2 = trimLongestCommonPrefix(s1, s2)
52+
53+
lenS1 := len(s1)
54+
lenS2 := len(s2)
55+
56+
// Init the row.
57+
var x []uint16
58+
if lenS1+1 > minLengthThreshold {
59+
x = make([]uint16, lenS1+1)
60+
} else {
61+
// We make a small optimization here for small strings.
62+
// Because a slice of constant length is effectively an array,
63+
// it does not allocate. So we can re-slice it to the right length
64+
// as long as it is below a desired threshold.
65+
x = make([]uint16, minLengthThreshold)
66+
x = x[:lenS1+1]
67+
}
68+
69+
// we start from 1 because index 0 is already 0.
70+
for i := 1; i < len(x); i++ {
71+
x[i] = uint16(i)
72+
}
73+
74+
// hoist bounds checks out of the loops
75+
_ = x[lenS1]
76+
y := x[1:]
77+
y = y[:lenS1]
78+
// fill in the rest
79+
for i := range lenS2 {
80+
prev := uint16(i + 1)
81+
for j := range lenS1 {
82+
current := x[j] // match
83+
if s2[i] != s1[j] {
84+
current = min(x[j], prev, y[j]) + 1
85+
}
86+
x[j] = prev
87+
prev = current
88+
}
89+
x[lenS1] = prev
90+
}
91+
return int(x[lenS1])
92+
}
93+
94+
func trimLongestCommonSuffix(a, b []rune) ([]rune, []rune) {
95+
m := min(len(a), len(b))
96+
a2 := a[len(a)-m:]
97+
b2 := b[len(b)-m:]
98+
i := len(a2)
99+
b2 = b2[:i] // hoist bounds checks out of the loop
100+
// Find the longest common suffix length using slices.IndexFunc
101+
suffixLen := 0
102+
for k := 1; k <= len(a2); k++ {
103+
if a2[len(a2)-k] != b2[len(b2)-k] {
104+
break
105+
}
106+
suffixLen = k
107+
}
108+
i -= len(a2) - suffixLen
109+
return a[:len(a)-len(a2)+i], b[:len(b)-len(b2)+i]
110+
}
111+
112+
func trimLongestCommonPrefix(a, b []rune) ([]rune, []rune) {
113+
m := min(len(a), len(b))
114+
i := m
115+
for k := range m {
116+
if a[k] != b[k] {
117+
i = k
118+
break
119+
}
120+
}
121+
if i == m {
122+
i = m
123+
}
124+
125+
return a[i:], b[i:]
126+
}

cookiefarm/client/internal/exploit/parse.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
const (
1616
INFO = "info"
1717
EXPLOIT = "exploit_info"
18+
PROCESS = "process_info"
1819
FAILED = "failed"
1920
ERROR = "error"
2021
FATAL = "fatal"
@@ -42,9 +43,25 @@ func (p *Parser) Parse(line string) (database.Flag, string, error) {
4243
return p.parseStats(line)
4344
}
4445

46+
if strings.Contains(line, PROCESS) {
47+
return p.parseProcessInfo(line)
48+
}
49+
4550
return p.parseFlag(line)
4651
}
4752

53+
func (*Parser) parseProcessInfo(line string) (database.Flag, string, error) {
54+
var out ProcessInfoOutput
55+
56+
err := json.Unmarshal([]byte(line), &out)
57+
if err != nil {
58+
return database.Flag{}, INVALID, errors.New(line)
59+
}
60+
61+
msg := fmt.Sprintf("Process Info: PID: %d, TeamID: %d, Event: %s", out.PID, out.TeamID, out.Event)
62+
return database.Flag{}, PROCESS, errors.New(msg)
63+
}
64+
4865
func (*Parser) parseStats(line string) (database.Flag, string, error) {
4966
var out StatusBatchOutput
5067
if err := json.Unmarshal([]byte(line), &out); err != nil {
@@ -64,7 +81,7 @@ func (p *Parser) parseFlag(line string) (database.Flag, string, error) {
6481
}
6582

6683
switch out.Status {
67-
case INFO, EXPLOIT, ERROR, DEBUG, FATAL:
84+
case INFO, EXPLOIT, ERROR, DEBUG, FATAL, PROCESS:
6885
return database.Flag{}, out.Status, errors.New(out.Message)
6986
case FAILED:
7087
return database.Flag{}, out.Status, errors.New(out.Message)

cookiefarm/client/internal/exploit/run.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,50 @@ func formatMessage(raw string, err error) string {
141141
return raw
142142
}
143143

144+
func checkServiceName(cm *config.ConfigManager, serviceName string) error {
145+
minDistance := 1000 // Set a high initial value for minimum distance
146+
minDistanceName := ""
147+
if cm.MapServiceToPort(serviceName) == 0 {
148+
services := cm.Get().Shared.Services
149+
150+
for name := range services {
151+
nameDistance := ComputeDistance(serviceName, name)
152+
if nameDistance <= minDistance {
153+
minDistance = nameDistance
154+
minDistanceName = name
155+
}
156+
}
157+
158+
return fmt.Errorf("service name %s is not mapped to any port, did you mean %s (port %d)?", serviceName, minDistanceName, cm.MapServiceToPort(minDistanceName))
159+
}
160+
161+
return nil
162+
}
163+
144164
func buildCmd(args ExploitArgs, test bool) string {
145165
cfg := config.GetInstance()
166+
// cfg.Read()
167+
168+
if err := checkServiceName(cfg, args.ServiceName); err != nil {
169+
logger.Log.Fatal().Err(err).Msg("Service name check failed")
170+
return ""
171+
}
146172

147173
cmd := fmt.Sprintf(
148-
"%s -s %s:%d -t %d -T %d -p %d -n %s",
174+
"%s -s %s:%d -t %d -W %d -p %d -n %s",
149175
args.ExploitPath,
150176
cfg.GetHost(),
151177
cfg.GetPort(),
152178
args.TickTime,
153-
args.ThreadCount,
179+
args.WorkerCount,
154180
cfg.MapServiceToPort(args.ServiceName),
155181
args.ServiceName,
156182
)
157183

184+
if args.ProcessMode {
185+
cmd += " -P"
186+
}
187+
158188
if test {
159189
cmd += " -x"
160190
}

0 commit comments

Comments
 (0)