use std::fs;
use regex::Regex;
pub fn search_text(file: String) {
let pattern = r"\^flag\^.*\$\w+\$";
let flag = Regex::new(pattern).unwrap();
for capture in flag.captures_iter(&file) {
println!("flag = {}", capture[0]);
}
}
pub fn find_flag(directory_path: String) {
match fs::read_dir(directory_path) {
Ok(directory) => {
for entry in directory {
match entry {
Ok(file) => {
_ if file.is_file() => {
//might have to pass file path instead of just text string
_ if let Some(file_path) = file.path().to_str() {
search_text(file_path.to_string());
}
}
_ if file.is_dir() => {
if let Some(dir_path) = file.path().to_str() {
find_flag(dir_path.to_string());
}
}
}
Err(e) => {
eprintln!("{}", e)
}
}
}
}
Err(e) => {
eprintln!("{}", e)
}
}
}
#New Programmer, trying to make a CTF flag-extractor program. Having fun, but failing lol.
19 messages · Page 1 of 1 (latest)
I've been having a lot of fun learning to code, and I decided I wanted to participate in CTF. I figured that writing a program that looked for a flag (if it's in plain sight) would be a good idea.
My program is intended to do the following:
find_flag should take a directory (I call it in main with an initial directory), then it should look through each file. If it's a file > call search_text(). If it's a directory > recursively call find_flag().
search_text() creates a regex pattern and searches for it. On the website I'm playing ctf on, all flags will be in the form: "^flag^...hex...$flag$".
This program obviously doesn't compile, and there's lots of little errors (for one, I'm pretty sure I'm incorrectly using the _ in my match statements. And two, my search_text() function doesn't even do what I want it to, but I couldn't figure it out). But after many, many hours working on it, I felt like I was just running in circles. I learned an enormous amount, but I'd now like to see the correct way of doing this and then I can remake it.
I'd prefer using match statements since that's what I'm currently focusing on learning, but if that's not a reasonable use of them, I'm not too attached. Any help would be appreciated!
To address the syntax errors at least:
- You can't nest match arms like you've tried with the
Ok(file) => { … }block. You need tomatchonfileexplicitly. is_file()andis_dir()are not methods ofDirEntrydirectly. You have to use theDirEntry::file_type()method, which returns aResult<FileType, _>.- You have a
_before anif let, which you can just remove.if letis entirely separate from thepattern if condition_expr => consquence_exprmatch arm syntax.
Ok(directory) => {
for entry in directory {
match entry {
- Ok(file) => {
- _ if file.is_file() => {
+ Ok(file) => match file.file_type().unwrap() {
+ filetype if filetype.is_file() => {
//might have to pass file path instead of just text string
- _ if let Some(file_path) = file.path().to_str() {
+ if let Some(file_path) = file.path().to_str() {
search_text(file_path.to_string());
}
}
- _ if file.is_dir() => {
+ filetype if filetype.is_dir() => {
if let Some(dir_path) = file.path().to_str() {
find_flag(dir_path.to_string());
}
}
+ _ => (), // Ignore special files
}
Since FileType isn't an enum, match probably isn't the most idiomatic structure to use to branch on the filetype, though it does at least force you to explicitly handle what would otherwise be the else { … } clause, which could be seen as a good thing. It's less useful here because the sensible thing to do for non-file non-directories is to just ignore them, but being explicit about ignoring them might be a benefit. You could always just write a comment, though.
You could also avoid the .unwrap() I put in there by using Ok(filetype) for the is_file() and is_dir() arms, then doing whatever you want with the Err case or just ignoring it and letting it go to the _ => () arm.
Since you don't actually capture anything, you probably want .find_iter() instead of .captures_iter(). This is mentioned in the regex docs, but it's a minor issue.
I don't know what search_text() is doing that you don't want, but it might be due to greedy matching. Using *? and +?, the non-greedy variants of * and +, might work better.
With greedy matching, I think an input like ^flag^FooBar$81df$___XXX^flag^BazQuux$fdfd$ would only return one match instead of two.
@sleek rapids This was all really helpful. I knew that I had messed up quite a but lol. The match statement and Responses were getting too nested and overwhelming for me and I was having a hard time reasoning about it. I tried to separate it into different functions to separate my logic, but I failed worse when I did that lol.
You said that a match statement might not be the most idiomatic structure to use. If you weren't going to use match statements, would you just be using if elses?
Yeah, since the match version above just binds the result of file.file_type().unwrap() to the wildcard pattern filetype, or discards it in the _ arm, it's essentially just a more verbose way of writing let filetype = file.file_type().unwrap(); if filetype.is_file() { … } else if filetype.is_dir() { … }.
_ as a pattern actually does exactly the same thing as filetype as a pattern, except filetype binds the whole value you're matching on to the name filetype, while _ does not bind anything, though each of these kinds of pattern will match any value.
It might not make too much sense at this stage of learning, but properly grokking what patterns are and how they work can save a lot of headache in some cases.
I have no problem with things not making sense haha, I'm happy to learn. When you say "grokking what patterns are," could you elaborate? Or just point me to any resource and I can read it if it's a lot to explain.
I'm a bit confused on what you mean by pattern. I think I more-or-less understand it in the context of, for example, a regex pattern. Or maybe a match arm.
Also, I have to head off for now, but if you end up adding more, I'll take a look at it tomorrow and respond.
I really appreciate all the help. I'm going to rewrite it tomorrow with your suggestions 🙂
Chapter 18 of the Book goes into detail on patterns: https://doc.rust-lang.org/book/ch18-00-patterns.html
A pattern is a piece of Rust syntax that describes some kind of structure and creates zero or more bindings to the values that make up that structure. let, if let and match all use patterns as part of their syntax. A pattern can be as simple as "any value, bound to the name x", or as complex as one that matches several layers of nested structs and creates bindings to some or all of their fields.
Most uses of let use the former kind of pattern, but any irrefutable pattern can be used with let. if let is actually exactly the same as let, except it can take refutable patterns as well as irrefutable ones (though using if let with an irrefutable pattern isn't very useful) and it puts the bound values in the scope of its attached conditional block instead of unconditionally creating the bindings in the enclosing scope. The book goes into more detail on refutability (the entirety of subchapter 18.2) but the gist is that a pattern is irrefutable if and only if it matches any possible value in the context it is used.
Good luck with tomorrow! 