
The Cowin portal set up for booking vaccination slots in India has (had?) a captcha to make sure bots are not able to book slots. They used an open source library calledsvg-captcha to generate the captcha.
I created achrome extension to speed up the booking process by auto filling inputs which will mostly be constant - and I included piece of code in it which could read the captcha and decode it.
In this post I will be explaining how that captcha decoder works!
Now that my github README is gone where credits for captcha decode were visible, I would like to give credit to@ayushchd for the idea. I used the captcha decode code snippet from his extension "Cowin Bot". His idea was the driver to this analysis I did to understand the logic behind the decode.
What is svg?
SVG is scalable vector graphics. SVG image is made by defining a starting point, lines drawn from one point to another, curves and fill colours! It is simple collection of definitions of all these attributes.
I will explain how we can draw the character "L" with svg.
Numbers in red are the co-ordinates of points that make the shape "L"
Numbers in blue are line numbers and these will help you understand which line we are drawing when we do as I have referenced below
How is "L" made?
The shape "L" we are drawing is made of following
- Starting point at (150,0)
- A line from (150,0) to (150,300) [1]
- A line from (150,300) to (300,300) [2]
- A line from (300,300) to (300,250) [3]
- A line from (300,250) to (200,250) [4]
- A line from (200,250) to (200,0) [5]
- A line from (200,0) to (150,0) [6]
Numbers enclosed between "[" and "]" correspond to line numbers in the figure above
Starting point (origin)
To draw any new shape, we define a starting point - relative to (0,0) for this particular shape. We define that by writing "M" (M-command ormoveto) followed by absolute point on a 2d plane. So, if we want to leave some margin on left of the shape, we would start at (150, 0) [this is in pixels] - we define origin as
M150 0
'M' followed by a point denotes the starting point of the shape
Line from (x1, y1) to (x2, y2)
To achieve the shape above, we will have to draw multiple lines. For example we have to draw a line from our starting point (150,0) to (150,300).
To understand how to draw a line, we need to know that we can only draw a line from the point where our cursor currently is to an ending point that we define (I know this is a line segment and not a line, because line extends to infinity and blah, but for sake of simplicity, let us call it as a line). For example, as we define a starting point with 'M', the cursor moves to the starting point.
Therefore, to draw the line[1] from (150,0) to (150,300), as we defined the starting point, our cursor is already at (150,0). We now just have to define the ending point for line [1].
We do this with "L" (L-command orlineto)
L150 300
Thus we have drawn a line form (150,0) to (150,300).
In similar fashion we can draw other lines too
L300 300 [2] L300 250 [3] L200 250 [4] L200 0 [5] L150 0 [6]
Numbers in the square brackets are just for our information and are not written in the actual svg definition
[There is some other way to do this. Instead of always specifying endpoints of lines, if we know that we just want to move vertically or horizontally, we can use "H" (H-command or horizontal lineto) and "V" (V-command or vertical lineto) commands to just define length of that line segment, but let us save that for sometime later for now.]
Writing these as svg
I explained how we can define points and lines, we need to tell the processor or parser that these are attributes of an SVG.
All we discussed is part of "path" element of svg and those points and lines are values for the "d" attribute of the path element.
Thus we define the full svg as:
<svgxmlns="http://www.w3.org/2000/svg"height="500"width="400"><pathd=" M150 0 L150 300 L300 300 L300 250 L200 250 L200 0 L150 0 "/></svg>
[You should read more about the lowercase variants of "L", "H" and "V" to understand what they do and when to use them!]
[There are few more commands like "C", "Q", "Z" which you can read more about]
[There is a command Z which defines a line segment from the cursor to origin "M", we could use it above by replacingL150 0
withZ
]
Now if you take thesvg
above and put it in html file, the html file in the browser will show the shape "L"! (like in the figure above, except for the annotations)
How the library creates captcha?
Thesvg-captcha
library which I mentioned above has shapes of alphanumeric characters defined in.ttf
file.
The font file is loaded in the library and with openType.js library the ASCII characters are converted to "path" - as we discussed above - these paths are basically the values ofd
attribute of path element in the svg - for example, "S" is created as
<svgxmlns="http://www.w3.org/2000/svg"height="300"width="300"><pathd="M70.85 40.26L70.90 40.31L70.75 40.15Q67.42 40.40 65.78 39.87L65.80 39.89L65.74 39.83Q63.71 39.14 63.41 35.75L63.48 35.82L64.91 34.74L64.81 34.63Q65.61 34.18 66.37 33.68L66.42 33.73L66.43 33.74Q66.03 35.52 67.63 36.73L67.69 36.79L67.72 36.83Q68.92 37.76 71.05 37.57L71.04 37.55L71.11 37.62Q75.53 37.24 75.34 33.70L75.31 33.67L75.25 33.62Q75.20 31.47 72.57 30.25L72.56 30.24L72.54 30.22Q69.71 29.18 67.35 27.96L67.36 27.98L67.23 27.84Q64.81 26.64 63.82 21.96L63.76 21.89L63.74 21.88Q63.65 21.45 63.54 20.69L63.58 20.73L63.51 20.66Q63.51 19.97 63.59 19.40L63.64 19.46L63.61 19.43Q63.77 17.83 65.02 17.37L64.96 17.31L64.92 17.28Q67.33 16.41 71.33 16.60L71.41 16.69L71.30 16.57Q73.28 16.83 74.08 16.91L73.93 16.77L73.93 16.77Q75.50 17.04 76.57 17.50L76.61 17.54L76.57 17.51Q78.59 17.88 78.82 20.36L78.94 20.48L78.81 20.35Q77.85 21.14 75.65 22.48L75.69 22.52L75.57 22.41Q75.18 19.43 70.81 19.43L70.76 19.39L70.79 19.42Q68.93 19.46 67.94 20.14L68.01 20.21L67.88 20.08Q66.80 20.68 67.03 22.47L67.05 22.48L67.05 22.49Q67.22 24.60 70.19 26.12L70.25 26.18L70.27 26.20Q70.68 26.31 75.18 27.95L75.15 27.92L75.27 28.04Q77.91 29.54 78.33 33.91L78.20 33.78L78.25 33.84Q78.21 33.95 78.29 35.21L78.32 35.24L78.44 35.36Q78.39 38.01 76.83 39.12L76.94 39.23L76.88 39.17Q74.93 40.03 70.74 40.15ZM73.13 42.61L73.17 42.66L73.20 42.68Q74.50 42.62 76.48 42.62L76.48 42.61L76.62 42.75Q78.73 42.77 79.98 42.35L79.84 42.20L79.82 42.19Q81.19 41.50 81.11 39.71L81.03 39.62L81.05 39.65Q81.01 38.65 80.63 36.59L80.60 36.57L80.65 36.62Q79.66 31.86 77.57 29.99L77.61 30.04L77.59 30.01Q76.86 28.48 75.52 27.76L75.46 27.69L70.14 25.61L70.12 25.59Q69.82 25.45 69.37 25.22L69.40 25.25L69.25 24.84L69.22 24.46L69.37 24.61Q69.11 23.09 70.25 22.48L70.23 22.47L70.34 22.57Q70.97 21.87 72.68 21.68L72.85 21.85L72.78 21.78Q73.86 21.60 75.00 22.06L74.96 22.02L74.96 22.02Q75.03 22.12 75.22 22.96L75.32 23.06L75.26 23.00Q75.41 22.82 75.91 22.59L75.93 22.61L75.91 22.59Q76.75 23.62 76.86 24.76L76.83 24.72L76.83 24.72Q76.98 24.76 80.63 22.17L80.54 22.08L80.62 22.16Q80.40 19.58 78.95 18.89L78.94 18.88L78.86 18.80Q78.36 17.61 76.91 17.08L76.90 17.06L76.83 17.00Q74.63 16.28 71.43 16.28L71.39 16.24L71.43 16.28Q66.35 16.12 64.49 16.80L64.56 16.87L64.65 16.96Q63.29 17.39 63.18 19.07L63.16 19.06L63.15 19.04Q63.04 19.50 63.46 21.71L63.61 21.86L63.51 21.77Q64.11 25.45 66.28 27.70L66.43 27.84L66.46 27.87Q67.26 29.58 68.76 30.27L68.77 30.27L68.73 30.24Q70.11 30.80 74.03 32.36L74.03 32.36L74.10 32.46L74.76 32.82L74.84 32.94L74.77 32.87Q74.98 33.35 75.02 33.69L75.02 33.69L74.88 33.55Q75.08 37.14 71.16 37.29L71.22 37.35L71.06 37.19Q69.98 37.29 68.68 36.91L68.69 36.91L68.72 36.94Q68.44 36.17 68.44 35.44L68.30 35.31L68.36 35.37Q68.43 35.17 68.47 34.90L68.43 34.87L68.47 34.91Q68.00 35.09 67.20 35.58L67.17 35.55L67.15 35.53Q66.75 34.52 66.90 33.15L66.83 33.07L66.82 33.06Q64.72 34.24 63.12 35.65L63.10 35.63L63.16 35.69Q63.26 36.55 63.34 37.58L63.33 37.57L63.31 37.55Q63.68 39.33 64.97 40.09L64.95 40.06L64.95 40.07Q66.16 41.89 68.71 42.27L68.66 42.22L68.57 42.12Q70.14 42.37 73.07 42.56Z"/></svg>
Now if you look at the library clearly, it also adds noise in terms of lines which are drawn over the captcha - for example
<svgxmlns="http://www.w3.org/2000/svg"width="150"height="50"viewBox="0,0,150,50"><pathfill="#333"d="M64.89 40.36L64.86 40.33L64.86 40.33Q61.98 36.54 59.36 27.90L59.36 27.90L59.29 27.83Q58.61 25.67 57.81 23.54L57.90 23.63L55.05 32.42L55.07 32.44Q53.17 37.29 50.78 40.52L50.80 40.55L50.68 40.42Q49.88 40.54 48.24 40.69L48.26 40.71L48.24 40.69Q48.45 39.61 48.45 38.35L48.32 38.22L48.45 38.34Q48.43 32.32 45.69 26.19L45.73 26.22L45.58 26.07Q42.48 19.20 36.61 14.21L36.69 14.29L36.75 14.36Q38.87 15.03 41.23 15.56L41.07 15.40L41.19 15.52Q49.82 23.92 51.12 34.89L51.10 34.87L51.05 34.82Q52.79 31.61 54.24 26.25L54.15 26.16L54.14 26.14Q56.16 18.69 56.66 17.28L56.74 17.36L59.02 17.40L58.93 17.31Q59.73 19.44 60.37 21.76L60.32 21.71L61.43 26.01L61.60 26.18Q63.08 31.55 64.49 34.78L64.45 34.75L64.58 34.88Q66.23 23.24 74.15 15.89L74.06 15.80L74.02 15.76Q75.61 15.49 78.58 14.84L78.50 14.76L78.63 14.88Q73.26 19.15 70.22 25.28L70.28 25.34L70.27 25.34Q67.08 31.47 67.08 38.25L67.22 38.38L67.07 38.24Q67.19 39.46 67.26 40.60L67.20 40.54L66.12 40.49L66.12 40.49Q65.51 40.41 64.94 40.41ZM71.21 43.33L71.16 43.28L71.19 43.31Q69.86 39.66 69.97 35.77L69.99 35.79L70.00 35.80Q70.37 24.29 79.74 16.03L79.64 15.94L79.74 16.03Q78.69 16.16 76.75 16.70L76.79 16.74L76.88 16.83Q77.76 15.93 79.67 14.14L79.62 14.09L79.72 14.19Q77.00 14.90 74.07 15.39L74.00 15.32L74.05 15.37Q66.56 22.50 64.65 31.98L64.59 31.91L64.69 32.01Q64.12 30.38 61.27 18.92L61.22 18.88L61.34 18.99Q60.74 18.89 59.79 18.89L59.96 19.06L59.52 17.86L59.50 17.84Q59.39 17.31 59.16 16.81L59.30 16.95L56.25 16.80L56.26 16.80Q55.60 19.42 54.32 24.54L54.26 24.48L54.22 24.43Q53.03 29.65 52.04 32.12L51.92 31.99L52.03 32.11Q50.37 24.13 44.81 17.81L44.93 17.93L44.87 17.86Q44.37 17.79 43.42 17.60L43.36 17.54L43.40 17.57Q42.62 16.68 41.05 15.04L41.21 15.20L41.14 15.13Q37.65 14.23 35.48 13.43L35.65 13.60L35.62 13.56Q41.28 18.23 44.55 24.44L44.44 24.33L44.58 24.47Q48.00 31.05 48.00 38.05L47.88 37.93L47.92 37.97Q47.92 39.53 47.73 41.13L47.86 41.26L47.83 41.23Q47.97 41.14 48.42 41.04L48.50 41.12L48.41 41.03Q48.85 40.91 49.07 40.91L49.13 40.97L49.24 42.11L49.22 42.08Q49.20 42.53 49.28 43.06L49.17 42.95L49.21 42.99Q50.38 42.94 52.66 42.79L52.65 42.78L52.57 42.70Q56.31 37.42 59.05 28.01L59.10 28.06L59.05 28.01Q61.82 36.80 64.79 40.75L64.68 40.64L64.71 40.67Q65.29 40.80 66.13 40.92L66.07 40.85L66.04 40.83Q66.87 41.84 67.93 42.91L68.00 42.98L67.97 42.94Q68.78 43.03 71.18 43.30Z"/><pathfill="#222"d="M26.73 41.05L26.85 41.17L25.07 41.10L25.16 41.19Q21.71 41.13 20.76 39.00L20.84 39.09L20.76 39.00Q22.03 38.07 23.48 36.96L23.46 36.95L23.44 36.92Q24.03 39.11 26.88 38.96L26.91 38.98L26.87 38.94Q27.71 38.95 28.74 38.72L28.68 38.66L28.74 38.72Q29.63 38.17 29.56 37.21L29.47 37.12L29.49 37.15Q29.35 35.98 27.56 35.30L27.70 35.44L23.81 33.83L23.74 33.75Q21.52 32.53 21.17 28.99L21.15 28.96L21.15 28.96Q20.89 26.76 23.06 26.19L23.04 26.17L23.16 26.29Q24.09 26.04 27.06 26.04L27.03 26.01L27.06 26.04Q31.99 26.09 32.90 28.61L32.74 28.45L32.79 28.50Q32.19 29.07 31.46 29.53L31.45 29.51L30.09 30.52L30.10 30.53Q29.52 28.53 26.28 28.30L26.23 28.26L26.29 28.31Q25.83 28.46 24.80 28.88L24.74 28.82L24.81 28.89Q24.07 29.07 24.07 30.17L24.24 30.34L24.15 30.24Q24.43 31.25 26.26 31.94L26.23 31.91L26.38 32.06Q27.33 32.32 29.99 33.42L30.02 33.45L30.11 33.54Q31.82 34.26 32.01 36.81L32.09 36.90L32.03 36.83Q32.16 37.53 32.08 38.52L32.05 38.49L32.01 38.45Q32.10 39.45 31.52 40.10L31.41 39.98L31.48 40.05Q29.77 41.04 26.72 41.04ZM31.51 43.66L31.52 43.67L31.61 43.76Q32.55 43.82 33.92 43.52L33.84 43.44L33.93 43.53Q35.03 42.88 34.84 41.66L34.68 41.50L34.84 41.66Q34.60 40.92 34.29 39.32L34.43 39.47L34.36 39.39Q33.87 36.50 32.08 35.28L32.04 35.24L32.12 35.32Q31.43 33.75 30.17 33.03L30.33 33.19L30.21 33.08Q28.97 32.67 26.50 31.72L26.53 31.75L26.60 31.82Q26.73 31.35 27.00 31.23L26.97 31.21L26.89 31.12Q27.49 30.66 28.14 30.62L28.25 30.74L28.19 30.67Q28.93 30.58 29.70 30.84L29.73 30.88L29.73 30.96L29.92 31.03L30.03 30.95L30.03 30.99L30.02 30.98Q31.10 31.45 31.29 32.71L31.32 32.74L31.49 32.90Q32.61 31.93 34.51 30.26L34.54 30.28L34.46 30.20Q34.35 29.63 33.36 28.45L33.38 28.47L33.22 28.31Q32.12 25.65 27.01 25.54L27.01 25.53L27.11 25.63Q23.81 25.46 21.83 26.03L21.95 26.15L21.96 26.15Q20.42 26.59 20.65 28.69L20.69 28.73L20.75 28.79Q20.85 30.03 21.76 32.12L21.78 32.15L21.78 32.14Q22.29 33.22 23.31 33.94L23.39 34.01L23.33 33.96Q23.93 35.35 25.22 36.04L25.34 36.16L25.30 36.12Q26.18 36.31 27.13 36.69L27.12 36.68L29.14 37.56L29.11 37.53Q28.78 38.65 26.84 38.53L26.74 38.43L26.84 38.54Q26.31 38.50 25.09 38.19L25.21 38.31L25.09 38.04L25.00 38.18L25.03 38.21Q24.20 37.88 23.55 36.39L23.50 36.33L23.52 36.35Q21.44 37.96 20.33 39.11L20.20 38.97L20.18 38.95Q20.52 39.90 21.54 40.70L21.65 40.81L21.45 40.91L21.38 40.84Q22.94 43.09 27.17 43.47L27.05 43.35L27.07 43.37Q28.41 43.57 31.65 43.80Z"/><pathfill="#333"d="M116.76 18.55L116.71 18.50L116.62 18.41Q116.01 18.90 115.86 19.70L115.83 19.67L115.73 24.71L115.82 24.80Q116.85 24.69 119.06 24.42L119.15 24.51L119.18 24.54Q119.14 24.96 119.10 25.80L119.09 25.79L119.08 25.78Q118.99 26.49 118.99 26.94L119.09 27.04L117.49 27.11L117.41 27.03Q116.58 27.08 115.70 27.08L115.70 27.08L115.79 27.17Q115.71 31.28 115.64 39.65L115.69 39.70L115.79 39.81Q113.44 39.74 111.95 40.42L112.02 40.49L112.04 40.51Q113.06 34.30 112.95 27.06L112.89 27.01L113.08 27.19Q112.25 26.98 110.96 26.71L111.08 26.84L111.02 26.78Q110.98 25.75 110.79 23.81L110.82 23.83L110.90 23.91Q112.04 24.45 112.99 24.64L112.94 24.59L112.95 24.60Q112.81 23.84 112.50 21.37L112.57 21.43L112.53 21.39Q112.30 19.34 112.30 18.39L112.33 18.42L112.34 18.43Q112.42 16.83 113.83 16.26L113.69 16.12L113.79 16.23Q114.49 15.74 119.32 14.90L119.40 14.98L119.43 15.01Q120.11 14.74 120.95 14.63L121.02 14.70L121.07 14.75Q120.84 15.51 120.69 16.38L120.63 16.32L120.30 17.85L120.44 18.00Q119.67 17.72 119.17 17.83L119.26 17.93L119.12 17.78Q118.65 17.92 116.74 18.53ZM122.03 19.52L122.03 19.51L122.02 19.50Q122.44 17.37 123.05 14.82L123.05 14.83L123.12 14.89Q122.49 15.21 121.20 15.82L121.09 15.72L121.54 14.08L121.55 14.08Q120.39 14.34 117.92 14.87L117.87 14.82L117.85 14.80Q116.68 14.88 113.48 15.80L113.40 15.72L113.56 15.88Q111.83 16.13 111.83 17.99L111.94 18.11L111.92 18.08Q111.94 18.41 112.02 18.75L112.00 18.73L112.04 18.77Q112.28 20.12 112.40 21.45L112.30 21.35L112.56 24.09L112.52 24.05Q111.10 23.62 110.45 23.16L110.59 23.30L110.51 23.22Q110.56 24.11 110.60 25.13L110.68 25.22L110.75 27.15L110.76 27.16Q111.43 27.30 112.23 27.37L112.36 27.51L112.27 29.51L112.74 29.60L112.65 29.51Q112.64 35.74 111.65 41.19L111.47 41.01L111.52 41.06Q112.49 40.55 113.67 40.28L113.74 40.34L113.50 42.36L113.50 42.35Q114.71 42.04 116.01 42.00L116.05 42.04L116.02 42.02Q117.31 42.05 118.56 42.31L118.52 42.27L118.61 42.36Q117.77 37.37 117.77 29.41L117.72 29.36L117.66 29.30Q118.86 29.40 120.95 29.48L120.98 29.50L121.01 27.66L121.00 27.66Q120.99 26.70 121.03 25.75L120.95 25.67L120.94 25.65Q120.49 25.78 119.43 26.05L119.34 25.96L119.38 26.00Q119.38 25.32 119.50 23.99L119.54 24.02L119.51 24.00Q118.61 24.16 117.81 24.24L117.93 24.36L117.85 24.27Q117.84 22.86 117.95 21.45L118.04 21.54L117.97 21.47Q118.16 20.86 118.69 20.51L118.70 20.52L118.78 20.60Q119.60 19.79 120.42 19.71L120.45 19.74L120.38 19.67Q121.37 19.76 122.21 19.69Z"/><pathfill="#333"d="M85.94 41.03L85.92 41.01L85.86 40.94Q81.76 29.77 76.63 24.78L76.58 24.74L76.54 24.69Q77.97 25.29 80.94 25.94L80.95 25.94L81.01 26.00Q84.63 30.01 87.18 36.67L87.11 36.60L87.03 36.51Q89.87 29.34 92.50 26.30L92.38 26.18L92.46 26.26Q94.27 25.98 96.71 25.30L96.71 25.30L96.86 25.45Q93.67 28.16 91.50 32.73L91.45 32.68L91.45 32.67Q91.04 33.29 87.65 40.90L87.79 41.04L87.64 40.90Q87.16 41.10 85.94 41.02ZM90.62 43.50L90.46 43.34L90.61 43.49Q93.81 31.04 98.38 26.36L98.38 26.36L96.88 26.84L96.99 26.95Q96.12 27.10 95.28 27.22L95.38 27.32L95.35 27.29Q95.86 26.92 96.67 26.04L96.57 25.94L96.56 25.92Q97.34 25.02 97.80 24.60L97.94 24.73L97.96 24.76Q95.60 25.25 92.17 25.75L92.34 25.91L92.36 25.94Q89.52 28.99 87.46 34.59L87.45 34.57L87.49 34.61Q85.85 30.43 84.02 27.95L84.07 28.00L83.58 28.04L83.48 27.95Q83.19 27.92 82.96 27.92L82.91 27.87L83.01 27.97Q82.81 27.65 81.10 25.60L81.03 25.52L81.12 25.62Q77.76 25.11 75.51 23.97L75.46 23.92L75.57 24.03Q81.62 29.66 85.61 41.38L85.64 41.41L86.64 41.42L86.53 41.31Q86.87 41.96 87.71 43.25L87.76 43.30L89.13 43.30L89.25 43.42Q89.83 43.28 90.48 43.36Z"/><pathd="M7 30 C80 13,82 19,139 22"stroke="#222"fill="none"/></svg>
The library basically generates the random group of characters (4 characters in this case, but you can tell the library how many characters should be there in the captcha). - The basic shape of the character is the base
- Each character is translated to
path
element - Character is shifted back and forth depending on its position in the captcha - this is done by setting the arguments for different commands (M, C, L, Q, H, V, etc.)
- While defining the arguments for commands above, random distortion is added by adding +/- (~0.1) in each value.
- Noise is added by drawing random curves over the captcha
- The order of
path
elements in svg is shuffled - but the definition ofpath
contains information about character's position in captcha (because the origin "M" point and other lines, curves are well defined for their start and endpoints). Thus the order of elements inside the captcha does change the order of characters in the image. - The full svg is returned as an output.
Reverse engineering - understanding
Break down the problem
Now that we know how captcha is created, we just have to
- Filter out the noise in the captcha
- Define the position of each
path
element - Map back each
path
element to the original character
Filter out the noise
If you take a closer look at the captcha generated, the noise lines are of the form
<pathd="M7 30 C80 13,82 19,139 22"stroke="#222"fill="none"/>
Thed
attribute is too small to make a real character and there is nofill
attribute.
I observed many captchas generated from the library and came to a conclusion that it is safe to assume - All thepath
elements with fill="none" contribute to noise lines.
Thus if we just leave out all such path elements, we are done with filtering the noise out.
Define the position of eachpath
element
Or we can say arrange all the path elements in correct order!
As we know,
- each path element has a defined starting point (origin) defined with
M
in itsd
attribute - the characters in captcha are arranged horizontally - making the origin points lie on same horizontal line making them differ just in x-coordinate
Thus sorting the path elements by the x-coordinates of their origin should do our job!
(There are random distortions added, which distort each point by +/- (< 0.1px) - but we can ignore that for sorting as they are not going to affect the relative position of paths)
Mappingpath
to original character
This is the most important and tricky part!
Assume in this section that we are talking about a single character in the captcha.
The library generates character and distorts each part of itsd
attribute by +/- (less than 0.1) px
letdistortion=(Math.random()*0.2)-0.1;
From this, it is clear that a character "R" generated by this library may not be exactly similar to another "R" generated by it.
But, the important thing to note in this is, the order of commands (M, L, Q, C, Z, etc.) to generate R will always be the same! Thus if we ignore the arguments of these commands, the order of commands is going to be same for any "R" generated by the library with same font style!
Also, by observation, I can say that all the alphanumeric characters in both upper and lower cases - each one of them, has a unique order of commands used to draw them!
Algorithm
Did it click yet?
Yes,
- I generated paths for all alphanumeric characters (uppercase and lowercase) using the library
- For each character, I found the series of commands which draw it (just ignored the numbers in the
d
attribute of path) - Stored the series of commands as string for each of the character in a map with the series of commands as key and the original character as the value
This map now serves as the model to decode the captcha.
All we have to do is,
For each
path
element (corresponding to a character) in captchafind the
d
attribute- For instance in the svg shown for "S" above, d attribute is:```
M70.85 40.26L70.90 40.31L70.75 40.15Q67.42 40.40 65.78 39.87L65.80 39.89L65.74 39.83Q63.71 39.14 63.41 35.75L63.48 35.82L64.91 34.74L64.81 34.63Q65.61 34.18 66.37 33.68L66.42 33.73L66.43 33.74Q66.03 35.52 67.63 36.73L67.69 36.79L67.72 36.83Q68.92 37.76 71.05 37.57L71.04 37.55L71.11 37.62Q75.53 37.24 75.34 33.70L75.31 33.67L75.25 33.62Q75.20 31.47 72.57 30.25L72.56 30.24L72.54 30.22Q69.71 29.18 67.35 27.96L67.36 27.98L67.23 27.84Q64.81 26.64 63.82 21.96L63.76 21.89L63.74 21.88Q63.65 21.45 63.54 20.69L63.58 20.73L63.51 20.66Q63.51 19.97 63.59 19.40L63.64 19.46L63.61 19.43Q63.77 17.83 65.02 17.37L64.96 17.31L64.92 17.28Q67.33 16.41 71.33 16.60L71.41 16.69L71.30 16.57Q73.28 16.83 74.08 16.91L73.93 16.77L73.93 16.77Q75.50 17.04 76.57 17.50L76.61 17.54L76.57 17.51Q78.59 17.88 78.82 20.36L78.94 20.48L78.81 20.35Q77.85 21.14 75.65 22.48L75.69 22.52L75.57 22.41Q75.18 19.43 70.81 19.43L70.76 19.39L70.79 19.42Q68.93 19.46 67.94 20.14L68.01 20.21L67.88 20.08Q66.80 20.68 67.03 22.47L67.05 22.48L67.05 22.49Q67.22 24.60 70.19 26.12L70.25 26.18L70.27 26.20Q70.68 26.31 75.18 27.95L75.15 27.92L75.27 28.04Q77.91 29.54 78.33 33.91L78.20 33.78L78.25 33.84Q78.21 33.95 78.29 35.21L78.32 35.24L78.44 35.36Q78.39 38.01 76.83 39.12L76.94 39.23L76.88 39.17Q74.93 40.03 70.74 40.15ZM73.13 42.61L73.17 42.66L73.20 42.68Q74.50 42.62 76.48 42.62L76.48 42.61L76.62 42.75Q78.73 42.77 79.98 42.35L79.84 42.20L79.82 42.19Q81.19 41.50 81.11 39.71L81.03 39.62L81.05 39.65Q81.01 38.65 80.63 36.59L80.60 36.57L80.65 36.62Q79.66 31.86 77.57 29.99L77.61 30.04L77.59 30.01Q76.86 28.48 75.52 27.76L75.46 27.69L70.14 25.61L70.12 25.59Q69.82 25.45 69.37 25.22L69.40 25.25L69.25 24.84L69.22 24.46L69.37 24.61Q69.11 23.09 70.25 22.48L70.23 22.47L70.34 22.57Q70.97 21.87 72.68 21.68L72.85 21.85L72.78 21.78Q73.86 21.60 75.00 22.06L74.96 22.02L74.96 22.02Q75.03 22.12 75.22 22.96L75.32 23.06L75.26 23.00Q75.41 22.82 75.91 22.59L75.93 22.61L75.91 22.59Q76.75 23.62 76.86 24.76L76.83 24.72L76.83 24.72Q76.98 24.76 80.63 22.17L80.54 22.08L80.62 22.16Q80.40 19.58 78.95 18.89L78.94 18.88L78.86 18.80Q78.36 17.61 76.91 17.08L76.90 17.06L76.83 17.00Q74.63 16.28 71.43 16.28L71.39 16.24L71.43 16.28Q66.35 16.12 64.49 16.80L64.56 16.87L64.65 16.96Q63.29 17.39 63.18 19.07L63.16 19.06L63.15 19.04Q63.04 19.50 63.46 21.71L63.61 21.86L63.51 21.77Q64.11 25.45 66.28 27.70L66.43 27.84L66.46 27.87Q67.26 29.58 68.76 30.27L68.77 30.27L68.73 30.24Q70.11 30.80 74.03 32.36L74.03 32.36L74.10 32.46L74.76 32.82L74.84 32.94L74.77 32.87Q74.98 33.35 75.02 33.69L75.02 33.69L74.88 33.55Q75.08 37.14 71.16 37.29L71.22 37.35L71.06 37.19Q69.98 37.29 68.68 36.91L68.69 36.91L68.72 36.94Q68.44 36.17 68.44 35.44L68.30 35.31L68.36 35.37Q68.43 35.17 68.47 34.90L68.43 34.87L68.47 34.91Q68.00 35.09 67.20 35.58L67.17 35.55L67.15 35.53Q66.75 34.52 66.90 33.15L66.83 33.07L66.82 33.06Q64.72 34.24 63.12 35.65L63.10 35.63L63.16 35.69Q63.26 36.55 63.34 37.58L63.33 37.57L63.31 37.55Q63.68 39.33 64.97 40.09L64.95 40.06L64.95 40.07Q66.16 41.89 68.71 42.27L68.66 42.22L68.57 42.12Q70.14 42.37 73.07 42.56Z
- ignore all the whitespaces and numbers to create string containing series of commands - Corresponding series for "S" above would be: ```MLLQLLQLLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQZMLLQLLQLLQLLQLLQLLQLLLQLLLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLLLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQZ
- find the character corresponding to this string in the map we created
Codes
Script to find svg for all alphanumerics (node js)
This node script runs on modified version of librarysvg-captcha - I have modified the files in my fork - check default branch
The following script can be found at./index.js
in my fork
letcaptchacreator=require('./lib');// import the svg-captcha libraryconstfs=require('fs');letchars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";letobj={};for(leti=0;i<chars.length;i++){obj[chars[i]]=captchacreator.create(chars[i]).data;}letans=JSON.stringify(obj);try{constdata=fs.writeFileSync('texts.json',ans)}catch(err){console.error(err)}
Script to filter out numbers ind
attribute to create the model (reverse map)
importjsonimportbase64defisval(c):nms=['1','2','3','4','5','6','7','8','9','0']nms=nms+["","."]returncnotinnmsdeffilter(t):ans=''foriint:ifisval(i):ans+=ireturnansdefreadfileandgetjson():withopen("texts.json")asf_:returnjson.load(f_)deffilter_data(data):retdata={}fork,vindata.items():retdata[filter(v)]=kreturnjson.dumps(retdata)defwrite_file(jsonstr):withopen("final.json","w+")asf_:f_.write(jsonstr)write_file(filter_data(readfileandgetjson()))defcreate_base64(jsonstr):encoded=base64.b64encode(jsonstr)print(encoded)create_base64(filter_data(readfileandgetjson()))
Code which decodes captcha in the webpage
Note that the model is stored as a base64 encoded string in the following script. This base64 string is generated by using the functioncreate_base64
defined in the python script above
letmodel="BASE64 ENCODED STRING OF REVERSE MAP CREATED";varparsed_model=JSON.parse(atob(model));varparser=newDOMParser();varsvg=parser.parseFromString(atob($("img#captchaImage").attr("src").split("base64,")[1]),"image/svg+xml");$(svg).find('path').each((_,p)=>{if($(p).attr('stroke')!=undefined)$(p).remove();})vals=[];$(svg).find('path').each((_,p)=>{idx=parseInt($(p).attr("d").split(".")[0].replace("M",""));vals.push(idx);})varsorted=[...vals].sort(function(a,b){returna-b;})varsolution=['','','','',''];$(svg).find('path').each((idx,p)=>{varpattern=$(p).attr('d').replace(/[\d\.\s]/g,"");solution[sorted.indexOf(vals[idx])]=parsed_model[pattern];});console.log(solution);//solution array contains the decoded captcha
Top comments(4)

Hello,
Thank you for sharing this tutorial.
I have an issue. When I convert and compare the SVG code according to the tutorial, I realize that the letters are different in the display compared to the code. What I mean is that the order of<path>
in the code is different from the display order of<path>
. Is there a way to solve this issue?

- LocationHyderabad
- WorkHero at Cronlogy Technologies
- Joined
Nicely done!
Some comments may only be visible to logged-in visitors.Sign in to view all comments.
For further actions, you may consider blocking this person and/orreporting abuse