After opening and exploring `server.go` script, you can find line 141 in main
func:  
`       http.HandleFunc("/login", loginHandler)`.

Now we understand, that url with `/login` at the end calls `loginHandler`
function.

Let's look inside:

```  
func loginHandler(w http.ResponseWriter, r *http.Request) {

	if err := r.ParseForm(); err != nil {  
		fmt.Fprintf(w, "500: %v", err)  
		return  
	}  
	username := r.FormValue("username")  
	password := r.FormValue("password")

	if strings.ToLower(username) != "admin" {  
		fmt.Fprintf(w, "User not found!\n")  
		return  
	}

	if MD5([]byte(password)) == "90829146b3603e2e7daf5031b2103e9e" {  
		fmt.Fprintf(w, "Login successful! Flag is %s\n", flag)  
	} else {  
		fmt.Fprintf(w, "Password is not correct!\nExpected %s, got %s\n", "90829146b3603e2e7daf5031b2103e9e", MD5([]byte(password)))  
	}  
}  
```  
So, there is 2 values which server checks: `username` and `password`.

It means, that correct url-request looks like this:
`http://tasks.kksctf.ru:30020/login?username=admin&password=qwerty`

(Of course, we want to log in as admin, and `admin` is common username for
it).  
***  
With `qwerty` password, server returns this:  
```  
Password is not correct!  
Expected 90829146b3603e2e7daf5031b2103e9e, got
67e129628458ce06fbc5bb76a58c5ca4  
```  
Because, as we have seen earlier, `MD5()` func result compares with
`90829146b3603e2e7daf5031b2103e9e`.

To understand, how we can hack it, let's dive into this function:  
```  
func MD5(data []byte) string {  
	b := digest(data)  
  
	return hex.EncodeToString(b[:])  
}  
```  
and further into `digest(data)`:  
```  
func digest(data []byte) [16]byte {  
	digest := md5.Sum(data)  
  
	s := len(data) / 4  
	if s > 4 {  
		s = 4  
	}  
  
	for ss := 0; ss < s; ss++ {  
		var F uint32

		F = ^F

		for i := 0; i < 4; i++ {  
			F = (F << 8) ^ table[(F>>24)^(uint32(data[ss*4+i])&0xff)]  
		}  
		F = ^F

		digest[ss*4+3] = byte((F >> 24) & 0xff)  
		digest[ss*4+2] = byte((F >> 16) & 0xff)  
		digest[ss*4+1] = byte((F >> 8) & 0xff)  
		digest[ss*4+0] = byte(F & 0xff)  
	}  
  
	return digest  
}  
```  
Let's look at:  
```  
s := len(data) / 4  
if s > 4 {  
   s = 4  
}  
```  
What is `s`? It represents, how many groups of 4 bytes are in our password

(If password length > 16, than 17, 18 and other characters simply ignores).

Further we iterate not through every byte, but every group of 4 bytes:  
```  
for ss := 0; ss < s; ss++ {  
   ...  
}  
```  
It's important. Algorithm processes the password in groups of 4 bytes
**separately**.

Notice, that if password is less than 4 characters, program never gets into
this loop, and the whole function return just password's md5 hash.

At the end of this loop there are such lines:  
```  
digest[ss*4+3] = byte((F >> 24) & 0xff)  
digest[ss*4+2] = byte((F >> 16) & 0xff)  
digest[ss*4+1] = byte((F >> 8) & 0xff)  
digest[ss*4+0] = byte(F & 0xff)  
```  
We can see, that 4 bytes of `F` variable (`F` is `uint32`, so it takes only 4
bytes) writes to the current group of 4 bytes.

First useful fact, is that bytes are written in reverse order (little-endian).

Second — is that every byte is rewritten, so nothing remains of md5 and we
don't need to reverse it!

Final hash - is simply some 4 numbers (`F`).

Now we can try to reverse this function deeper to find characters from `F`,
but let's notice, that we can simply bruteforce every such group of 4
characters!

Byte is 256 possible values, so 4 bytes is 256⁴ = 4294967296, which is not too
much.  
***  
But we need to gets this F's to compare with. Here is our admin's password
hash: `90829146b3603e2e7daf5031b2103e9e`.

Firstly, split it into 4 bytes groups: `90829146 b3603e2e 7daf5031 b2103e9e`.

Now remember, that it is little-endian, so reverse bytes for every number:
`46918290 2e3e60b3 3150af7d 9e3e10b2`.

Now we have F's to compare with!

