✝️ Psalm 34:18 The Lord is near to the brokenhearted and saves the crushed in spirit. # I wanted to document trying to find a vuln in talkd, so lets begin! ## Part 1: Reverse engineering First of all, I copied talkd to ~/hope (hope I can find a vuln here lmao), then I opened it in ida pro! ![[ida_talkd_01.jpg]] gethostname is called with a length of 255 and if the result's 31st bit is not zero then we exit. The current working directory is attempted to be set to /dev/, if the attempt fails then we exit. If there was no command line argument then we jump to loc\_1000018A8, if not then we check if the first command line argument does not start with - then we go jump to loc\_1000018A8. ![[ida_talkd_02.png]] If the next character of the first command line argument does not start with d then PC is set to loc\_1000018A8, if the next character is not zero then we branch to loc\_1000018A8. 1 is stored at \[X9 + dword_100008498\]. At loc\_1000018A8 the pseudo instruction ADRL is used to load 0x1000019C0 into X16 and then signal is called with the first argument being 14 and the next argument being that address in X16, being treated as a function. The function is just this: ![[ida_talkd_03.png]] Note that 0x10008478 is in bss. Anyways, after signal is done, alarm is called with 0x1E in W0, note that W0 is the lower 32 bits of X0 and writing to a W register zero extends the entire X version of that same register, X21 is set to the result of the bitwise ORR operation between SP + 0x60 + var\_50 and 2. X22 is set to the page where qword_100008478 is in. W23 is set to 16, X20 is set to 0x100008480, X24 is set to a pointer to the string "sendto: %m" and X25 is set to a pointer "recv: %m". ![[ida_talkd_04.png]] Next, at loc_1000018F8 W0 is set to 0, X1 is set to X19, X2 is set to 0x54, remember setting a W register zero extends the entire X register. W3 is set to 0 and recv is called and if the bytes received was not 0x54 then the 31st bit of W0 is checked to see if it is zero and if it is then we jump to loc_1000018F8, if it isnt \_\_error is called and W8 is set to 32 bits at X0 being treated as an address and X2 is set to X25, if W8 is 4 then we jump to loc_1000018F8, else call a logging function with W0 being set to 4 and then we jump to loc_1000018F8. Now, if 0x54 bytes were received time is called with X0 being set to 0 and the result is stored in 0x100008478. X8 is set to 64 bits at X19 plus 0x1A being treated as an address and is stored at X21 being treated as an address. X8 is set to 64 bits at X19 plus 0x20 being treated as an address and is stored at X21 plus 6 being treated as an address. W8 is set to the 8 bits at X19 plus 0x19 being treated as an address and the lower 8 bits of W8 is stored at SP + 0x60 + var\_50.sa\_family being treated as an address. Now, 8 bits at SP + 0x60 + var\_50.sa being treated as an address is set to the lower 8 bits of W23. and sub\_1000009E4 is called with X19 as the first argument and X20 as the second argument. X4 is set to SP plus 0x60 + var\_50. W0 is set to 0, X1 is set to X20, W2 is set to 0x18, W3 is set to 0, and W5 is set to 16, and then sendto is called. X1 is set to X24 and if 0x18 bytes were sent then we jump to loc_1000018F8 else a logging function is called with W0 being set to 4 and then we jump to loc_1000018F8. Now, in sub\_1000009E4 this happens: ![[ida_talkd_05.png]] First of all, X19 is set to the second argument, then the zeroth byte of the second argument is set to 1, the first byte of the second argument is set to the first byte of the first argument, 4 bytes starting at the 4th byte of the second argument are set to 0. If the zeroth byte of the first argument is not 1 we jump to loc\_100000B14, X20 is set to the first argument. 4 bytes starting at byte 4 at the first argument is copied to W8 and the byte order of W8 is reversed and 4 bytes starting at byte 4 at the first argument is set to W8. The byte order of 2 bytes starting at byte 8 at the first argument is reversed. The value of those 2 bytes together before they swapped places is checked to be 0x200, if the value is not 0x200 then we jump to loc_\100000B30. The byte order of 2 bytes starting at byte 0x18 at the first argument is reversed. The value of those 2 bytes together before they swapped places is checked to be 0x200, if the value is not 0x200 then we jump to loc_\100000B50. Now, we are here: ![[ida_talkd_06.png]] If byte 0x2C of the first argument is 0 then we jump to loc\_100000A9C, W0 is set to the lower 8 bits of W8 and the bits above bit 7 of W0 are set to bit 7 of W8, then bit 7 of W8 is checked if it is not zero, and if it is not zero we go to loc\_100000A88 where there is a check if W0 is printable, if W0 is not printable then we go to loc\_100000B90, if W0 is printable we go to loc\_100000A94. If bit 7 of W8 is 0 then there is a check to see if W0 is printable, if it is we go to loc\_100000A94 where W8 is set to 4 bytes at X21 being treated as an address and X21 is increased by 1 and if W8 is not 0 then we go to loc\_100000A6C. If it is 0 then we go to loc\_100000A9C. At loc\_100000A9C W8 is set to 4 bytes at the first argument plus 0x28 being treated as an address and the 4 bytes in W8 are reversed and 4 bytes at the first argument plus 0x28 being treated as an address are set to W8. X21 is set to 0x100008498 and W8 is set to 4 bytes at X21 being treated as an address and if W8 is 0 then we branch to loc\_100000AC8. If not, sub\_100001310 is called with a pointer to the string "process_request" and the first argument, and then go to loc\_100000AC8. At loc\_100000AC8 8 bits at the first argument plus 1 being treated as an address are copied to W8 and W8 is checked if it is bigger than 1 and if it is we go to loc\_100000B70, before looking at what is next, lets check out sub\_100001310. ![[ida_talkd_07.jpg]] Wow! It is a logging function, how nice! Anyways, we left off here, so let us keep going! ![[ida_talkd_08.png]] Anyways, remember if W8 is greater than 1 then we go to loc\_100000B70, at loc\_100000B70 if W8 is equal to 2 then we go to loc_\100000BE4 which calls sub\_10000126C with 8 bits at X20 plus 4 being treated as an address and the result is written to the byte at X19 plus 2 and then PC is set to loc\_100000C0C. So if W8 was not equal to 2 then we check if it is not equal to 3 and if it isnt equal to 3 then we go to loc\_100000BBC and there W8 is set to 5 and PC is set to loc\_100000BF8 and there W8 is set to 8 bits at X19 plus 2 being treated as an address and then PC is set to loc\_100000C0C. If W8 is equal to 3 then sub\_100000C44 is called with the first and second argument and after that, PC is set to loc\_100000C0C. If W8 is not greater than 1 then there is a check if W8 is zero or not, if W8 is zero then sub\_100001014 is called with the first argument, if the function returned 0 then PC is set to loc\_100000C00 and at loc\_100000C00 sub\_10000113C is called with the first and second argument then PC goes to loc\_100000C0C. If that function did not return 0 then W8 is set to 32 bits at the result of the function plus 4 being treated as an address and W8's bytes are reversed and W8 is stored at X19 plus 4 being treated as an address. 8 zero bits are stored at X19 plus 2 being treated as an address and PC is set to loc\_100000C0C. If W8 is not 1 then PC is set to loc\_100000BBC, if W8 is 1 then sub\_100000E80 is called with the first argument and if the result of the function is 0 then W8 is set to 1 and PC is set to loc\_100000BF8. If the result of that function is not 0 then 4 bytes at the result of the function plus 4 is stored at the second argument plus 4 being treated as an address. 16 bytes at the result of the function plus 8 are stored at the second argument plus 8 being treated as an address. W8 is set to 2 bytes at the result of the function plus 8 being treated as an address, the bytes of W8 are reversed and W8 is shifted to the right by 16 and is stored at the second argument plus 8 being treated as an address. PC is set to loc\_100000BDC. Now we are here: ![[ida_talkd_09.png]] If 4 bytes at X21 being treated as an address are zero then we return, else sub\_1000013D4 is attempted to be called with a pointer to the string process_request and the second argument. Now that we covered sub\_1000009E4, let's cover these functions: sub\_10000126C, sub\_100000C44, sub\_100001014, sub\_10000113C, sub\_100000E80, and sub\_1000013D4, starting with sub\_10000126C. ![[ida_talkd_10.png]] X19 is set to X0, the first argument of this function and X22 is set to 0x100008498 and 4 bytes at X22 being treated as an address are copied to W8, if W8 is zero we skip the logging function, else the logging function is called. X8 is set to the page where 0x100008410 is and X20 is set to 8 bytes at 0x100008410 and if X20 is 0 we return 1, if X20 is not 0 then X21 is set to 0x100001E91 and PC is set to loc\_1000012C0. W8 is set to 4 bytes at X20 plus 4 being treated as an address, if W8 is equal to W19 then we go to loc\_1000012F0, at that location X0 is set to X20 and sub\_100000F88 is called and after it is called W0 is set to 0 and the function returns, else W8 is set to 4 bytes at X22 being treated as an address. If W8 is 0 then PC is set to loc\_1000012E0. The function sub\_100001310 is called with X21 and X20, PC is set to loc\_1000012E0 and at loc\_1000012E0 X20 is set to 8 bytes at X20 plus 0x60 and X20 is checked if it is not zero and if it is not zero then PC is set to loc\_1000012C0. If X20 is zero then this function returns 1 else we go to loc\_1000012F0. Now, lets look at sub\_100000F88. ![[ida_talkd_11.png]] The only argument for this function is stored in X19, W8 is set to 4 bytes at 0x100008498, if W8 is zero then we skip that logging function else it gets called. X9 is set the whatever page 0x100008410 is in and X8 is set to 8 bytes at 0x100008410. If X8 is equal to X19 then we go to loc\_100001004 where X8 is set to 8 bytes at X19 plus 0x60 being treated as an address, X8 is stored at 0x100008410 and if X8 is zero we go to loc\_100000FE4 else we go to loc\_100000FDC. If X8 is not equal to X19 then X8 is set to 8 bytes at X19 plus 0x60 being treated as an address and X9 is set to 8 bytes at X19 plus 0x68 being treated as an address, if X9 is zero then jump to loc\_10000FD8 else X8 is copied to X9 plus 0x60 being treated as an address and PC is set to loc\_10000FD8. If X8 is zero then we go to loc\_100000FE4 else we go to loc\_100000FDC and at loc\_100000FDC, X9 is set to X19 plus 0x68 being treated as an address and X9 is copied to X8 plus 0x68 being treated as an address and PC is set to loc\_100000FE4. At loc\_100000FE4... well it's just a cleanup and safety thing, assuming all is well, \_free is called with X19. The next function to look at is sub\_100000C44: ![[ida_talkd_12.png]] The second argument of that function is stored in X19 and the first argument of that function is stored in X20, sub\_100000D00 is called with the first argument plus 0x38 and the first argument plus 0x44, if the result of that function was not zero then we return, else we set X0 to the first argument plus 0x1C, W1 is set to 4, W2 is set to 2 and \_gethostbyaddr is called, if the result is 0 then we set W0 to 3 and return. X21 is set to the result of \_gethostbyaddr and sub\_100001014 is called with the first argument, if the resulst of the function is zero we jump to loc\_100000CC4 where sub\_10000113C is called with the first and second argument and then PC is set to loc\_100000CD0. If the result of sub\_100001014 was not zero then X22 is set to the result of sub\_100001014 and W9 is set to 4 bytes at X20 plus 4 being treated as an address and W8 is set to X0 plus 4 being treated as an address and if W9 is less than W8 then W0 is set to 0, the bytes in W8 are reversed and W8 is stored at X19 plus 4 being treated as an address and the function returns. If W9 is not less than W8 then sub\_10001234 is called and the result is stored in X22 plus 4 being treated as an address and W8 is set to the bytes reversed of the result and W8 is stored in X19 plus 4 being treated as an address and PC goes to loc\_100000CD0. At loc\_100000CD0, X1 is set to 8 bytes at X21 being treated as an address and sub\_1000014CC is called with the first argument and after that the function returns. In this function, let's look at sub\_100000D00 first! ![[ida_talkd_13.png]] X19 is set to the second argument and X20 is set to the first argument, after some setup \_setutxent is called... What on God's green earth are \_setutxent, \_getutxent, \_endutxent (look at the image below for that last one lol)???? Time to research! After millions of hours of research (pulling up the man page!) I figured out what these functions are and what they do! So first of all, there is this file called utmpx in this directory /var/run/, and that file contains a certain number of entries! Now, to learn more about this thingy I made a little parser: ``` .global _main .section __TEXT,__text _main: ORR X22, X30, XZR SUB SP, SP, #64 BL _setutxent ORR W19, WZR, WZR _get_entries: BL _getutxent CBZ X0, _quit STP X19, X0, [SP] LDR W1, [X0, #256] REV W1, W1 ADD X2, X0, #260 STP X1, X2, [SP, #16] ADD X1, X0, #292 LDP W1, W2, [X1] STP X1, X2, [SP, #32] ADD X0, X0, #304 ADD X20, X0, #16 LDR X0, [X0] STR X0, [SP, #48] ADD X0, SP, #48 BL _ctime STP X0, X20, [SP, #48] ADRP X0, entry@PAGE ADD X0, X0, entry@PAGEOFF ADD X19, X19, #1 BL _printf B _get_entries _quit: BL _endutxent ADD SP, SP, #64 ORR X30, X22, XZR MOVZ W0, #225 RET .section __DATA,__data entry: .asciz "ENTRY %lu\n\tLOGIN NAME: %.256s\n\tID: 0x%08x\n\tTTY NAME: %.32s\n\tPROCESS ID: %d\n\tTYPE: %hd\n\tTIME: %.25s\tHOST NAME: %.256s\n" ``` I got 17 entries in total (0-16) and ENTRY NUMBER SEVENTEEN SEEMS VERY VERY INTERESTING... (☟︎⚐︎🏱︎☜︎ ✡︎⚐︎🕆︎ ☝︎⚐︎❄︎ ❄︎☟︎☜︎ ☼︎☜︎☞︎☜︎☼︎☜︎☠︎👍︎☜︎ ☹︎⚐︎☹︎). Anyways, lets get back to it! So, \_setutxent opens the file I mentioned above and seeks to the start of the file, \_getutxent reads an entry and seeks to the next entry, and \_endutxent closes the file. Also the string /dev/ is stored at X29 plus var\_7c, we then get the first entry and if it is NULL we branch to loc\_100000E30 and that just sets 1 to the return value. If the first entry is not NULL then: ![[ida_talkd_14.png]] After some setup happens like X25 being set to 0, PC is set to loc\_100000D88 and if the TYPE field of the current entry is not 7 then PC is set to loc\_100000DFC. If the LOGIN NAME does not have the same contents of the first argument then we go to loc\_100000DFC. If X25 is not zero then we jump to loc\_100000DB0, If the first byte at the second argument is not zero then we go to loc\_100000DEC. Now, we are at loc\_100000DB0 where if X25 is zero then W21 is set to W24 or else set to W21 (lol), quick break cuz I just wanna say I like the CSEL instruction, I want to know where the idea itself came from... Ok unpause! stat is called on the file in /dev/ with the same name as the TTY NAME field in the entries and if the result is not zero we branch to loc\_100000DEC and there is a check if the second argument is equal to the TTY NAME field of the current entry and if the strings match we return 0 else we jump to loc\_100000DFC and there the next entry is grabbed and is checked if it is NULL and if it is not NULL then we jump to loc\_100000D88 else the function returns. Anyways, if the result of stat was zero then \[SP,#0x110+var\_110+4\] is checked if it's 4th bit is 0 and if it is we go to loc\_100000DFC else 8 bytes at \[SP,#0x110+var\_F0\] are checked if they are greater than X25 and if they are we jump to loc\_100000E0C else we jump to loc\_100000DEC. At loc\_100000E0C X19 is set to hold the current entry's TTY NAME and the next entry is grabbed, W12 is set to 0, X25 is set to X26, and if the current entry is NULL we return, else we jump to loc\_100000D8... Ok that function was pretty cool! I didn't know anything about those functions before! You know like I wonder what a logic machine would even look like and how would it work, id classify it as a computer because of the rules and state but how practi--- ANYWAYS! Now, we have sub\_100001014! time to PTRACE_PEEK this function (lol): ![[ida_talkd_15.png]] \_clock\_gettime is called the first argument being 6 and the next argument qword\_100008400 being the buffer. After that function is done, the first 8 bytes of the buffer are stored in X23, X24 is set to 0x10008494 and if the 4 bytes starting at that address are zero we branch to loc\_10000106C, else we call sub\_100001310 with the string "find_request" and the argument for sub\_10001014. (the call for sub\_100001310 is not in this above or below image but it is here 1 image below the image below... Sorry, but atleast you can see sub\_10001310!) ![[ida_talkd_18.png]] The first byte of the second argument is compared against 5 and if the carry flag is cleared then some logging stuff happens with "leave_invite" else it happens with "type %d" and %d is replaced with whatever X8 is. The function just logs stuff and returns! After sub\_100001310 returns PC is set to loc\_10000106C, Now at loc\_10000106C: ![[ida_talkd_16.png]] If X25 is set to the 8 bytes at 0x100008410 and if they are NULL then we return 0 else X0 is set to the address of the string "deleting expired entry" and X21 is set to 0x100001E91 and at that address 0 is stored. PC is set to loc\_100000EF4 and there X22 is set to X25 and X25 is set to the value at X25 plus 0x60 being treated as an address, now the value at X22 + 0x58 being treated like an address minus the first 8 bytes of the buffer that was called with \_clock\_gettime in this function is checked if it is less than 0x3d and if it is less than 0x3d then there is a check if the 4 bytes at X22 + 0x58 being treated like an address (ALSO STORED IN X8) is zero and if it is then we jump to loc\_1000010D0, else we call sub\_100001310 is called with 0x100001E91 and X22 and then PC is set to loc\_1000010D0. At loc\_1000010D0 a strcmp takes place with the argument plus 0x38 is compared against X22 plus 0x38, if they dont match PC is set to loc\_100001110 and at the location if X25 is not zero we jump to loc\_100001088 else we return 0. Anyways, another string comparison takes place with the argument plus 0x2C is compared against X22 plus 0x2C, if they dont match PC is set to loc\_100001110, if the first byte of both the argument and X22 dont match then we jump to loc\_100001110 and if 8 bytes at the argument plus 0x28 and 8 bytes at X22 plus 0x28 dont match we go to loc\_100001110 else we go to loc\_100001134 and there X23 is stored at X22 plus 0x58 and the function returns. Now, lets back up to the check with the value at X22 + 0x58 being treated like an address (ALSO STORED IN X8) minus the first 8 bytes of that buffer compared to 0x3D, if that is not less than 0x3D then there is a check if W8 is 0 and if it is then PC is set to loc\_1000010B4 and that calls sub\_100001310 with the address of the string "deleting expired entry" and X22, and after that function returns then PC is set to loc\_1000010B4 and there, sub\_100000F88 is called with X22 and then PC is set to loc\_100001110. That function is covered! Let's keep going! But first, I noticed I did not cover the paths for loc\_100000B14, loc\_100000B30, loc\_100000B50, and loc_100000B90... So the image below shows them! ![[ida_talkd_17.png]] (more todos here lol)