 Alright everybody. Thank you for coming to Decoding Recapture. I'm Jason Lee and this is Chad Hauke. We're programmers and entrepreneurs from Michigan who have a passion for security and automation. We're business partners and we've worked together on numerous projects throughout the years. Our company, Ziggy, provides information technology related consulting and contracting work for other businesses. The system which we are explaining today has been created to decode recaps with absolutely no human intervention. Chad's developed a system to effectively solve recapture images. He's developed new methods of analysis and software in order to bypass security measures used by them in both the past and the present. Virtually the entire development process took place in Chad's garage and I was involved throughout a majority of it as a person worthy of him to impress and bounce ideas back and forth with. Throughout this time I've come to understand quite a bit about both captures and the methods utilized to bypass them. We're here to talk today about identifying weaknesses in capture security and more importantly the methods in which Google's Spam Protection Service... Yes we are. We're here to talk about identifying weakness in capture security and more importantly the methods in which Google's Spam Protection Service recapture can be guessed accurately enough to be considered an effective defeat of their security. I will go into some detail about why recapture is chosen to crack and I'm just going to move on. Over 30 million recapture images are typed in every day in order to prove that the request was issued by a human and not a robot. It has often been regarded as one of the most secure captures available to third party web developers as a form of spam protection. It is considered and is currently used on many of the internet's most popular websites as their primary defense against automation and abuse of their services. This capture is a common sight for those registering or interacting with sites such as Craig's Facebook, Twitter and countless other web pages. The reason that websites are forced to use such measures is due to the prominence of internet spam in the east in which programmers would be able to automate and abuse services on a large scale without some sort of verification. For example, a large-scale system such as a botnet would be easily capable of causing havoc on websites such as Facebook through the creation of fake profiles and messages without protection such as an effective capture guarding it. It is also one of the many protections that Craig implements in order to fend off potential abusers from hosting numerous times down their system with automated software. Such measures are necessary in order to keep the ratio of legitimate use to illegitimate use down and provide a high level of user satisfaction. There is a variety of ways in which captures can be bypassed ranging from human data entry services to autonomous software that is able to bypass the distortion and manipulations present in these images and give a correct answer. An automated system that could only achieve a very small percentage of correct answers would be considered a viable method of circumventing a capture. Human-powered capture data entry services are utilized in order to circumvent what are considered to be strong captures. There is an advantage to this type of data entry service as they are able to accurately solve any capture that is readable by humans. There are many drawbacks to this form of capture circumvention, however. First and foremost, drawbacks at its cost. We experimented with human capture entry services in order to name our capture images that do experiment with training techniques. And the cheapest price we found was about a fifth of a sum per correct image. There are capture entry services that utilize incredibly cheap labor in impoverished countries where a couple of American dollars a day that a housewife with no work experience can earn and it can be a major asset to their family. While there is a willing workforce available, the additional cost and convenience of managing a workforce is frequently enough to make the price of a spammer's campaign too high. A second strong drawback to the human capture entry is the response time. In our testing, we would experience response times that are about 15 seconds and more commonly would exceed two minutes. Despite the large amount of people willing to do these types of services, they are still limited by their access to computers and internet connections. Often these types of data entry farms can become completely inaccessible due to the poor connectivity, and this poor connectivity also plays a role as many of these data entry workers may be on dial-up and experienced long load times due to the high level of image transfers required. Given that there is a cost per image and a limited pool of workers available at any given time causes long wait times due to the high demand of services by spammers and severely reduces the ability for these services to be used in a way that severely threatens the ratio of illegitimate to legitimate use of services online. An autonomous capture solving system are far greater threat to the balance of spam and legitimate use on the internet than any other capture bypassing system. The primary advantage of any computer powered capture cracking system is the ability for it to be scaled out to any number of computers and therefore give a potential attacker virtually unlimited capture solving capabilities. Through the utilization of botnets as resources for both proxies and processing capabilities, a lone individual could be responsible for causing a significant increase in the amount of illegitimate use on the internet. Such systems could be used to essentially devastate the usability of many web services by filling it with illegitimate activity and or fake users. In terms of modern captures, recapture is often regarded as one providing a high level of usability and protection, broken down to specifically what we look for when we're classifying a capture as level of security. The following is this breakdown of potential areas of weakness in a capture system and resembles the questions one must answer in order to determine the effectiveness of a capture, be it through blatant security mistakes or through the ability of an attacker to programmatically make a successful guess on the system. And one should always first consider the actual implementation security policies in developing a capture-based security system. If one wished to discover a weakness in a capture system, the easiest place to look first would be how the system itself works. And first, are there a limited number of total possible challenge images? If the system uses a fixed number of images, an attacker could potentially solve every possible image and then compare hashes of solved images to challenge images. Are there a limited number of potential answers? By limiting the total amount of possible answers, an attacker could greatly improve their system's effectiveness. Does the capture implementation contain insecure encoding? Could the correct response be derived from the image, name, form, challenge data, or a cookie? And then, does the system utilize poor authentication session constraints? In a system that does not constrain capture authentication challenges to a single-use user instance of a session could be vulnerable to having the same challenge request reused? Presuming that there are no blatant implementation weaknesses able to be exploited, the next logical step would be to attempt to solve the capture itself by autonomously classifying each image. In the following slides, I will present a majority of the questions one may ask about a capture in order to determine exactly which barriers must be overcome to make a successful guess by OCR. And then on the next slides, I'll have a couple of example capture images talking about each. So you can kind of see what I'm talking about, and I have a little scale here. And the higher the amount of yellow stars, the more security. Give you that specific type of protection. Does the image use static fonts? A capture that uses static fonts could be defeated by off-the-shelf OCR technology such as Tesseract, OCRatterG, OCR. There are multiple fonts, and every font would require its own data set, and one would have to develop a method of font detection for each individual one, making it a lot harder. Are the characters separated from each other? If it's easy for each character to be separated from the other characters, it allows for software to easily know at which points of the image-relevant data is located. If there is noise in the capture, it's static. If the noise in an image never changes, it would be simple to remove that noise in all images. If there is noise in the capture, can it be removed? If the image noise is created in a way that it can be programmatically detected and removed, then it becomes effectively worthless as a security measure. Are the characters or words rotated? If the text is not rotated, then it will not require any further modification. Ideal character alignment will greatly increase the effectiveness of an OCR program to classify each character and word. How deformed is the text? If the text is not too deformed, it will not always be solvable by a human. If the text is not properly deformed, it will require little effort to pass through an off-the-shelf OCR program, though. Is there a lack of variation in font color? If the text is always the same color, it makes it easily possible to remove all the other colors and leave only the most important data. Can the text easily be extracted from the background? If the background is easily distinguishable from the text, it may be removed leaving only the character data to analyze. If image distortions are present, can they be reversed? Image and text distortions are typically utilized in capture systems in order to defeat off-the-shelf OCR technology. If these distortions are able to be reversed, however, it can leave a capture vulnerable to attack. Analysis of these areas for recaptured security shows us both their primary strengths and their weaknesses. The implementation of serving up challenge images and authentication itself appears to be fairly secure. There is, however, one unavoidable weakness in the implementation that becomes evident, and that's that despite a virtually unlimited number of images, there is a fairly limited number of possible responses since they're all from the English language. Now with this, I've isolated each of their methods they use to prevent autonomous solving by OCR, and that's their large variety of fonts. The characters are dilated and therefore are hard to separate. There's added image noise from the inverted blob. The characters are frequently out of alignment. There's natural deformation of text since it's all characters that couldn't be easily read by an OCR in the first place, and then the ribboning distortion of the entire image. I'm going to turn over to Chad so he can explain exactly how to bypass each one. Okay, let's see here. Okay, so what the hell is this? You guys should be familiar with recapture. If not, you might want to visit another track and also start using the internet because it's about 90% of the capture systems out there use it. What I'm going to go over is the recapture background, which Jason already covered most of, the distortion removal, character segmentation, and recognition, the dictionary attack that I used on it, and then my experimental results, and then as a last minute addendum, I added how to decode the new recapture because a week before the presentation, they decided to change it. I'm not sure if they saw the schedule or not, but we'll see. And then I'll go over some sample decodings at the end. Recapture was developed at Carnegie Mellon University. It was acquired by Google, September 16th. They saw about 30 million captures a day, utilized by almost every site now. It's used to digitize books, and it's an OCR supplementation via dual capture request. And what I mean by that is they always send out two words. One of the words is the digitization word, and the other is a verification word. They assume that if you get the verification word correct, which lets you pass through to the next page, they assume that you also got the digitization word correct, which means they go back and digitize a book with that word. And they use a called word point system, and that means that if an OCR gets the word correct, it gives them a half point. If a person types in the word, then it's a full point. And once it reaches two and a half points, it assumes that the word has been solved. Some things you need to be aware of that you can only solve. You only need to solve one of the two words, and it's randomly placed. You can tell about half the time which word is the verification word just from experience, but it is randomly located, so not all the time. You can forget one of the characters and replace it with a character entirely differently if you want to, and it will still be solved. You need to keep a consistent 50% solution rate. Otherwise, you'll get flagged after about 32 times, and you'll have to solve both words perfectly. You cannot misplace a single character. You have to solve them both. And obvious solutions to that would be using a dynamic IP solution. That way you can just switch IPs whenever you hit that rate. The distortions on the recapture. There's a few instances to take care of. They've got the Blobman version, or at least that's what I call it. You can see the black circle there that's inverting some of the characters in the word. And there's some leftover artifacts you'll need to take care of sometimes. There's the vertical ribboning, which is kind of annoying. Then you've got character dilation, which makes it so you can't segment the characters easily. Then there's natural distortions and old distortions. You're going to really touch the first three. The first thing is to segment the two words. You can separate them and work on them individually. That's extremely straightforward. There's a huge gap in between it, so just do some vertical line tests and you can find out where the words separate at. Then to make everything easier, I turn a monochrome down to one bit. I did this for every one white pixel with a value of 0 to 50. I set it white and otherwise black. You lose some information in doing this because there's some anti-aliasing in the image. That goes away, but anyway, on to the blob. The blob is painful, but it's quite simple. In retrospect, you just need to set a temporary copy of the images. Then the simplest way to defeat any form of random inversion is to just outline the image. But I only use this to gather information. Then I pixelate the images. I just do this that way the next step is more efficient because the algorithms are cubed, which could be pretty intense if you don't pixelate it. Then I use an ellipse detection algorithm, which I didn't write. That's the only thing in here I didn't come up with on my own, so I'll talk about that later. Then with the discovered ellipse parameters, you re-invert the blob, and then you clean up leftover artifacts if there are any. Now, to outline it, it's really simple. You just check every black pixel for any surrounding white pixel. If it has a surrounding white pixel, then you mark that pixel white on a blank image, otherwise you leave it black. What you see here is a perfectly outlined image, and the inversion is gone, but you're just left with somewhat ellipses. It kind of looks like an ellipse anyway. Now, I just pixelate the image to make the next algorithm more efficient. You decide on a radius for this. I use two. For each white pixel, count every white pixel that falls within the circumference of the circle, the radius, and call this K. On a new blank image, make pixels white that have the largest K first and on down, as long as they don't come within two pixels of an already white pixel. This ensures that the largest pixel group wins get priority, and it's not biased. The ellipse detection algorithm is pretty intense, and I didn't write it, so you can either go online and learn about it. It's created by Z&G from back in 2002. I'll just run through it real fast. Okay, so another image is pixelated. You can run this algorithm without taking 30 seconds. The following algorithm compares every white pixel to every other white pixel. We'll call that first XI and the white pixel compared to XJ. You calculate the distance between XI and XJ divided by two, stored in A. If A is less than a predetermined ellipse minimum, we'll say 30. That's what I use in this project anyway. We move on to the next pixel. Same goes for a greater than threshold of 45. Then you calculate the angle of the two pixels and store it in alpha. Calculate the midpoint and store it in M. Now we'll once again look at every other white pixel, hence the O and cubed, and call this 1XK. For each XK, calculate the distance between it in M and store that in D. Compare that to a B threshold of 15 minimum 40 max. If it fails, move on to the next one. Then you calculate the angle between XK and M and store that in theta. For the heart of the algorithm, you calculate the B value of the ellipse for these points using a bit of algebra against the ellipse equation. You can come up with this equation right here. Now I'll calculate the B for you from all the other things you've already calculated. Then you increment the B count for the found B. If you found B is 2, then you record that 2 is shown up once. It appears from another XK that it's appeared twice, etc. Once all the XKs have been exhausted, find the B value that has appeared the most often, and compare this value against every other XIX pair's highest B count. Whichever has the largest amount of values, they create the most amount of similar B values, is the best fit ellipse for the image. Now that you have the best fit ellipse values, you can easily calculate everything you need to draw the ellipse. So in summary, the algorithm tries every combination of pixels as endpoints of the ellipse. From there, it finds the most amount of pixels that will create similar B value for an ellipse, which essentially means that they've landed on the ellipse circumference. Then out of all the XIXJ pairs, whichever one has the most amount of pixels along their ellipse is the winner. Okay, so you can see here where it found the ellipse is using that algorithm. Now that we have our estimations as to where the ellipses are, we'll go back to our pre-outlined images and remove the inversion. That's pretty straightforward. Obviously, if anything lands on or in the ellipse, and it's white, you set it black. If it's black, you set it white. If it's outside the ellipse, you ignore it. And that's what I did down here. And it comes out to this, and you can see it down here. There's this little leftover artifact, because it didn't perfectly find the ellipse and referendum. So I'm going to clean that up with an algorithm next. Okay, so take out the artifacts. I just use an algorithm called clean loaners. You pass two arguments. One's the radius of the area you want to check around the pixel, and the second is the ratio of white pixels you're expecting in that area. So if the rate is less than what you're expecting, then you remove the pixel or set it black. Otherwise, you ignore it. So in this example, I'll just show in two pixels, and say you set the radius to 2 and the minimum ratio to .2. In the first one, yellow, you have seven white pixels, and there's 25 totals, so seven out of 25 is .28, and that's greater than .2, so it passes, and you leave the white. On the second example, it's only three out of 25, and it does not pass. It's not greater than .2, so you remove it. You do that for every single pixel. That'll take care of the artifacts. You can see how the one that was down below referendum is gone now. Okay, so now we've got the vertical ribbon, and this is kind of a pain. What we use is an algorithm I refer to as the blanket algorithm. Basically, it involves the discovery of a series of tangents along the top and bottom of the word in order to reduce the severity of the vertical offset along the x-axis. Obvious pitfalls are when there are tails and heads to letters, you know, like P and D, et cetera. But it's usually not that much of a problem. Okay, so how this works is for every single pixel along the bottom or top of the word. In this example, I'll just go along the bottom, but without loss of generality. I'll start here at the green pixel, and you'll walk a pixel to the left, and if the slope from here to this pixel here is larger than what you currently have in the beginning, it's a zero, then you keep it. So now you'd start right here, and then you'd walk a pixel to the right. If the slope's less than what you previously have on the right, then you keep it. So you keep going all the way until you reach what can make it any larger on the left and smaller on the right, which is these two pixels. Then you interpolate where the pixel would land for the green one here, and that's your new pixel. This down here explains the algorithm. You can find it on my website, because I don't think the slides are actually on the CD. Okay, also, you want to ensure that the final slopes aren't too large, because if they are, then you're going to have a vertical line at some point, and you really don't want that. So once the best fitanger is found, the simple calculation can interpolate where the x would land on the tangent line, and now that it's been calculated for every pixel on the x-axis, you take the new values and smooth them out with some simple averaging along the 10 left pixels and 10 values and right values. So that was simple and solves all the ribbining techniques implemented by every capture system. I'm not sure if this already exists or not, but I've named it the blanket algorithm. Feel free to call it whatever you like. So this is how you remove the ribbining here. For me, it's not to be fixed to just drop each pixel by the vertical offset, just average the top and bottom blanket values and drop each pixel and I call them down to the floor, and you can see here how I'll presume the referendum. Now I no longer have an inverted blob, and the ribbining has been taken care of. It's not perfect, but it's pretty damn close, and all I promised was 10%, so... All right, this is the old capture from 2007, one of their originals. And while this isn't technically their current one right now, because they changed it like I said a week before, but I'm just gonna keep calling it current until I get to the end. Character dilation and the current version of recapture makes decoding significantly more challenging, because character segmentation is the largest challenge for many capture crackers and OCRs. The old capture was an absolute joke. And then there's some anti-aliasing exploits because in the original one, you could easily segment the characters based off of the anti-aliasing, and that line could easily be removed with that as well. You could just look for the darkest, most black pixels, and the line barely had any, so you could almost just completely remove it by doing that. And then OCR failure to generate natural word distortions, because obviously an OCR has a problem solving a word. It's gonna have some problems in the word, so that comes up with natural distortions in the capture. Okay, now we're gonna go on to character segmentation, which is one of the harder parts of this. You could brute-force characters start and stop points along the x-axis, and that's exponentially challenging, and it's just really not a good idea. So what I did is I came up with a bunch of educated guesses, and then I use C-maps, and I go into their training usage, and why I use C-maps instead of neural network. Yeah. What's that? I'll go into that shortly. Okay, so for the character segmentation, I find some dips in the top and bottom of the words after a specialized version of my blanket routine. You see how, if you ignore the lines, like the vertical lines, you can see how the blanket routine goes in and into the crevices of each character, and whenever it falls deep into one, there's nowhere to go, but up is where I marked the line, and those are the start and stop points that I'm gonna use to test for each character. It makes it a lot easier than trying every single point. From the second galore's potential starts to applications, from a brute-force of 126 and 140 down to an educated guess of 20 and 22, which is exponential, and so that brings it down to about 97.6 more efficient than testing every single point. Okay, so the C-map stands for character map. I just named it that, I don't know. It's a compilation of hundreds of sampled characters. The more it's trained, the more efficient it becomes. It works like a neural network, yet it's far more simple. I use it instead of a neural network because I tried all the different ways that people implement OCRs with their own networks, and my C-map worked the best, so that's why I stuck with it. At least with this CAPTCHA, that is. Once it's trained sufficiently, it's applied to every combination of the character start and stop points that match certain guidelines that determine their likelihood of a match. Okay, so you take a character group you'd like to train, discover the average dimensions for that character, then interpolate each character to that size, add up the matrices of pixels, and divide by their total, and you have yourself a C-map. So in this case, I use three eyes. You can see out of the first top left pixel, there's one in every one, so the C-map has a one. In the far right, there's only two out of three, so it's 0.6. And you just do that with hundreds of characters, and you get a basic average of each character. The final matrix can be used to determine the probability of a pixel existing for that eye, and below is a visual representation of the C-map that I used in this current CAPTCHA. The brighter the pixel, the more likely it exists for that character. And you can see how in words that are letters that are less trained, like Q, it's a lot more fuzzy, but like E, looks a lot better just because it was trained more often. Okay, so an interesting, unfixable flaw with recaptures, the fact that every word is, in fact, just the actual real dictionary-based word. So to improve the efficacy, we'll exploit that flaw with a dictionary list. We use a model that lists about 100,000 words, and you can find it on my website. So to apply the C-map in dictionary attack, for every combination of possible start-stop character points, calculate the height by finding the highest and lowest white pixels between them. For each character in the C-map, check to see if the dimensions of the potential character fall within a set threshold, or the dimensions of the C-map character. So for instance, if you had a W, and you were going to compare it against an I in the C-map, there'd be no reason to because the dimensions are off. If one does, then apply the image to the C-map character to calculate its probability of being the character. Now, using a dynamic algorithm, calculate the total probability of each word in your dictionary list for matching that word, or matching that capture. The word with the highest probability is your word about 10% of the time. Okay, so this is the dynamic algorithm. This just generates the probability that a word matches your C-map, or the ratio data that you calculated with the C-map. So every start and stop point, the probability for every character in the C-map, assuming that it passed the dimensions, this list of information was referred to as the CDR, and I'll illustrate this with just the example car. Imagine that there's three C's, two A's, and four R's, CDR values. So in the beginning here, you have the three C's, and their ratio data is five, two, and three, and it's found it at xi equals zero, and the x final is nine, and then that goes on for A and R, et cetera. So in the dynamic algorithm, you would multiply the ratio data by the width of the character. So in the first ones, C-zero, it'd be five times 10 on down, et cetera. So now for the next letter, A, you find out which C the A falls in and doesn't overlap, and the highest value of that you add it to it. So in the first one, A starts at four, goes to 11, so you come over here and you see that it doesn't land in anything except the second one, which is, let's see here. Oh yeah, so then you add eight to it, because that's the only one that it'll land in. So eight comes over here and gets added to the final product. Same goes for the next day. It runs from 10 to 18. It'll fit the first one and the last one, because 10 to 18 comes after nine and after nine here, and the highest one between those two is 50, so you add that to the final product, and so on and so forth until you have the final value and that would be 118. So the largest probability for the word car to fit from our CDR data calculated from the image is 118, and the real project uses fractions from zero to one instead of whole numbers. Okay, so the last step is to divide the entire total by the total width of the image, and the final product is the word's true probability and it'll be a value between zero and one, like all good probabilities should be. All right, so a few issues. There can be real problems where it's not the largest probability and that can be fixed by allowing wild cards, but that did not improve the algorithm, so I'm not talking about it. So you apply this algorithm to every word in your dictionary list and the highest word is the winner. Okay, so my experimental results from using all these techniques, it's difficult to determine because I didn't actually apply it to their system instead I downloaded tons of their captures, named them, and determined how well it worked. The problem with that is you can't know exactly which words are verification word, so I just use a couple of tricks to kind of guess statistically. So if the words are distorted beyond a certain point, or if they're shorter than a few letters, or if they contain numbers and it's definitely not the verification word, and in some instances it's impossible to tell the difference without trying to natively on recapture. But an analysis of 16,555 captures, and 13 of them contained a word containing a number, and 6,712 of them had a word of less than or equal to three characters in size, meaning that about 42% of the capture supply that were recaptured can have the verification word easily determined, and that statistic is not including overly distorted words, which can make that up to half. So the system was set up to run for a day testing 4,690 downloaded random captures that were not used to train the system in any way, instead downloaded the images and not run the system against recaptured servers, and they determined the words, which one is the verification word myself, and the results of this experiment came to 873 instances where one or both of the two words were correct that gives an efficacy of about 18.5%. After removing all instances where the correct word was less than or equal to three characters, there's a total of 630 corrects. That leaves an overall efficiency of 13.5%. Of course, there are the instances where the system is answering that digitization word correct instead of the verification word, where this happens half the time, as far as you're conservative, considering the digitization word is often more distorted. A safe conservative number for the results in this experiment that accidentally answered the word other than the verification word would be about 25%. So here's a breakdown of the captures solved with those 4,000-some captures. I'd say about 25% of them up here would be the digitization word, 25% verification word, and half of the time you know which one's which so you don't have to worry about it. Since half the time it is not possible to guess which word is the verification word and there is still a 50% chance, so that comes out to about 25%. But this is still a highly conservative number. But if you apply that to the 13% of efficiency rate, it comes out to about 10%. So you say about 3% of the time it gets the digitization word. And that took about 8 seconds to decode an entire capture or 4 seconds a word. The results were executed on a single core two and a half gigahertz processor. And the winning probabilities for the sample capture I used in these slides turned out to be presumed with a rate of 0.618 and moronomy with a rate of 0.478. Also I hardly trained the CMAP because it was tedious. Okay, so because they changed the capture a week before the presentation, I decided to go through and re-crack it again so I could tell you guys about how to do that. Oops. Okay, so this is the changes they made. They got rid of the inverted blob for some reason and they focused more on ribbon and you can see how it's much more ribboned. Okay, so change the old system to make it work with the new capture. So the step one, you come out the blob algorithm, which is convenient. Then you change the blanket algorithm's arguments to the following. Use a tangent line of 4 pixels to the left and 4 to the right as opposed to the previous 10. Then you change the surrounding values. Then you allow changes with a slope of up to 1. Step three, you train a new CMAP and step four, that's pretty much it. And that's pretty much all you need to do is a really simple change. It took about 5 minutes, I don't know why they made it easier for the presentation. So after angrily naming 500 new captures they were ran through the system with an accuracy of about 32%. Obviously without the inverted blob things become a bit simpler and hence more efficient. To ensure a conservative figure I had to arrive at 23.85%, but it would more likely be about 30%. 2.5% of the time both words were solved for guaranteed defeat. Also I once again only lightly trained the CMAP with just under 200 captures. Once I hit 31.8% I was content. It took about 66 minutes and 33 seconds to run through the 500 captures. It comes out to about 8 seconds per capture on the same computer system using the other results. And here's an image of the new CMAP. You can see it's a lot cleaner than the old one but it's much less. So you'll notice for instance the E is very clean and almost just looks like an anti-aliased E. This is because E is the most frequent letter in the English language and then J-axis E are hardly trained and sadly there is no Q since I didn't run into one of them which means capture with the Q will be unlikely to be solved. It's quite obvious a more diligent person could train this much more efficiently and maybe even get up to 50%. You can see over here how many times each letter was trained. E was only trained 11 times and then over here is the average dimensions. I know it's over. Anything else? We can't think of anything else to talk about. So I'm going to show a video of the capture decoder in action and then we'll go on to Q&A over in the other room. Special thanks to the sweet-ass shirts that Jinx, Green Sector and Whisper Systems gave us. You see this? So that's it working right now. You see it running through every single algorithm and then actually running it through the dictionary list and then how often it gets it so far it hasn't done. And it got successes right there. So I got one out of four so far. And since it can get one character wrong or skip it entirely I include words that were, you know, off by one so it got zesty instead of zest sessions and sessions. But yeah, that's pretty much it. I'm going to leave this running for a few more moments then go over to the Q&A room.