So, here is dirty bruteforce code (from `F = ^F` to `F = ^F` is original
algorithm code):  
```  
	for a := byte(0); a < 255; a++ {  
		for b := byte(0); b < 255; b++ {  
			for c := byte(0); c < 255; c++ {  
				for d := byte(0); d < 255; d++ {  
					var F uint32  
					F = ^F  
					for i := 0; i < 4; i++ {  
						if i == 0 {  
							F = (F << 8) ^ table[(F>>24)^(uint32(a)&0xff)]  
						}  
						if i == 1 {  
							F = (F << 8) ^ table[(F>>24)^(uint32(b)&0xff)]  
						}  
						if i == 2 {  
							F = (F << 8) ^ table[(F>>24)^(uint32(c)&0xff)]  
						}  
						if i == 3 {  
							F = (F << 8) ^ table[(F>>24)^(uint32(d)&0xff)]  
						}  
					}  
					F = ^F  
  
					if F == 0x46918290 {  
						fmt.Println("=== 1 ===")  
						fmt.Println(a)  
						fmt.Println(b)  
						fmt.Println(c)  
						fmt.Println(d)  
					}  
					if F == 0x2e3e60b3 {  
						fmt.Println("=== 2 ===")  
						fmt.Println(a)  
						fmt.Println(b)  
						fmt.Println(c)  
						fmt.Println(d)  
					}  
					if F == 0x3150af7d {  
						fmt.Println("=== 3 ===")  
						fmt.Println(a)  
						fmt.Println(b)  
						fmt.Println(c)  
						fmt.Println(d)  
					}  
					if F == 0x9e3e10b2 {  
						fmt.Println("=== 4 ===")  
						fmt.Println(a)  
						fmt.Println(b)  
						fmt.Println(c)  
						fmt.Println(d)  
					}  
				}  
			}  
		}  
	}  
```  
After about 20 seconds we get this:  
```  
=== 1 ===  
41  
82  
41  
99  
=== 4 ===  
71  
74  
75  
45  
=== 3 ===  
75  
62  
65  
119  
=== 2 ===  
107  
52  
114  
94  
```  
Turn it to right order: `41 82 41 99 107 52 114 94 75 62 65 119 71 74 75 45`

And then to ASCII: `)R)ck4r^K>AwGJK-`  
***  
Input it as password:
`http://tasks.kksctf.ru:30020/login?username=admin&password=)R)ck4r^K%3EAwGJK-`
and server returns...  
```  
Login successful! Flag is kks{1f_s0meth1ng_called_md5_1t_d0esnt_have_t0_be}  
```  
Nice, we found the flag without deep reversing! :D  

