So I am working on a challenge problem to find a vulnerability in a C program binary that allows a command to be executed by the program (using the effective UID in Linux).
I am really struggling to find how to do this with this particular program.
The disassembly of the function in question (main function):
************************************************************** * * * FUNCTION * ************************************************************** int __cdecl main(int argc, char * * argv) int EAX:4 <RETURN> int Stack[0x4]:4 argc char * * Stack[0x8]:4 argv XREF[2]: 000109b0(R), 000109dd(R) undefined4 Stack[-0x8]:4 local_8 XREF[1]: 00010bcb(R) int Stack[-0xc]:4 in XREF[5]: 000109f0(W), 000109f3(R), 00010ad4(R), 00010b27(R), 00010b59(R) int Stack[-0x10]:4 fd XREF[6]: 00010a1f(W), 00010a22(R), 00010aa5(R), 00010ab2(R), 00010ac9(R), 00010b4e(R) pid_t Stack[-0x14]:4 pid XREF[4]: 00010a6b(W), 00010a6e(R), 00010a8b(R), 00010b6a(R) int[2] Stack[-0x1c]:8 pipefd XREF[3,3]: 00010a3f(*), 00010a95(R), 00010b42(R), 00010abd(R), 00010b0f(R), 00010b36(R) char Stack[-0x1d]:1 c XREF[2]: 00010b14(*), 00010b23(*) int Stack[-0x24]:4 status XREF[2]: 00010b66(*), 00010b75(R) main XREF[5]: Entry Point(*), _start:00010866(*), 00010d30, 00010da0(*), 00011f34(*) 0001097d 55 PUSH EBP 0001097e 89 e5 MOV EBP,ESP 00010980 53 PUSH EBX 00010981 83 ec 1c SUB ESP,0x1c 00010984 e8 87 16 CALL <EXTERNAL>::geteuid __uid_t geteuid(void) 00 00 00010989 89 c3 MOV EBX,EAX 0001098b e8 80 16 CALL <EXTERNAL>::geteuid __uid_t geteuid(void) 00 00 00010990 53 PUSH EBX 00010991 50 PUSH EAX 00010992 e8 9d 16 CALL <EXTERNAL>::setreuid int setreuid(__uid_t __ruid, __u 00 00 00010997 83 c4 08 ADD ESP,0x8 0001099a e8 75 16 CALL <EXTERNAL>::getegid __gid_t getegid(void) 00 00 0001099f 89 c3 MOV EBX,EAX 000109a1 e8 6e 16 CALL <EXTERNAL>::getegid __gid_t getegid(void) 00 00 000109a6 53 PUSH EBX 000109a7 50 PUSH EAX 000109a8 e8 9b 16 CALL <EXTERNAL>::setregid int setregid(__gid_t __rgid, __g 00 00 000109ad 83 c4 08 ADD ESP,0x8 000109b0 8b 45 0c MOV EAX,dword ptr [EBP + argv] 000109b3 83 c0 04 ADD EAX,0x4 000109b6 8b 00 MOV EAX,dword ptr [EAX] 000109b8 85 c0 TEST EAX,EAX 000109ba 75 21 JNZ LAB_000109dd 000109bc a1 98 1f MOV EAX,[stderr] 01 00 000109c1 50 PUSH EAX 000109c2 6a 22 PUSH 0x22 000109c4 6a 01 PUSH 0x1 000109c6 68 50 0c PUSH s_Please_specify_the_file_to_verif_00010c50 = "Please specify the file to ve 01 00 000109cb e8 50 16 CALL <EXTERNAL>::fwrite size_t fwrite(void * __ptr, size 00 00 000109d0 83 c4 10 ADD ESP,0x10 000109d3 b8 01 00 MOV EAX,0x1 00 00 000109d8 e9 ee 01 JMP LAB_00010bcb 00 00 LAB_000109dd XREF[1]: 000109ba(j) 000109dd 8b 45 0c MOV EAX,dword ptr [EBP + argv] 000109e0 83 c0 04 ADD EAX,0x4 000109e3 8b 00 MOV EAX,dword ptr [EAX] 000109e5 6a 00 PUSH 0x0 000109e7 50 PUSH EAX 000109e8 e8 43 16 CALL <EXTERNAL>::open int open(char * __file, int __of 00 00 000109ed 83 c4 08 ADD ESP,0x8 000109f0 89 45 f8 MOV dword ptr [EBP + in],EAX 000109f3 83 7d f8 00 CMP dword ptr [EBP + in],0x0 000109f7 79 17 JNS LAB_00010a10 000109f9 68 73 0c PUSH DAT_00010c73 = 6Fh o 01 00 000109fe e8 19 16 CALL <EXTERNAL>::perror void perror(char * __s) 00 00 00010a03 83 c4 04 ADD ESP,0x4 00010a06 b8 02 00 MOV EAX,0x2 00 00 00010a0b e9 bb 01 JMP LAB_00010bcb 00 00 LAB_00010a10 XREF[1]: 000109f7(j) 00010a10 6a 02 PUSH 0x2 00010a12 68 78 0c PUSH s_/dev/null_00010c78 = "/dev/null" 01 00 00010a17 e8 14 16 CALL <EXTERNAL>::open int open(char * __file, int __of 00 00 00010a1c 83 c4 08 ADD ESP,0x8 00010a1f 89 45 f4 MOV dword ptr [EBP + fd],EAX 00010a22 83 7d f4 00 CMP dword ptr [EBP + fd],0x0 00010a26 79 17 JNS LAB_00010a3f 00010a28 68 73 0c PUSH DAT_00010c73 = 6Fh o 01 00 00010a2d e8 ea 15 CALL <EXTERNAL>::perror void perror(char * __s) 00 00 00010a32 83 c4 04 ADD ESP,0x4 00010a35 b8 05 00 MOV EAX,0x5 00 00 00010a3a e9 8c 01 JMP LAB_00010bcb 00 00 LAB_00010a3f XREF[1]: 00010a26(j) 00010a3f 8d 45 e8 LEA EAX=>pipefd,[EBP + -0x18] 00010a42 50 PUSH EAX 00010a43 e8 f8 15 CALL <EXTERNAL>::pipe int pipe(int * __pipedes) 00 00 00010a48 83 c4 04 ADD ESP,0x4 00010a4b 85 c0 TEST EAX,EAX 00010a4d 79 17 JNS LAB_00010a66 00010a4f 68 82 0c PUSH DAT_00010c82 = 70h p 01 00 00010a54 e8 c3 15 CALL <EXTERNAL>::perror void perror(char * __s) 00 00 00010a59 83 c4 04 ADD ESP,0x4 00010a5c b8 03 00 MOV EAX,0x3 00 00 00010a61 e9 65 01 JMP LAB_00010bcb 00 00 LAB_00010a66 XREF[1]: 00010a4d(j) 00010a66 e8 d9 15 CALL <EXTERNAL>::fork __pid_t fork(void) 00 00 00010a6b 89 45 f0 MOV dword ptr [EBP + pid],EAX 00010a6e 83 7d f0 00 CMP dword ptr [EBP + pid],0x0 00010a72 79 17 JNS LAB_00010a8b 00010a74 68 87 0c PUSH DAT_00010c87 = 66h f 01 00 00010a79 e8 9e 15 CALL <EXTERNAL>::perror void perror(char * __s) 00 00 00010a7e 83 c4 04 ADD ESP,0x4 00010a81 b8 04 00 MOV EAX,0x4 00 00 00010a86 e9 40 01 JMP LAB_00010bcb 00 00 LAB_00010a8b XREF[1]: 00010a72(j) 00010a8b 83 7d f0 00 CMP dword ptr [EBP + pid],0x0 00010a8f 0f 85 8c JNZ LAB_00010b21 00 00 00 00010a95 8b 45 e8 MOV EAX,dword ptr [EBP + pipefd[0]] 00010a98 6a 00 PUSH 0x0 00010a9a 50 PUSH EAX 00010a9b e8 60 15 CALL <EXTERNAL>::dup2 int dup2(int __fd, int __fd2) 00 00 00010aa0 83 c4 08 ADD ESP,0x8 00010aa3 6a 01 PUSH 0x1 00010aa5 ff 75 f4 PUSH dword ptr [EBP + fd] 00010aa8 e8 53 15 CALL <EXTERNAL>::dup2 int dup2(int __fd, int __fd2) 00 00 00010aad 83 c4 08 ADD ESP,0x8 00010ab0 6a 02 PUSH 0x2 00010ab2 ff 75 f4 PUSH dword ptr [EBP + fd] 00010ab5 e8 46 15 CALL <EXTERNAL>::dup2 int dup2(int __fd, int __fd2) 00 00 00010aba 83 c4 08 ADD ESP,0x8 00010abd 8b 45 ec MOV EAX,dword ptr [EBP + pipefd[1]] 00010ac0 50 PUSH EAX 00010ac1 e8 8a 15 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010ac6 83 c4 04 ADD ESP,0x4 00010ac9 ff 75 f4 PUSH dword ptr [EBP + fd] 00010acc e8 7f 15 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010ad1 83 c4 04 ADD ESP,0x4 00010ad4 ff 75 f8 PUSH dword ptr [EBP + in] 00010ad7 e8 74 15 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010adc 83 c4 04 ADD ESP,0x4 00010adf 6a 00 PUSH 0x0 00010ae1 68 8c 0c PUSH s_-asxml_00010c8c = "-asxml" 01 00 00010ae6 68 93 0c PUSH DAT_00010c93 = 74h t 01 00 00010aeb 68 93 0c PUSH DAT_00010c93 = 74h t 01 00 00010af0 e8 17 15 CALL <EXTERNAL>::execlp int execlp(char * __file, char * 00 00 00010af5 83 c4 10 ADD ESP,0x10 00010af8 68 98 0c PUSH s_execlp_00010c98 = "execlp" 01 00 00010afd e8 1a 15 CALL <EXTERNAL>::perror void perror(char * __s) 00 00 00010b02 83 c4 04 ADD ESP,0x4 00010b05 b8 05 00 MOV EAX,0x5 00 00 00010b0a e9 bc 00 JMP LAB_00010bcb 00 00 LAB_00010b0f XREF[1]: 00010b34(j) 00010b0f 8b 45 ec MOV EAX,dword ptr [EBP + pipefd[1]] 00010b12 6a 01 PUSH 0x1 00010b14 8d 55 e7 LEA EDX=>c,[EBP + -0x19] 00010b17 52 PUSH EDX 00010b18 50 PUSH EAX 00010b19 e8 1e 15 CALL <EXTERNAL>::write ssize_t write(int __fd, void * _ 00 00 00010b1e 83 c4 0c ADD ESP,0xc LAB_00010b21 XREF[1]: 00010a8f(j) 00010b21 6a 01 PUSH 0x1 00010b23 8d 45 e7 LEA EAX=>c,[EBP + -0x19] 00010b26 50 PUSH EAX 00010b27 ff 75 f8 PUSH dword ptr [EBP + in] 00010b2a e8 d5 14 CALL <EXTERNAL>::read ssize_t read(int __fd, void * __ 00 00 00010b2f 83 c4 0c ADD ESP,0xc 00010b32 85 c0 TEST EAX,EAX 00010b34 75 d9 JNZ LAB_00010b0f 00010b36 8b 45 ec MOV EAX,dword ptr [EBP + pipefd[1]] 00010b39 50 PUSH EAX 00010b3a e8 11 15 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010b3f 83 c4 04 ADD ESP,0x4 00010b42 8b 45 e8 MOV EAX,dword ptr [EBP + pipefd[0]] 00010b45 50 PUSH EAX 00010b46 e8 05 15 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010b4b 83 c4 04 ADD ESP,0x4 00010b4e ff 75 f4 PUSH dword ptr [EBP + fd] 00010b51 e8 fa 14 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010b56 83 c4 04 ADD ESP,0x4 00010b59 ff 75 f8 PUSH dword ptr [EBP + in] 00010b5c e8 ef 14 CALL <EXTERNAL>::close int close(int __fd) 00 00 00010b61 83 c4 04 ADD ESP,0x4 00010b64 6a 00 PUSH 0x0 00010b66 8d 45 e0 LEA EAX=>status,[EBP + -0x20] 00010b69 50 PUSH EAX 00010b6a ff 75 f0 PUSH dword ptr [EBP + pid] 00010b6d e8 b2 14 CALL <EXTERNAL>::waitpid __pid_t waitpid(__pid_t __pid, i 00 00 00010b72 83 c4 0c ADD ESP,0xc 00010b75 8b 45 e0 MOV EAX,dword ptr [EBP + status] 00010b78 c1 f8 08 SAR EAX,0x8 00010b7b 0f b6 c0 MOVZX EAX,AL 00010b7e 83 f8 01 CMP EAX,0x1 00010b81 74 18 JZ LAB_00010b9b 00010b83 83 f8 02 CMP EAX,0x2 00010b86 74 22 JZ LAB_00010baa 00010b88 85 c0 TEST EAX,EAX 00010b8a 75 2d JNZ LAB_00010bb9 00010b8c 68 9f 0c PUSH DAT_00010c9f = 4Fh O 01 00 00010b91 e8 92 14 CALL <EXTERNAL>::puts int puts(char * __s) 00 00 00010b96 83 c4 04 ADD ESP,0x4 00010b99 eb 2b JMP LAB_00010bc6 LAB_00010b9b XREF[1]: 00010b81(j) 00010b9b 68 a4 0c PUSH s_Your_file_is_not_completely_comp_00010ca4 = "Your file is not completely c 01 00 00010ba0 e8 83 14 CALL <EXTERNAL>::puts int puts(char * __s) 00 00 00010ba5 83 c4 04 ADD ESP,0x4 00010ba8 eb 1c JMP LAB_00010bc6 LAB_00010baa XREF[1]: 00010b86(j) 00010baa 68 ca 0c PUSH s_Your_file_contains_errors_00010cca = "Your file contains errors" 01 00 00010baf e8 74 14 CALL <EXTERNAL>::puts int puts(char * __s) 00 00 00010bb4 83 c4 04 ADD ESP,0x4 00010bb7 eb 0d JMP LAB_00010bc6 LAB_00010bb9 XREF[1]: 00010b8a(j) 00010bb9 68 e4 0c PUSH s_I_can't_tell_if_your_file_is_XHT_00010ce4 = "I can't tell if your file is 01 00 00010bbe e8 65 14 CALL <EXTERNAL>::puts int puts(char * __s) 00 00 00010bc3 83 c4 04 ADD ESP,0x4 LAB_00010bc6 XREF[3]: 00010b99(j), 00010ba8(j), 00010bb7(j) 00010bc6 b8 00 00 MOV EAX,0x0 00 00 LAB_00010bcb XREF[6]: 000109d8(j), 00010a0b(j), 00010a3a(j), 00010a61(j), 00010a86(j), 00010b0a(j) 00010bcb 8b 5d fc MOV EBX,dword ptr [EBP + local_8] 00010bce c9 LEAVE 00010bcf c3 RET
According to Ghidra, this decompiles to:
int main(int argc,char **argv) { __uid_t __euid; __uid_t __ruid; __gid_t __egid; __gid_t __rgid; int iVar1; int __fd; int iVar2; __pid_t __pid; ssize_t sVar3; uint uVar4; int status; char c; int pipefd [2]; pid_t pid; int fd; int in; __euid = geteuid(); __ruid = geteuid(); setreuid(__ruid,__euid); __egid = getegid(); __rgid = getegid(); setregid(__rgid,__egid); if (argv[1] == (char *)0x0) { fwrite("Please specify the file to verifyn",1,0x22,stderr); iVar1 = 1; } else { iVar1 = open(argv[1],0); if (iVar1 < 0) { perror("open"); iVar1 = 2; } else { __fd = open("/dev/null",2); if (__fd < 0) { perror("open"); iVar1 = 5; } else { iVar2 = pipe(pipefd); if (iVar2 < 0) { perror("pipe"); iVar1 = 3; } else { __pid = fork(); if (__pid < 0) { perror("fork"); iVar1 = 4; } else if (__pid == 0) { dup2(pipefd[0],0); dup2(__fd,1); dup2(__fd,2); close(pipefd[1]); close(__fd); close(iVar1); execlp("tidy","tidy","-asxml",0); perror("execlp"); iVar1 = 5; } else { while( true ) { sVar3 = read(iVar1,&c,1); if (sVar3 == 0) break; write(pipefd[1],&c,1); } close(pipefd[1]); close(pipefd[0]); close(__fd); close(iVar1); waitpid(__pid,&status,0); uVar4 = status >> 8 & 0xff; if (uVar4 == 1) { puts("Your file is not completely compliant"); } else if (uVar4 == 2) { puts("Your file contains errors"); } else if (uVar4 == 0) { puts("OK!"); } else { puts("I can't tell if your file is XHTML-compliant"); } iVar1 = 0; } } } } } return iVar1; }
It appears it is (to summarize) opening the file passed as the first argument using open in read only mode. If successful, it is forking and using the child process to execute tidy to validate the file is valid XHTML.
Nothing about it stands out to me as an obvious vulnerability that I can use here. I’ve looked into vulnerabilities for the tidy command, but wasn’t really able to find anything useful for this.
Any help would be much appreciated!
Advertisement
Answer
In regular C code, execlp("tidy","tidy","-asxml",0);
is incorrect as execlp()
expects a null pointer argument to mark the end of the argument list.
0
is a null pointer when used in a pointer context, which this is not. Yet on architectures where pointers have the same size and passing convention as int
, such as 32-bit linux, passing 0
or passing NULL
generate the same code, so sloppiness does not get punished.
In 64-bit mode, it would be incorrect to do so but you might get lucky with the x86_64 ABI and a 64-bit 0 value will be passed in this case.
In your own code, avoid such pitfalls and use NULL
or (char *)0
as the last argument for execlp()
. But on this listing, Ghidra produces code that generates the same assembly code, and in 32-bit mode, passing 0
or (char *)0
produce the same code, so no problem here.
In your context, execlp("tidy","tidy","-asxml",0);
shows another problem: it will look for an executable program with the name tidy
in the current PATH
and run this program as tidy
with a command line argument -asxml
. Since it changed the effective uid and gid, this is a problem if the program is setuid root because you can create a program named tidy
in a directory appearing in the PATH
variable before the system directories and this program will be run with the modified rights.
Another potential problem is the program does not check for failure of the system calls setreuid()
and setregid()
. Although these calls are unlikely to fail for the arguments passed, as documented in the manual pages, it is a grave security error to omit checking for a failure return from setreuid()
. In case of failure, the real and effective uid (or gid) is not changed and the process may fork and exec with root privileges.