-
Notifications
You must be signed in to change notification settings - Fork 138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance the constraints for argv
and envp
#206
Enhance the constraints for argv
and envp
#206
Conversation
580a65d
to
47b6911
Compare
Thanks for this nice enhancement! I haven't looked into the implementation yet, but you may want to resolve the tests failure first: https://github.com/david942j/one_gadget/actions/runs/5506576476/job/15242511295 Because your changes there seem to be more gadgets found. |
Ok, done. |
eaf91d0
to
654bba2
Compare
|
||
def check_nonstack_argv(arg, allow_null) | ||
if allow_null | ||
"[#{arg}] == NULL || #{arg} == NULL || #{arg} is a valid argv" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be simplified to "#{arg} == NULL || #{arg} is a valid argv" because [#{arg}] == NULL
also means #{arg} is a valid argv
if allow_null | ||
"[#{arg}] == NULL || #{arg} == NULL || #{arg} is a valid argv" | ||
else | ||
"[#{arg}] == NULL || #{arg} is a valid argv" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can simply be #{arg} is a valid argv
# TODO: Handle the case when libc will write something into envp | ||
cons = global_var?(envp[0]) ? nil : "#{arg} == NULL || {#{envp.join(', ')}, ...} is a valid envp" | ||
else | ||
cons = "[#{arg}] == NULL || #{arg} == NULL || #{arg} is a valid envp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"#{arg} == NULL || #{arg} is a valid envp"
lib/one_gadget/gadget.rb
Outdated
@@ -81,6 +83,7 @@ def calculate_score(expr) | |||
when /^writable/ then calculate_writable_score(expr.sub('writable: ', '')) | |||
when / == NULL$/ then calculate_null_score(expr.sub(' == NULL', '')) | |||
when / <= 0$/ then calculate_null_score(expr.sub(' <= 0', '')) | |||
when / is a valid (argv|envp)$/ then 0.2 # TODO: Calculate the score more precisely |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel we can give it score 1, since it basically means something on stack is NULL (same as the case "[sp+xx] == NULL is easy" in calculate_null_score
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some reservations about assigning a score of 1.
Is it truly correct to attribute such a high score to it? This is particularly relevant when we examine cases like {"sh", "-c", x, NULL}
.
In such instances, the value of x must hold a readable and contextually meaningful address.
However, manipulating x to an address that meets these criteria might not easy to be feasible most of the time (?).
Furthermore, when addressing cases like {"/bin/sh", x, NULL}
, the score attributed to this constraint should probably equal to or less than the score derived from calculate_null_score(x)
, instead of always 1.
To address the scenario I mentioned earlier, my initial approach involved creating a set of comprehensive conditions, such as "[#{arg}] == NULL || ... is a valid argv"
.
By assigning a relatively low score of 0.2 to the constraint for is valid argv
, the dominant influence within the entire ||
constraint becomes [#{arg}] == NULL
for the majority of cases.
The is valid argv
constraint essentially serves as supplementary information and acts as a fallback solution. Its significance truly emerges when our attempts to satisfy all other constraints have proven unsuccessful.
Nevertheless, my approach has given rise to an additional predicament, as can be gleaned from this instance: #206 (comment).
So, I think we have the following options:
- Simplify the constraints when we generated it, i.e. directly fixing somewhere like this: Enhance the constraints for
argv
andenvp
#206 (comment), and re-parsing the...
in... is a valid argv/envp
for calculating the more accurate score here. - Keep this for now, until we want more accurate score :). (I personally prefer this option haha. Even if we make the score more accurate, it probably doesn't have a difference in the same output level. In addition, I found the verbose constraints like
[r12] == NULL || r12 is a valid envp
to be more intuitive than a singler12 is a valid envp
, although[r12] == NULL
is redundancy.)
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation and I am convinced it's different from the NULL cases indeed. Let's go with current implementation and also remove TODO: Calculate the score more precisely
, as you said I don't think it's worthy to to enhance it. Instead, add a comment that "/ is a valid (argv|envp)$/" usually means the register has to be a readable and pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, no problem!
Before this commit, one_gadget can't handle `argv` and `envp` well, especially when `argv` or `envp` is on the stack. For example, with libc 2.37 from http://archive.ubuntu.com/ubuntu/pool/main/g/glibc//libc6_2.37-0ubuntu2_amd64.deb , one_gadget might found gadget like this: ``` 0xe3219 execve("/bin/sh", rbp-0x50, r13) constraints: address rbp-0x48 is writable [rbp-0x50] == NULL || rbp-0x50 == NULL [r13] == NULL || r13 == NULL ``` But this is not true, the disassembly of this gadget is something like: ``` e2f95: 49 c7 47 10 00 00 00 mov QWORD PTR [r15+0x10],0x0 e2f9c: 00 e2f9d: 4c 89 ea mov rdx,r13 e2fa0: 4c 89 fe mov rsi,r15 e2fa3: 48 89 cf mov rdi,rcx e2fa6: e8 75 f5 ff ff call e2520 <execve> ... ... e3219: 48 8d 0d b2 1f 0d 00 lea rcx,[rip+0xd1fb2] # 1b51d2 <not_available+0x174> e3220: 4c 89 65 b8 mov QWORD PTR [rbp-0x48],r12 e3224: 4c 8d 7d b0 lea r15,[rbp-0x50] e3228: 48 89 4d b0 mov QWORD PTR [rbp-0x50],rcx e322c: e9 64 fd ff ff jmp e2f95 <execvpe+0x275> ``` So there are several problems: 1. `[rbp-0x50]` doesn't need to be `NULL`, because it will eventually be set to `rip+0xd1fb2`(the address of "/bin/sh"). In addition, `rbp-0x50` cannot be `NULL` because `rbp-0x50` should be writable. 2. When `execve` is called, its argv will be `[rbp-0x50]`. `[rbp-0x50]` will be set to the address of "/bin/sh", `[rbp-0x48]` will be set to `r12`, and `[rbp-0x40]` will be set to `NULL`. So the correct constraint is that `r12` need to be NULL or somehow `{"/bin/sh", r12, NULL}` is a valid argv (e.g. `r12` points to address of "-"). 3. When `execve` is called, `r13` will become its envp. But `[r13]` or `r13` doesn't always need to be `NULL`. Actually, the content of `envp` is not very important most of the time, if it's not `NULL`, we just need to make sure it's pointing to a readable address with some continuous readable addresses inside it that make r13 can become a valid envp. After this commit, one_gadget will find the correct constraints for this gadget and show more useful information: ``` 0xe3219 execve("/bin/sh", rbp-0x50, r13) constraints: address rbp-0x48 is writable r12 == NULL || {"/bin/sh", r12, NULL} is a valid argv [r13] == NULL || r13 == NULL || r13 is a valid envp ```
654bba2
to
dac0f72
Compare
6c229b2
to
683f20e
Compare
See david942j#206 (comment) for some discussions/explanations about this.
Merged, thanks a lot for the contribution! |
This patch seemed to work well during the recent CTF challenge I played, and I have actually seen someone fail to solve the challenge because they get misled by the old version one_gadget lol. Also seems we need to update the gadgets/constraints saved on the remote(?), seems like I still need to add a I can probably help if I have some time(if needed :p), could you give me some guides to helping maintain these? |
remote actually means on github, let me do the update and release a new version |
v1.9.0 released |
Thanks! |
Before this PR, one_gadget can't handle
argv
andenvp
well, especially whenargv
is on the stack.For example, with libc 2.37 from http://archive.ubuntu.com/ubuntu/pool/main/g/glibc//libc6_2.37-0ubuntu2_amd64.deb, one_gadget might found the gadget like this:
But this is not true, the disassembly of this gadget is something like:
So there are several problems:
[rbp-0x50]
doesn't need to beNULL
, because it will eventually be set torip+0xd1fb2
(the address of "/bin/sh"). In addition,rbp-0x50
cannot beNULL
becauserbp-0x50
should be writable.execve
is called, its argv will be[rbp-0x50]
.[rbp-0x50]
will be set to the address of "/bin/sh",[rbp-0x48]
will be set tor12
, and[rbp-0x40]
will be set toNULL
. So the correct constraint is thatr12
need to be NULL or somehow{"/bin/sh", r12, NULL}
is a valid argv (e.g.r12
points to address of "-").execve
is called,r13
will become its envp. But[r13]
orr13
doesn't always need to beNULL
. Actually, the content ofenvp
is not very important most of the time, if it's notNULL
, we just need to make sure it's pointing to a readable address with some continuous readable addresses inside it that make r13 can become a valid envp.After this PR, one_gadget will find the correct constraints for this gadget and show more useful information:
More before/after for the libc in http://archive.ubuntu.com/ubuntu/pool/main/g/glibc//libc6_2.37-0ubuntu2_amd64.deb:
^ Similar to the first example,
[rbp-0x50]
is not control by user, the correct constraints arerax == NULL
or{"/bin/sh", rax, NULL}
is a valid argv.^ By showing more details, users can easier know that if they can control
[rsp+0x70]
to store a readable address(since sh won't bother by weirdargv[0]
) and luckily[rsp+0x78]
isNULL
, they can spawn the shell.Not very sure if this PR fully fixes the problem that #120 mentioned, but hopefully it should at least fix the case when argv is on the stack for x86 libc.
Also, the enhancements and fixes introduced by this PR still have some TODOs (I mentioned them in the comment, e.g. the scoring parts).
Currently, I don't have a good idea for resolving them, let me know if you have any ideas that I can improve in this PR.
(Or maybe we can just leave them for now and fix them in another PR if they don't affect too much :p)