bhcli

A TUI for chatting on LE PHP Chats (onion)
git clone https://git.dasho.dev/n0tr1v/bhcli.git
Log | Files | Refs | README

commit 480140a7b98b4530bc293f858b0be91cd0a2bf2b
parent 3eda987d1a7167154b615a2933ca3062bc746d90
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Wed, 29 Mar 2023 01:54:17 -0700

work for dif 3

Diffstat:
Msrc/lechatphp/mod.rs | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 224 insertions(+), 4 deletions(-)

diff --git a/src/lechatphp/mod.rs b/src/lechatphp/mod.rs @@ -1,4 +1,5 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; use base64::{engine::general_purpose, Engine as _}; use image::{ColorType, DynamicImage, GenericImageView, Rgba}; use lazy_static::lazy_static; @@ -88,10 +89,13 @@ fn get_letter_img(letter: char) -> Option<DynamicImage> { pub fn solve_b64(b64_str: &str) -> Option<String> { let img_dec = general_purpose::STANDARD.decode(b64_str.strip_prefix("data:image/gif;base64,")?).ok()?; let img = image::load_from_memory(&img_dec).ok()?; - solve_difficulty2(img) + if img.dimensions().0 > 60 { + return solve_difficulty3(&img); + } + solve_difficulty2(&img) } -fn _solve_difficulty1(img: DynamicImage) -> Option<String> { +fn _solve_difficulty1(img: &DynamicImage) -> Option<String> { let mut answer = String::new(); for i in 0..5 { let sub_img = img.crop_imm(5 + ((8+1)*i), 7, 8, 14); @@ -113,7 +117,7 @@ fn _solve_difficulty1(img: DynamicImage) -> Option<String> { Some(answer) } -fn solve_difficulty2(img: DynamicImage) -> Option<String> { +fn solve_difficulty2(img: &DynamicImage) -> Option<String> { let on_color = Rgba::from([252, 254, 252, 255]); let mut answer = String::new(); for i in 0..5 { @@ -133,4 +137,220 @@ fn solve_difficulty2(img: DynamicImage) -> Option<String> { } } Some(answer) +} + +#[derive(Clone, Debug)] +struct Letter { + offset_x: u32, + offset_y: u32, + character: char, +} + +impl Letter { + fn key(&self) -> String { + format!("{}_{}_{}", self.character, self.offset_x, self.offset_y) + } +} + +// SolveDifficulty3 solve captcha for difficulty 3 +// For each pixel, verify if a match is found. If we do have a match, +// verify that we have some "red" in it. +// +// Red circle is 17x17 (initial point) +fn solve_difficulty3(img: &DynamicImage) -> Option<String> { + let image_width = 150; + let image_height = 200; + let min_px_for_letter = 21; + let min_starting_pt_red_px = 50; + let mut answer = String::new(); + + let mut letters_map: HashMap<char, Letter> = HashMap::new(); + + // Step1: Find all letters with red on the center + for y in 0..image_height { + for x in 0..image_width { + let letter_img = img.crop_imm(x, y, 8, 14); + + // We know that minimum amount of pixels on to form a letter is 21 + // We can skip squares that do not have this prerequisite + if count_px_on(&letter_img) < min_px_for_letter { + continue + } + + // Check middle pixels for red, if no red pixels, we can ignore that square + if !has_red_in_center_area(&letter_img) { + continue + } + + for c in ALPHABET1.chars() { + let good_letter_img = get_letter_img(c).unwrap(); + if !img_contains_letter(&letter_img, &good_letter_img) { + continue; + } + + // "w" fits in "W". So if we find "W" 1 px bellow, discard "w" + if c == 'w' { + let capital_w_img = get_letter_img('W').unwrap(); + let one_px_down_img = img.crop_imm(x, y+1, 8, 14); + if img_contains_letter(&one_px_down_img, &capital_w_img) { + continue; + } + } else if c == 'k' { + let capital_k_img = get_letter_img('K').unwrap(); + let one_px_up_img = img.crop_imm(x+1, y+1, 8, 14); + if img_contains_letter(&one_px_up_img, &capital_k_img) { + continue; + } + } + + letters_map.insert(c, Letter{offset_x: x, offset_y: y, character: c}); // Keep letters in hashmap for easy access + break; + } + } + } + + if letters_map.len() != 5 { + return None; // did not find exactly 5 letters + } + + let mut starting: Option<Letter> = None; + // Step2: Find the starting letter + for (_, letter) in letters_map.iter() { + let square = img.crop_imm(letter.offset_x-5, letter.offset_y-3, 8+5+6, 14+3+2); + let count_red = count_red_px(&square); + if count_red > min_starting_pt_red_px { + starting = Some(letter.clone()); + break; + } + } + + if starting.is_none() { + return None; // could not find starting letter + } + + println!("STARTING IS {:?}", starting.clone().unwrap()); + + let mut code = String::new(); + let mut letter = starting.unwrap(); + let mut visited = HashSet::<String>::new(); + for i in 0..5 { + if visited.contains(&letter.key()) { + return None; // already visited node + } + code.push(letter.character); + visited.insert(letter.key()); + if i == 4 { + break; + } + + let mut retry = 0; + 'the_loop: loop { + retry += 1; + let square = img.crop_imm(letter.offset_x-5, letter.offset_y-3, 8+5+6, 14+3+2); + // let red_px_pts = get_contour_red_pixels(); + // square.save(format!("captcha_{}.gif", letter.key())).ok()?; + break; + } + } + + Some(answer) +} + +#[derive(Debug)] +struct Point { + x: u32, + y: u32, +} + +// fn get_contour_red_pixels(img: &DynamicImage) -> Vec<Point> { +// let top_left_pt = img.Bounds().Min; +// let bottom_right_pt = img.Bounds().Max; +// for i := 0; i < img.Bounds().Dx(); i++ { +// pxColor := img.At(top_left_pt.X+i, top_left_pt.Y) +// if pxColor == redColor { +// out = append(out, image.Point{X: top_left_pt.X + i, Y: top_left_pt.Y}) +// } +// pxColor = img.At(top_left_pt.X+i, bottom_right_pt.Y-1) +// if pxColor == redColor { +// out = append(out, image.Point{X: top_left_pt.X + i, Y: bottom_right_pt.Y - 1}) +// } +// } +// for i := 1; i < img.Bounds().Dy()-1; i++ { +// pxColor := img.At(top_left_pt.X, top_left_pt.Y+i) +// if pxColor == redColor { +// out = append(out, image.Point{X: top_left_pt.X, Y: top_left_pt.Y + i}) +// } +// if img.Bounds().Dx() < img.Bounds().Dy() { +// if img.Bounds().Max.X == 150 { +// continue; +// } +// } +// pxColor = img.At(bottom_right_pt.X-1, top_left_pt.Y+i) +// if pxColor == redColor { +// out = append(out, image.Point{X: bottom_right_pt.X - 1, Y: top_left_pt.Y + i}) +// } +// } +// return vec![]; +// } + +// give an image and a valid letter image, return either or not the letter is in that image. +fn img_contains_letter(img: &DynamicImage, letter_img1: &DynamicImage) -> bool { + let on_color = Rgba::from([252, 254, 252, 255]); + let red_color = Rgba::from([204, 2, 4, 255]); + for y in 0..std::cmp::min(img.dimensions().1, letter_img1.dimensions().1) { + for x in 0..std::cmp::min(img.dimensions().0, letter_img1.dimensions().0) { + let good_letter_color = letter_img1.get_pixel(x, y); + let letter_img_color = img.get_pixel(x, y); + // If we find an Off pixel where it's supposed to be On, skip that letter + if (good_letter_color == on_color || good_letter_color == red_color) && + (letter_img_color != on_color && letter_img_color != red_color) { + return false; + } + } + } + true +} + +fn has_red_in_center_area(letter_img: &DynamicImage) -> bool { + let red_color = Rgba::from([204, 2, 4, 255]); + for y in 5..=std::cmp::min(letter_img.dimensions().1, 7) { + for x in 3..=std::cmp::min(letter_img.dimensions().0, 5) { + let letter_img_color = letter_img.get_pixel(x, y); + if letter_img_color == red_color { + return true; + } + } + } + false +} + +// Count pixels that are On (either white or red) +fn count_px_on(img: &DynamicImage) -> usize { + let on_color = Rgba::from([252, 254, 252, 255]); + let red_color = Rgba::from([204, 2, 4, 255]); + let mut count_on = 0; + for y in 0..std::cmp::min(img.dimensions().1, 14) { + for x in 0..std::cmp::min(img.dimensions().0, 8) { + let c = img.get_pixel(x, y); + if c == on_color || c == red_color { + count_on += 1 + } + } + } + count_on +} + +// Count pixels that are red +fn count_red_px(img: &DynamicImage) -> usize { + let red_color = Rgba::from([204, 2, 4, 255]); + let mut count_on = 0; + for y in 0..img.dimensions().1 { + for x in 0..img.dimensions().0 { + let c = img.get_pixel(x, y); + if c == red_color { + count_on += 1 + } + } + } + count_on } \ No newline at end of file