Original writeup (https://github.com/RichardTry/kksctf-hashfunction).After opening and exploring `server.go` script, you can find line 141 in main
func:  
` http.HandleFunc("/login", loginHandler)`.

Now we understand, that url with `/login` at the end calls `loginHandler`
function.

Let's look inside:

```  
func loginHandler(w http.ResponseWriter, r *http.Request) {

if err := r.ParseForm(); err != nil {  
fmt.Fprintf(w, "500: %v", err)  
return  
}  
username := r.FormValue("username")  
password := r.FormValue("password")

if strings.ToLower(username) != "admin" {  
fmt.Fprintf(w, "User not found!\n")  
return  
}

if MD5([]byte(password)) == "90829146b3603e2e7daf5031b2103e9e" {  
fmt.Fprintf(w, "Login successful! Flag is %s\n", flag)  
} else {  
fmt.Fprintf(w, "Password is not correct!\nExpected %s, got %s\n",
"90829146b3603e2e7daf5031b2103e9e", MD5([]byte(password)))  
}  
}  
```  
So, there is 2 values which server checks: `username` and `password`.

It means, that correct url-request looks like this:
`http://tasks.kksctf.ru:30020/login?username=admin&password=qwerty`

(Of course, we want to log in as admin, and `admin` is common username for
it).  
***  
With `qwerty` password, server returns this:  
```  
Password is not correct!  
Expected 90829146b3603e2e7daf5031b2103e9e, got
67e129628458ce06fbc5bb76a58c5ca4  
```  
Because, as we have seen earlier, `MD5()` func result compares with
`90829146b3603e2e7daf5031b2103e9e`.

To understand, how we can hack it, let's dive into this function:  
```  
func MD5(data []byte) string {  
b := digest(data)  
  
return hex.EncodeToString(b[:])  
}  
```  
and further into `digest(data)`:  
```  
func digest(data []byte) [16]byte {  
digest := md5.Sum(data)  
  
s := len(data) / 4  
if s > 4 {  
s = 4  
}  
  
for ss := 0; ss < s; ss++ {  
var F uint32

F = ^F

for i := 0; i < 4; i++ {  
F = (F << 8) ^ table[(F>>24)^(uint32(data[ss*4+i])&0xff)]  
}  
F = ^F

digest[ss*4+3] = byte((F >> 24) & 0xff)  
digest[ss*4+2] = byte((F >> 16) & 0xff)  
digest[ss*4+1] = byte((F >> 8) & 0xff)  
digest[ss*4+0] = byte(F & 0xff)  
}  
  
return digest  
}  
```  
Let's look at:  
```  
s := len(data) / 4  
if s > 4 {  
s = 4  
}  
```  
What is `s`? It represents, how many groups of 4 bytes are in our password

(If password length > 16, than 17, 18 and other characters simply ignores).

Further we iterate not through every byte, but every group of 4 bytes:  
```  
for ss := 0; ss < s; ss++ {  
...  
}  
```  
It's important. Algorithm processes the password in groups of 4 bytes
**separately**.

Notice, that if password is less than 4 characters, program never gets into
this loop, and the whole function return just password's md5 hash.

At the end of this loop there are such lines:  
```  
digest[ss*4+3] = byte((F >> 24) & 0xff)  
digest[ss*4+2] = byte((F >> 16) & 0xff)  
digest[ss*4+1] = byte((F >> 8) & 0xff)  
digest[ss*4+0] = byte(F & 0xff)  
```  
We can see, that 4 bytes of `F` variable (`F` is `uint32`, so it takes only 4
bytes) writes to the current group of 4 bytes.

First useful fact, is that bytes are written in reverse order (little-endian).

Second — is that every byte is rewritten, so nothing remains of md5 and we
don't need to reverse it!

Final hash - is simply some 4 numbers (`F`).

Now we can try to reverse this function deeper to find characters from `F`,
but let's notice, that we can simply bruteforce every such group of 4
characters!

Byte is 256 possible values, so 4 bytes is 256⁴ = 4294967296, which is not too
much.  
***  
But we need to gets this F's to compare with. Here is our admin's password
hash: `90829146b3603e2e7daf5031b2103e9e`.

Firstly, split it into 4 bytes groups: `90829146 b3603e2e 7daf5031 b2103e9e`.

Now remember, that it is little-endian, so reverse bytes for every number:
`46918290 2e3e60b3 3150af7d 9e3e10b2`.

Now we have F's to compare with!

So, here is dirty bruteforce code (from `F = ^F` to `F = ^F` is original
algorithm code):  
```  
for a := byte(0); a < 255; a++ {  
for b := byte(0); b < 255; b++ {  
for c := byte(0); c < 255; c++ {  
for d := byte(0); d < 255; d++ {  
var F uint32  
F = ^F  
for i := 0; i < 4; i++ {  
if i == 0 {  
F = (F << 8) ^ table[(F>>24)^(uint32(a)&0xff)]  
}  
if i == 1 {  
F = (F << 8) ^ table[(F>>24)^(uint32(b)&0xff)]  
}  
if i == 2 {  
F = (F << 8) ^ table[(F>>24)^(uint32(c)&0xff)]  
}  
if i == 3 {  
F = (F << 8) ^ table[(F>>24)^(uint32(d)&0xff)]  
}  
}  
F = ^F  
  
if F == 0x46918290 {  
fmt.Println("=== 1 ===")  
fmt.Println(a)  
fmt.Println(b)  
fmt.Println(c)  
fmt.Println(d)  
}  
if F == 0x2e3e60b3 {  
fmt.Println("=== 2 ===")  
fmt.Println(a)  
fmt.Println(b)  
fmt.Println(c)  
fmt.Println(d)  
}  
if F == 0x3150af7d {  
fmt.Println("=== 3 ===")  
fmt.Println(a)  
fmt.Println(b)  
fmt.Println(c)  
fmt.Println(d)  
}  
if F == 0x9e3e10b2 {  
fmt.Println("=== 4 ===")  
fmt.Println(a)  
fmt.Println(b)  
fmt.Println(c)  
fmt.Println(d)  
}  
}  
}  
}  
}  
```  
After about 20 seconds we get this:  
```  
=== 1 ===  
41  
82  
41  
99  
=== 4 ===  
71  
74  
75  
45  
=== 3 ===  
75  
62  
65  
119  
=== 2 ===  
107  
52  
114  
94  
```  
Turn it to right order: `41 82 41 99 107 52 114 94 75 62 65 119 71 74 75 45`

And then to ASCII: `)R)ck4r^K>AwGJK-`  
***  
Input it as password:
`http://tasks.kksctf.ru:30020/login?username=admin&password=)R)ck4r^K%3EAwGJK-`
and server returns...  
```  
Login successful! Flag is kks{1f_s0meth1ng_called_md5_1t_d0esnt_have_t0_be}  
```  
Nice, we found the flag without deep reversing! :D  

Original writeup (https://github.com/RichardTry/kksctf-hashfunction).